bitfab 0.13.8 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-QT7HWOKU.js +131 -0
- package/dist/chunk-QT7HWOKU.js.map +1 -0
- package/dist/{chunk-4ANYHNQJ.js → chunk-YPG3XIG4.js} +372 -13
- package/dist/chunk-YPG3XIG4.js.map +1 -0
- package/dist/index.cjs +427 -391
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -13
- package/dist/index.d.ts +11 -13
- package/dist/index.js +6 -6
- package/dist/node.cjs +427 -391
- package/dist/node.cjs.map +1 -1
- package/dist/node.js +5 -5
- package/dist/{replay-F7K2JQCZ.js → replay-3MQS22GS.js} +62 -16
- package/dist/replay-3MQS22GS.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-4ANYHNQJ.js.map +0 -1
- package/dist/chunk-VFGUZWAV.js +0 -467
- package/dist/chunk-VFGUZWAV.js.map +0 -1
- package/dist/replay-F7K2JQCZ.js.map +0 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var BitfabError = class extends Error {
|
|
3
|
+
constructor(message, url) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.url = url;
|
|
6
|
+
this.name = "BitfabError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// src/asyncStorage.ts
|
|
11
|
+
var AsyncLocalStorageClass = null;
|
|
12
|
+
var initDone = false;
|
|
13
|
+
function registerAsyncLocalStorageClass(cls) {
|
|
14
|
+
if (!AsyncLocalStorageClass) {
|
|
15
|
+
AsyncLocalStorageClass = cls;
|
|
16
|
+
}
|
|
17
|
+
initDone = true;
|
|
18
|
+
}
|
|
19
|
+
function assertAsyncStorageRegistered() {
|
|
20
|
+
if (!AsyncLocalStorageClass) {
|
|
21
|
+
console.warn(
|
|
22
|
+
"Bitfab: AsyncLocalStorage not available \u2014 nested span context will not propagate."
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
var asyncStorageReady = (typeof process !== "undefined" && process.versions?.node ? (
|
|
27
|
+
// The join trick hides "node:async_hooks" from static analysis so
|
|
28
|
+
// bundlers that ban Node.js built-ins don't fail at build time.
|
|
29
|
+
// webpackIgnore tells webpack/turbopack to emit a native import()
|
|
30
|
+
// so Node.js can resolve the module at runtime.
|
|
31
|
+
import(
|
|
32
|
+
/* webpackIgnore: true */
|
|
33
|
+
["node", "async_hooks"].join(":")
|
|
34
|
+
).then(
|
|
35
|
+
(mod) => {
|
|
36
|
+
registerAsyncLocalStorageClass(mod.AsyncLocalStorage);
|
|
37
|
+
}
|
|
38
|
+
).catch(() => {
|
|
39
|
+
})
|
|
40
|
+
) : Promise.resolve()).then(() => {
|
|
41
|
+
initDone = true;
|
|
42
|
+
});
|
|
43
|
+
function isAsyncStorageInitDone() {
|
|
44
|
+
return initDone;
|
|
45
|
+
}
|
|
46
|
+
function createAsyncLocalStorage() {
|
|
47
|
+
return AsyncLocalStorageClass ? new AsyncLocalStorageClass() : null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/replayContext.ts
|
|
51
|
+
var replayContextStorage = null;
|
|
52
|
+
var replayContextReady = asyncStorageReady.then(() => {
|
|
53
|
+
replayContextStorage = createAsyncLocalStorage();
|
|
54
|
+
});
|
|
55
|
+
function getReplayContext() {
|
|
56
|
+
return replayContextStorage?.getStore() ?? null;
|
|
57
|
+
}
|
|
58
|
+
function runWithReplayContext(ctx, fn) {
|
|
59
|
+
if (replayContextStorage) {
|
|
60
|
+
return replayContextStorage.run(ctx, fn);
|
|
61
|
+
}
|
|
62
|
+
return fn();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/serialize.ts
|
|
66
|
+
import superjson from "superjson";
|
|
67
|
+
var MAX_SERIALIZED_BYTES = 512e3;
|
|
68
|
+
function describeValue(value) {
|
|
69
|
+
try {
|
|
70
|
+
const ctorName = value?.constructor?.name;
|
|
71
|
+
if (ctorName && ctorName !== "Object") {
|
|
72
|
+
return ctorName;
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
return typeof value;
|
|
77
|
+
}
|
|
78
|
+
function unserializableStub(value, reason) {
|
|
79
|
+
let summary;
|
|
80
|
+
try {
|
|
81
|
+
summary = `<unserializable: ${describeValue(value)} (${reason})>`;
|
|
82
|
+
} catch {
|
|
83
|
+
summary = `<unserializable (${reason})>`;
|
|
84
|
+
}
|
|
85
|
+
return { json: summary };
|
|
86
|
+
}
|
|
87
|
+
function serializeValue(value) {
|
|
88
|
+
try {
|
|
89
|
+
const { json, meta } = superjson.serialize(value);
|
|
90
|
+
let size;
|
|
91
|
+
try {
|
|
92
|
+
size = JSON.stringify(json).length;
|
|
93
|
+
} catch {
|
|
94
|
+
return unserializableStub(value, "stringify_failed_after_superjson");
|
|
95
|
+
}
|
|
96
|
+
if (size > MAX_SERIALIZED_BYTES) {
|
|
97
|
+
return unserializableStub(value, `too_large_${size}_bytes`);
|
|
98
|
+
}
|
|
99
|
+
return meta ? { json, meta } : { json };
|
|
100
|
+
} catch {
|
|
101
|
+
try {
|
|
102
|
+
return { json: JSON.parse(JSON.stringify(value)) };
|
|
103
|
+
} catch {
|
|
104
|
+
return unserializableStub(value, "json_stringify_failed");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function deserializeValue(serialized) {
|
|
109
|
+
if (serialized.meta === void 0) {
|
|
110
|
+
return serialized.json;
|
|
111
|
+
}
|
|
112
|
+
return superjson.deserialize({
|
|
113
|
+
json: serialized.json,
|
|
114
|
+
meta: serialized.meta
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export {
|
|
119
|
+
BitfabError,
|
|
120
|
+
registerAsyncLocalStorageClass,
|
|
121
|
+
assertAsyncStorageRegistered,
|
|
122
|
+
asyncStorageReady,
|
|
123
|
+
isAsyncStorageInitDone,
|
|
124
|
+
createAsyncLocalStorage,
|
|
125
|
+
replayContextReady,
|
|
126
|
+
getReplayContext,
|
|
127
|
+
runWithReplayContext,
|
|
128
|
+
serializeValue,
|
|
129
|
+
deserializeValue
|
|
130
|
+
};
|
|
131
|
+
//# sourceMappingURL=chunk-QT7HWOKU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/asyncStorage.ts","../src/replayContext.ts","../src/serialize.ts"],"sourcesContent":["/**\n * Shared error type for Bitfab SDK runtime errors. Lives in its own\n * module to avoid import cycles between `http.ts` and modules that need\n * to throw structured errors (e.g. `dbSnapshot.ts` validation).\n */\n\nexport class BitfabError extends Error {\n constructor(\n message: string,\n public readonly url?: string,\n ) {\n super(message)\n this.name = \"BitfabError\"\n }\n}\n","/**\n * Shared AsyncLocalStorage loader.\n *\n * Provides two ways to initialize AsyncLocalStorage:\n *\n * 1. **Synchronous registration** (preferred for Node.js):\n * `asyncStorageNode.ts` calls `registerAsyncLocalStorageClass()` at module\n * evaluation time, so the class is available immediately — no async gap.\n * The `node.ts` entry point imports it before anything else.\n *\n * 2. **Async dynamic import** (fallback for the default entry point):\n * Loads `node:async_hooks` via a bundler-safe dynamic import. This is used\n * by the default `index.ts` entry point so the SDK works in browsers\n * (where the import silently fails) and in Node.js when imported via the\n * default entry point.\n *\n * ## Why the dynamic import looks like this\n *\n * We need to handle three environments:\n *\n * 1. **Pure Node.js** — `import(\"node:async_hooks\")` works natively.\n * 2. **Webpack/Turbopack (Next.js server)** — The bundler processes\n * `import()` calls at build time. The `webpackIgnore` magic comment tells\n * webpack (and turbopack) to emit a native `import()` call instead of\n * trying to resolve it, so Node.js handles it at runtime.\n * 3. **Browsers / Edge** — The `process.versions?.node` guard prevents\n * execution entirely. If it somehow runs, `.catch(() => {})` swallows\n * the failure.\n */\n\nexport interface AsyncLocalStorageLike<T> {\n getStore(): T | undefined\n run<R>(store: T, fn: () => R): R\n}\n\nlet AsyncLocalStorageClass: (new () => AsyncLocalStorageLike<unknown>) | null =\n null\nlet initDone = false\n\n/**\n * Register the AsyncLocalStorage class synchronously.\n *\n * Called by `asyncStorageNode.ts` at module evaluation time so the class\n * is available before any span is created — no async gap, no race condition.\n *\n * Safe to call multiple times; subsequent calls are no-ops.\n */\nexport function registerAsyncLocalStorageClass(\n cls: new () => AsyncLocalStorageLike<unknown>,\n): void {\n if (!AsyncLocalStorageClass) {\n AsyncLocalStorageClass = cls\n }\n initDone = true\n}\n\n/**\n * Assert that AsyncLocalStorage was registered successfully.\n *\n * Called by `node.ts` after importing `asyncStorageNode.ts` to catch\n * import-order bugs at startup rather than silently degrading to the\n * browser fallback (flat spans with no nesting).\n *\n * This should ONLY be called from the Node.js entry point where we\n * know `node:async_hooks` must be available.\n */\nexport function assertAsyncStorageRegistered(): void {\n if (!AsyncLocalStorageClass) {\n console.warn(\n \"Bitfab: AsyncLocalStorage not available — nested span context will not propagate.\",\n )\n }\n}\n\nexport const asyncStorageReady: Promise<void> = (\n typeof process !== \"undefined\" && process.versions?.node\n ? // The join trick hides \"node:async_hooks\" from static analysis so\n // bundlers that ban Node.js built-ins don't fail at build time.\n // webpackIgnore tells webpack/turbopack to emit a native import()\n // so Node.js can resolve the module at runtime.\n import(\n /* webpackIgnore: true */\n [\"node\", \"async_hooks\"].join(\":\")\n )\n .then(\n (mod: {\n AsyncLocalStorage: new () => AsyncLocalStorageLike<unknown>\n }) => {\n registerAsyncLocalStorageClass(mod.AsyncLocalStorage)\n },\n )\n .catch(() => {})\n : Promise.resolve()\n).then(() => {\n initDone = true\n})\n\nexport function isAsyncStorageInitDone(): boolean {\n return initDone\n}\n\nexport function createAsyncLocalStorage<T>(): AsyncLocalStorageLike<T> | null {\n return AsyncLocalStorageClass\n ? (new AsyncLocalStorageClass() as AsyncLocalStorageLike<T>)\n : null\n}\n","/**\n * Replay context propagation via AsyncLocalStorage.\n *\n * When set, the withSpan wrapper injects testRunId into the span payload\n * so that new spans created during replay are linked to the test run.\n * Optionally carries a mock tree so child spans can return historical\n * outputs instead of executing.\n */\n\nimport {\n type AsyncLocalStorageLike,\n asyncStorageReady,\n createAsyncLocalStorage,\n} from \"./asyncStorage.js\"\n\n/** A single span entry in the mock tree with its historical output. */\nexport interface MockSpan {\n sourceSpanId: string\n output: unknown\n outputMeta?: unknown\n}\n\n/**\n * Per-item DB branch resolved by the Bitfab service from the source\n * trace's `dbSnapshotRef`. Carried on the replay context so that\n * customer code reads `databaseUrl` through `ReplayEnvironment`, and so\n * the process-isolated replay runner can materialize it into a `.env`\n * overlay file before customer code initializes its DB client.\n *\n * `neonBranchId` is the literal Neon branch id; passing it to\n * `releaseDbBranchLease` deletes that branch.\n */\nexport interface DbBranchLease {\n neonBranchId: string\n /** Env var name the customer's app reads, e.g. \"DATABASE_URL\". */\n envKey: string\n databaseUrl: string\n expiresAt: string\n providerConsoleUrl?: string\n readOnly?: boolean\n}\n\n/**\n * Pre-built lookup table of historical span outputs.\n * Keys are `${traceFunctionKey}:${spanName}:${callIndex}` so that repeated\n * calls with the same (key, name) are matched by call order, but spans\n * sharing only the traceFunctionKey (different name) do not collide.\n */\nexport interface MockTree {\n spans: Map<string, MockSpan>\n}\n\nexport interface ReplayContext {\n testRunId: string\n traceId?: string\n inputSourceSpanId?: string\n /**\n * External trace ID from `external_traces.id`. Used for span-chain\n * lookup against the source platform's trace tree (Braintrust, etc.).\n * NOT the same as the Bitfab `traceId` — see `sourceBitfabTraceId`.\n */\n inputSourceTraceId?: string\n /**\n * The Bitfab `traces.id` of the historical trace that produced this\n * replay item's input. This is what customer-facing surfaces (e.g.\n * `ReplayEnvironment.traceId`) should expose, since it's the ID the\n * customer sees in the Bitfab dashboard.\n */\n sourceBitfabTraceId?: string\n mockTree?: MockTree\n callCounters?: Map<string, number>\n mockStrategy?: \"none\" | \"all\" | \"marked\"\n dbBranchLease?: DbBranchLease\n /**\n * Collector for the replay item's trace-persistence work. When present,\n * the root span's send path pushes a promise that resolves only after\n * every span upload AND the trace completion have been sent (registered\n * synchronously at send time, so the replay runner can await it after\n * the wrapped fn resolves). This is what lets replay guarantee traces\n * are persisted server-side before `completeReplay` builds the\n * trace-ID mapping. Absent outside replay, where sends stay\n * fire-and-forget.\n */\n pendingPersistence?: Promise<unknown>[]\n}\n\nlet replayContextStorage: AsyncLocalStorageLike<ReplayContext | null> | null =\n null\n\nexport const replayContextReady: Promise<void> = asyncStorageReady.then(() => {\n replayContextStorage = createAsyncLocalStorage<ReplayContext | null>()\n})\n\n/** Get the current replay context, if any. */\nexport function getReplayContext(): ReplayContext | null {\n return replayContextStorage?.getStore() ?? null\n}\n\n/** Run a function within a replay context. */\nexport function runWithReplayContext<T>(ctx: ReplayContext, fn: () => T): T {\n if (replayContextStorage) {\n return replayContextStorage.run(ctx, fn)\n }\n return fn()\n}\n","/**\n * Serialization utilities for Bitfab SDK.\n *\n * This module provides serialization with type metadata preservation,\n * using superjson for handling special JavaScript types like Date, Map,\n * Set, BigInt, undefined, etc.\n */\n\nimport superjson from \"superjson\"\n\n/**\n * Serialized value with JSON data and optional superjson meta for type preservation.\n *\n * The json field contains the JSON-serializable data.\n * The meta field (if present) contains superjson type information for deserializing\n * special types like Date, Map, Set, BigInt, etc.\n */\nexport interface SerializedValue {\n json: unknown\n meta?: unknown\n}\n\n// Cap on serialized payload size. superjson can succeed on values like SDK\n// client instances (OpenAI, etc.) and produce hundreds of KB to MB of useless\n// internal state. Anything beyond this is replaced with a stub so the span\n// still ships and the trace isn't dropped server-side.\nconst MAX_SERIALIZED_BYTES = 512_000\n\nfunction describeValue(value: unknown): string {\n try {\n const ctorName = (value as { constructor?: { name?: string } })?.constructor\n ?.name\n if (ctorName && ctorName !== \"Object\") {\n return ctorName\n }\n } catch {\n // Property access on `value` can throw (Proxy, poisoned getter).\n }\n return typeof value\n}\n\nfunction unserializableStub(value: unknown, reason: string): SerializedValue {\n let summary: string\n try {\n summary = `<unserializable: ${describeValue(value)} (${reason})>`\n } catch {\n summary = `<unserializable (${reason})>`\n }\n return { json: summary }\n}\n\n/**\n * Serialize a value using superjson for trace storage.\n *\n * Handles arbitrary JavaScript values including:\n * - Date, RegExp, Error\n * - Map, Set\n * - BigInt\n * - undefined (in objects/arrays)\n * - Circular references\n *\n * Guarantees:\n * - Never throws. Pathological inputs (SDK clients, proxies, poisoned\n * getters, circular graphs that defeat superjson) return a stub string.\n * - Never returns a payload larger than MAX_SERIALIZED_BYTES; oversized\n * inputs are replaced with a stub. Without this the wire-side\n * `JSON.stringify` in http.ts can produce a request that times out or\n * gets rejected, leaving a trace with zero spans.\n *\n * @param value - Any JavaScript value to serialize\n * @returns SerializedValue with 'json' field containing the data.\n * If type metadata is needed for reconstruction, includes 'meta' field.\n *\n * @example\n * ```typescript\n * const result = serializeValue(new Date('2024-01-15T10:30:00Z'))\n * // result.json contains the ISO string\n * // result.meta contains type info for Date reconstruction\n * ```\n */\nexport function serializeValue(value: unknown): SerializedValue {\n try {\n const { json, meta } = superjson.serialize(value)\n\n let size: number\n try {\n size = JSON.stringify(json).length\n } catch {\n return unserializableStub(value, \"stringify_failed_after_superjson\")\n }\n if (size > MAX_SERIALIZED_BYTES) {\n return unserializableStub(value, `too_large_${size}_bytes`)\n }\n\n return meta ? { json, meta } : { json }\n } catch {\n try {\n return { json: JSON.parse(JSON.stringify(value)) }\n } catch {\n return unserializableStub(value, \"json_stringify_failed\")\n }\n }\n}\n\n/**\n * Deserialize a value that was serialized with serializeValue.\n *\n * @param serialized - A SerializedValue object with 'json' and optional 'meta'\n * @returns The reconstructed JavaScript value\n *\n * @example\n * ```typescript\n * const serialized = serializeValue(new Date('2024-01-15'))\n * const date = deserializeValue(serialized)\n * // date is a Date object\n * ```\n */\nexport function deserializeValue(serialized: SerializedValue): unknown {\n if (serialized.meta === undefined) {\n // No metadata, return as-is\n return serialized.json\n }\n\n // Use superjson to deserialize with type reconstruction\n // Cast json to the expected superjson type\n type SuperJSONResult = Parameters<typeof superjson.deserialize>[0]\n return superjson.deserialize({\n json: serialized.json as SuperJSONResult[\"json\"],\n meta: serialized.meta as SuperJSONResult[\"meta\"],\n })\n}\n"],"mappings":";AAMO,IAAM,cAAN,cAA0B,MAAM;AAAA,EACrC,YACE,SACgB,KAChB;AACA,UAAM,OAAO;AAFG;AAGhB,SAAK,OAAO;AAAA,EACd;AACF;;;ACqBA,IAAI,yBACF;AACF,IAAI,WAAW;AAUR,SAAS,+BACd,KACM;AACN,MAAI,CAAC,wBAAwB;AAC3B,6BAAyB;AAAA,EAC3B;AACA,aAAW;AACb;AAYO,SAAS,+BAAqC;AACnD,MAAI,CAAC,wBAAwB;AAC3B,YAAQ;AAAA,MACN;AAAA,IACF;AAAA,EACF;AACF;AAEO,IAAM,qBACX,OAAO,YAAY,eAAe,QAAQ,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,EAKhD;AAAA;AAAA,IAEE,CAAC,QAAQ,aAAa,EAAE,KAAK,GAAG;AAAA,IAE/B;AAAA,IACC,CAAC,QAEK;AACJ,qCAA+B,IAAI,iBAAiB;AAAA,IACtD;AAAA,EACF,EACC,MAAM,MAAM;AAAA,EAAC,CAAC;AAAA,IACjB,QAAQ,QAAQ,GACpB,KAAK,MAAM;AACX,aAAW;AACb,CAAC;AAEM,SAAS,yBAAkC;AAChD,SAAO;AACT;AAEO,SAAS,0BAA8D;AAC5E,SAAO,yBACF,IAAI,uBAAuB,IAC5B;AACN;;;ACnBA,IAAI,uBACF;AAEK,IAAM,qBAAoC,kBAAkB,KAAK,MAAM;AAC5E,yBAAuB,wBAA8C;AACvE,CAAC;AAGM,SAAS,mBAAyC;AACvD,SAAO,sBAAsB,SAAS,KAAK;AAC7C;AAGO,SAAS,qBAAwB,KAAoB,IAAgB;AAC1E,MAAI,sBAAsB;AACxB,WAAO,qBAAqB,IAAI,KAAK,EAAE;AAAA,EACzC;AACA,SAAO,GAAG;AACZ;;;AChGA,OAAO,eAAe;AAkBtB,IAAM,uBAAuB;AAE7B,SAAS,cAAc,OAAwB;AAC7C,MAAI;AACF,UAAM,WAAY,OAA+C,aAC7D;AACJ,QAAI,YAAY,aAAa,UAAU;AACrC,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO,OAAO;AAChB;AAEA,SAAS,mBAAmB,OAAgB,QAAiC;AAC3E,MAAI;AACJ,MAAI;AACF,cAAU,oBAAoB,cAAc,KAAK,CAAC,KAAK,MAAM;AAAA,EAC/D,QAAQ;AACN,cAAU,oBAAoB,MAAM;AAAA,EACtC;AACA,SAAO,EAAE,MAAM,QAAQ;AACzB;AA+BO,SAAS,eAAe,OAAiC;AAC9D,MAAI;AACF,UAAM,EAAE,MAAM,KAAK,IAAI,UAAU,UAAU,KAAK;AAEhD,QAAI;AACJ,QAAI;AACF,aAAO,KAAK,UAAU,IAAI,EAAE;AAAA,IAC9B,QAAQ;AACN,aAAO,mBAAmB,OAAO,kCAAkC;AAAA,IACrE;AACA,QAAI,OAAO,sBAAsB;AAC/B,aAAO,mBAAmB,OAAO,aAAa,IAAI,QAAQ;AAAA,IAC5D;AAEA,WAAO,OAAO,EAAE,MAAM,KAAK,IAAI,EAAE,KAAK;AAAA,EACxC,QAAQ;AACN,QAAI;AACF,aAAO,EAAE,MAAM,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC,EAAE;AAAA,IACnD,QAAQ;AACN,aAAO,mBAAmB,OAAO,uBAAuB;AAAA,IAC1D;AAAA,EACF;AACF;AAeO,SAAS,iBAAiB,YAAsC;AACrE,MAAI,WAAW,SAAS,QAAW;AAEjC,WAAO,WAAW;AAAA,EACpB;AAKA,SAAO,UAAU,YAAY;AAAA,IAC3B,MAAM,WAAW;AAAA,IACjB,MAAM,WAAW;AAAA,EACnB,CAAC;AACH;","names":[]}
|
|
@@ -1,14 +1,350 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BitfabError,
|
|
3
|
-
DEFAULT_SERVICE_URL,
|
|
4
|
-
HttpClient,
|
|
5
3
|
asyncStorageReady,
|
|
6
4
|
createAsyncLocalStorage,
|
|
7
5
|
deserializeValue,
|
|
8
6
|
getReplayContext,
|
|
9
7
|
isAsyncStorageInitDone,
|
|
10
8
|
serializeValue
|
|
11
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QT7HWOKU.js";
|
|
10
|
+
|
|
11
|
+
// src/version.generated.ts
|
|
12
|
+
var __version__ = "0.15.0";
|
|
13
|
+
|
|
14
|
+
// src/constants.ts
|
|
15
|
+
var DEFAULT_SERVICE_URL = "https://bitfab.ai";
|
|
16
|
+
|
|
17
|
+
// src/http.ts
|
|
18
|
+
var pendingTracePromises = /* @__PURE__ */ new Set();
|
|
19
|
+
function awaitOnExit(promise) {
|
|
20
|
+
pendingTracePromises.add(promise);
|
|
21
|
+
void promise.finally(() => {
|
|
22
|
+
pendingTracePromises.delete(promise);
|
|
23
|
+
}).catch(() => {
|
|
24
|
+
});
|
|
25
|
+
return promise;
|
|
26
|
+
}
|
|
27
|
+
async function flushTraces(timeoutMs = 5e3) {
|
|
28
|
+
if (pendingTracePromises.size === 0) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
await Promise.race([
|
|
32
|
+
Promise.allSettled(Array.from(pendingTracePromises)),
|
|
33
|
+
new Promise((resolve) => setTimeout(resolve, timeoutMs))
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
36
|
+
if (typeof process !== "undefined" && process.versions != null && process.versions.node != null) {
|
|
37
|
+
let isFlushing = false;
|
|
38
|
+
process.on("beforeExit", () => {
|
|
39
|
+
if (pendingTracePromises.size > 0 && !isFlushing) {
|
|
40
|
+
isFlushing = true;
|
|
41
|
+
Promise.allSettled(
|
|
42
|
+
Array.from(pendingTracePromises).map(
|
|
43
|
+
(p) => p.catch(() => {
|
|
44
|
+
})
|
|
45
|
+
)
|
|
46
|
+
).then(() => {
|
|
47
|
+
isFlushing = false;
|
|
48
|
+
}).catch(() => {
|
|
49
|
+
isFlushing = false;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
var HttpClient = class {
|
|
55
|
+
constructor(config) {
|
|
56
|
+
this.apiKey = config.apiKey;
|
|
57
|
+
this.serviceUrl = config.serviceUrl;
|
|
58
|
+
this.timeout = config.timeout ?? 12e4;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Make an HTTP request to the Bitfab API. Defaults to POST; pass
|
|
62
|
+
* `options.method` to use a different verb (e.g. "PATCH").
|
|
63
|
+
*
|
|
64
|
+
* @param endpoint - The API endpoint (without base URL)
|
|
65
|
+
* @param payload - The request body
|
|
66
|
+
* @param options - Optional request options
|
|
67
|
+
* @returns The parsed JSON response
|
|
68
|
+
* @throws {BitfabError} If the request fails
|
|
69
|
+
*/
|
|
70
|
+
async request(endpoint, payload, options) {
|
|
71
|
+
const url = `${this.serviceUrl}${endpoint}`;
|
|
72
|
+
const timeout = options?.timeout ?? this.timeout;
|
|
73
|
+
const method = options?.method ?? "POST";
|
|
74
|
+
const controller = new AbortController();
|
|
75
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
76
|
+
let body;
|
|
77
|
+
let serializationError;
|
|
78
|
+
try {
|
|
79
|
+
body = JSON.stringify(payload);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
serializationError = error instanceof Error ? error.message : String(error);
|
|
82
|
+
body = JSON.stringify({
|
|
83
|
+
...Object.fromEntries(
|
|
84
|
+
Object.entries(payload).filter(
|
|
85
|
+
([, v]) => typeof v === "string" || typeof v === "number"
|
|
86
|
+
)
|
|
87
|
+
),
|
|
88
|
+
rawSpan: {},
|
|
89
|
+
errors: [
|
|
90
|
+
{ source: "sdk", step: "json_serialize", error: serializationError }
|
|
91
|
+
]
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const response = await fetch(url, {
|
|
96
|
+
method,
|
|
97
|
+
headers: {
|
|
98
|
+
"Content-Type": "application/json",
|
|
99
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
100
|
+
},
|
|
101
|
+
body,
|
|
102
|
+
signal: controller.signal
|
|
103
|
+
});
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
const errorText = await response.text();
|
|
106
|
+
throw new BitfabError(
|
|
107
|
+
`HTTP ${response.status}: ${errorText.slice(0, 500)}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
const result = await response.json();
|
|
111
|
+
if (result.error) {
|
|
112
|
+
if (result.url) {
|
|
113
|
+
throw new BitfabError(
|
|
114
|
+
`${result.error} Configure it at: ${this.serviceUrl}${result.url}`,
|
|
115
|
+
result.url
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
throw new BitfabError(result.error);
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error instanceof BitfabError) {
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
if (error instanceof Error) {
|
|
126
|
+
if (error.name === "AbortError") {
|
|
127
|
+
throw new BitfabError(`Request timed out after ${timeout}ms`);
|
|
128
|
+
}
|
|
129
|
+
throw new BitfabError(error.message);
|
|
130
|
+
}
|
|
131
|
+
throw new BitfabError("Unknown error occurred");
|
|
132
|
+
} finally {
|
|
133
|
+
clearTimeout(timeoutId);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Look up a function by name.
|
|
138
|
+
* Blocks until complete - needed for function execution.
|
|
139
|
+
*/
|
|
140
|
+
async lookupFunction(name) {
|
|
141
|
+
return this.request("/api/sdk/functions/lookup", { name });
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Send an internal trace (from BAML execution).
|
|
145
|
+
* Fire-and-forget with awaitOnExit - doesn't block the caller.
|
|
146
|
+
*/
|
|
147
|
+
sendInternalTrace(functionId, payload) {
|
|
148
|
+
void awaitOnExit(
|
|
149
|
+
this.request(`/api/sdk/functions/${functionId}/traces`, {
|
|
150
|
+
...payload,
|
|
151
|
+
sdkVersion: __version__
|
|
152
|
+
})
|
|
153
|
+
).catch((error) => {
|
|
154
|
+
try {
|
|
155
|
+
console.error("Bitfab: Failed to create trace:", error);
|
|
156
|
+
} catch {
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Send an external span (from withSpan wrapper or OpenAI tracing).
|
|
162
|
+
* Fire-and-forget with awaitOnExit - doesn't block the caller.
|
|
163
|
+
* Returns the tracked promise so callers can optionally await it.
|
|
164
|
+
*/
|
|
165
|
+
sendExternalSpan(payload) {
|
|
166
|
+
return awaitOnExit(
|
|
167
|
+
this.request("/api/sdk/externalSpans", {
|
|
168
|
+
...payload,
|
|
169
|
+
sdkVersion: __version__
|
|
170
|
+
})
|
|
171
|
+
).catch((error) => {
|
|
172
|
+
try {
|
|
173
|
+
console.error("Bitfab: Failed to create external span:", error);
|
|
174
|
+
} catch {
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Send an external trace (from OpenAI tracing).
|
|
180
|
+
* Fire-and-forget with awaitOnExit - doesn't block the caller.
|
|
181
|
+
* Returns the tracked promise so callers can optionally await it
|
|
182
|
+
* (the replay path does, so trace completions are persisted before
|
|
183
|
+
* `completeReplay` builds the trace-ID mapping).
|
|
184
|
+
*/
|
|
185
|
+
sendExternalTrace(payload) {
|
|
186
|
+
return awaitOnExit(
|
|
187
|
+
this.request("/api/sdk/externalTraces", {
|
|
188
|
+
...payload,
|
|
189
|
+
sdkVersion: __version__
|
|
190
|
+
})
|
|
191
|
+
).catch((error) => {
|
|
192
|
+
try {
|
|
193
|
+
console.error("Bitfab: Failed to create external trace:", error);
|
|
194
|
+
} catch {
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Partial update of an existing external trace identified by sourceTraceId.
|
|
200
|
+
* Used by the detached `client.getTrace(id)` handle. Fire-and-forget;
|
|
201
|
+
* returns a tracked promise that callers may optionally await.
|
|
202
|
+
*/
|
|
203
|
+
patchTrace(sourceTraceId, payload) {
|
|
204
|
+
const endpoint = `/api/sdk/externalTraces/${encodeURIComponent(sourceTraceId)}`;
|
|
205
|
+
return awaitOnExit(
|
|
206
|
+
this.request(endpoint, payload, { method: "PATCH" })
|
|
207
|
+
).catch((error) => {
|
|
208
|
+
try {
|
|
209
|
+
console.error("Bitfab: Failed to patch trace:", error);
|
|
210
|
+
} catch {
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Start a replay session by fetching historical traces.
|
|
216
|
+
* Blocking call — creates a test run and returns lightweight item references.
|
|
217
|
+
*/
|
|
218
|
+
async startReplay(traceFunctionKey, limit, traceIds, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId) {
|
|
219
|
+
const payload = { traceFunctionKey };
|
|
220
|
+
if (limit !== void 0) {
|
|
221
|
+
payload.limit = limit;
|
|
222
|
+
}
|
|
223
|
+
if (traceIds) {
|
|
224
|
+
payload.traceIds = traceIds;
|
|
225
|
+
}
|
|
226
|
+
if (codeChangeDescription !== void 0) {
|
|
227
|
+
payload.codeChangeDescription = codeChangeDescription;
|
|
228
|
+
}
|
|
229
|
+
if (codeChangeFiles !== void 0) {
|
|
230
|
+
payload.codeChangeFiles = codeChangeFiles;
|
|
231
|
+
}
|
|
232
|
+
if (includeDbBranchLease) {
|
|
233
|
+
payload.includeDbBranchLease = true;
|
|
234
|
+
}
|
|
235
|
+
if (experimentGroupId !== void 0) {
|
|
236
|
+
payload.experimentGroupId = experimentGroupId;
|
|
237
|
+
}
|
|
238
|
+
const timeout = includeDbBranchLease ? 18e4 : 3e4;
|
|
239
|
+
return this.request("/api/sdk/replay/start", payload, {
|
|
240
|
+
timeout
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Fetch an external span by ID.
|
|
245
|
+
* Blocking GET request.
|
|
246
|
+
*/
|
|
247
|
+
async getExternalSpan(spanId) {
|
|
248
|
+
const url = `${this.serviceUrl}/api/sdk/externalSpans/${spanId}`;
|
|
249
|
+
const controller = new AbortController();
|
|
250
|
+
const timeoutId = setTimeout(() => controller.abort(), 3e4);
|
|
251
|
+
try {
|
|
252
|
+
const response = await fetch(url, {
|
|
253
|
+
method: "GET",
|
|
254
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
255
|
+
signal: controller.signal
|
|
256
|
+
});
|
|
257
|
+
if (!response.ok) {
|
|
258
|
+
const errorText = await response.text();
|
|
259
|
+
throw new BitfabError(
|
|
260
|
+
`HTTP ${response.status}: ${errorText.slice(0, 500)}`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
return await response.json();
|
|
264
|
+
} catch (error) {
|
|
265
|
+
if (error instanceof BitfabError) {
|
|
266
|
+
throw error;
|
|
267
|
+
}
|
|
268
|
+
if (error instanceof Error) {
|
|
269
|
+
if (error.name === "AbortError") {
|
|
270
|
+
throw new BitfabError("Request timed out after 30000ms");
|
|
271
|
+
}
|
|
272
|
+
throw new BitfabError(error.message);
|
|
273
|
+
}
|
|
274
|
+
throw new BitfabError("Unknown error occurred");
|
|
275
|
+
} finally {
|
|
276
|
+
clearTimeout(timeoutId);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Fetch the span tree for a root span.
|
|
281
|
+
* Blocking GET request.
|
|
282
|
+
*/
|
|
283
|
+
async getSpanTree(externalSpanId) {
|
|
284
|
+
const url = `${this.serviceUrl}/api/sdk/replay/spanTree/${externalSpanId}`;
|
|
285
|
+
const controller = new AbortController();
|
|
286
|
+
const timeoutId = setTimeout(() => controller.abort(), 3e4);
|
|
287
|
+
try {
|
|
288
|
+
const response = await fetch(url, {
|
|
289
|
+
method: "GET",
|
|
290
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
291
|
+
signal: controller.signal
|
|
292
|
+
});
|
|
293
|
+
if (!response.ok) {
|
|
294
|
+
const errorText = await response.text();
|
|
295
|
+
throw new BitfabError(
|
|
296
|
+
`HTTP ${response.status}: ${errorText.slice(0, 500)}`
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
return await response.json();
|
|
300
|
+
} catch (error) {
|
|
301
|
+
if (error instanceof BitfabError) {
|
|
302
|
+
throw error;
|
|
303
|
+
}
|
|
304
|
+
if (error instanceof Error) {
|
|
305
|
+
if (error.name === "AbortError") {
|
|
306
|
+
throw new BitfabError("Request timed out after 30000ms");
|
|
307
|
+
}
|
|
308
|
+
throw new BitfabError(error.message);
|
|
309
|
+
}
|
|
310
|
+
throw new BitfabError("Unknown error occurred");
|
|
311
|
+
} finally {
|
|
312
|
+
clearTimeout(timeoutId);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Mark a replay test run as completed.
|
|
317
|
+
* Blocking call.
|
|
318
|
+
*/
|
|
319
|
+
async completeReplay(testRunId) {
|
|
320
|
+
return this.request(
|
|
321
|
+
"/api/sdk/replay/complete",
|
|
322
|
+
{ testRunId },
|
|
323
|
+
{ timeout: 3e4 }
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Ask the server to materialize a per-trace DB branch lease from a
|
|
328
|
+
* captured `dbSnapshotRef`. Blocking — the resolver creates a Neon
|
|
329
|
+
* snapshot + preview branch and polls operations to readiness, which
|
|
330
|
+
* can take seconds.
|
|
331
|
+
*/
|
|
332
|
+
async resolveDbBranchLease(testRunId, traceId, dbSnapshotRef) {
|
|
333
|
+
return this.request(
|
|
334
|
+
"/api/sdk/replay/resolveDbBranchLease",
|
|
335
|
+
{ testRunId, traceId, dbSnapshotRef },
|
|
336
|
+
{ timeout: 9e4 }
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
/** Release a previously-resolved DB branch by deleting its Neon branch. Idempotent server-side. */
|
|
340
|
+
async releaseDbBranchLease(neonBranchId) {
|
|
341
|
+
await this.request(
|
|
342
|
+
"/api/sdk/replay/releaseDbBranchLease",
|
|
343
|
+
{ neonBranchId },
|
|
344
|
+
{ timeout: 3e4 }
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
12
348
|
|
|
13
349
|
// src/claudeAgentSdk.ts
|
|
14
350
|
function nowIso() {
|
|
@@ -2162,9 +2498,18 @@ var Bitfab = class {
|
|
|
2162
2498
|
spanType: options.type ?? "custom"
|
|
2163
2499
|
};
|
|
2164
2500
|
const sendSpan = async (params) => {
|
|
2501
|
+
const replayCtx = getReplayContext();
|
|
2502
|
+
const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
|
|
2503
|
+
let resolvePersistence;
|
|
2504
|
+
if (persistenceCollector) {
|
|
2505
|
+
persistenceCollector.push(
|
|
2506
|
+
new Promise((resolve) => {
|
|
2507
|
+
resolvePersistence = resolve;
|
|
2508
|
+
})
|
|
2509
|
+
);
|
|
2510
|
+
}
|
|
2165
2511
|
try {
|
|
2166
2512
|
const endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2167
|
-
const replayCtx = getReplayContext();
|
|
2168
2513
|
const spanPromise = self.sendWrapperSpan({
|
|
2169
2514
|
...baseSpanParams,
|
|
2170
2515
|
...params,
|
|
@@ -2179,13 +2524,17 @@ var Bitfab = class {
|
|
|
2179
2524
|
if (isRootSpan) {
|
|
2180
2525
|
const pending = pendingSpanPromises.get(traceId) ?? [];
|
|
2181
2526
|
pending.push(spanPromise);
|
|
2182
|
-
|
|
2183
|
-
Promise.allSettled(pending)
|
|
2184
|
-
|
|
2185
|
-
|
|
2527
|
+
if (persistenceCollector) {
|
|
2528
|
+
await Promise.allSettled(pending);
|
|
2529
|
+
} else {
|
|
2530
|
+
await Promise.race([
|
|
2531
|
+
Promise.allSettled(pending),
|
|
2532
|
+
new Promise((resolve) => setTimeout(resolve, 5e3))
|
|
2533
|
+
]);
|
|
2534
|
+
}
|
|
2186
2535
|
pendingSpanPromises.delete(traceId);
|
|
2187
2536
|
const traceState = activeTraceStates.get(traceId);
|
|
2188
|
-
self.sendTraceCompletion({
|
|
2537
|
+
const completionPromise = self.sendTraceCompletion({
|
|
2189
2538
|
traceFunctionKey,
|
|
2190
2539
|
traceId,
|
|
2191
2540
|
startedAt: traceState?.startedAt ?? startedAt,
|
|
@@ -2198,6 +2547,9 @@ var Bitfab = class {
|
|
|
2198
2547
|
dbSnapshotRef: traceState?.dbSnapshotRef
|
|
2199
2548
|
});
|
|
2200
2549
|
activeTraceStates.delete(traceId);
|
|
2550
|
+
if (persistenceCollector) {
|
|
2551
|
+
await completionPromise;
|
|
2552
|
+
}
|
|
2201
2553
|
} else {
|
|
2202
2554
|
const pending = pendingSpanPromises.get(traceId);
|
|
2203
2555
|
if (pending) {
|
|
@@ -2207,6 +2559,8 @@ var Bitfab = class {
|
|
|
2207
2559
|
}
|
|
2208
2560
|
}
|
|
2209
2561
|
} catch {
|
|
2562
|
+
} finally {
|
|
2563
|
+
resolvePersistence?.();
|
|
2210
2564
|
}
|
|
2211
2565
|
};
|
|
2212
2566
|
const replayCtxForMock = getReplayContext();
|
|
@@ -2359,7 +2713,7 @@ var Bitfab = class {
|
|
|
2359
2713
|
if (params.dbSnapshotRef) {
|
|
2360
2714
|
rawTrace.db_snapshot_ref = params.dbSnapshotRef;
|
|
2361
2715
|
}
|
|
2362
|
-
this.httpClient.sendExternalTrace({
|
|
2716
|
+
return this.httpClient.sendExternalTrace({
|
|
2363
2717
|
type: "sdk-function",
|
|
2364
2718
|
source: "typescript-sdk-function",
|
|
2365
2719
|
traceFunctionKey: params.traceFunctionKey,
|
|
@@ -2433,11 +2787,13 @@ var Bitfab = class {
|
|
|
2433
2787
|
*
|
|
2434
2788
|
* @param traceFunctionKey - The trace function key to replay
|
|
2435
2789
|
* @param fn - The function to replay (must be the return value of `withSpan`)
|
|
2436
|
-
* @param options - Optional replay options
|
|
2790
|
+
* @param options - Optional replay options. `limit` and `traceIds` are
|
|
2791
|
+
* mutually exclusive — an explicit ID list already determines how many
|
|
2792
|
+
* traces replay, so passing both throws a BitfabError.
|
|
2437
2793
|
* @returns ReplayResult with items, testRunId, and testRunUrl
|
|
2438
2794
|
*/
|
|
2439
2795
|
async replay(traceFunctionKey, fn, options) {
|
|
2440
|
-
const { replay: doReplay } = await import("./replay-
|
|
2796
|
+
const { replay: doReplay } = await import("./replay-3MQS22GS.js");
|
|
2441
2797
|
return doReplay(
|
|
2442
2798
|
this.httpClient,
|
|
2443
2799
|
this.serviceUrl,
|
|
@@ -2503,6 +2859,9 @@ var BitfabFunction = class {
|
|
|
2503
2859
|
};
|
|
2504
2860
|
|
|
2505
2861
|
export {
|
|
2862
|
+
__version__,
|
|
2863
|
+
DEFAULT_SERVICE_URL,
|
|
2864
|
+
flushTraces,
|
|
2506
2865
|
BitfabClaudeAgentHandler,
|
|
2507
2866
|
SUPPORTED_PROVIDERS,
|
|
2508
2867
|
BitfabLangGraphCallbackHandler,
|
|
@@ -2513,4 +2872,4 @@ export {
|
|
|
2513
2872
|
Bitfab,
|
|
2514
2873
|
BitfabFunction
|
|
2515
2874
|
};
|
|
2516
|
-
//# sourceMappingURL=chunk-
|
|
2875
|
+
//# sourceMappingURL=chunk-YPG3XIG4.js.map
|