diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cec8b65d0c..09cb6cead7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3796,12 +3796,12 @@ importers: '@rivetkit/bare-ts': specifier: ^0.6.2 version: 0.6.2 + '@rivetkit/fdb-tuple': + specifier: https://pkg.pr.new/rivet-dev/fdb-tuple/@rivetkit/fdb-tuple@32f95e2 + version: https://pkg.pr.new/rivet-dev/fdb-tuple/@rivetkit/fdb-tuple@32f95e2 cbor-x: specifier: ^1.6.0 version: 1.6.0 - fdb-tuple: - specifier: ^1.0.0 - version: 1.0.0 vbare: specifier: ^0.0.4 version: 0.0.4 @@ -3833,12 +3833,12 @@ importers: '@rivetkit/bare-ts': specifier: ^0.6.2 version: 0.6.2 + '@rivetkit/fdb-tuple': + specifier: https://pkg.pr.new/rivet-dev/fdb-tuple/@rivetkit/fdb-tuple@32f95e2 + version: https://pkg.pr.new/rivet-dev/fdb-tuple/@rivetkit/fdb-tuple@32f95e2 cbor-x: specifier: ^1.6.0 version: 1.6.0 - fdb-tuple: - specifier: ^1.0.0 - version: 1.0.0 vbare: specifier: ^0.0.4 version: 0.0.4 @@ -8262,6 +8262,10 @@ packages: '@rivetkit/fast-json-patch@3.1.2': resolution: {integrity: sha512-CtA50xgsSSzICQduF/NDShPRzvucnNvsW/lQO0WgMTT1XAj9Lfae4pm7r3llFwilgG+9iq76Hv1LUqNy72v6yw==} + '@rivetkit/fdb-tuple@https://pkg.pr.new/rivet-dev/fdb-tuple/@rivetkit/fdb-tuple@32f95e2': + resolution: {tarball: https://pkg.pr.new/rivet-dev/fdb-tuple/@rivetkit/fdb-tuple@32f95e2} + version: 1.0.0 + '@rivetkit/on-change@6.0.2-rc.1': resolution: {integrity: sha512-5RC9Ze/wTKqSlJvopdCgr+EfyV93+iiH8Thog0QXrl8PT1unuBNw/jadXNMtwgAxrIaCJL+JLaHQH9w7rqpMDw==} engines: {node: '>=20'} @@ -11620,9 +11624,6 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - fdb-tuple@1.0.0: - resolution: {integrity: sha512-8jSvKPCYCgTpi9Pt87qlfTk6griyMx4Gk3Xv31Dp72Qp8b6XgIyFsMm8KzPmFJ9iJ8K4pGvRxvOS8D0XGnrkjw==} - fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -21285,6 +21286,8 @@ snapshots: '@rivetkit/fast-json-patch@3.1.2': {} + '@rivetkit/fdb-tuple@https://pkg.pr.new/rivet-dev/fdb-tuple/@rivetkit/fdb-tuple@32f95e2': {} + '@rivetkit/on-change@6.0.2-rc.1': {} '@rolldown/pluginutils@1.0.0-beta.27': {} @@ -25443,8 +25446,6 @@ snapshots: dependencies: pend: 1.2.0 - fdb-tuple@1.0.0: {} - fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 diff --git a/rivetkit-typescript/packages/traces/package.json b/rivetkit-typescript/packages/traces/package.json index 08eab034e0..cff26c0aab 100644 --- a/rivetkit-typescript/packages/traces/package.json +++ b/rivetkit-typescript/packages/traces/package.json @@ -12,6 +12,16 @@ ], "exports": { ".": { + "browser": { + "import": { + "types": "./dist/tsup/browser.d.ts", + "default": "./dist/tsup/browser.js" + }, + "require": { + "types": "./dist/tsup/browser.d.cts", + "default": "./dist/tsup/browser.cjs" + } + }, "import": { "types": "./dist/tsup/index.d.ts", "default": "./dist/tsup/index.js" @@ -36,7 +46,7 @@ "node": ">=18.0.0" }, "scripts": { - "build": "pnpm run compile:bare && tsup src/index.ts src/reader.ts", + "build": "pnpm run compile:bare && tsup src/index.ts src/reader.ts src/browser.ts", "compile:bare": "tsx scripts/compile-bare.ts compile schemas/v1.bare -o dist/schemas/v1.ts", "check-types": "pnpm run compile:bare && tsc --noEmit", "test": "pnpm run compile:bare && vitest run" @@ -44,7 +54,7 @@ "dependencies": { "@rivetkit/bare-ts": "^0.6.2", "cbor-x": "^1.6.0", - "fdb-tuple": "^1.0.0", + "@rivetkit/fdb-tuple": "https://pkg.pr.new/rivet-dev/fdb-tuple/@rivetkit/fdb-tuple@32f95e2", "vbare": "^0.0.4" }, "devDependencies": { diff --git a/rivetkit-typescript/packages/traces/src/browser.ts b/rivetkit-typescript/packages/traces/src/browser.ts new file mode 100644 index 0000000000..e46fba1f9e --- /dev/null +++ b/rivetkit-typescript/packages/traces/src/browser.ts @@ -0,0 +1,84 @@ +// Browser stub for @rivetkit/traces +// This file is used as the browser entry point to prevent Node.js-specific code from being bundled + +import type { + OtlpAnyValue, + OtlpExportTraceServiceRequestJson, + OtlpInstrumentationScope, + OtlpKeyValue, + OtlpResource, + OtlpResourceSpans, + OtlpScopeSpans, + OtlpSpan, + OtlpSpanEvent, + OtlpSpanLink, + OtlpSpanStatus, +} from "./otlp.js"; +import type { + EndSpanOptions, + EventOptions, + ReadRangeOptions, + ReadRangeResult, + ReadRangeWire, + SpanHandle, + SpanStatusInput, + StartSpanOptions, + Traces, + TracesDriver, + TracesOptions, + UpdateSpanOptions, +} from "./types.js"; + +function notSupported(name: string): never { + throw new Error( + `@rivetkit/traces: ${name} is not supported in the browser. Traces are only available on the server.`, + ); +} + +export function createTraces( + _options: TracesOptions, +): Traces { + notSupported("createTraces"); +} + +export function encodeReadRangeWire(_wire: ReadRangeWire): Uint8Array { + notSupported("encodeReadRangeWire"); +} + +export function decodeReadRangeWire(_bytes: Uint8Array): ReadRangeWire { + notSupported("decodeReadRangeWire"); +} + +export function readRangeWireToOtlp( + _wire: ReadRangeWire, + _resource?: OtlpResource, +): { otlp: OtlpExportTraceServiceRequestJson; clamped: boolean } { + notSupported("readRangeWireToOtlp"); +} + +// Re-export types (these are safe for browsers) +export type { + EndSpanOptions, + EventOptions, + ReadRangeOptions, + ReadRangeResult, + ReadRangeWire, + SpanHandle, + SpanStatusInput, + StartSpanOptions, + Traces, + TracesDriver, + TracesOptions, + UpdateSpanOptions, + OtlpAnyValue, + OtlpExportTraceServiceRequestJson, + OtlpInstrumentationScope, + OtlpKeyValue, + OtlpResource, + OtlpResourceSpans, + OtlpScopeSpans, + OtlpSpan, + OtlpSpanEvent, + OtlpSpanLink, + OtlpSpanStatus, +}; diff --git a/rivetkit-typescript/packages/traces/src/traces.ts b/rivetkit-typescript/packages/traces/src/traces.ts index 36c4e80c30..b0b2f45fcf 100644 --- a/rivetkit-typescript/packages/traces/src/traces.ts +++ b/rivetkit-typescript/packages/traces/src/traces.ts @@ -2,17 +2,16 @@ import { AsyncLocalStorage } from "node:async_hooks"; import { Buffer } from "node:buffer"; import { randomBytes } from "node:crypto"; import { performance } from "node:perf_hooks"; +import { pack, unpack } from "@rivetkit/fdb-tuple"; import { decode as decodeCbor, encode as encodeCbor } from "cbor-x"; -import { pack, unpack } from "fdb-tuple"; import { - CHUNK_VERSIONED, - CURRENT_VERSION, - encodeRecord, type ActiveSpanRef, type Attributes, + CHUNK_VERSIONED, type Chunk, + CURRENT_VERSION, + encodeRecord, type KeyValue, - type Record as TraceRecord, type RecordBody, type SpanEnd, type SpanEvent, @@ -26,6 +25,7 @@ import { type SpanUpdate, type StringId, type TraceId, + type Record as TraceRecord, } from "../schemas/versioned.js"; import { hexFromBytes, @@ -177,7 +177,10 @@ export function createTraces( const unixMs = anchor.unixMs + (performance.now() - anchor.monoMs); const wholeMs = Math.floor(unixMs); const fracMs = unixMs - wholeMs; - return BigInt(wholeMs) * 1_000_000n + BigInt(Math.floor(fracMs * 1_000_000)); + return ( + BigInt(wholeMs) * 1_000_000n + + BigInt(Math.floor(fracMs * 1_000_000)) + ); } function createChunkState(bucketStartSec: number): ChunkState { @@ -223,9 +226,10 @@ export function createTraces( return id; } - function encodeAttributes( - attributes?: Record, - ): { attributes: Attributes; dropped: number } { + function encodeAttributes(attributes?: Record): { + attributes: Attributes; + dropped: number; + } { const list: KeyValue[] = []; let dropped = 0; if (!attributes) { @@ -239,7 +243,10 @@ export function createTraces( } try { const encoded = encodeCbor(sanitized); - list.push({ key: internString(key), value: toArrayBuffer(encoded) }); + list.push({ + key: internString(key), + value: toArrayBuffer(encoded), + }); } catch { dropped++; } @@ -275,11 +282,12 @@ export function createTraces( return value; } - function encodeLinks( - links?: StartSpanOptions["links"], - ): { links: SpanLink[]; dropped: number } { + function encodeLinks(links?: StartSpanOptions["links"]): { + links: SpanLink[]; + dropped: number; + } { const result: SpanLink[] = []; - let dropped = 0; + const dropped = 0; if (!links) { return { links: result, dropped }; } @@ -379,9 +387,10 @@ export function createTraces( }; } - function encodeAttributeMap( - attributes: AttributeMap, - ): { attributes: Attributes; dropped: number } { + function encodeAttributeMap(attributes: AttributeMap): { + attributes: Attributes; + dropped: number; + } { const list: KeyValue[] = []; let dropped = 0; for (const [key, value] of attributes.entries()) { @@ -392,7 +401,10 @@ export function createTraces( } try { const encoded = encodeCbor(sanitized); - list.push({ key: internString(key), value: toArrayBuffer(encoded) }); + list.push({ + key: internString(key), + value: toArrayBuffer(encoded), + }); } catch { dropped++; } @@ -425,9 +437,7 @@ export function createTraces( const key = strings[kv.key] ?? ""; try { map.set(key, decodeCbor(toUint8Array(kv.value)) as unknown); - } catch { - continue; - } + } catch {} } return map; } @@ -445,15 +455,15 @@ export function createTraces( })); } - function encodeLinkState( - links: LinkState[], - ): { links: SpanLink[]; dropped: number } { + function encodeLinkState(links: LinkState[]): { + links: SpanLink[]; + dropped: number; + } { const result: SpanLink[] = []; - let dropped = 0; + const dropped = 0; for (const link of links) { - const { attributes, dropped: droppedAttributes } = encodeAttributeMap( - link.attributes, - ); + const { attributes, dropped: droppedAttributes } = + encodeAttributeMap(link.attributes); result.push({ traceId: link.traceId, spanId: link.spanId, @@ -522,7 +532,10 @@ export function createTraces( chunk, CURRENT_VERSION, ); - const key = buildChunkKey(currentChunk.bucketStartSec, currentChunk.chunkId); + const key = buildChunkKey( + currentChunk.bucketStartSec, + currentChunk.chunkId, + ); const maxRecordNs = chunk.records.length > 0 ? chunk.baseUnixNs + @@ -608,7 +621,9 @@ export function createTraces( assertActive(parent); } const spanIdBytes = randomBytes(SPAN_ID_BYTES); - const traceIdBytes = parent ? parent.traceId : randomBytes(TRACE_ID_BYTES); + const traceIdBytes = parent + ? parent.traceId + : randomBytes(TRACE_ID_BYTES); const spanId = toArrayBuffer(spanIdBytes); const traceId = toArrayBuffer(traceIdBytes); const parentSpanId = parent ? toArrayBuffer(parent.spanId) : null; @@ -650,7 +665,8 @@ export function createTraces( droppedLinksCount: spanStart.droppedLinksCount, status: null, startTimeUnixNs: - currentChunk.baseUnixNs + currentChunk.records[recordIndex].timeOffsetNs, + currentChunk.baseUnixNs + + currentChunk.records[recordIndex].timeOffsetNs, depth, bytesSinceSnapshot: encodedBytes, lastSnapshotMonoMs: performance.now(), @@ -711,7 +727,11 @@ export function createTraces( const { encodedBytes } = appendRecord( () => ({ tag: "SpanEvent", - val: createSpanEventRecord(toArrayBuffer(handle.spanId), name, options), + val: createSpanEventRecord( + toArrayBuffer(handle.spanId), + name, + options, + ), }), options?.timeUnixMs, ); @@ -731,7 +751,10 @@ export function createTraces( dropSpan(handle.spanId); } - function maybeSnapshot(spanId: SpanId | Uint8Array, state: SpanState): void { + function maybeSnapshot( + spanId: SpanId | Uint8Array, + state: SpanState, + ): void { if ( state.bytesSinceSnapshot < snapshotBytesThreshold && performance.now() - state.lastSnapshotMonoMs < snapshotIntervalMs @@ -748,10 +771,10 @@ export function createTraces( activeSpanRefs.set(key, { ...ref, latestSnapshotKey: { - prefix: KEY_PREFIX.DATA, - bucketStartSec: BigInt(currentChunk.bucketStartSec), - chunkId: currentChunk.chunkId, - recordIndex, + prefix: KEY_PREFIX.DATA, + bucketStartSec: BigInt(currentChunk.bucketStartSec), + chunkId: currentChunk.chunkId, + recordIndex, }, }); } @@ -1064,7 +1087,10 @@ export function createTraces( }; } - function buildChunkKey(bucketStartSec: number, chunkId: number): Uint8Array { + function buildChunkKey( + bucketStartSec: number, + chunkId: number, + ): Uint8Array { return pack([KEY_PREFIX.DATA, bucketStartSec, chunkId]); } @@ -1076,11 +1102,11 @@ export function createTraces( } } - async function loadBaseRecord( - ref: ActiveSpanRef, - ): Promise< - { record: TraceRecord; strings: readonly string[]; absNs: bigint } | null - > { + async function loadBaseRecord(ref: ActiveSpanRef): Promise<{ + record: TraceRecord; + strings: readonly string[]; + absNs: bigint; + } | null> { const key = ref.latestSnapshotKey ?? ref.startKey; const bucketStartSec = toNumber(key.bucketStartSec); const fromMemory = findChunkInMemory(bucketStartSec, key.chunkId); diff --git a/rivetkit-typescript/packages/traces/tests/traces.test.ts b/rivetkit-typescript/packages/traces/tests/traces.test.ts index 61ca974ab2..4bd89b19c4 100644 --- a/rivetkit-typescript/packages/traces/tests/traces.test.ts +++ b/rivetkit-typescript/packages/traces/tests/traces.test.ts @@ -1,9 +1,9 @@ -import { describe, expect, it, vi } from "vitest"; import { performance } from "node:perf_hooks"; -import { pack, unpack } from "fdb-tuple"; +import { pack, unpack } from "@rivetkit/fdb-tuple"; +import { describe, expect, it, vi } from "vitest"; +import { CHUNK_VERSIONED } from "../schemas/versioned.js"; import { createTraces } from "../src/index.js"; import type { TracesDriver } from "../src/types.js"; -import { CHUNK_VERSIONED } from "../schemas/versioned.js"; class InMemoryTracesDriver implements TracesDriver { private store = new Map(); @@ -31,13 +31,18 @@ class InMemoryTracesDriver implements TracesDriver { } } - async list(prefix: Uint8Array): Promise> { + async list( + prefix: Uint8Array, + ): Promise> { const prefixBuf = Buffer.from(prefix); const entries: Array<{ key: Uint8Array; value: Uint8Array }> = []; for (const [key, value] of this.store.entries()) { const keyBuf = Buffer.from(key, "hex"); if (hasPrefix(keyBuf, prefixBuf)) { - entries.push({ key: new Uint8Array(keyBuf), value: new Uint8Array(value) }); + entries.push({ + key: new Uint8Array(keyBuf), + value: new Uint8Array(value), + }); } } entries.sort((a, b) => Buffer.compare(a.key, b.key)); @@ -61,7 +66,10 @@ class InMemoryTracesDriver implements TracesDriver { if (Buffer.compare(keyBuf, endBuf) >= 0) { continue; } - entries.push({ key: new Uint8Array(keyBuf), value: new Uint8Array(value) }); + entries.push({ + key: new Uint8Array(keyBuf), + value: new Uint8Array(value), + }); } entries.sort((a, b) => Buffer.compare(a.key, b.key)); @@ -74,7 +82,9 @@ class InMemoryTracesDriver implements TracesDriver { return entries; } - async batch(writes: Array<{ key: Uint8Array; value: Uint8Array }>): Promise { + async batch( + writes: Array<{ key: Uint8Array; value: Uint8Array }>, + ): Promise { for (const write of writes) { this.store.set(toKey(write.key), new Uint8Array(write.value)); } @@ -143,7 +153,9 @@ function installFakeClock(initialUnixMs = 1_700_000_000_000): FakeClock { let unixMs = initialUnixMs; let monoMs = 0; const dateSpy = vi.spyOn(Date, "now").mockImplementation(() => unixMs); - const perfSpy = vi.spyOn(performance, "now").mockImplementation(() => monoMs); + const perfSpy = vi + .spyOn(performance, "now") + .mockImplementation(() => monoMs); return { nowUnixMs: () => unixMs, @@ -171,11 +183,15 @@ describe("traces", () => { const traces = createTraces({ driver, resource: { - attributes: [{ key: "service.name", value: { stringValue: "test" } }], + attributes: [ + { key: "service.name", value: { stringValue: "test" } }, + ], }, }); - const root = traces.startSpan("root", { attributes: { foo: "bar" } }); + const root = traces.startSpan("root", { + attributes: { foo: "bar" }, + }); traces.setAttributes(root, { count: 2 }); traces.emitEvent(root, "evt", { attributes: { ok: true } }); traces.setStatus(root, { code: "OK" }); @@ -257,8 +273,9 @@ describe("traces", () => { const entries = driver.entries(); expect(entries.length).toBeGreaterThan(1); - const decoded = entries.map((entry) => - unpack(Buffer.from(entry.key)) as [number, number, number], + const decoded = entries.map( + (entry) => + unpack(Buffer.from(entry.key)) as [number, number, number], ); for (const tuple of decoded) { expect(tuple[0]).toBe(1); @@ -267,7 +284,8 @@ describe("traces", () => { const prev = decoded[i - 1]; const curr = decoded[i]; const inOrder = - curr[1] > prev[1] || (curr[1] === prev[1] && curr[2] >= prev[2]); + curr[1] > prev[1] || + (curr[1] === prev[1] && curr[2] >= prev[2]); expect(inOrder).toBe(true); } } finally { @@ -290,7 +308,9 @@ describe("traces", () => { await traces.flush(); const entry = driver.entries()[0]; - const chunk = CHUNK_VERSIONED.deserializeWithEmbeddedVersion(entry.value); + const chunk = CHUNK_VERSIONED.deserializeWithEmbeddedVersion( + entry.value, + ); const hasSnapshot = chunk.records.some( (record) => record.body.tag === "SpanSnapshot", ); @@ -707,7 +727,9 @@ describe("traces", () => { const spans = result.otlp.resourceSpans[0].scopeSpans[0].spans; expect(spans).toHaveLength(1); - expect(spans[0].events?.map((evt) => evt.name)).toEqual(["forward"]); + expect(spans[0].events?.map((evt) => evt.name)).toEqual([ + "forward", + ]); } finally { clock.restore(); } diff --git a/rivetkit-typescript/packages/workflow-engine/package.json b/rivetkit-typescript/packages/workflow-engine/package.json index f96b4bf6dd..1bf9fd02f6 100644 --- a/rivetkit-typescript/packages/workflow-engine/package.json +++ b/rivetkit-typescript/packages/workflow-engine/package.json @@ -53,7 +53,7 @@ "dependencies": { "@rivetkit/bare-ts": "^0.6.2", "cbor-x": "^1.6.0", - "fdb-tuple": "^1.0.0", + "@rivetkit/fdb-tuple": "https://pkg.pr.new/rivet-dev/fdb-tuple/@rivetkit/fdb-tuple@32f95e2", "vbare": "^0.0.4" }, "devDependencies": { diff --git a/rivetkit-typescript/packages/workflow-engine/src/keys.ts b/rivetkit-typescript/packages/workflow-engine/src/keys.ts index cf7a36f81d..1774e1c0f2 100644 --- a/rivetkit-typescript/packages/workflow-engine/src/keys.ts +++ b/rivetkit-typescript/packages/workflow-engine/src/keys.ts @@ -3,7 +3,7 @@ * All keys are encoded as tuples with integer prefixes for proper sorting. */ -import * as tuple from "fdb-tuple"; +import * as tuple from "@rivetkit/fdb-tuple"; import type { Location, LoopIterationMarker, PathSegment } from "./types.js"; // === Key Prefixes === @@ -80,34 +80,19 @@ function tupleElementsToLocation(elements: TupleItem[]): Location { // === Helper Functions === -/** - * Convert Buffer to Uint8Array. - */ -function bufferToUint8Array(buf: Buffer): Uint8Array { - return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); -} - -/** - * Convert Uint8Array to Buffer. - */ -function uint8ArrayToBuffer(arr: Uint8Array): Buffer { - return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength); -} - /** * Pack tuple items and return as Uint8Array. */ function pack(items: TupleItem | TupleItem[]): Uint8Array { const buf = tuple.pack(items); - return bufferToUint8Array(buf); + return buf; } /** * Unpack a Uint8Array and return tuple items. */ function unpack(data: Uint8Array): TupleItem[] { - const buf = uint8ArrayToBuffer(data); - return tuple.unpack(buf) as TupleItem[]; + return tuple.unpack(data) as TupleItem[]; } // === Key Builders ===