@tanstack/offline-transactions 0.1.1 → 0.1.3

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.
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const api = require("@opentelemetry/api");
4
3
  const db = require("@tanstack/db");
5
4
  const OfflineTransaction = require("./OfflineTransaction.cjs");
6
5
  function isPromiseLike(value) {
@@ -26,25 +25,14 @@ function createOfflineAction(options, mutationFn, persistTransaction, executor)
26
25
  throw new db.OnMutateMustBeSynchronousError();
27
26
  }
28
27
  });
29
- const tracer = api.trace.getTracer(`@tanstack/offline-transactions`, `0.0.1`);
30
- const span = tracer.startSpan(`offlineAction.${mutationFnName}`);
31
- const ctx = api.trace.setSpan(api.context.active(), span);
32
- console.log(`starting offlineAction span`, { tracer, span, ctx });
33
- const commitPromise = api.context.with(ctx, () => {
34
- return (async () => {
35
- try {
36
- await transaction.commit();
37
- span.setStatus({ code: api.SpanStatusCode.OK });
38
- span.end();
39
- console.log(`ended offlineAction span - success`);
40
- } catch (error) {
41
- span.recordException(error);
42
- span.setStatus({ code: api.SpanStatusCode.ERROR });
43
- span.end();
44
- console.log(`ended offlineAction span - error`);
45
- }
46
- })();
47
- });
28
+ const commitPromise = (async () => {
29
+ try {
30
+ await transaction.commit();
31
+ console.log(`offlineAction committed - success`);
32
+ } catch {
33
+ console.log(`offlineAction commit failed - error`);
34
+ }
35
+ })();
48
36
  commitPromise.catch(() => {
49
37
  });
50
38
  return transaction;
@@ -1 +1 @@
1
- {"version":3,"file":"OfflineAction.cjs","sources":["../../../src/api/OfflineAction.ts"],"sourcesContent":["import { SpanStatusCode, context, trace } from \"@opentelemetry/api\"\nimport { OnMutateMustBeSynchronousError } from \"@tanstack/db\"\nimport { OfflineTransaction } from \"./OfflineTransaction\"\nimport type { Transaction } from \"@tanstack/db\"\nimport type {\n CreateOfflineActionOptions,\n OfflineMutationFn,\n OfflineTransaction as OfflineTransactionType,\n} from \"../types\"\n\nfunction isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n return (\n !!value &&\n (typeof value === `object` || typeof value === `function`) &&\n typeof (value as { then?: unknown }).then === `function`\n )\n}\n\nexport function createOfflineAction<T>(\n options: CreateOfflineActionOptions<T>,\n mutationFn: OfflineMutationFn,\n persistTransaction: (tx: OfflineTransactionType) => Promise<void>,\n executor: any\n): (variables: T) => Transaction {\n const { mutationFnName, onMutate } = options\n console.log(`createOfflineAction 2`, options)\n\n return (variables: T): Transaction => {\n const offlineTransaction = new OfflineTransaction(\n {\n mutationFnName,\n autoCommit: false,\n },\n mutationFn,\n persistTransaction,\n executor\n )\n\n const transaction = offlineTransaction.mutate(() => {\n console.log(`mutate`)\n const maybePromise = onMutate(variables) as unknown\n\n if (isPromiseLike(maybePromise)) {\n throw new OnMutateMustBeSynchronousError()\n }\n })\n\n // Immediately commit with span instrumentation\n const tracer = trace.getTracer(`@tanstack/offline-transactions`, `0.0.1`)\n const span = tracer.startSpan(`offlineAction.${mutationFnName}`)\n const ctx = trace.setSpan(context.active(), span)\n console.log(`starting offlineAction span`, { tracer, span, ctx })\n\n // Execute the commit within the span context\n // The key is to return the promise synchronously from context.with() so context binds to it\n const commitPromise = context.with(ctx, () => {\n // Return the promise synchronously - this is critical for context propagation in browsers\n return (async () => {\n try {\n await transaction.commit()\n span.setStatus({ code: SpanStatusCode.OK })\n span.end()\n console.log(`ended offlineAction span - success`)\n } catch (error) {\n span.recordException(error as Error)\n span.setStatus({ code: SpanStatusCode.ERROR })\n span.end()\n console.log(`ended offlineAction span - error`)\n }\n })()\n })\n\n // Don't await - this is fire-and-forget for optimistic actions\n // But catch to prevent unhandled rejection\n commitPromise.catch(() => {\n // Already handled in try/catch above\n })\n\n return transaction\n }\n}\n"],"names":["OfflineTransaction","OnMutateMustBeSynchronousError","trace","context","SpanStatusCode"],"mappings":";;;;;AAUA,SAAS,cAAc,OAA+C;AACpE,SACE,CAAC,CAAC,UACD,OAAO,UAAU,YAAY,OAAO,UAAU,eAC/C,OAAQ,MAA6B,SAAS;AAElD;AAEO,SAAS,oBACd,SACA,YACA,oBACA,UAC+B;AAC/B,QAAM,EAAE,gBAAgB,SAAA,IAAa;AACrC,UAAQ,IAAI,yBAAyB,OAAO;AAE5C,SAAO,CAAC,cAA8B;AACpC,UAAM,qBAAqB,IAAIA,mBAAAA;AAAAA,MAC7B;AAAA,QACE;AAAA,QACA,YAAY;AAAA,MAAA;AAAA,MAEd;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAGF,UAAM,cAAc,mBAAmB,OAAO,MAAM;AAClD,cAAQ,IAAI,QAAQ;AACpB,YAAM,eAAe,SAAS,SAAS;AAEvC,UAAI,cAAc,YAAY,GAAG;AAC/B,cAAM,IAAIC,GAAAA,+BAAA;AAAA,MACZ;AAAA,IACF,CAAC;AAGD,UAAM,SAASC,IAAAA,MAAM,UAAU,kCAAkC,OAAO;AACxE,UAAM,OAAO,OAAO,UAAU,iBAAiB,cAAc,EAAE;AAC/D,UAAM,MAAMA,IAAAA,MAAM,QAAQC,IAAAA,QAAQ,OAAA,GAAU,IAAI;AAChD,YAAQ,IAAI,+BAA+B,EAAE,QAAQ,MAAM,KAAK;AAIhE,UAAM,gBAAgBA,IAAAA,QAAQ,KAAK,KAAK,MAAM;AAE5C,cAAQ,YAAY;AAClB,YAAI;AACF,gBAAM,YAAY,OAAA;AAClB,eAAK,UAAU,EAAE,MAAMC,IAAAA,eAAe,IAAI;AAC1C,eAAK,IAAA;AACL,kBAAQ,IAAI,oCAAoC;AAAA,QAClD,SAAS,OAAO;AACd,eAAK,gBAAgB,KAAc;AACnC,eAAK,UAAU,EAAE,MAAMA,IAAAA,eAAe,OAAO;AAC7C,eAAK,IAAA;AACL,kBAAQ,IAAI,kCAAkC;AAAA,QAChD;AAAA,MACF,GAAA;AAAA,IACF,CAAC;AAID,kBAAc,MAAM,MAAM;AAAA,IAE1B,CAAC;AAED,WAAO;AAAA,EACT;AACF;;"}
1
+ {"version":3,"file":"OfflineAction.cjs","sources":["../../../src/api/OfflineAction.ts"],"sourcesContent":["import { OnMutateMustBeSynchronousError } from \"@tanstack/db\"\nimport { OfflineTransaction } from \"./OfflineTransaction\"\nimport type { Transaction } from \"@tanstack/db\"\nimport type {\n CreateOfflineActionOptions,\n OfflineMutationFn,\n OfflineTransaction as OfflineTransactionType,\n} from \"../types\"\n\nfunction isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n return (\n !!value &&\n (typeof value === `object` || typeof value === `function`) &&\n typeof (value as { then?: unknown }).then === `function`\n )\n}\n\nexport function createOfflineAction<T>(\n options: CreateOfflineActionOptions<T>,\n mutationFn: OfflineMutationFn,\n persistTransaction: (tx: OfflineTransactionType) => Promise<void>,\n executor: any\n): (variables: T) => Transaction {\n const { mutationFnName, onMutate } = options\n console.log(`createOfflineAction 2`, options)\n\n return (variables: T): Transaction => {\n const offlineTransaction = new OfflineTransaction(\n {\n mutationFnName,\n autoCommit: false,\n },\n mutationFn,\n persistTransaction,\n executor\n )\n\n const transaction = offlineTransaction.mutate(() => {\n console.log(`mutate`)\n const maybePromise = onMutate(variables) as unknown\n\n if (isPromiseLike(maybePromise)) {\n throw new OnMutateMustBeSynchronousError()\n }\n })\n\n // Immediately commit\n const commitPromise = (async () => {\n try {\n await transaction.commit()\n console.log(`offlineAction committed - success`)\n } catch {\n console.log(`offlineAction commit failed - error`)\n }\n })()\n\n // Don't await - this is fire-and-forget for optimistic actions\n // But catch to prevent unhandled rejection\n commitPromise.catch(() => {\n // Already handled in try/catch above\n })\n\n return transaction\n }\n}\n"],"names":["OfflineTransaction","OnMutateMustBeSynchronousError"],"mappings":";;;;AASA,SAAS,cAAc,OAA+C;AACpE,SACE,CAAC,CAAC,UACD,OAAO,UAAU,YAAY,OAAO,UAAU,eAC/C,OAAQ,MAA6B,SAAS;AAElD;AAEO,SAAS,oBACd,SACA,YACA,oBACA,UAC+B;AAC/B,QAAM,EAAE,gBAAgB,SAAA,IAAa;AACrC,UAAQ,IAAI,yBAAyB,OAAO;AAE5C,SAAO,CAAC,cAA8B;AACpC,UAAM,qBAAqB,IAAIA,mBAAAA;AAAAA,MAC7B;AAAA,QACE;AAAA,QACA,YAAY;AAAA,MAAA;AAAA,MAEd;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAGF,UAAM,cAAc,mBAAmB,OAAO,MAAM;AAClD,cAAQ,IAAI,QAAQ;AACpB,YAAM,eAAe,SAAS,SAAS;AAEvC,UAAI,cAAc,YAAY,GAAG;AAC/B,cAAM,IAAIC,GAAAA,+BAAA;AAAA,MACZ;AAAA,IACF,CAAC;AAGD,UAAM,iBAAiB,YAAY;AACjC,UAAI;AACF,cAAM,YAAY,OAAA;AAClB,gBAAQ,IAAI,mCAAmC;AAAA,MACjD,QAAQ;AACN,gBAAQ,IAAI,qCAAqC;AAAA,MACnD;AAAA,IACF,GAAA;AAIA,kBAAc,MAAM,MAAM;AAAA,IAE1B,CAAC;AAED,WAAO;AAAA,EACT;AACF;;"}
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const api = require("@opentelemetry/api");
4
3
  const db = require("@tanstack/db");
5
4
  const types = require("../types.cjs");
6
5
  class OfflineTransaction {
@@ -20,8 +19,6 @@ class OfflineTransaction {
20
19
  id: this.offlineId,
21
20
  autoCommit: false,
22
21
  mutationFn: async () => {
23
- const activeSpan = api.trace.getSpan(api.context.active());
24
- const spanContext = activeSpan?.spanContext();
25
22
  const offlineTransaction = {
26
23
  id: this.offlineId,
27
24
  mutationFnName: this.mutationFnName,
@@ -32,12 +29,7 @@ class OfflineTransaction {
32
29
  retryCount: 0,
33
30
  nextAttemptAt: Date.now(),
34
31
  metadata: this.metadata,
35
- spanContext: spanContext ? {
36
- traceId: spanContext.traceId,
37
- spanId: spanContext.spanId,
38
- traceFlags: spanContext.traceFlags,
39
- traceState: spanContext.traceState?.serialize()
40
- } : void 0,
32
+ spanContext: void 0,
41
33
  version: 1
42
34
  };
43
35
  const completionPromise = this.executor.waitForTransactionCompletion(
@@ -1 +1 @@
1
- {"version":3,"file":"OfflineTransaction.cjs","sources":["../../../src/api/OfflineTransaction.ts"],"sourcesContent":["import { context, trace } from \"@opentelemetry/api\"\nimport { createTransaction } from \"@tanstack/db\"\nimport { NonRetriableError } from \"../types\"\nimport type { PendingMutation, Transaction } from \"@tanstack/db\"\nimport type {\n CreateOfflineTransactionOptions,\n OfflineMutationFn,\n OfflineTransaction as OfflineTransactionType,\n} from \"../types\"\n\nexport class OfflineTransaction {\n private offlineId: string\n private mutationFnName: string\n private autoCommit: boolean\n private idempotencyKey: string\n private metadata: Record<string, any>\n private transaction: Transaction | null = null\n private persistTransaction: (tx: OfflineTransactionType) => Promise<void>\n private executor: any // Will be typed properly - reference to OfflineExecutor\n\n constructor(\n options: CreateOfflineTransactionOptions,\n mutationFn: OfflineMutationFn,\n persistTransaction: (tx: OfflineTransactionType) => Promise<void>,\n executor: any\n ) {\n this.offlineId = crypto.randomUUID()\n this.mutationFnName = options.mutationFnName\n this.autoCommit = options.autoCommit ?? true\n this.idempotencyKey = options.idempotencyKey ?? crypto.randomUUID()\n this.metadata = options.metadata ?? {}\n this.persistTransaction = persistTransaction\n this.executor = executor\n }\n\n mutate(callback: () => void): Transaction {\n this.transaction = createTransaction({\n id: this.offlineId,\n autoCommit: false,\n mutationFn: async () => {\n // This is the blocking mutationFn that waits for the executor\n // First persist the transaction to the outbox\n const activeSpan = trace.getSpan(context.active())\n const spanContext = activeSpan?.spanContext()\n\n const offlineTransaction: OfflineTransactionType = {\n id: this.offlineId,\n mutationFnName: this.mutationFnName,\n mutations: this.transaction!.mutations,\n keys: this.extractKeys(this.transaction!.mutations),\n idempotencyKey: this.idempotencyKey,\n createdAt: new Date(),\n retryCount: 0,\n nextAttemptAt: Date.now(),\n metadata: this.metadata,\n spanContext: spanContext\n ? {\n traceId: spanContext.traceId,\n spanId: spanContext.spanId,\n traceFlags: spanContext.traceFlags,\n traceState: spanContext.traceState?.serialize(),\n }\n : undefined,\n version: 1,\n }\n\n const completionPromise = this.executor.waitForTransactionCompletion(\n this.offlineId\n )\n\n try {\n await this.persistTransaction(offlineTransaction)\n // Now block and wait for the executor to complete the real mutation\n await completionPromise\n } catch (error) {\n const normalizedError =\n error instanceof Error ? error : new Error(String(error))\n this.executor.rejectTransaction(this.offlineId, normalizedError)\n throw error\n }\n\n return\n },\n metadata: this.metadata,\n })\n\n this.transaction.mutate(() => {\n callback()\n })\n\n if (this.autoCommit) {\n // Auto-commit for direct OfflineTransaction usage\n this.commit().catch((error) => {\n console.error(`Auto-commit failed:`, error)\n throw error\n })\n }\n\n return this.transaction\n }\n\n async commit(): Promise<Transaction> {\n if (!this.transaction) {\n throw new Error(`No mutations to commit. Call mutate() first.`)\n }\n\n try {\n // Commit the TanStack DB transaction\n // This will trigger the mutationFn which handles persistence and waiting\n await this.transaction.commit()\n return this.transaction\n } catch (error) {\n // Only rollback for NonRetriableError - other errors should allow retry\n if (error instanceof NonRetriableError) {\n this.transaction.rollback()\n }\n throw error\n }\n }\n\n rollback(): void {\n if (this.transaction) {\n this.transaction.rollback()\n }\n }\n\n private extractKeys(mutations: Array<PendingMutation>): Array<string> {\n return mutations.map((mutation) => mutation.globalKey)\n }\n\n get id(): string {\n return this.offlineId\n }\n}\n"],"names":["createTransaction","trace","context","NonRetriableError"],"mappings":";;;;;AAUO,MAAM,mBAAmB;AAAA;AAAA,EAU9B,YACE,SACA,YACA,oBACA,UACA;AATF,SAAQ,cAAkC;AAUxC,SAAK,YAAY,OAAO,WAAA;AACxB,SAAK,iBAAiB,QAAQ;AAC9B,SAAK,aAAa,QAAQ,cAAc;AACxC,SAAK,iBAAiB,QAAQ,kBAAkB,OAAO,WAAA;AACvD,SAAK,WAAW,QAAQ,YAAY,CAAA;AACpC,SAAK,qBAAqB;AAC1B,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,OAAO,UAAmC;AACxC,SAAK,cAAcA,qBAAkB;AAAA,MACnC,IAAI,KAAK;AAAA,MACT,YAAY;AAAA,MACZ,YAAY,YAAY;AAGtB,cAAM,aAAaC,IAAAA,MAAM,QAAQC,IAAAA,QAAQ,QAAQ;AACjD,cAAM,cAAc,YAAY,YAAA;AAEhC,cAAM,qBAA6C;AAAA,UACjD,IAAI,KAAK;AAAA,UACT,gBAAgB,KAAK;AAAA,UACrB,WAAW,KAAK,YAAa;AAAA,UAC7B,MAAM,KAAK,YAAY,KAAK,YAAa,SAAS;AAAA,UAClD,gBAAgB,KAAK;AAAA,UACrB,+BAAe,KAAA;AAAA,UACf,YAAY;AAAA,UACZ,eAAe,KAAK,IAAA;AAAA,UACpB,UAAU,KAAK;AAAA,UACf,aAAa,cACT;AAAA,YACE,SAAS,YAAY;AAAA,YACrB,QAAQ,YAAY;AAAA,YACpB,YAAY,YAAY;AAAA,YACxB,YAAY,YAAY,YAAY,UAAA;AAAA,UAAU,IAEhD;AAAA,UACJ,SAAS;AAAA,QAAA;AAGX,cAAM,oBAAoB,KAAK,SAAS;AAAA,UACtC,KAAK;AAAA,QAAA;AAGP,YAAI;AACF,gBAAM,KAAK,mBAAmB,kBAAkB;AAEhD,gBAAM;AAAA,QACR,SAAS,OAAO;AACd,gBAAM,kBACJ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAC1D,eAAK,SAAS,kBAAkB,KAAK,WAAW,eAAe;AAC/D,gBAAM;AAAA,QACR;AAEA;AAAA,MACF;AAAA,MACA,UAAU,KAAK;AAAA,IAAA,CAChB;AAED,SAAK,YAAY,OAAO,MAAM;AAC5B,eAAA;AAAA,IACF,CAAC;AAED,QAAI,KAAK,YAAY;AAEnB,WAAK,OAAA,EAAS,MAAM,CAAC,UAAU;AAC7B,gBAAQ,MAAM,uBAAuB,KAAK;AAC1C,cAAM;AAAA,MACR,CAAC;AAAA,IACH;AAEA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,SAA+B;AACnC,QAAI,CAAC,KAAK,aAAa;AACrB,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AAEA,QAAI;AAGF,YAAM,KAAK,YAAY,OAAA;AACvB,aAAO,KAAK;AAAA,IACd,SAAS,OAAO;AAEd,UAAI,iBAAiBC,MAAAA,mBAAmB;AACtC,aAAK,YAAY,SAAA;AAAA,MACnB;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,WAAiB;AACf,QAAI,KAAK,aAAa;AACpB,WAAK,YAAY,SAAA;AAAA,IACnB;AAAA,EACF;AAAA,EAEQ,YAAY,WAAkD;AACpE,WAAO,UAAU,IAAI,CAAC,aAAa,SAAS,SAAS;AAAA,EACvD;AAAA,EAEA,IAAI,KAAa;AACf,WAAO,KAAK;AAAA,EACd;AACF;;"}
1
+ {"version":3,"file":"OfflineTransaction.cjs","sources":["../../../src/api/OfflineTransaction.ts"],"sourcesContent":["import { createTransaction } from \"@tanstack/db\"\nimport { NonRetriableError } from \"../types\"\nimport type { PendingMutation, Transaction } from \"@tanstack/db\"\nimport type {\n CreateOfflineTransactionOptions,\n OfflineMutationFn,\n OfflineTransaction as OfflineTransactionType,\n} from \"../types\"\n\nexport class OfflineTransaction {\n private offlineId: string\n private mutationFnName: string\n private autoCommit: boolean\n private idempotencyKey: string\n private metadata: Record<string, any>\n private transaction: Transaction | null = null\n private persistTransaction: (tx: OfflineTransactionType) => Promise<void>\n private executor: any // Will be typed properly - reference to OfflineExecutor\n\n constructor(\n options: CreateOfflineTransactionOptions,\n mutationFn: OfflineMutationFn,\n persistTransaction: (tx: OfflineTransactionType) => Promise<void>,\n executor: any\n ) {\n this.offlineId = crypto.randomUUID()\n this.mutationFnName = options.mutationFnName\n this.autoCommit = options.autoCommit ?? true\n this.idempotencyKey = options.idempotencyKey ?? crypto.randomUUID()\n this.metadata = options.metadata ?? {}\n this.persistTransaction = persistTransaction\n this.executor = executor\n }\n\n mutate(callback: () => void): Transaction {\n this.transaction = createTransaction({\n id: this.offlineId,\n autoCommit: false,\n mutationFn: async () => {\n // This is the blocking mutationFn that waits for the executor\n // First persist the transaction to the outbox\n const offlineTransaction: OfflineTransactionType = {\n id: this.offlineId,\n mutationFnName: this.mutationFnName,\n mutations: this.transaction!.mutations,\n keys: this.extractKeys(this.transaction!.mutations),\n idempotencyKey: this.idempotencyKey,\n createdAt: new Date(),\n retryCount: 0,\n nextAttemptAt: Date.now(),\n metadata: this.metadata,\n spanContext: undefined,\n version: 1,\n }\n\n const completionPromise = this.executor.waitForTransactionCompletion(\n this.offlineId\n )\n\n try {\n await this.persistTransaction(offlineTransaction)\n // Now block and wait for the executor to complete the real mutation\n await completionPromise\n } catch (error) {\n const normalizedError =\n error instanceof Error ? error : new Error(String(error))\n this.executor.rejectTransaction(this.offlineId, normalizedError)\n throw error\n }\n\n return\n },\n metadata: this.metadata,\n })\n\n this.transaction.mutate(() => {\n callback()\n })\n\n if (this.autoCommit) {\n // Auto-commit for direct OfflineTransaction usage\n this.commit().catch((error) => {\n console.error(`Auto-commit failed:`, error)\n throw error\n })\n }\n\n return this.transaction\n }\n\n async commit(): Promise<Transaction> {\n if (!this.transaction) {\n throw new Error(`No mutations to commit. Call mutate() first.`)\n }\n\n try {\n // Commit the TanStack DB transaction\n // This will trigger the mutationFn which handles persistence and waiting\n await this.transaction.commit()\n return this.transaction\n } catch (error) {\n // Only rollback for NonRetriableError - other errors should allow retry\n if (error instanceof NonRetriableError) {\n this.transaction.rollback()\n }\n throw error\n }\n }\n\n rollback(): void {\n if (this.transaction) {\n this.transaction.rollback()\n }\n }\n\n private extractKeys(mutations: Array<PendingMutation>): Array<string> {\n return mutations.map((mutation) => mutation.globalKey)\n }\n\n get id(): string {\n return this.offlineId\n }\n}\n"],"names":["createTransaction","NonRetriableError"],"mappings":";;;;AASO,MAAM,mBAAmB;AAAA;AAAA,EAU9B,YACE,SACA,YACA,oBACA,UACA;AATF,SAAQ,cAAkC;AAUxC,SAAK,YAAY,OAAO,WAAA;AACxB,SAAK,iBAAiB,QAAQ;AAC9B,SAAK,aAAa,QAAQ,cAAc;AACxC,SAAK,iBAAiB,QAAQ,kBAAkB,OAAO,WAAA;AACvD,SAAK,WAAW,QAAQ,YAAY,CAAA;AACpC,SAAK,qBAAqB;AAC1B,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,OAAO,UAAmC;AACxC,SAAK,cAAcA,qBAAkB;AAAA,MACnC,IAAI,KAAK;AAAA,MACT,YAAY;AAAA,MACZ,YAAY,YAAY;AAGtB,cAAM,qBAA6C;AAAA,UACjD,IAAI,KAAK;AAAA,UACT,gBAAgB,KAAK;AAAA,UACrB,WAAW,KAAK,YAAa;AAAA,UAC7B,MAAM,KAAK,YAAY,KAAK,YAAa,SAAS;AAAA,UAClD,gBAAgB,KAAK;AAAA,UACrB,+BAAe,KAAA;AAAA,UACf,YAAY;AAAA,UACZ,eAAe,KAAK,IAAA;AAAA,UACpB,UAAU,KAAK;AAAA,UACf,aAAa;AAAA,UACb,SAAS;AAAA,QAAA;AAGX,cAAM,oBAAoB,KAAK,SAAS;AAAA,UACtC,KAAK;AAAA,QAAA;AAGP,YAAI;AACF,gBAAM,KAAK,mBAAmB,kBAAkB;AAEhD,gBAAM;AAAA,QACR,SAAS,OAAO;AACd,gBAAM,kBACJ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAC1D,eAAK,SAAS,kBAAkB,KAAK,WAAW,eAAe;AAC/D,gBAAM;AAAA,QACR;AAEA;AAAA,MACF;AAAA,MACA,UAAU,KAAK;AAAA,IAAA,CAChB;AAED,SAAK,YAAY,OAAO,MAAM;AAC5B,eAAA;AAAA,IACF,CAAC;AAED,QAAI,KAAK,YAAY;AAEnB,WAAK,OAAA,EAAS,MAAM,CAAC,UAAU;AAC7B,gBAAQ,MAAM,uBAAuB,KAAK;AAC1C,cAAM;AAAA,MACR,CAAC;AAAA,IACH;AAEA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,SAA+B;AACnC,QAAI,CAAC,KAAK,aAAa;AACrB,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AAEA,QAAI;AAGF,YAAM,KAAK,YAAY,OAAA;AACvB,aAAO,KAAK;AAAA,IACd,SAAS,OAAO;AAEd,UAAI,iBAAiBC,MAAAA,mBAAmB;AACtC,aAAK,YAAY,SAAA;AAAA,MACnB;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,WAAiB;AACf,QAAI,KAAK,aAAa;AACpB,WAAK,YAAY,SAAA;AAAA,IACnB;AAAA,EACF;AAAA,EAEQ,YAAY,WAAkD;AACpE,WAAO,UAAU,IAAI,CAAC,aAAa,SAAS,SAAS;AAAA,EACvD;AAAA,EAEA,IAAI,KAAa;AACf,WAAO,KAAK;AAAA,EACd;AACF;;"}
@@ -1,21 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const api = require("@opentelemetry/api");
4
3
  const RetryPolicy = require("../retry/RetryPolicy.cjs");
5
4
  const types = require("../types.cjs");
6
5
  const tracer = require("../telemetry/tracer.cjs");
7
6
  const HANDLED_EXECUTION_ERROR = Symbol(`HandledExecutionError`);
8
- function toSpanContext(serialized) {
9
- if (!serialized) {
10
- return void 0;
11
- }
12
- return {
13
- traceId: serialized.traceId,
14
- spanId: serialized.spanId,
15
- traceFlags: serialized.traceFlags,
16
- traceState: serialized.traceState ? api.createTraceState(serialized.traceState) : void 0
17
- };
18
- }
19
7
  class TransactionExecutor {
20
8
  constructor(scheduler, outbox, config, offlineExecutor) {
21
9
  this.isExecuting = false;
@@ -86,9 +74,6 @@ class TransactionExecutor {
86
74
  err[HANDLED_EXECUTION_ERROR] = true;
87
75
  throw err;
88
76
  }
89
- },
90
- {
91
- parentContext: toSpanContext(transaction.spanContext)
92
77
  }
93
78
  );
94
79
  } catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"TransactionExecutor.cjs","sources":["../../../src/executor/TransactionExecutor.ts"],"sourcesContent":["import { createTraceState } from \"@opentelemetry/api\"\nimport { DefaultRetryPolicy } from \"../retry/RetryPolicy\"\nimport { NonRetriableError } from \"../types\"\nimport { withNestedSpan } from \"../telemetry/tracer\"\nimport type { SpanContext } from \"@opentelemetry/api\"\nimport type { KeyScheduler } from \"./KeyScheduler\"\nimport type { OutboxManager } from \"../outbox/OutboxManager\"\nimport type {\n OfflineConfig,\n OfflineTransaction,\n SerializedSpanContext,\n} from \"../types\"\n\nconst HANDLED_EXECUTION_ERROR = Symbol(`HandledExecutionError`)\n\nfunction toSpanContext(\n serialized?: SerializedSpanContext\n): SpanContext | undefined {\n if (!serialized) {\n return undefined\n }\n\n return {\n traceId: serialized.traceId,\n spanId: serialized.spanId,\n traceFlags: serialized.traceFlags,\n traceState: serialized.traceState\n ? createTraceState(serialized.traceState)\n : undefined,\n }\n}\n\nexport class TransactionExecutor {\n private scheduler: KeyScheduler\n private outbox: OutboxManager\n private config: OfflineConfig\n private retryPolicy: DefaultRetryPolicy\n private isExecuting = false\n private executionPromise: Promise<void> | null = null\n private offlineExecutor: any // Reference to OfflineExecutor for signaling\n private retryTimer: ReturnType<typeof setTimeout> | null = null\n\n constructor(\n scheduler: KeyScheduler,\n outbox: OutboxManager,\n config: OfflineConfig,\n offlineExecutor: any\n ) {\n this.scheduler = scheduler\n this.outbox = outbox\n this.config = config\n this.retryPolicy = new DefaultRetryPolicy(10, config.jitter ?? true)\n this.offlineExecutor = offlineExecutor\n }\n\n async execute(transaction: OfflineTransaction): Promise<void> {\n this.scheduler.schedule(transaction)\n await this.executeAll()\n }\n\n async executeAll(): Promise<void> {\n if (this.isExecuting) {\n return this.executionPromise!\n }\n\n this.isExecuting = true\n this.executionPromise = this.runExecution()\n\n try {\n await this.executionPromise\n } finally {\n this.isExecuting = false\n this.executionPromise = null\n }\n }\n\n private async runExecution(): Promise<void> {\n const maxConcurrency = this.config.maxConcurrency ?? 3\n\n while (this.scheduler.getPendingCount() > 0) {\n const batch = this.scheduler.getNextBatch(maxConcurrency)\n\n if (batch.length === 0) {\n break\n }\n\n const executions = batch.map((transaction) =>\n this.executeTransaction(transaction)\n )\n await Promise.allSettled(executions)\n }\n\n // Schedule next retry after execution completes\n this.scheduleNextRetry()\n }\n\n private async executeTransaction(\n transaction: OfflineTransaction\n ): Promise<void> {\n try {\n await withNestedSpan(\n `transaction.execute`,\n {\n \"transaction.id\": transaction.id,\n \"transaction.mutationFnName\": transaction.mutationFnName,\n \"transaction.retryCount\": transaction.retryCount,\n \"transaction.keyCount\": transaction.keys.length,\n },\n async (span) => {\n this.scheduler.markStarted(transaction)\n\n if (transaction.retryCount > 0) {\n span.setAttribute(`retry.attempt`, transaction.retryCount)\n }\n\n try {\n const result = await this.runMutationFn(transaction)\n\n this.scheduler.markCompleted(transaction)\n await this.outbox.remove(transaction.id)\n\n span.setAttribute(`result`, `success`)\n this.offlineExecutor.resolveTransaction(transaction.id, result)\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(String(error))\n\n span.setAttribute(`result`, `error`)\n\n await this.handleError(transaction, err)\n ;(err as any)[HANDLED_EXECUTION_ERROR] = true\n throw err\n }\n },\n {\n parentContext: toSpanContext(transaction.spanContext),\n }\n )\n } catch (error) {\n if (\n error instanceof Error &&\n (error as any)[HANDLED_EXECUTION_ERROR] === true\n ) {\n return\n }\n\n throw error\n }\n }\n\n private async runMutationFn(transaction: OfflineTransaction): Promise<void> {\n const mutationFn = this.config.mutationFns[transaction.mutationFnName]\n\n if (!mutationFn) {\n const errorMessage = `Unknown mutation function: ${transaction.mutationFnName}`\n\n if (this.config.onUnknownMutationFn) {\n this.config.onUnknownMutationFn(transaction.mutationFnName, transaction)\n }\n\n throw new NonRetriableError(errorMessage)\n }\n\n // Mutations are already PendingMutation objects with collections attached\n // from the deserializer, so we can use them directly\n const transactionWithMutations = {\n id: transaction.id,\n mutations: transaction.mutations,\n metadata: transaction.metadata ?? {},\n }\n\n await mutationFn({\n transaction: transactionWithMutations as any,\n idempotencyKey: transaction.idempotencyKey,\n })\n }\n\n private async handleError(\n transaction: OfflineTransaction,\n error: Error\n ): Promise<void> {\n return withNestedSpan(\n `transaction.handleError`,\n {\n \"transaction.id\": transaction.id,\n \"error.name\": error.name,\n \"error.message\": error.message,\n },\n async (span) => {\n const shouldRetry = this.retryPolicy.shouldRetry(\n error,\n transaction.retryCount\n )\n\n span.setAttribute(`shouldRetry`, shouldRetry)\n\n if (!shouldRetry) {\n this.scheduler.markCompleted(transaction)\n await this.outbox.remove(transaction.id)\n console.warn(\n `Transaction ${transaction.id} failed permanently:`,\n error\n )\n\n span.setAttribute(`result`, `permanent_failure`)\n // Signal permanent failure to the waiting transaction\n this.offlineExecutor.rejectTransaction(transaction.id, error)\n return\n }\n\n const delay = this.retryPolicy.calculateDelay(transaction.retryCount)\n const updatedTransaction: OfflineTransaction = {\n ...transaction,\n retryCount: transaction.retryCount + 1,\n nextAttemptAt: Date.now() + delay,\n lastError: {\n name: error.name,\n message: error.message,\n stack: error.stack,\n },\n }\n\n span.setAttribute(`retryDelay`, delay)\n span.setAttribute(`nextRetryCount`, updatedTransaction.retryCount)\n\n this.scheduler.markFailed(transaction)\n this.scheduler.updateTransaction(updatedTransaction)\n\n try {\n await this.outbox.update(transaction.id, updatedTransaction)\n span.setAttribute(`result`, `scheduled_retry`)\n } catch (persistError) {\n span.recordException(persistError as Error)\n span.setAttribute(`result`, `persist_failed`)\n throw persistError\n }\n\n // Schedule retry timer\n this.scheduleNextRetry()\n }\n )\n }\n\n async loadPendingTransactions(): Promise<void> {\n const transactions = await this.outbox.getAll()\n let filteredTransactions = transactions\n\n if (this.config.beforeRetry) {\n filteredTransactions = this.config.beforeRetry(transactions)\n }\n\n for (const transaction of filteredTransactions) {\n this.scheduler.schedule(transaction)\n }\n\n // Reset retry delays for all loaded transactions so they can run immediately\n this.resetRetryDelays()\n\n // Schedule retry timer for loaded transactions\n this.scheduleNextRetry()\n\n const removedTransactions = transactions.filter(\n (tx) => !filteredTransactions.some((filtered) => filtered.id === tx.id)\n )\n\n if (removedTransactions.length > 0) {\n await this.outbox.removeMany(removedTransactions.map((tx) => tx.id))\n }\n }\n\n clear(): void {\n this.scheduler.clear()\n this.clearRetryTimer()\n }\n\n getPendingCount(): number {\n return this.scheduler.getPendingCount()\n }\n\n private scheduleNextRetry(): void {\n // Clear existing timer\n this.clearRetryTimer()\n\n // Find the earliest retry time among pending transactions\n const earliestRetryTime = this.getEarliestRetryTime()\n\n if (earliestRetryTime === null) {\n return // No transactions pending retry\n }\n\n const delay = Math.max(0, earliestRetryTime - Date.now())\n\n this.retryTimer = setTimeout(() => {\n this.executeAll().catch((error) => {\n console.warn(`Failed to execute retry batch:`, error)\n })\n }, delay)\n }\n\n private getEarliestRetryTime(): number | null {\n const allTransactions = this.scheduler.getAllPendingTransactions()\n\n if (allTransactions.length === 0) {\n return null\n }\n\n return Math.min(...allTransactions.map((tx) => tx.nextAttemptAt))\n }\n\n private clearRetryTimer(): void {\n if (this.retryTimer) {\n clearTimeout(this.retryTimer)\n this.retryTimer = null\n }\n }\n\n getRunningCount(): number {\n return this.scheduler.getRunningCount()\n }\n\n resetRetryDelays(): void {\n const allTransactions = this.scheduler.getAllPendingTransactions()\n const updatedTransactions = allTransactions.map((transaction) => ({\n ...transaction,\n nextAttemptAt: Date.now(),\n }))\n\n this.scheduler.updateTransactions(updatedTransactions)\n }\n}\n"],"names":["createTraceState","DefaultRetryPolicy","withNestedSpan","NonRetriableError"],"mappings":"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"}
1
+ {"version":3,"file":"TransactionExecutor.cjs","sources":["../../../src/executor/TransactionExecutor.ts"],"sourcesContent":["import { DefaultRetryPolicy } from \"../retry/RetryPolicy\"\nimport { NonRetriableError } from \"../types\"\nimport { withNestedSpan } from \"../telemetry/tracer\"\nimport type { KeyScheduler } from \"./KeyScheduler\"\nimport type { OutboxManager } from \"../outbox/OutboxManager\"\nimport type { OfflineConfig, OfflineTransaction } from \"../types\"\n\nconst HANDLED_EXECUTION_ERROR = Symbol(`HandledExecutionError`)\n\nexport class TransactionExecutor {\n private scheduler: KeyScheduler\n private outbox: OutboxManager\n private config: OfflineConfig\n private retryPolicy: DefaultRetryPolicy\n private isExecuting = false\n private executionPromise: Promise<void> | null = null\n private offlineExecutor: any // Reference to OfflineExecutor for signaling\n private retryTimer: ReturnType<typeof setTimeout> | null = null\n\n constructor(\n scheduler: KeyScheduler,\n outbox: OutboxManager,\n config: OfflineConfig,\n offlineExecutor: any\n ) {\n this.scheduler = scheduler\n this.outbox = outbox\n this.config = config\n this.retryPolicy = new DefaultRetryPolicy(10, config.jitter ?? true)\n this.offlineExecutor = offlineExecutor\n }\n\n async execute(transaction: OfflineTransaction): Promise<void> {\n this.scheduler.schedule(transaction)\n await this.executeAll()\n }\n\n async executeAll(): Promise<void> {\n if (this.isExecuting) {\n return this.executionPromise!\n }\n\n this.isExecuting = true\n this.executionPromise = this.runExecution()\n\n try {\n await this.executionPromise\n } finally {\n this.isExecuting = false\n this.executionPromise = null\n }\n }\n\n private async runExecution(): Promise<void> {\n const maxConcurrency = this.config.maxConcurrency ?? 3\n\n while (this.scheduler.getPendingCount() > 0) {\n const batch = this.scheduler.getNextBatch(maxConcurrency)\n\n if (batch.length === 0) {\n break\n }\n\n const executions = batch.map((transaction) =>\n this.executeTransaction(transaction)\n )\n await Promise.allSettled(executions)\n }\n\n // Schedule next retry after execution completes\n this.scheduleNextRetry()\n }\n\n private async executeTransaction(\n transaction: OfflineTransaction\n ): Promise<void> {\n try {\n await withNestedSpan(\n `transaction.execute`,\n {\n \"transaction.id\": transaction.id,\n \"transaction.mutationFnName\": transaction.mutationFnName,\n \"transaction.retryCount\": transaction.retryCount,\n \"transaction.keyCount\": transaction.keys.length,\n },\n async (span) => {\n this.scheduler.markStarted(transaction)\n\n if (transaction.retryCount > 0) {\n span.setAttribute(`retry.attempt`, transaction.retryCount)\n }\n\n try {\n const result = await this.runMutationFn(transaction)\n\n this.scheduler.markCompleted(transaction)\n await this.outbox.remove(transaction.id)\n\n span.setAttribute(`result`, `success`)\n this.offlineExecutor.resolveTransaction(transaction.id, result)\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(String(error))\n\n span.setAttribute(`result`, `error`)\n\n await this.handleError(transaction, err)\n ;(err as any)[HANDLED_EXECUTION_ERROR] = true\n throw err\n }\n }\n )\n } catch (error) {\n if (\n error instanceof Error &&\n (error as any)[HANDLED_EXECUTION_ERROR] === true\n ) {\n return\n }\n\n throw error\n }\n }\n\n private async runMutationFn(transaction: OfflineTransaction): Promise<void> {\n const mutationFn = this.config.mutationFns[transaction.mutationFnName]\n\n if (!mutationFn) {\n const errorMessage = `Unknown mutation function: ${transaction.mutationFnName}`\n\n if (this.config.onUnknownMutationFn) {\n this.config.onUnknownMutationFn(transaction.mutationFnName, transaction)\n }\n\n throw new NonRetriableError(errorMessage)\n }\n\n // Mutations are already PendingMutation objects with collections attached\n // from the deserializer, so we can use them directly\n const transactionWithMutations = {\n id: transaction.id,\n mutations: transaction.mutations,\n metadata: transaction.metadata ?? {},\n }\n\n await mutationFn({\n transaction: transactionWithMutations as any,\n idempotencyKey: transaction.idempotencyKey,\n })\n }\n\n private async handleError(\n transaction: OfflineTransaction,\n error: Error\n ): Promise<void> {\n return withNestedSpan(\n `transaction.handleError`,\n {\n \"transaction.id\": transaction.id,\n \"error.name\": error.name,\n \"error.message\": error.message,\n },\n async (span) => {\n const shouldRetry = this.retryPolicy.shouldRetry(\n error,\n transaction.retryCount\n )\n\n span.setAttribute(`shouldRetry`, shouldRetry)\n\n if (!shouldRetry) {\n this.scheduler.markCompleted(transaction)\n await this.outbox.remove(transaction.id)\n console.warn(\n `Transaction ${transaction.id} failed permanently:`,\n error\n )\n\n span.setAttribute(`result`, `permanent_failure`)\n // Signal permanent failure to the waiting transaction\n this.offlineExecutor.rejectTransaction(transaction.id, error)\n return\n }\n\n const delay = this.retryPolicy.calculateDelay(transaction.retryCount)\n const updatedTransaction: OfflineTransaction = {\n ...transaction,\n retryCount: transaction.retryCount + 1,\n nextAttemptAt: Date.now() + delay,\n lastError: {\n name: error.name,\n message: error.message,\n stack: error.stack,\n },\n }\n\n span.setAttribute(`retryDelay`, delay)\n span.setAttribute(`nextRetryCount`, updatedTransaction.retryCount)\n\n this.scheduler.markFailed(transaction)\n this.scheduler.updateTransaction(updatedTransaction)\n\n try {\n await this.outbox.update(transaction.id, updatedTransaction)\n span.setAttribute(`result`, `scheduled_retry`)\n } catch (persistError) {\n span.recordException(persistError as Error)\n span.setAttribute(`result`, `persist_failed`)\n throw persistError\n }\n\n // Schedule retry timer\n this.scheduleNextRetry()\n }\n )\n }\n\n async loadPendingTransactions(): Promise<void> {\n const transactions = await this.outbox.getAll()\n let filteredTransactions = transactions\n\n if (this.config.beforeRetry) {\n filteredTransactions = this.config.beforeRetry(transactions)\n }\n\n for (const transaction of filteredTransactions) {\n this.scheduler.schedule(transaction)\n }\n\n // Reset retry delays for all loaded transactions so they can run immediately\n this.resetRetryDelays()\n\n // Schedule retry timer for loaded transactions\n this.scheduleNextRetry()\n\n const removedTransactions = transactions.filter(\n (tx) => !filteredTransactions.some((filtered) => filtered.id === tx.id)\n )\n\n if (removedTransactions.length > 0) {\n await this.outbox.removeMany(removedTransactions.map((tx) => tx.id))\n }\n }\n\n clear(): void {\n this.scheduler.clear()\n this.clearRetryTimer()\n }\n\n getPendingCount(): number {\n return this.scheduler.getPendingCount()\n }\n\n private scheduleNextRetry(): void {\n // Clear existing timer\n this.clearRetryTimer()\n\n // Find the earliest retry time among pending transactions\n const earliestRetryTime = this.getEarliestRetryTime()\n\n if (earliestRetryTime === null) {\n return // No transactions pending retry\n }\n\n const delay = Math.max(0, earliestRetryTime - Date.now())\n\n this.retryTimer = setTimeout(() => {\n this.executeAll().catch((error) => {\n console.warn(`Failed to execute retry batch:`, error)\n })\n }, delay)\n }\n\n private getEarliestRetryTime(): number | null {\n const allTransactions = this.scheduler.getAllPendingTransactions()\n\n if (allTransactions.length === 0) {\n return null\n }\n\n return Math.min(...allTransactions.map((tx) => tx.nextAttemptAt))\n }\n\n private clearRetryTimer(): void {\n if (this.retryTimer) {\n clearTimeout(this.retryTimer)\n this.retryTimer = null\n }\n }\n\n getRunningCount(): number {\n return this.scheduler.getRunningCount()\n }\n\n resetRetryDelays(): void {\n const allTransactions = this.scheduler.getAllPendingTransactions()\n const updatedTransactions = allTransactions.map((transaction) => ({\n ...transaction,\n nextAttemptAt: Date.now(),\n }))\n\n this.scheduler.updateTransactions(updatedTransactions)\n }\n}\n"],"names":["DefaultRetryPolicy","withNestedSpan","NonRetriableError"],"mappings":"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"}
@@ -27,7 +27,7 @@ class OutboxManager {
27
27
  );
28
28
  }
29
29
  async get(id) {
30
- return tracer.withSpan(`outbox.get`, { "transaction.id": id }, async (span) => {
30
+ return tracer.withSpan(`outbox.get`, {}, async (span) => {
31
31
  const key = this.getStorageKey(id);
32
32
  const data = await this.storage.get(key);
33
33
  if (!data) {
@@ -80,7 +80,7 @@ class OutboxManager {
80
80
  );
81
81
  }
82
82
  async update(id, updates) {
83
- return tracer.withSpan(`outbox.update`, { "transaction.id": id }, async () => {
83
+ return tracer.withSpan(`outbox.update`, {}, async () => {
84
84
  const existing = await this.get(id);
85
85
  if (!existing) {
86
86
  throw new Error(`Transaction ${id} not found`);
@@ -90,7 +90,7 @@ class OutboxManager {
90
90
  });
91
91
  }
92
92
  async remove(id) {
93
- return tracer.withSpan(`outbox.remove`, { "transaction.id": id }, async () => {
93
+ return tracer.withSpan(`outbox.remove`, {}, async () => {
94
94
  const key = this.getStorageKey(id);
95
95
  await this.storage.delete(key);
96
96
  });
@@ -1 +1 @@
1
- {"version":3,"file":"OutboxManager.cjs","sources":["../../../src/outbox/OutboxManager.ts"],"sourcesContent":["import { withSpan } from \"../telemetry/tracer\"\nimport { TransactionSerializer } from \"./TransactionSerializer\"\nimport type { OfflineTransaction, StorageAdapter } from \"../types\"\nimport type { Collection } from \"@tanstack/db\"\n\nexport class OutboxManager {\n private storage: StorageAdapter\n private serializer: TransactionSerializer\n private keyPrefix = `tx:`\n\n constructor(\n storage: StorageAdapter,\n collections: Record<string, Collection>\n ) {\n this.storage = storage\n this.serializer = new TransactionSerializer(collections)\n }\n\n private getStorageKey(id: string): string {\n return `${this.keyPrefix}${id}`\n }\n\n async add(transaction: OfflineTransaction): Promise<void> {\n return withSpan(\n `outbox.add`,\n {\n \"transaction.id\": transaction.id,\n \"transaction.mutationFnName\": transaction.mutationFnName,\n \"transaction.keyCount\": transaction.keys.length,\n },\n async () => {\n const key = this.getStorageKey(transaction.id)\n const serialized = this.serializer.serialize(transaction)\n await this.storage.set(key, serialized)\n }\n )\n }\n\n async get(id: string): Promise<OfflineTransaction | null> {\n return withSpan(`outbox.get`, { \"transaction.id\": id }, async (span) => {\n const key = this.getStorageKey(id)\n const data = await this.storage.get(key)\n\n if (!data) {\n span.setAttribute(`result`, `not_found`)\n return null\n }\n\n try {\n const transaction = this.serializer.deserialize(data)\n span.setAttribute(`result`, `found`)\n return transaction\n } catch (error) {\n console.warn(`Failed to deserialize transaction ${id}:`, error)\n span.setAttribute(`result`, `deserialize_error`)\n return null\n }\n })\n }\n\n async getAll(): Promise<Array<OfflineTransaction>> {\n return withSpan(`outbox.getAll`, {}, async (span) => {\n const keys = await this.storage.keys()\n const transactionKeys = keys.filter((key) =>\n key.startsWith(this.keyPrefix)\n )\n\n span.setAttribute(`transactionCount`, transactionKeys.length)\n\n const transactions: Array<OfflineTransaction> = []\n\n for (const key of transactionKeys) {\n const data = await this.storage.get(key)\n if (data) {\n try {\n const transaction = this.serializer.deserialize(data)\n transactions.push(transaction)\n } catch (error) {\n console.warn(\n `Failed to deserialize transaction from key ${key}:`,\n error\n )\n }\n }\n }\n\n return transactions.sort(\n (a, b) => a.createdAt.getTime() - b.createdAt.getTime()\n )\n })\n }\n\n async getByKeys(keys: Array<string>): Promise<Array<OfflineTransaction>> {\n const allTransactions = await this.getAll()\n const keySet = new Set(keys)\n\n return allTransactions.filter((transaction) =>\n transaction.keys.some((key) => keySet.has(key))\n )\n }\n\n async update(\n id: string,\n updates: Partial<OfflineTransaction>\n ): Promise<void> {\n return withSpan(`outbox.update`, { \"transaction.id\": id }, async () => {\n const existing = await this.get(id)\n if (!existing) {\n throw new Error(`Transaction ${id} not found`)\n }\n\n const updated = { ...existing, ...updates }\n await this.add(updated)\n })\n }\n\n async remove(id: string): Promise<void> {\n return withSpan(`outbox.remove`, { \"transaction.id\": id }, async () => {\n const key = this.getStorageKey(id)\n await this.storage.delete(key)\n })\n }\n\n async removeMany(ids: Array<string>): Promise<void> {\n return withSpan(`outbox.removeMany`, { count: ids.length }, async () => {\n await Promise.all(ids.map((id) => this.remove(id)))\n })\n }\n\n async clear(): Promise<void> {\n const keys = await this.storage.keys()\n const transactionKeys = keys.filter((key) => key.startsWith(this.keyPrefix))\n\n await Promise.all(transactionKeys.map((key) => this.storage.delete(key)))\n }\n\n async count(): Promise<number> {\n const keys = await this.storage.keys()\n return keys.filter((key) => key.startsWith(this.keyPrefix)).length\n }\n}\n"],"names":["TransactionSerializer","withSpan"],"mappings":";;;;AAKO,MAAM,cAAc;AAAA,EAKzB,YACE,SACA,aACA;AALF,SAAQ,YAAY;AAMlB,SAAK,UAAU;AACf,SAAK,aAAa,IAAIA,sBAAAA,sBAAsB,WAAW;AAAA,EACzD;AAAA,EAEQ,cAAc,IAAoB;AACxC,WAAO,GAAG,KAAK,SAAS,GAAG,EAAE;AAAA,EAC/B;AAAA,EAEA,MAAM,IAAI,aAAgD;AACxD,WAAOC,OAAAA;AAAAA,MACL;AAAA,MACA;AAAA,QACE,kBAAkB,YAAY;AAAA,QAC9B,8BAA8B,YAAY;AAAA,QAC1C,wBAAwB,YAAY,KAAK;AAAA,MAAA;AAAA,MAE3C,YAAY;AACV,cAAM,MAAM,KAAK,cAAc,YAAY,EAAE;AAC7C,cAAM,aAAa,KAAK,WAAW,UAAU,WAAW;AACxD,cAAM,KAAK,QAAQ,IAAI,KAAK,UAAU;AAAA,MACxC;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,MAAM,IAAI,IAAgD;AACxD,WAAOA,OAAAA,SAAS,cAAc,EAAE,kBAAkB,GAAA,GAAM,OAAO,SAAS;AACtE,YAAM,MAAM,KAAK,cAAc,EAAE;AACjC,YAAM,OAAO,MAAM,KAAK,QAAQ,IAAI,GAAG;AAEvC,UAAI,CAAC,MAAM;AACT,aAAK,aAAa,UAAU,WAAW;AACvC,eAAO;AAAA,MACT;AAEA,UAAI;AACF,cAAM,cAAc,KAAK,WAAW,YAAY,IAAI;AACpD,aAAK,aAAa,UAAU,OAAO;AACnC,eAAO;AAAA,MACT,SAAS,OAAO;AACd,gBAAQ,KAAK,qCAAqC,EAAE,KAAK,KAAK;AAC9D,aAAK,aAAa,UAAU,mBAAmB;AAC/C,eAAO;AAAA,MACT;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,SAA6C;AACjD,WAAOA,OAAAA,SAAS,iBAAiB,CAAA,GAAI,OAAO,SAAS;AACnD,YAAM,OAAO,MAAM,KAAK,QAAQ,KAAA;AAChC,YAAM,kBAAkB,KAAK;AAAA,QAAO,CAAC,QACnC,IAAI,WAAW,KAAK,SAAS;AAAA,MAAA;AAG/B,WAAK,aAAa,oBAAoB,gBAAgB,MAAM;AAE5D,YAAM,eAA0C,CAAA;AAEhD,iBAAW,OAAO,iBAAiB;AACjC,cAAM,OAAO,MAAM,KAAK,QAAQ,IAAI,GAAG;AACvC,YAAI,MAAM;AACR,cAAI;AACF,kBAAM,cAAc,KAAK,WAAW,YAAY,IAAI;AACpD,yBAAa,KAAK,WAAW;AAAA,UAC/B,SAAS,OAAO;AACd,oBAAQ;AAAA,cACN,8CAA8C,GAAG;AAAA,cACjD;AAAA,YAAA;AAAA,UAEJ;AAAA,QACF;AAAA,MACF;AAEA,aAAO,aAAa;AAAA,QAClB,CAAC,GAAG,MAAM,EAAE,UAAU,YAAY,EAAE,UAAU,QAAA;AAAA,MAAQ;AAAA,IAE1D,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAU,MAAyD;AACvE,UAAM,kBAAkB,MAAM,KAAK,OAAA;AACnC,UAAM,SAAS,IAAI,IAAI,IAAI;AAE3B,WAAO,gBAAgB;AAAA,MAAO,CAAC,gBAC7B,YAAY,KAAK,KAAK,CAAC,QAAQ,OAAO,IAAI,GAAG,CAAC;AAAA,IAAA;AAAA,EAElD;AAAA,EAEA,MAAM,OACJ,IACA,SACe;AACf,WAAOA,OAAAA,SAAS,iBAAiB,EAAE,kBAAkB,GAAA,GAAM,YAAY;AACrE,YAAM,WAAW,MAAM,KAAK,IAAI,EAAE;AAClC,UAAI,CAAC,UAAU;AACb,cAAM,IAAI,MAAM,eAAe,EAAE,YAAY;AAAA,MAC/C;AAEA,YAAM,UAAU,EAAE,GAAG,UAAU,GAAG,QAAA;AAClC,YAAM,KAAK,IAAI,OAAO;AAAA,IACxB,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,OAAO,IAA2B;AACtC,WAAOA,OAAAA,SAAS,iBAAiB,EAAE,kBAAkB,GAAA,GAAM,YAAY;AACrE,YAAM,MAAM,KAAK,cAAc,EAAE;AACjC,YAAM,KAAK,QAAQ,OAAO,GAAG;AAAA,IAC/B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,WAAW,KAAmC;AAClD,WAAOA,OAAAA,SAAS,qBAAqB,EAAE,OAAO,IAAI,OAAA,GAAU,YAAY;AACtE,YAAM,QAAQ,IAAI,IAAI,IAAI,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC,CAAC;AAAA,IACpD,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAuB;AAC3B,UAAM,OAAO,MAAM,KAAK,QAAQ,KAAA;AAChC,UAAM,kBAAkB,KAAK,OAAO,CAAC,QAAQ,IAAI,WAAW,KAAK,SAAS,CAAC;AAE3E,UAAM,QAAQ,IAAI,gBAAgB,IAAI,CAAC,QAAQ,KAAK,QAAQ,OAAO,GAAG,CAAC,CAAC;AAAA,EAC1E;AAAA,EAEA,MAAM,QAAyB;AAC7B,UAAM,OAAO,MAAM,KAAK,QAAQ,KAAA;AAChC,WAAO,KAAK,OAAO,CAAC,QAAQ,IAAI,WAAW,KAAK,SAAS,CAAC,EAAE;AAAA,EAC9D;AACF;;"}
1
+ {"version":3,"file":"OutboxManager.cjs","sources":["../../../src/outbox/OutboxManager.ts"],"sourcesContent":["import { withSpan } from \"../telemetry/tracer\"\nimport { TransactionSerializer } from \"./TransactionSerializer\"\nimport type { OfflineTransaction, StorageAdapter } from \"../types\"\nimport type { Collection } from \"@tanstack/db\"\n\nexport class OutboxManager {\n private storage: StorageAdapter\n private serializer: TransactionSerializer\n private keyPrefix = `tx:`\n\n constructor(\n storage: StorageAdapter,\n collections: Record<string, Collection>\n ) {\n this.storage = storage\n this.serializer = new TransactionSerializer(collections)\n }\n\n private getStorageKey(id: string): string {\n return `${this.keyPrefix}${id}`\n }\n\n async add(transaction: OfflineTransaction): Promise<void> {\n return withSpan(\n `outbox.add`,\n {\n \"transaction.id\": transaction.id,\n \"transaction.mutationFnName\": transaction.mutationFnName,\n \"transaction.keyCount\": transaction.keys.length,\n },\n async () => {\n const key = this.getStorageKey(transaction.id)\n const serialized = this.serializer.serialize(transaction)\n await this.storage.set(key, serialized)\n }\n )\n }\n\n async get(id: string): Promise<OfflineTransaction | null> {\n return withSpan(`outbox.get`, { \"transaction.id\": id }, async (span) => {\n const key = this.getStorageKey(id)\n const data = await this.storage.get(key)\n\n if (!data) {\n span.setAttribute(`result`, `not_found`)\n return null\n }\n\n try {\n const transaction = this.serializer.deserialize(data)\n span.setAttribute(`result`, `found`)\n return transaction\n } catch (error) {\n console.warn(`Failed to deserialize transaction ${id}:`, error)\n span.setAttribute(`result`, `deserialize_error`)\n return null\n }\n })\n }\n\n async getAll(): Promise<Array<OfflineTransaction>> {\n return withSpan(`outbox.getAll`, {}, async (span) => {\n const keys = await this.storage.keys()\n const transactionKeys = keys.filter((key) =>\n key.startsWith(this.keyPrefix)\n )\n\n span.setAttribute(`transactionCount`, transactionKeys.length)\n\n const transactions: Array<OfflineTransaction> = []\n\n for (const key of transactionKeys) {\n const data = await this.storage.get(key)\n if (data) {\n try {\n const transaction = this.serializer.deserialize(data)\n transactions.push(transaction)\n } catch (error) {\n console.warn(\n `Failed to deserialize transaction from key ${key}:`,\n error\n )\n }\n }\n }\n\n return transactions.sort(\n (a, b) => a.createdAt.getTime() - b.createdAt.getTime()\n )\n })\n }\n\n async getByKeys(keys: Array<string>): Promise<Array<OfflineTransaction>> {\n const allTransactions = await this.getAll()\n const keySet = new Set(keys)\n\n return allTransactions.filter((transaction) =>\n transaction.keys.some((key) => keySet.has(key))\n )\n }\n\n async update(\n id: string,\n updates: Partial<OfflineTransaction>\n ): Promise<void> {\n return withSpan(`outbox.update`, { \"transaction.id\": id }, async () => {\n const existing = await this.get(id)\n if (!existing) {\n throw new Error(`Transaction ${id} not found`)\n }\n\n const updated = { ...existing, ...updates }\n await this.add(updated)\n })\n }\n\n async remove(id: string): Promise<void> {\n return withSpan(`outbox.remove`, { \"transaction.id\": id }, async () => {\n const key = this.getStorageKey(id)\n await this.storage.delete(key)\n })\n }\n\n async removeMany(ids: Array<string>): Promise<void> {\n return withSpan(`outbox.removeMany`, { count: ids.length }, async () => {\n await Promise.all(ids.map((id) => this.remove(id)))\n })\n }\n\n async clear(): Promise<void> {\n const keys = await this.storage.keys()\n const transactionKeys = keys.filter((key) => key.startsWith(this.keyPrefix))\n\n await Promise.all(transactionKeys.map((key) => this.storage.delete(key)))\n }\n\n async count(): Promise<number> {\n const keys = await this.storage.keys()\n return keys.filter((key) => key.startsWith(this.keyPrefix)).length\n }\n}\n"],"names":["TransactionSerializer","withSpan"],"mappings":"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"}
@@ -1,89 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const api = require("@opentelemetry/api");
4
- const TRACER = api.trace.getTracer(`@tanstack/offline-transactions`, `0.0.1`);
5
- function getParentContext(options) {
6
- if (options?.parentContext) {
7
- const parentSpan = api.trace.wrapSpanContext(options.parentContext);
8
- return api.trace.setSpan(api.context.active(), parentSpan);
3
+ const noopSpan = {
4
+ setAttribute: () => {
5
+ },
6
+ setAttributes: () => {
7
+ },
8
+ setStatus: () => {
9
+ },
10
+ recordException: () => {
11
+ },
12
+ end: () => {
9
13
  }
10
- return api.context.active();
14
+ };
15
+ async function withSpan(name, attrs, fn, _options) {
16
+ return await fn(noopSpan);
11
17
  }
12
- async function withSpan(name, attrs, fn, options) {
13
- const parentCtx = getParentContext(options);
14
- const span = TRACER.startSpan(name, void 0, parentCtx);
15
- const filteredAttrs = {};
16
- for (const [key, value] of Object.entries(attrs)) {
17
- if (value !== void 0) {
18
- filteredAttrs[key] = value;
19
- }
20
- }
21
- span.setAttributes(filteredAttrs);
22
- try {
23
- const result = await fn(span);
24
- span.setStatus({ code: api.SpanStatusCode.OK });
25
- return result;
26
- } catch (error) {
27
- span.setStatus({
28
- code: api.SpanStatusCode.ERROR,
29
- message: error instanceof Error ? error.message : String(error)
30
- });
31
- span.recordException(error);
32
- throw error;
33
- } finally {
34
- span.end();
35
- }
18
+ async function withNestedSpan(name, attrs, fn, _options) {
19
+ return await fn(noopSpan);
36
20
  }
37
- async function withNestedSpan(name, attrs, fn, options) {
38
- const parentCtx = getParentContext(options);
39
- const span = TRACER.startSpan(name, void 0, parentCtx);
40
- const filteredAttrs = {};
41
- for (const [key, value] of Object.entries(attrs)) {
42
- if (value !== void 0) {
43
- filteredAttrs[key] = value;
44
- }
45
- }
46
- span.setAttributes(filteredAttrs);
47
- const ctx = api.trace.setSpan(parentCtx, span);
48
- try {
49
- const result = await api.context.with(ctx, () => fn(span));
50
- span.setStatus({ code: api.SpanStatusCode.OK });
51
- return result;
52
- } catch (error) {
53
- span.setStatus({
54
- code: api.SpanStatusCode.ERROR,
55
- message: error instanceof Error ? error.message : String(error)
56
- });
57
- span.recordException(error);
58
- throw error;
59
- } finally {
60
- span.end();
61
- }
62
- }
63
- function withSyncSpan(name, attrs, fn, options) {
64
- const parentCtx = getParentContext(options);
65
- const span = TRACER.startSpan(name, void 0, parentCtx);
66
- const filteredAttrs = {};
67
- for (const [key, value] of Object.entries(attrs)) {
68
- if (value !== void 0) {
69
- filteredAttrs[key] = value;
70
- }
71
- }
72
- span.setAttributes(filteredAttrs);
73
- try {
74
- const result = fn(span);
75
- span.setStatus({ code: api.SpanStatusCode.OK });
76
- return result;
77
- } catch (error) {
78
- span.setStatus({
79
- code: api.SpanStatusCode.ERROR,
80
- message: error instanceof Error ? error.message : String(error)
81
- });
82
- span.recordException(error);
83
- throw error;
84
- } finally {
85
- span.end();
86
- }
21
+ function withSyncSpan(name, attrs, fn, _options) {
22
+ return fn(noopSpan);
87
23
  }
88
24
  exports.withNestedSpan = withNestedSpan;
89
25
  exports.withSpan = withSpan;
@@ -1 +1 @@
1
- {"version":3,"file":"tracer.cjs","sources":["../../../src/telemetry/tracer.ts"],"sourcesContent":["import { SpanStatusCode, context, trace } from \"@opentelemetry/api\"\nimport type { Span, SpanContext } from \"@opentelemetry/api\"\n\nconst TRACER = trace.getTracer(`@tanstack/offline-transactions`, `0.0.1`)\n\nexport interface SpanAttrs {\n [key: string]: string | number | boolean | undefined\n}\n\ninterface WithSpanOptions {\n parentContext?: SpanContext\n}\n\nfunction getParentContext(options?: WithSpanOptions) {\n if (options?.parentContext) {\n const parentSpan = trace.wrapSpanContext(options.parentContext)\n return trace.setSpan(context.active(), parentSpan)\n }\n\n return context.active()\n}\n\n/**\n * Lightweight span wrapper with error handling.\n * Uses OpenTelemetry API which is no-op when tracing is disabled.\n *\n * By default, creates spans at the current context level (siblings).\n * Use withNestedSpan if you want parent-child relationships.\n */\nexport async function withSpan<T>(\n name: string,\n attrs: SpanAttrs,\n fn: (span: Span) => Promise<T>,\n options?: WithSpanOptions\n): Promise<T> {\n const parentCtx = getParentContext(options)\n const span = TRACER.startSpan(name, undefined, parentCtx)\n\n // Filter out undefined attributes\n const filteredAttrs: Record<string, string | number | boolean> = {}\n for (const [key, value] of Object.entries(attrs)) {\n if (value !== undefined) {\n filteredAttrs[key] = value\n }\n }\n\n span.setAttributes(filteredAttrs)\n\n try {\n const result = await fn(span)\n span.setStatus({ code: SpanStatusCode.OK })\n return result\n } catch (error) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n })\n span.recordException(error as Error)\n throw error\n } finally {\n span.end()\n }\n}\n\n/**\n * Like withSpan but propagates context so child spans nest properly.\n * Use this when you want operations inside fn to be child spans.\n */\nexport async function withNestedSpan<T>(\n name: string,\n attrs: SpanAttrs,\n fn: (span: Span) => Promise<T>,\n options?: WithSpanOptions\n): Promise<T> {\n const parentCtx = getParentContext(options)\n const span = TRACER.startSpan(name, undefined, parentCtx)\n\n // Filter out undefined attributes\n const filteredAttrs: Record<string, string | number | boolean> = {}\n for (const [key, value] of Object.entries(attrs)) {\n if (value !== undefined) {\n filteredAttrs[key] = value\n }\n }\n\n span.setAttributes(filteredAttrs)\n\n // Set the span as active context so child spans nest properly\n const ctx = trace.setSpan(parentCtx, span)\n\n try {\n // Execute the function within the span's context\n const result = await context.with(ctx, () => fn(span))\n span.setStatus({ code: SpanStatusCode.OK })\n return result\n } catch (error) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n })\n span.recordException(error as Error)\n throw error\n } finally {\n span.end()\n }\n}\n\n/**\n * Creates a synchronous span for non-async operations\n */\nexport function withSyncSpan<T>(\n name: string,\n attrs: SpanAttrs,\n fn: (span: Span) => T,\n options?: WithSpanOptions\n): T {\n const parentCtx = getParentContext(options)\n const span = TRACER.startSpan(name, undefined, parentCtx)\n\n // Filter out undefined attributes\n const filteredAttrs: Record<string, string | number | boolean> = {}\n for (const [key, value] of Object.entries(attrs)) {\n if (value !== undefined) {\n filteredAttrs[key] = value\n }\n }\n\n span.setAttributes(filteredAttrs)\n\n try {\n const result = fn(span)\n span.setStatus({ code: SpanStatusCode.OK })\n return result\n } catch (error) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n })\n span.recordException(error as Error)\n throw error\n } finally {\n span.end()\n }\n}\n\n/**\n * Get the current tracer instance\n */\nexport function getTracer() {\n return TRACER\n}\n"],"names":["trace","context","SpanStatusCode"],"mappings":";;;AAGA,MAAM,SAASA,IAAAA,MAAM,UAAU,kCAAkC,OAAO;AAUxE,SAAS,iBAAiB,SAA2B;AACnD,MAAI,SAAS,eAAe;AAC1B,UAAM,aAAaA,IAAAA,MAAM,gBAAgB,QAAQ,aAAa;AAC9D,WAAOA,IAAAA,MAAM,QAAQC,IAAAA,QAAQ,OAAA,GAAU,UAAU;AAAA,EACnD;AAEA,SAAOA,IAAAA,QAAQ,OAAA;AACjB;AASA,eAAsB,SACpB,MACA,OACA,IACA,SACY;AACZ,QAAM,YAAY,iBAAiB,OAAO;AAC1C,QAAM,OAAO,OAAO,UAAU,MAAM,QAAW,SAAS;AAGxD,QAAM,gBAA2D,CAAA;AACjE,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,QAAI,UAAU,QAAW;AACvB,oBAAc,GAAG,IAAI;AAAA,IACvB;AAAA,EACF;AAEA,OAAK,cAAc,aAAa;AAEhC,MAAI;AACF,UAAM,SAAS,MAAM,GAAG,IAAI;AAC5B,SAAK,UAAU,EAAE,MAAMC,IAAAA,eAAe,IAAI;AAC1C,WAAO;AAAA,EACT,SAAS,OAAO;AACd,SAAK,UAAU;AAAA,MACb,MAAMA,IAAAA,eAAe;AAAA,MACrB,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAAA,CAC/D;AACD,SAAK,gBAAgB,KAAc;AACnC,UAAM;AAAA,EACR,UAAA;AACE,SAAK,IAAA;AAAA,EACP;AACF;AAMA,eAAsB,eACpB,MACA,OACA,IACA,SACY;AACZ,QAAM,YAAY,iBAAiB,OAAO;AAC1C,QAAM,OAAO,OAAO,UAAU,MAAM,QAAW,SAAS;AAGxD,QAAM,gBAA2D,CAAA;AACjE,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,QAAI,UAAU,QAAW;AACvB,oBAAc,GAAG,IAAI;AAAA,IACvB;AAAA,EACF;AAEA,OAAK,cAAc,aAAa;AAGhC,QAAM,MAAMF,IAAAA,MAAM,QAAQ,WAAW,IAAI;AAEzC,MAAI;AAEF,UAAM,SAAS,MAAMC,YAAQ,KAAK,KAAK,MAAM,GAAG,IAAI,CAAC;AACrD,SAAK,UAAU,EAAE,MAAMC,IAAAA,eAAe,IAAI;AAC1C,WAAO;AAAA,EACT,SAAS,OAAO;AACd,SAAK,UAAU;AAAA,MACb,MAAMA,IAAAA,eAAe;AAAA,MACrB,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAAA,CAC/D;AACD,SAAK,gBAAgB,KAAc;AACnC,UAAM;AAAA,EACR,UAAA;AACE,SAAK,IAAA;AAAA,EACP;AACF;AAKO,SAAS,aACd,MACA,OACA,IACA,SACG;AACH,QAAM,YAAY,iBAAiB,OAAO;AAC1C,QAAM,OAAO,OAAO,UAAU,MAAM,QAAW,SAAS;AAGxD,QAAM,gBAA2D,CAAA;AACjE,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,QAAI,UAAU,QAAW;AACvB,oBAAc,GAAG,IAAI;AAAA,IACvB;AAAA,EACF;AAEA,OAAK,cAAc,aAAa;AAEhC,MAAI;AACF,UAAM,SAAS,GAAG,IAAI;AACtB,SAAK,UAAU,EAAE,MAAMA,IAAAA,eAAe,IAAI;AAC1C,WAAO;AAAA,EACT,SAAS,OAAO;AACd,SAAK,UAAU;AAAA,MACb,MAAMA,IAAAA,eAAe;AAAA,MACrB,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAAA,CAC/D;AACD,SAAK,gBAAgB,KAAc;AACnC,UAAM;AAAA,EACR,UAAA;AACE,SAAK,IAAA;AAAA,EACP;AACF;;;;"}
1
+ {"version":3,"file":"tracer.cjs","sources":["../../../src/telemetry/tracer.ts"],"sourcesContent":["export interface SpanAttrs {\n [key: string]: string | number | boolean | undefined\n}\n\ninterface WithSpanOptions {\n parentContext?: any\n}\n\n// No-op span implementation\nconst noopSpan = {\n setAttribute: () => {},\n setAttributes: () => {},\n setStatus: () => {},\n recordException: () => {},\n end: () => {},\n}\n\n/**\n * Lightweight span wrapper with error handling.\n * No-op implementation - telemetry has been removed.\n *\n * By default, creates spans at the current context level (siblings).\n * Use withNestedSpan if you want parent-child relationships.\n */\nexport async function withSpan<T>(\n name: string,\n attrs: SpanAttrs,\n fn: (span: any) => Promise<T>,\n _options?: WithSpanOptions\n): Promise<T> {\n return await fn(noopSpan)\n}\n\n/**\n * Like withSpan but propagates context so child spans nest properly.\n * No-op implementation - telemetry has been removed.\n */\nexport async function withNestedSpan<T>(\n name: string,\n attrs: SpanAttrs,\n fn: (span: any) => Promise<T>,\n _options?: WithSpanOptions\n): Promise<T> {\n return await fn(noopSpan)\n}\n\n/**\n * Creates a synchronous span for non-async operations\n * No-op implementation - telemetry has been removed.\n */\nexport function withSyncSpan<T>(\n name: string,\n attrs: SpanAttrs,\n fn: (span: any) => T,\n _options?: WithSpanOptions\n): T {\n return fn(noopSpan)\n}\n\n/**\n * Get the current tracer instance\n * No-op implementation - telemetry has been removed.\n */\nexport function getTracer() {\n return null\n}\n"],"names":[],"mappings":";;AASA,MAAM,WAAW;AAAA,EACf,cAAc,MAAM;AAAA,EAAC;AAAA,EACrB,eAAe,MAAM;AAAA,EAAC;AAAA,EACtB,WAAW,MAAM;AAAA,EAAC;AAAA,EAClB,iBAAiB,MAAM;AAAA,EAAC;AAAA,EACxB,KAAK,MAAM;AAAA,EAAC;AACd;AASA,eAAsB,SACpB,MACA,OACA,IACA,UACY;AACZ,SAAO,MAAM,GAAG,QAAQ;AAC1B;AAMA,eAAsB,eACpB,MACA,OACA,IACA,UACY;AACZ,SAAO,MAAM,GAAG,QAAQ;AAC1B;AAMO,SAAS,aACd,MACA,OACA,IACA,UACG;AACH,SAAO,GAAG,QAAQ;AACpB;;;;"}
@@ -1,29 +1,30 @@
1
- import { Span, SpanContext } from '@opentelemetry/api';
2
1
  export interface SpanAttrs {
3
2
  [key: string]: string | number | boolean | undefined;
4
3
  }
5
4
  interface WithSpanOptions {
6
- parentContext?: SpanContext;
5
+ parentContext?: any;
7
6
  }
8
7
  /**
9
8
  * Lightweight span wrapper with error handling.
10
- * Uses OpenTelemetry API which is no-op when tracing is disabled.
9
+ * No-op implementation - telemetry has been removed.
11
10
  *
12
11
  * By default, creates spans at the current context level (siblings).
13
12
  * Use withNestedSpan if you want parent-child relationships.
14
13
  */
15
- export declare function withSpan<T>(name: string, attrs: SpanAttrs, fn: (span: Span) => Promise<T>, options?: WithSpanOptions): Promise<T>;
14
+ export declare function withSpan<T>(name: string, attrs: SpanAttrs, fn: (span: any) => Promise<T>, _options?: WithSpanOptions): Promise<T>;
16
15
  /**
17
16
  * Like withSpan but propagates context so child spans nest properly.
18
- * Use this when you want operations inside fn to be child spans.
17
+ * No-op implementation - telemetry has been removed.
19
18
  */
20
- export declare function withNestedSpan<T>(name: string, attrs: SpanAttrs, fn: (span: Span) => Promise<T>, options?: WithSpanOptions): Promise<T>;
19
+ export declare function withNestedSpan<T>(name: string, attrs: SpanAttrs, fn: (span: any) => Promise<T>, _options?: WithSpanOptions): Promise<T>;
21
20
  /**
22
21
  * Creates a synchronous span for non-async operations
22
+ * No-op implementation - telemetry has been removed.
23
23
  */
24
- export declare function withSyncSpan<T>(name: string, attrs: SpanAttrs, fn: (span: Span) => T, options?: WithSpanOptions): T;
24
+ export declare function withSyncSpan<T>(name: string, attrs: SpanAttrs, fn: (span: any) => T, _options?: WithSpanOptions): T;
25
25
  /**
26
26
  * Get the current tracer instance
27
+ * No-op implementation - telemetry has been removed.
27
28
  */
28
- export declare function getTracer(): import('@opentelemetry/api').Tracer;
29
+ export declare function getTracer(): null;
29
30
  export {};
@@ -1,4 +1,3 @@
1
- import { trace, context, SpanStatusCode } from "@opentelemetry/api";
2
1
  import { OnMutateMustBeSynchronousError } from "@tanstack/db";
3
2
  import { OfflineTransaction } from "./OfflineTransaction.js";
4
3
  function isPromiseLike(value) {
@@ -24,25 +23,14 @@ function createOfflineAction(options, mutationFn, persistTransaction, executor)
24
23
  throw new OnMutateMustBeSynchronousError();
25
24
  }
26
25
  });
27
- const tracer = trace.getTracer(`@tanstack/offline-transactions`, `0.0.1`);
28
- const span = tracer.startSpan(`offlineAction.${mutationFnName}`);
29
- const ctx = trace.setSpan(context.active(), span);
30
- console.log(`starting offlineAction span`, { tracer, span, ctx });
31
- const commitPromise = context.with(ctx, () => {
32
- return (async () => {
33
- try {
34
- await transaction.commit();
35
- span.setStatus({ code: SpanStatusCode.OK });
36
- span.end();
37
- console.log(`ended offlineAction span - success`);
38
- } catch (error) {
39
- span.recordException(error);
40
- span.setStatus({ code: SpanStatusCode.ERROR });
41
- span.end();
42
- console.log(`ended offlineAction span - error`);
43
- }
44
- })();
45
- });
26
+ const commitPromise = (async () => {
27
+ try {
28
+ await transaction.commit();
29
+ console.log(`offlineAction committed - success`);
30
+ } catch {
31
+ console.log(`offlineAction commit failed - error`);
32
+ }
33
+ })();
46
34
  commitPromise.catch(() => {
47
35
  });
48
36
  return transaction;
@@ -1 +1 @@
1
- {"version":3,"file":"OfflineAction.js","sources":["../../../src/api/OfflineAction.ts"],"sourcesContent":["import { SpanStatusCode, context, trace } from \"@opentelemetry/api\"\nimport { OnMutateMustBeSynchronousError } from \"@tanstack/db\"\nimport { OfflineTransaction } from \"./OfflineTransaction\"\nimport type { Transaction } from \"@tanstack/db\"\nimport type {\n CreateOfflineActionOptions,\n OfflineMutationFn,\n OfflineTransaction as OfflineTransactionType,\n} from \"../types\"\n\nfunction isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n return (\n !!value &&\n (typeof value === `object` || typeof value === `function`) &&\n typeof (value as { then?: unknown }).then === `function`\n )\n}\n\nexport function createOfflineAction<T>(\n options: CreateOfflineActionOptions<T>,\n mutationFn: OfflineMutationFn,\n persistTransaction: (tx: OfflineTransactionType) => Promise<void>,\n executor: any\n): (variables: T) => Transaction {\n const { mutationFnName, onMutate } = options\n console.log(`createOfflineAction 2`, options)\n\n return (variables: T): Transaction => {\n const offlineTransaction = new OfflineTransaction(\n {\n mutationFnName,\n autoCommit: false,\n },\n mutationFn,\n persistTransaction,\n executor\n )\n\n const transaction = offlineTransaction.mutate(() => {\n console.log(`mutate`)\n const maybePromise = onMutate(variables) as unknown\n\n if (isPromiseLike(maybePromise)) {\n throw new OnMutateMustBeSynchronousError()\n }\n })\n\n // Immediately commit with span instrumentation\n const tracer = trace.getTracer(`@tanstack/offline-transactions`, `0.0.1`)\n const span = tracer.startSpan(`offlineAction.${mutationFnName}`)\n const ctx = trace.setSpan(context.active(), span)\n console.log(`starting offlineAction span`, { tracer, span, ctx })\n\n // Execute the commit within the span context\n // The key is to return the promise synchronously from context.with() so context binds to it\n const commitPromise = context.with(ctx, () => {\n // Return the promise synchronously - this is critical for context propagation in browsers\n return (async () => {\n try {\n await transaction.commit()\n span.setStatus({ code: SpanStatusCode.OK })\n span.end()\n console.log(`ended offlineAction span - success`)\n } catch (error) {\n span.recordException(error as Error)\n span.setStatus({ code: SpanStatusCode.ERROR })\n span.end()\n console.log(`ended offlineAction span - error`)\n }\n })()\n })\n\n // Don't await - this is fire-and-forget for optimistic actions\n // But catch to prevent unhandled rejection\n commitPromise.catch(() => {\n // Already handled in try/catch above\n })\n\n return transaction\n }\n}\n"],"names":[],"mappings":";;;AAUA,SAAS,cAAc,OAA+C;AACpE,SACE,CAAC,CAAC,UACD,OAAO,UAAU,YAAY,OAAO,UAAU,eAC/C,OAAQ,MAA6B,SAAS;AAElD;AAEO,SAAS,oBACd,SACA,YACA,oBACA,UAC+B;AAC/B,QAAM,EAAE,gBAAgB,SAAA,IAAa;AACrC,UAAQ,IAAI,yBAAyB,OAAO;AAE5C,SAAO,CAAC,cAA8B;AACpC,UAAM,qBAAqB,IAAI;AAAA,MAC7B;AAAA,QACE;AAAA,QACA,YAAY;AAAA,MAAA;AAAA,MAEd;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAGF,UAAM,cAAc,mBAAmB,OAAO,MAAM;AAClD,cAAQ,IAAI,QAAQ;AACpB,YAAM,eAAe,SAAS,SAAS;AAEvC,UAAI,cAAc,YAAY,GAAG;AAC/B,cAAM,IAAI,+BAAA;AAAA,MACZ;AAAA,IACF,CAAC;AAGD,UAAM,SAAS,MAAM,UAAU,kCAAkC,OAAO;AACxE,UAAM,OAAO,OAAO,UAAU,iBAAiB,cAAc,EAAE;AAC/D,UAAM,MAAM,MAAM,QAAQ,QAAQ,OAAA,GAAU,IAAI;AAChD,YAAQ,IAAI,+BAA+B,EAAE,QAAQ,MAAM,KAAK;AAIhE,UAAM,gBAAgB,QAAQ,KAAK,KAAK,MAAM;AAE5C,cAAQ,YAAY;AAClB,YAAI;AACF,gBAAM,YAAY,OAAA;AAClB,eAAK,UAAU,EAAE,MAAM,eAAe,IAAI;AAC1C,eAAK,IAAA;AACL,kBAAQ,IAAI,oCAAoC;AAAA,QAClD,SAAS,OAAO;AACd,eAAK,gBAAgB,KAAc;AACnC,eAAK,UAAU,EAAE,MAAM,eAAe,OAAO;AAC7C,eAAK,IAAA;AACL,kBAAQ,IAAI,kCAAkC;AAAA,QAChD;AAAA,MACF,GAAA;AAAA,IACF,CAAC;AAID,kBAAc,MAAM,MAAM;AAAA,IAE1B,CAAC;AAED,WAAO;AAAA,EACT;AACF;"}
1
+ {"version":3,"file":"OfflineAction.js","sources":["../../../src/api/OfflineAction.ts"],"sourcesContent":["import { OnMutateMustBeSynchronousError } from \"@tanstack/db\"\nimport { OfflineTransaction } from \"./OfflineTransaction\"\nimport type { Transaction } from \"@tanstack/db\"\nimport type {\n CreateOfflineActionOptions,\n OfflineMutationFn,\n OfflineTransaction as OfflineTransactionType,\n} from \"../types\"\n\nfunction isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n return (\n !!value &&\n (typeof value === `object` || typeof value === `function`) &&\n typeof (value as { then?: unknown }).then === `function`\n )\n}\n\nexport function createOfflineAction<T>(\n options: CreateOfflineActionOptions<T>,\n mutationFn: OfflineMutationFn,\n persistTransaction: (tx: OfflineTransactionType) => Promise<void>,\n executor: any\n): (variables: T) => Transaction {\n const { mutationFnName, onMutate } = options\n console.log(`createOfflineAction 2`, options)\n\n return (variables: T): Transaction => {\n const offlineTransaction = new OfflineTransaction(\n {\n mutationFnName,\n autoCommit: false,\n },\n mutationFn,\n persistTransaction,\n executor\n )\n\n const transaction = offlineTransaction.mutate(() => {\n console.log(`mutate`)\n const maybePromise = onMutate(variables) as unknown\n\n if (isPromiseLike(maybePromise)) {\n throw new OnMutateMustBeSynchronousError()\n }\n })\n\n // Immediately commit\n const commitPromise = (async () => {\n try {\n await transaction.commit()\n console.log(`offlineAction committed - success`)\n } catch {\n console.log(`offlineAction commit failed - error`)\n }\n })()\n\n // Don't await - this is fire-and-forget for optimistic actions\n // But catch to prevent unhandled rejection\n commitPromise.catch(() => {\n // Already handled in try/catch above\n })\n\n return transaction\n }\n}\n"],"names":[],"mappings":";;AASA,SAAS,cAAc,OAA+C;AACpE,SACE,CAAC,CAAC,UACD,OAAO,UAAU,YAAY,OAAO,UAAU,eAC/C,OAAQ,MAA6B,SAAS;AAElD;AAEO,SAAS,oBACd,SACA,YACA,oBACA,UAC+B;AAC/B,QAAM,EAAE,gBAAgB,SAAA,IAAa;AACrC,UAAQ,IAAI,yBAAyB,OAAO;AAE5C,SAAO,CAAC,cAA8B;AACpC,UAAM,qBAAqB,IAAI;AAAA,MAC7B;AAAA,QACE;AAAA,QACA,YAAY;AAAA,MAAA;AAAA,MAEd;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAGF,UAAM,cAAc,mBAAmB,OAAO,MAAM;AAClD,cAAQ,IAAI,QAAQ;AACpB,YAAM,eAAe,SAAS,SAAS;AAEvC,UAAI,cAAc,YAAY,GAAG;AAC/B,cAAM,IAAI,+BAAA;AAAA,MACZ;AAAA,IACF,CAAC;AAGD,UAAM,iBAAiB,YAAY;AACjC,UAAI;AACF,cAAM,YAAY,OAAA;AAClB,gBAAQ,IAAI,mCAAmC;AAAA,MACjD,QAAQ;AACN,gBAAQ,IAAI,qCAAqC;AAAA,MACnD;AAAA,IACF,GAAA;AAIA,kBAAc,MAAM,MAAM;AAAA,IAE1B,CAAC;AAED,WAAO;AAAA,EACT;AACF;"}
@@ -1,4 +1,3 @@
1
- import { trace, context } from "@opentelemetry/api";
2
1
  import { createTransaction } from "@tanstack/db";
3
2
  import { NonRetriableError } from "../types.js";
4
3
  class OfflineTransaction {
@@ -18,8 +17,6 @@ class OfflineTransaction {
18
17
  id: this.offlineId,
19
18
  autoCommit: false,
20
19
  mutationFn: async () => {
21
- const activeSpan = trace.getSpan(context.active());
22
- const spanContext = activeSpan?.spanContext();
23
20
  const offlineTransaction = {
24
21
  id: this.offlineId,
25
22
  mutationFnName: this.mutationFnName,
@@ -30,12 +27,7 @@ class OfflineTransaction {
30
27
  retryCount: 0,
31
28
  nextAttemptAt: Date.now(),
32
29
  metadata: this.metadata,
33
- spanContext: spanContext ? {
34
- traceId: spanContext.traceId,
35
- spanId: spanContext.spanId,
36
- traceFlags: spanContext.traceFlags,
37
- traceState: spanContext.traceState?.serialize()
38
- } : void 0,
30
+ spanContext: void 0,
39
31
  version: 1
40
32
  };
41
33
  const completionPromise = this.executor.waitForTransactionCompletion(
@@ -1 +1 @@
1
- {"version":3,"file":"OfflineTransaction.js","sources":["../../../src/api/OfflineTransaction.ts"],"sourcesContent":["import { context, trace } from \"@opentelemetry/api\"\nimport { createTransaction } from \"@tanstack/db\"\nimport { NonRetriableError } from \"../types\"\nimport type { PendingMutation, Transaction } from \"@tanstack/db\"\nimport type {\n CreateOfflineTransactionOptions,\n OfflineMutationFn,\n OfflineTransaction as OfflineTransactionType,\n} from \"../types\"\n\nexport class OfflineTransaction {\n private offlineId: string\n private mutationFnName: string\n private autoCommit: boolean\n private idempotencyKey: string\n private metadata: Record<string, any>\n private transaction: Transaction | null = null\n private persistTransaction: (tx: OfflineTransactionType) => Promise<void>\n private executor: any // Will be typed properly - reference to OfflineExecutor\n\n constructor(\n options: CreateOfflineTransactionOptions,\n mutationFn: OfflineMutationFn,\n persistTransaction: (tx: OfflineTransactionType) => Promise<void>,\n executor: any\n ) {\n this.offlineId = crypto.randomUUID()\n this.mutationFnName = options.mutationFnName\n this.autoCommit = options.autoCommit ?? true\n this.idempotencyKey = options.idempotencyKey ?? crypto.randomUUID()\n this.metadata = options.metadata ?? {}\n this.persistTransaction = persistTransaction\n this.executor = executor\n }\n\n mutate(callback: () => void): Transaction {\n this.transaction = createTransaction({\n id: this.offlineId,\n autoCommit: false,\n mutationFn: async () => {\n // This is the blocking mutationFn that waits for the executor\n // First persist the transaction to the outbox\n const activeSpan = trace.getSpan(context.active())\n const spanContext = activeSpan?.spanContext()\n\n const offlineTransaction: OfflineTransactionType = {\n id: this.offlineId,\n mutationFnName: this.mutationFnName,\n mutations: this.transaction!.mutations,\n keys: this.extractKeys(this.transaction!.mutations),\n idempotencyKey: this.idempotencyKey,\n createdAt: new Date(),\n retryCount: 0,\n nextAttemptAt: Date.now(),\n metadata: this.metadata,\n spanContext: spanContext\n ? {\n traceId: spanContext.traceId,\n spanId: spanContext.spanId,\n traceFlags: spanContext.traceFlags,\n traceState: spanContext.traceState?.serialize(),\n }\n : undefined,\n version: 1,\n }\n\n const completionPromise = this.executor.waitForTransactionCompletion(\n this.offlineId\n )\n\n try {\n await this.persistTransaction(offlineTransaction)\n // Now block and wait for the executor to complete the real mutation\n await completionPromise\n } catch (error) {\n const normalizedError =\n error instanceof Error ? error : new Error(String(error))\n this.executor.rejectTransaction(this.offlineId, normalizedError)\n throw error\n }\n\n return\n },\n metadata: this.metadata,\n })\n\n this.transaction.mutate(() => {\n callback()\n })\n\n if (this.autoCommit) {\n // Auto-commit for direct OfflineTransaction usage\n this.commit().catch((error) => {\n console.error(`Auto-commit failed:`, error)\n throw error\n })\n }\n\n return this.transaction\n }\n\n async commit(): Promise<Transaction> {\n if (!this.transaction) {\n throw new Error(`No mutations to commit. Call mutate() first.`)\n }\n\n try {\n // Commit the TanStack DB transaction\n // This will trigger the mutationFn which handles persistence and waiting\n await this.transaction.commit()\n return this.transaction\n } catch (error) {\n // Only rollback for NonRetriableError - other errors should allow retry\n if (error instanceof NonRetriableError) {\n this.transaction.rollback()\n }\n throw error\n }\n }\n\n rollback(): void {\n if (this.transaction) {\n this.transaction.rollback()\n }\n }\n\n private extractKeys(mutations: Array<PendingMutation>): Array<string> {\n return mutations.map((mutation) => mutation.globalKey)\n }\n\n get id(): string {\n return this.offlineId\n }\n}\n"],"names":[],"mappings":";;;AAUO,MAAM,mBAAmB;AAAA;AAAA,EAU9B,YACE,SACA,YACA,oBACA,UACA;AATF,SAAQ,cAAkC;AAUxC,SAAK,YAAY,OAAO,WAAA;AACxB,SAAK,iBAAiB,QAAQ;AAC9B,SAAK,aAAa,QAAQ,cAAc;AACxC,SAAK,iBAAiB,QAAQ,kBAAkB,OAAO,WAAA;AACvD,SAAK,WAAW,QAAQ,YAAY,CAAA;AACpC,SAAK,qBAAqB;AAC1B,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,OAAO,UAAmC;AACxC,SAAK,cAAc,kBAAkB;AAAA,MACnC,IAAI,KAAK;AAAA,MACT,YAAY;AAAA,MACZ,YAAY,YAAY;AAGtB,cAAM,aAAa,MAAM,QAAQ,QAAQ,QAAQ;AACjD,cAAM,cAAc,YAAY,YAAA;AAEhC,cAAM,qBAA6C;AAAA,UACjD,IAAI,KAAK;AAAA,UACT,gBAAgB,KAAK;AAAA,UACrB,WAAW,KAAK,YAAa;AAAA,UAC7B,MAAM,KAAK,YAAY,KAAK,YAAa,SAAS;AAAA,UAClD,gBAAgB,KAAK;AAAA,UACrB,+BAAe,KAAA;AAAA,UACf,YAAY;AAAA,UACZ,eAAe,KAAK,IAAA;AAAA,UACpB,UAAU,KAAK;AAAA,UACf,aAAa,cACT;AAAA,YACE,SAAS,YAAY;AAAA,YACrB,QAAQ,YAAY;AAAA,YACpB,YAAY,YAAY;AAAA,YACxB,YAAY,YAAY,YAAY,UAAA;AAAA,UAAU,IAEhD;AAAA,UACJ,SAAS;AAAA,QAAA;AAGX,cAAM,oBAAoB,KAAK,SAAS;AAAA,UACtC,KAAK;AAAA,QAAA;AAGP,YAAI;AACF,gBAAM,KAAK,mBAAmB,kBAAkB;AAEhD,gBAAM;AAAA,QACR,SAAS,OAAO;AACd,gBAAM,kBACJ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAC1D,eAAK,SAAS,kBAAkB,KAAK,WAAW,eAAe;AAC/D,gBAAM;AAAA,QACR;AAEA;AAAA,MACF;AAAA,MACA,UAAU,KAAK;AAAA,IAAA,CAChB;AAED,SAAK,YAAY,OAAO,MAAM;AAC5B,eAAA;AAAA,IACF,CAAC;AAED,QAAI,KAAK,YAAY;AAEnB,WAAK,OAAA,EAAS,MAAM,CAAC,UAAU;AAC7B,gBAAQ,MAAM,uBAAuB,KAAK;AAC1C,cAAM;AAAA,MACR,CAAC;AAAA,IACH;AAEA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,SAA+B;AACnC,QAAI,CAAC,KAAK,aAAa;AACrB,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AAEA,QAAI;AAGF,YAAM,KAAK,YAAY,OAAA;AACvB,aAAO,KAAK;AAAA,IACd,SAAS,OAAO;AAEd,UAAI,iBAAiB,mBAAmB;AACtC,aAAK,YAAY,SAAA;AAAA,MACnB;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,WAAiB;AACf,QAAI,KAAK,aAAa;AACpB,WAAK,YAAY,SAAA;AAAA,IACnB;AAAA,EACF;AAAA,EAEQ,YAAY,WAAkD;AACpE,WAAO,UAAU,IAAI,CAAC,aAAa,SAAS,SAAS;AAAA,EACvD;AAAA,EAEA,IAAI,KAAa;AACf,WAAO,KAAK;AAAA,EACd;AACF;"}
1
+ {"version":3,"file":"OfflineTransaction.js","sources":["../../../src/api/OfflineTransaction.ts"],"sourcesContent":["import { createTransaction } from \"@tanstack/db\"\nimport { NonRetriableError } from \"../types\"\nimport type { PendingMutation, Transaction } from \"@tanstack/db\"\nimport type {\n CreateOfflineTransactionOptions,\n OfflineMutationFn,\n OfflineTransaction as OfflineTransactionType,\n} from \"../types\"\n\nexport class OfflineTransaction {\n private offlineId: string\n private mutationFnName: string\n private autoCommit: boolean\n private idempotencyKey: string\n private metadata: Record<string, any>\n private transaction: Transaction | null = null\n private persistTransaction: (tx: OfflineTransactionType) => Promise<void>\n private executor: any // Will be typed properly - reference to OfflineExecutor\n\n constructor(\n options: CreateOfflineTransactionOptions,\n mutationFn: OfflineMutationFn,\n persistTransaction: (tx: OfflineTransactionType) => Promise<void>,\n executor: any\n ) {\n this.offlineId = crypto.randomUUID()\n this.mutationFnName = options.mutationFnName\n this.autoCommit = options.autoCommit ?? true\n this.idempotencyKey = options.idempotencyKey ?? crypto.randomUUID()\n this.metadata = options.metadata ?? {}\n this.persistTransaction = persistTransaction\n this.executor = executor\n }\n\n mutate(callback: () => void): Transaction {\n this.transaction = createTransaction({\n id: this.offlineId,\n autoCommit: false,\n mutationFn: async () => {\n // This is the blocking mutationFn that waits for the executor\n // First persist the transaction to the outbox\n const offlineTransaction: OfflineTransactionType = {\n id: this.offlineId,\n mutationFnName: this.mutationFnName,\n mutations: this.transaction!.mutations,\n keys: this.extractKeys(this.transaction!.mutations),\n idempotencyKey: this.idempotencyKey,\n createdAt: new Date(),\n retryCount: 0,\n nextAttemptAt: Date.now(),\n metadata: this.metadata,\n spanContext: undefined,\n version: 1,\n }\n\n const completionPromise = this.executor.waitForTransactionCompletion(\n this.offlineId\n )\n\n try {\n await this.persistTransaction(offlineTransaction)\n // Now block and wait for the executor to complete the real mutation\n await completionPromise\n } catch (error) {\n const normalizedError =\n error instanceof Error ? error : new Error(String(error))\n this.executor.rejectTransaction(this.offlineId, normalizedError)\n throw error\n }\n\n return\n },\n metadata: this.metadata,\n })\n\n this.transaction.mutate(() => {\n callback()\n })\n\n if (this.autoCommit) {\n // Auto-commit for direct OfflineTransaction usage\n this.commit().catch((error) => {\n console.error(`Auto-commit failed:`, error)\n throw error\n })\n }\n\n return this.transaction\n }\n\n async commit(): Promise<Transaction> {\n if (!this.transaction) {\n throw new Error(`No mutations to commit. Call mutate() first.`)\n }\n\n try {\n // Commit the TanStack DB transaction\n // This will trigger the mutationFn which handles persistence and waiting\n await this.transaction.commit()\n return this.transaction\n } catch (error) {\n // Only rollback for NonRetriableError - other errors should allow retry\n if (error instanceof NonRetriableError) {\n this.transaction.rollback()\n }\n throw error\n }\n }\n\n rollback(): void {\n if (this.transaction) {\n this.transaction.rollback()\n }\n }\n\n private extractKeys(mutations: Array<PendingMutation>): Array<string> {\n return mutations.map((mutation) => mutation.globalKey)\n }\n\n get id(): string {\n return this.offlineId\n }\n}\n"],"names":[],"mappings":";;AASO,MAAM,mBAAmB;AAAA;AAAA,EAU9B,YACE,SACA,YACA,oBACA,UACA;AATF,SAAQ,cAAkC;AAUxC,SAAK,YAAY,OAAO,WAAA;AACxB,SAAK,iBAAiB,QAAQ;AAC9B,SAAK,aAAa,QAAQ,cAAc;AACxC,SAAK,iBAAiB,QAAQ,kBAAkB,OAAO,WAAA;AACvD,SAAK,WAAW,QAAQ,YAAY,CAAA;AACpC,SAAK,qBAAqB;AAC1B,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,OAAO,UAAmC;AACxC,SAAK,cAAc,kBAAkB;AAAA,MACnC,IAAI,KAAK;AAAA,MACT,YAAY;AAAA,MACZ,YAAY,YAAY;AAGtB,cAAM,qBAA6C;AAAA,UACjD,IAAI,KAAK;AAAA,UACT,gBAAgB,KAAK;AAAA,UACrB,WAAW,KAAK,YAAa;AAAA,UAC7B,MAAM,KAAK,YAAY,KAAK,YAAa,SAAS;AAAA,UAClD,gBAAgB,KAAK;AAAA,UACrB,+BAAe,KAAA;AAAA,UACf,YAAY;AAAA,UACZ,eAAe,KAAK,IAAA;AAAA,UACpB,UAAU,KAAK;AAAA,UACf,aAAa;AAAA,UACb,SAAS;AAAA,QAAA;AAGX,cAAM,oBAAoB,KAAK,SAAS;AAAA,UACtC,KAAK;AAAA,QAAA;AAGP,YAAI;AACF,gBAAM,KAAK,mBAAmB,kBAAkB;AAEhD,gBAAM;AAAA,QACR,SAAS,OAAO;AACd,gBAAM,kBACJ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAC1D,eAAK,SAAS,kBAAkB,KAAK,WAAW,eAAe;AAC/D,gBAAM;AAAA,QACR;AAEA;AAAA,MACF;AAAA,MACA,UAAU,KAAK;AAAA,IAAA,CAChB;AAED,SAAK,YAAY,OAAO,MAAM;AAC5B,eAAA;AAAA,IACF,CAAC;AAED,QAAI,KAAK,YAAY;AAEnB,WAAK,OAAA,EAAS,MAAM,CAAC,UAAU;AAC7B,gBAAQ,MAAM,uBAAuB,KAAK;AAC1C,cAAM;AAAA,MACR,CAAC;AAAA,IACH;AAEA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,SAA+B;AACnC,QAAI,CAAC,KAAK,aAAa;AACrB,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AAEA,QAAI;AAGF,YAAM,KAAK,YAAY,OAAA;AACvB,aAAO,KAAK;AAAA,IACd,SAAS,OAAO;AAEd,UAAI,iBAAiB,mBAAmB;AACtC,aAAK,YAAY,SAAA;AAAA,MACnB;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,WAAiB;AACf,QAAI,KAAK,aAAa;AACpB,WAAK,YAAY,SAAA;AAAA,IACnB;AAAA,EACF;AAAA,EAEQ,YAAY,WAAkD;AACpE,WAAO,UAAU,IAAI,CAAC,aAAa,SAAS,SAAS;AAAA,EACvD;AAAA,EAEA,IAAI,KAAa;AACf,WAAO,KAAK;AAAA,EACd;AACF;"}
@@ -1,19 +1,7 @@
1
- import { createTraceState } from "@opentelemetry/api";
2
1
  import { DefaultRetryPolicy } from "../retry/RetryPolicy.js";
3
2
  import { NonRetriableError } from "../types.js";
4
3
  import { withNestedSpan } from "../telemetry/tracer.js";
5
4
  const HANDLED_EXECUTION_ERROR = Symbol(`HandledExecutionError`);
6
- function toSpanContext(serialized) {
7
- if (!serialized) {
8
- return void 0;
9
- }
10
- return {
11
- traceId: serialized.traceId,
12
- spanId: serialized.spanId,
13
- traceFlags: serialized.traceFlags,
14
- traceState: serialized.traceState ? createTraceState(serialized.traceState) : void 0
15
- };
16
- }
17
5
  class TransactionExecutor {
18
6
  constructor(scheduler, outbox, config, offlineExecutor) {
19
7
  this.isExecuting = false;
@@ -84,9 +72,6 @@ class TransactionExecutor {
84
72
  err[HANDLED_EXECUTION_ERROR] = true;
85
73
  throw err;
86
74
  }
87
- },
88
- {
89
- parentContext: toSpanContext(transaction.spanContext)
90
75
  }
91
76
  );
92
77
  } catch (error) {