@tanstack/offline-transactions 1.0.56 → 1.0.59

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.
Files changed (60) hide show
  1. package/README.md +157 -23
  2. package/dist/cjs/OfflineExecutor.cjs +6 -3
  3. package/dist/cjs/OfflineExecutor.cjs.map +1 -1
  4. package/dist/cjs/connectivity/OnlineDetector.cjs +1 -1
  5. package/dist/cjs/connectivity/OnlineDetector.cjs.map +1 -1
  6. package/dist/cjs/connectivity/ReactNativeOnlineDetector.cjs +1 -7
  7. package/dist/cjs/connectivity/ReactNativeOnlineDetector.cjs.map +1 -1
  8. package/dist/cjs/executor/KeyScheduler.cjs +24 -25
  9. package/dist/cjs/executor/KeyScheduler.cjs.map +1 -1
  10. package/dist/cjs/executor/KeyScheduler.d.cts +4 -2
  11. package/dist/cjs/executor/TransactionExecutor.cjs +134 -35
  12. package/dist/cjs/executor/TransactionExecutor.cjs.map +1 -1
  13. package/dist/cjs/executor/TransactionExecutor.d.cts +4 -0
  14. package/dist/cjs/outbox/OutboxManager.cjs +70 -25
  15. package/dist/cjs/outbox/OutboxManager.cjs.map +1 -1
  16. package/dist/cjs/outbox/OutboxManager.d.cts +8 -0
  17. package/dist/cjs/outbox/TransactionSerializer.cjs +140 -22
  18. package/dist/cjs/outbox/TransactionSerializer.cjs.map +1 -1
  19. package/dist/cjs/outbox/TransactionSerializer.d.cts +2 -0
  20. package/dist/cjs/storage/IndexedDBAdapter.cjs +1 -0
  21. package/dist/cjs/storage/IndexedDBAdapter.cjs.map +1 -1
  22. package/dist/cjs/storage/LocalStorageAdapter.cjs +1 -1
  23. package/dist/cjs/storage/LocalStorageAdapter.cjs.map +1 -1
  24. package/dist/cjs/types.cjs.map +1 -1
  25. package/dist/cjs/types.d.cts +5 -1
  26. package/dist/esm/OfflineExecutor.js +6 -3
  27. package/dist/esm/OfflineExecutor.js.map +1 -1
  28. package/dist/esm/connectivity/OnlineDetector.js +1 -1
  29. package/dist/esm/connectivity/OnlineDetector.js.map +1 -1
  30. package/dist/esm/connectivity/ReactNativeOnlineDetector.js +1 -7
  31. package/dist/esm/connectivity/ReactNativeOnlineDetector.js.map +1 -1
  32. package/dist/esm/executor/KeyScheduler.d.ts +4 -2
  33. package/dist/esm/executor/KeyScheduler.js +24 -25
  34. package/dist/esm/executor/KeyScheduler.js.map +1 -1
  35. package/dist/esm/executor/TransactionExecutor.d.ts +4 -0
  36. package/dist/esm/executor/TransactionExecutor.js +134 -35
  37. package/dist/esm/executor/TransactionExecutor.js.map +1 -1
  38. package/dist/esm/outbox/OutboxManager.d.ts +8 -0
  39. package/dist/esm/outbox/OutboxManager.js +72 -27
  40. package/dist/esm/outbox/OutboxManager.js.map +1 -1
  41. package/dist/esm/outbox/TransactionSerializer.d.ts +2 -0
  42. package/dist/esm/outbox/TransactionSerializer.js +140 -22
  43. package/dist/esm/outbox/TransactionSerializer.js.map +1 -1
  44. package/dist/esm/storage/IndexedDBAdapter.js +1 -0
  45. package/dist/esm/storage/IndexedDBAdapter.js.map +1 -1
  46. package/dist/esm/storage/LocalStorageAdapter.js +1 -1
  47. package/dist/esm/storage/LocalStorageAdapter.js.map +1 -1
  48. package/dist/esm/types.d.ts +5 -1
  49. package/dist/esm/types.js.map +1 -1
  50. package/package.json +2 -2
  51. package/src/OfflineExecutor.ts +6 -4
  52. package/src/connectivity/OnlineDetector.ts +6 -1
  53. package/src/connectivity/ReactNativeOnlineDetector.ts +1 -11
  54. package/src/executor/KeyScheduler.ts +29 -26
  55. package/src/executor/TransactionExecutor.ts +164 -50
  56. package/src/outbox/OutboxManager.ts +85 -29
  57. package/src/outbox/TransactionSerializer.ts +209 -26
  58. package/src/storage/IndexedDBAdapter.ts +1 -0
  59. package/src/storage/LocalStorageAdapter.ts +1 -1
  60. package/src/types.ts +5 -1
@@ -1,14 +1,22 @@
1
1
  import { OfflineTransaction, StorageAdapter } from '../types.cjs';
2
2
  import { Collection } from '@tanstack/db';
3
+ export declare class OutboxTransactionNotFoundError extends Error {
4
+ }
3
5
  export declare class OutboxManager {
4
6
  private storage;
5
7
  private serializer;
6
8
  private keyPrefix;
9
+ private removalRevision;
10
+ private activeReads;
11
+ private removedAtRevision;
7
12
  constructor(storage: StorageAdapter, collections: Record<string, Collection<any, any, any, any, any>>);
8
13
  private getStorageKey;
14
+ private recordRemovals;
15
+ private pruneRemovalHistory;
9
16
  add(transaction: OfflineTransaction): Promise<void>;
10
17
  get(id: string): Promise<OfflineTransaction | null>;
11
18
  getAll(): Promise<Array<OfflineTransaction>>;
19
+ withAll<T>(consume: (transactions: Array<OfflineTransaction>) => T): Promise<T>;
12
20
  getByKeys(keys: Array<string>): Promise<Array<OfflineTransaction>>;
13
21
  update(id: string, updates: Partial<OfflineTransaction>): Promise<void>;
14
22
  remove(id: string): Promise<void>;
@@ -1,5 +1,48 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const temporalConstructorNames = [
4
+ `Duration`,
5
+ `Instant`,
6
+ `PlainDate`,
7
+ `PlainDateTime`,
8
+ `PlainMonthDay`,
9
+ `PlainTime`,
10
+ `PlainYearMonth`,
11
+ `ZonedDateTime`
12
+ ];
13
+ function getTemporalConstructorName(type) {
14
+ if (typeof type !== `string` || !type.startsWith(`Temporal.`)) return;
15
+ const constructorName = type.slice(
16
+ `Temporal.`.length
17
+ );
18
+ return temporalConstructorNames.includes(constructorName) ? constructorName : void 0;
19
+ }
20
+ function requireTemporalConstructor(name) {
21
+ const constructor = globalThis.Temporal?.[name];
22
+ if (typeof constructor?.from !== `function`)
23
+ throw new MissingTemporalConstructorError(
24
+ `Missing global Temporal.${name} constructor`
25
+ );
26
+ return constructor;
27
+ }
28
+ function serializeTemporalValue(value, name) {
29
+ const constructor = requireTemporalConstructor(name);
30
+ const toString = constructor.prototype?.toString;
31
+ if (typeof toString !== `function` || toString === Object.prototype.toString)
32
+ return;
33
+ try {
34
+ const serialized = toString.call(value);
35
+ return typeof serialized === `string` ? serialized : void 0;
36
+ } catch {
37
+ return;
38
+ }
39
+ }
40
+ function mayBeNativeScalar(value) {
41
+ const prototype = Object.getPrototypeOf(value);
42
+ return prototype !== null && prototype !== Object.prototype && !Array.isArray(value);
43
+ }
44
+ class MissingTemporalConstructorError extends Error {
45
+ }
3
46
  function setDataProperty(object, key, value) {
4
47
  if (key !== `__proto__`) object[key] = value;
5
48
  else
@@ -10,6 +53,10 @@ function setDataProperty(object, key, value) {
10
53
  writable: true
11
54
  });
12
55
  }
56
+ function assertOutboxPhase(phase) {
57
+ if (phase !== void 0 && phase !== `deletion-pending` && phase !== `rejection-pending`)
58
+ throw new Error(`Unsupported transaction outbox phase`);
59
+ }
13
60
  class TransactionSerializer {
14
61
  constructor(collections) {
15
62
  this.collections = collections;
@@ -19,10 +66,12 @@ class TransactionSerializer {
19
66
  }
20
67
  }
21
68
  serialize(transaction) {
69
+ assertOutboxPhase(transaction.outboxPhase);
22
70
  const serialized = {
23
71
  ...transaction,
24
- valueEncoding: 2,
72
+ valueEncoding: 3,
25
73
  createdAt: transaction.createdAt.toISOString(),
74
+ metadata: this.serializeValue(transaction.metadata, `metadata`),
26
75
  mutations: transaction.mutations.map(
27
76
  (mutation) => this.serializeMutation(mutation)
28
77
  )
@@ -32,13 +81,15 @@ class TransactionSerializer {
32
81
  deserialize(data) {
33
82
  const {
34
83
  valueEncoding,
84
+ outboxPhase,
35
85
  ...parsed
36
86
  } = JSON.parse(data);
37
- if (valueEncoding !== void 0 && valueEncoding !== 2) {
87
+ if (valueEncoding !== void 0 && valueEncoding !== 2 && valueEncoding !== 3) {
38
88
  throw new Error(
39
89
  `Unsupported transaction value encoding: ${valueEncoding}`
40
90
  );
41
91
  }
92
+ assertOutboxPhase(outboxPhase);
42
93
  const createdAt = new Date(parsed.createdAt);
43
94
  if (isNaN(createdAt.getTime())) {
44
95
  throw new Error(
@@ -47,9 +98,11 @@ class TransactionSerializer {
47
98
  }
48
99
  return {
49
100
  ...parsed,
101
+ ...outboxPhase === void 0 ? {} : { outboxPhase },
50
102
  createdAt,
103
+ metadata: valueEncoding === 3 ? this.deserializeValue(parsed.metadata, valueEncoding) : parsed.metadata,
51
104
  mutations: parsed.mutations.map(
52
- (mutationData) => this.deserializeMutation(mutationData, valueEncoding === 2)
105
+ (mutationData) => this.deserializeMutation(mutationData, valueEncoding)
53
106
  )
54
107
  };
55
108
  }
@@ -70,19 +123,19 @@ class TransactionSerializer {
70
123
  // Store registry key instead of collection.id
71
124
  };
72
125
  }
73
- deserializeMutation(data, escapedObjects) {
126
+ deserializeMutation(data, valueEncoding) {
74
127
  const collection = this.collections[data.collectionId];
75
128
  if (!collection) {
76
129
  throw new Error(`Collection with id ${data.collectionId} not found`);
77
130
  }
78
- const modified = this.deserializeValue(data.modified, escapedObjects);
131
+ const modified = this.deserializeValue(data.modified, valueEncoding);
79
132
  const key = modified ? collection.getKeyFromItem(modified) : null;
80
133
  return {
81
134
  globalKey: data.globalKey,
82
135
  type: data.type,
83
136
  modified,
84
- original: this.deserializeValue(data.original, escapedObjects),
85
- changes: this.deserializeValue(data.changes, escapedObjects) ?? {},
137
+ original: this.deserializeValue(data.original, valueEncoding),
138
+ changes: this.deserializeValue(data.changes, valueEncoding) ?? {},
86
139
  collection,
87
140
  // These fields would need to be reconstructed by the executor
88
141
  mutationId: ``,
@@ -95,25 +148,81 @@ class TransactionSerializer {
95
148
  updatedAt: /* @__PURE__ */ new Date()
96
149
  };
97
150
  }
98
- serializeValue(value) {
99
- if (value === null || value === void 0) {
100
- return value;
101
- }
102
- if (value instanceof Date) {
151
+ serializeValue(value, jsonKey, ancestors = /* @__PURE__ */ new WeakSet()) {
152
+ if (value === null || typeof value !== `object`) return value;
153
+ if (jsonKey !== false && value instanceof Date) {
103
154
  return { __type: `Date`, value: value.toISOString() };
104
155
  }
105
- if (typeof value === `object`) {
106
- const result = Array.isArray(value) ? [] : {};
107
- for (const key in value) {
108
- if (Object.prototype.hasOwnProperty.call(value, key)) {
109
- setDataProperty(result, key, this.serializeValue(value[key]));
156
+ const temporalConstructorName = jsonKey !== false && mayBeNativeScalar(value) ? getTemporalConstructorName(value[Symbol.toStringTag]) : void 0;
157
+ if (temporalConstructorName) {
158
+ const temporalValue = serializeTemporalValue(
159
+ value,
160
+ temporalConstructorName
161
+ );
162
+ if (temporalValue !== void 0)
163
+ return {
164
+ __type: `Temporal`,
165
+ type: `Temporal.${temporalConstructorName}`,
166
+ value: temporalValue
167
+ };
168
+ }
169
+ if (ancestors.has(value))
170
+ throw new TypeError(`Converting circular structure to JSON`);
171
+ const toJSON = typeof jsonKey === `string` && value.toJSON;
172
+ if (typeof toJSON === `function`) {
173
+ const replacement = toJSON.call(value, jsonKey);
174
+ if (replacement === value) {
175
+ return this.serializeValue(replacement, false, ancestors);
176
+ }
177
+ ancestors.add(value);
178
+ try {
179
+ return this.serializeValue(replacement, false, ancestors);
180
+ } finally {
181
+ ancestors.delete(value);
182
+ }
183
+ }
184
+ if (jsonKey !== void 0 && (value instanceof Boolean || value instanceof BigInt || value instanceof Number || value instanceof String)) {
185
+ return value.valueOf();
186
+ }
187
+ ancestors.add(value);
188
+ const isArray = Array.isArray(value);
189
+ const result = isArray ? [] : {};
190
+ try {
191
+ if (isArray) {
192
+ const length = value.length;
193
+ for (let index = 0; index < length; index++) {
194
+ const key = String(index);
195
+ setDataProperty(
196
+ result,
197
+ key,
198
+ this.serializeValue(
199
+ value[index],
200
+ jsonKey === void 0 ? void 0 : key,
201
+ ancestors
202
+ )
203
+ );
204
+ }
205
+ } else {
206
+ for (const key of Object.keys(value)) {
207
+ setDataProperty(
208
+ result,
209
+ key,
210
+ this.serializeValue(
211
+ value[key],
212
+ jsonKey === void 0 ? void 0 : key,
213
+ ancestors
214
+ )
215
+ );
110
216
  }
111
217
  }
112
- return !Array.isArray(value) && Object.prototype.hasOwnProperty.call(value, `__type`) ? { __type: `Object`, value: result } : result;
218
+ } finally {
219
+ ancestors.delete(value);
113
220
  }
114
- return value;
221
+ if (jsonKey === false && typeof result.toJSON === `function`)
222
+ delete result.toJSON;
223
+ return !isArray && Object.prototype.hasOwnProperty.call(value, `__type`) ? { __type: `Object`, value: result } : result;
115
224
  }
116
- deserializeValue(value, escapedObjects) {
225
+ deserializeValue(value, valueEncoding) {
117
226
  if (value === null || value === void 0) {
118
227
  return value;
119
228
  }
@@ -129,8 +238,16 @@ class TransactionSerializer {
129
238
  }
130
239
  return date;
131
240
  }
241
+ if (valueEncoding === 3 && typeof value === `object` && value.__type === `Temporal`) {
242
+ const constructorName = getTemporalConstructorName(value.type);
243
+ if (!constructorName)
244
+ throw new Error(`Corrupted Temporal marker: invalid type field`);
245
+ if (typeof value.value !== `string`)
246
+ throw new Error(`Corrupted Temporal marker: missing value field`);
247
+ return requireTemporalConstructor(constructorName).from(value.value);
248
+ }
132
249
  if (typeof value === `object`) {
133
- if (escapedObjects && value.__type === `Object`) {
250
+ if (valueEncoding !== void 0 && value.__type === `Object`) {
134
251
  if (value.value === null || typeof value.value !== `object` || Array.isArray(value.value)) {
135
252
  throw new Error(`Corrupted Object marker: expected an object value`);
136
253
  }
@@ -142,7 +259,7 @@ class TransactionSerializer {
142
259
  setDataProperty(
143
260
  result,
144
261
  key,
145
- this.deserializeValue(value[key], escapedObjects)
262
+ this.deserializeValue(value[key], valueEncoding)
146
263
  );
147
264
  }
148
265
  }
@@ -164,5 +281,6 @@ class TransactionSerializer {
164
281
  return error;
165
282
  }
166
283
  }
284
+ exports.MissingTemporalConstructorError = MissingTemporalConstructorError;
167
285
  exports.TransactionSerializer = TransactionSerializer;
168
286
  //# sourceMappingURL=TransactionSerializer.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"TransactionSerializer.cjs","sources":["../../../src/outbox/TransactionSerializer.ts"],"sourcesContent":["import type {\n OfflineTransaction,\n SerializedError,\n SerializedMutation,\n SerializedOfflineTransaction,\n} from '../types'\nimport type { Collection, PendingMutation } from '@tanstack/db'\n\nfunction setDataProperty(\n object: Record<string, unknown>,\n key: string,\n value: unknown,\n): void {\n if (key !== `__proto__`) object[key] = value\n else\n Object.defineProperty(object, key, {\n value,\n enumerable: true,\n configurable: true,\n writable: true,\n })\n}\n\nexport class TransactionSerializer {\n private collections: Record<string, Collection<any, any, any, any, any>>\n private collectionIdToKey: Map<string, string>\n\n constructor(\n collections: Record<string, Collection<any, any, any, any, any>>,\n ) {\n this.collections = collections\n // Create reverse lookup from collection.id to registry key\n this.collectionIdToKey = new Map()\n for (const [key, collection] of Object.entries(collections)) {\n this.collectionIdToKey.set(collection.id, key)\n }\n }\n\n serialize(transaction: OfflineTransaction): string {\n const serialized: SerializedOfflineTransaction = {\n ...transaction,\n valueEncoding: 2,\n createdAt: transaction.createdAt.toISOString(),\n mutations: transaction.mutations.map((mutation) =>\n this.serializeMutation(mutation),\n ),\n }\n return JSON.stringify(serialized)\n }\n\n deserialize(data: string): OfflineTransaction {\n // Old records retain their Date-marker meaning. New records also escape\n // marker-shaped user objects; the encoding tag is not application metadata.\n const {\n valueEncoding,\n ...parsed\n }: Omit<SerializedOfflineTransaction, `valueEncoding`> & {\n valueEncoding?: unknown\n } = JSON.parse(data)\n if (valueEncoding !== undefined && valueEncoding !== 2) {\n throw new Error(\n `Unsupported transaction value encoding: ${valueEncoding}`,\n )\n }\n\n const createdAt = new Date(parsed.createdAt)\n if (isNaN(createdAt.getTime())) {\n throw new Error(\n `Failed to deserialize transaction: invalid createdAt value \"${parsed.createdAt}\"`,\n )\n }\n\n return {\n ...parsed,\n createdAt,\n mutations: parsed.mutations.map((mutationData) =>\n this.deserializeMutation(mutationData, valueEncoding === 2),\n ),\n }\n }\n\n private serializeMutation(mutation: PendingMutation): SerializedMutation {\n const registryKey = this.collectionIdToKey.get(mutation.collection.id)\n if (!registryKey) {\n throw new Error(\n `Collection with id ${mutation.collection.id} not found in registry`,\n )\n }\n\n return {\n globalKey: mutation.globalKey,\n type: mutation.type,\n modified: this.serializeValue(mutation.modified),\n original: this.serializeValue(mutation.original),\n changes: this.serializeValue(mutation.changes),\n collectionId: registryKey, // Store registry key instead of collection.id\n }\n }\n\n private deserializeMutation(\n data: SerializedMutation,\n escapedObjects: boolean,\n ): PendingMutation {\n const collection = this.collections[data.collectionId]\n if (!collection) {\n throw new Error(`Collection with id ${data.collectionId} not found`)\n }\n\n const modified = this.deserializeValue(data.modified, escapedObjects)\n\n // Extract the key from the modified data using the collection's getKey function\n // This is needed for optimistic state restoration to work correctly\n const key = modified ? collection.getKeyFromItem(modified) : null\n\n // Create a partial PendingMutation - we can't fully reconstruct it but\n // we provide what we can. The executor will need to handle the rest.\n return {\n globalKey: data.globalKey,\n type: data.type as any,\n modified,\n original: this.deserializeValue(data.original, escapedObjects),\n changes: this.deserializeValue(data.changes, escapedObjects) ?? {},\n collection,\n // These fields would need to be reconstructed by the executor\n mutationId: ``, // Will be regenerated\n key,\n metadata: undefined,\n syncMetadata: {},\n optimistic: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n } as PendingMutation\n }\n\n private serializeValue(value: any): any {\n if (value === null || value === undefined) {\n return value\n }\n\n if (value instanceof Date) {\n return { __type: `Date`, value: value.toISOString() }\n }\n\n if (typeof value === `object`) {\n const result: any = Array.isArray(value) ? [] : {}\n for (const key in value) {\n if (Object.prototype.hasOwnProperty.call(value, key)) {\n setDataProperty(result, key, this.serializeValue(value[key]))\n }\n }\n return !Array.isArray(value) &&\n Object.prototype.hasOwnProperty.call(value, `__type`)\n ? { __type: `Object`, value: result }\n : result\n }\n\n return value\n }\n\n private deserializeValue(value: any, escapedObjects: boolean): any {\n if (value === null || value === undefined) {\n return value\n }\n\n if (typeof value === `object` && value.__type === `Date`) {\n if (value.value === undefined || value.value === null) {\n throw new Error(`Corrupted Date marker: missing value field`)\n }\n const date = new Date(value.value)\n if (isNaN(date.getTime())) {\n throw new Error(\n `Failed to deserialize Date marker: invalid date value \"${value.value}\"`,\n )\n }\n return date\n }\n\n if (typeof value === `object`) {\n // Unwrap once, then decode only the fields: the object's own __type is data.\n if (escapedObjects && value.__type === `Object`) {\n if (\n value.value === null ||\n typeof value.value !== `object` ||\n Array.isArray(value.value)\n ) {\n throw new Error(`Corrupted Object marker: expected an object value`)\n }\n value = value.value\n }\n const result: any = Array.isArray(value) ? [] : {}\n for (const key in value) {\n if (Object.prototype.hasOwnProperty.call(value, key)) {\n setDataProperty(\n result,\n key,\n this.deserializeValue(value[key], escapedObjects),\n )\n }\n }\n return result\n }\n\n return value\n }\n\n serializeError(error: Error): SerializedError {\n return {\n name: error.name,\n message: error.message,\n stack: error.stack,\n }\n }\n\n deserializeError(data: SerializedError): Error {\n const error = new Error(data.message)\n error.name = data.name\n error.stack = data.stack\n return error\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TransactionSerializer.cjs","sources":["../../../src/outbox/TransactionSerializer.ts"],"sourcesContent":["import type {\n OfflineTransaction,\n SerializedError,\n SerializedMutation,\n SerializedOfflineTransaction,\n} from '../types'\nimport type { Collection, PendingMutation } from '@tanstack/db'\n\nconst temporalConstructorNames = [\n `Duration`,\n `Instant`,\n `PlainDate`,\n `PlainDateTime`,\n `PlainMonthDay`,\n `PlainTime`,\n `PlainYearMonth`,\n `ZonedDateTime`,\n] as const\n\ntype TemporalConstructorName = (typeof temporalConstructorNames)[number]\ntype TemporalConstructor = {\n from: (value: string) => unknown\n prototype?: { toString?: () => string }\n}\n\nfunction getTemporalConstructorName(\n type: unknown,\n): TemporalConstructorName | undefined {\n if (typeof type !== `string` || !type.startsWith(`Temporal.`)) return\n const constructorName = type.slice(\n `Temporal.`.length,\n ) as TemporalConstructorName\n return temporalConstructorNames.includes(constructorName)\n ? constructorName\n : undefined\n}\n\nfunction requireTemporalConstructor(\n name: TemporalConstructorName,\n): TemporalConstructor {\n const constructor = (\n globalThis as {\n Temporal?: Partial<Record<TemporalConstructorName, TemporalConstructor>>\n }\n ).Temporal?.[name]\n if (typeof constructor?.from !== `function`)\n throw new MissingTemporalConstructorError(\n `Missing global Temporal.${name} constructor`,\n )\n return constructor\n}\n\nfunction serializeTemporalValue(\n value: object,\n name: TemporalConstructorName,\n): string | undefined {\n const constructor = requireTemporalConstructor(name)\n const toString = constructor.prototype?.toString\n if (typeof toString !== `function` || toString === Object.prototype.toString)\n return\n try {\n const serialized = toString.call(value)\n return typeof serialized === `string` ? serialized : undefined\n } catch {\n // Temporal prototype methods brand-check their receiver. A matching\n // Symbol.toStringTag without the corresponding internal slots is user data.\n return\n }\n}\n\nfunction mayBeNativeScalar(value: object): boolean {\n const prototype = Object.getPrototypeOf(value)\n return (\n prototype !== null &&\n prototype !== Object.prototype &&\n !Array.isArray(value)\n )\n}\n\nexport class MissingTemporalConstructorError extends Error {}\n\nfunction setDataProperty(\n object: Record<string, unknown>,\n key: string,\n value: unknown,\n): void {\n if (key !== `__proto__`) object[key] = value\n else\n Object.defineProperty(object, key, {\n value,\n enumerable: true,\n configurable: true,\n writable: true,\n })\n}\n\nfunction assertOutboxPhase(\n phase: unknown,\n): asserts phase is `deletion-pending` | `rejection-pending` | undefined {\n if (\n phase !== undefined &&\n phase !== `deletion-pending` &&\n phase !== `rejection-pending`\n )\n throw new Error(`Unsupported transaction outbox phase`)\n}\n\nexport class TransactionSerializer {\n private collections: Record<string, Collection<any, any, any, any, any>>\n private collectionIdToKey: Map<string, string>\n\n constructor(\n collections: Record<string, Collection<any, any, any, any, any>>,\n ) {\n this.collections = collections\n // Create reverse lookup from collection.id to registry key\n this.collectionIdToKey = new Map()\n for (const [key, collection] of Object.entries(collections)) {\n this.collectionIdToKey.set(collection.id, key)\n }\n }\n\n serialize(transaction: OfflineTransaction): string {\n assertOutboxPhase(transaction.outboxPhase)\n const serialized: SerializedOfflineTransaction = {\n ...transaction,\n valueEncoding: 3,\n createdAt: transaction.createdAt.toISOString(),\n metadata: this.serializeValue(transaction.metadata, `metadata`),\n mutations: transaction.mutations.map((mutation) =>\n this.serializeMutation(mutation),\n ),\n }\n return JSON.stringify(serialized)\n }\n\n deserialize(data: string): OfflineTransaction {\n // Old records retain their Date-marker meaning. New records also escape\n // marker-shaped user objects; the encoding tag is not application metadata.\n const {\n valueEncoding,\n outboxPhase,\n ...parsed\n }: Omit<SerializedOfflineTransaction, `valueEncoding` | `outboxPhase`> & {\n valueEncoding?: unknown\n outboxPhase?: unknown\n } = JSON.parse(data)\n if (\n valueEncoding !== undefined &&\n valueEncoding !== 2 &&\n valueEncoding !== 3\n ) {\n throw new Error(\n `Unsupported transaction value encoding: ${valueEncoding}`,\n )\n }\n assertOutboxPhase(outboxPhase)\n\n const createdAt = new Date(parsed.createdAt)\n if (isNaN(createdAt.getTime())) {\n throw new Error(\n `Failed to deserialize transaction: invalid createdAt value \"${parsed.createdAt}\"`,\n )\n }\n\n return {\n ...parsed,\n ...(outboxPhase === undefined ? {} : { outboxPhase }),\n createdAt,\n metadata:\n valueEncoding === 3\n ? this.deserializeValue(parsed.metadata, valueEncoding)\n : parsed.metadata,\n mutations: parsed.mutations.map((mutationData) =>\n this.deserializeMutation(mutationData, valueEncoding),\n ),\n }\n }\n\n private serializeMutation(mutation: PendingMutation): SerializedMutation {\n const registryKey = this.collectionIdToKey.get(mutation.collection.id)\n if (!registryKey) {\n throw new Error(\n `Collection with id ${mutation.collection.id} not found in registry`,\n )\n }\n\n return {\n globalKey: mutation.globalKey,\n type: mutation.type,\n modified: this.serializeValue(mutation.modified),\n original: this.serializeValue(mutation.original),\n changes: this.serializeValue(mutation.changes),\n collectionId: registryKey, // Store registry key instead of collection.id\n }\n }\n\n private deserializeMutation(\n data: SerializedMutation,\n valueEncoding: 2 | 3 | undefined,\n ): PendingMutation {\n const collection = this.collections[data.collectionId]\n if (!collection) {\n throw new Error(`Collection with id ${data.collectionId} not found`)\n }\n\n const modified = this.deserializeValue(data.modified, valueEncoding)\n\n // Extract the key from the modified data using the collection's getKey function\n // This is needed for optimistic state restoration to work correctly\n const key = modified ? collection.getKeyFromItem(modified) : null\n\n // Create a partial PendingMutation - we can't fully reconstruct it but\n // we provide what we can. The executor will need to handle the rest.\n return {\n globalKey: data.globalKey,\n type: data.type as any,\n modified,\n original: this.deserializeValue(data.original, valueEncoding),\n changes: this.deserializeValue(data.changes, valueEncoding) ?? {},\n collection,\n // These fields would need to be reconstructed by the executor\n mutationId: ``, // Will be regenerated\n key,\n metadata: undefined,\n syncMetadata: {},\n optimistic: true,\n createdAt: new Date(),\n updatedAt: new Date(),\n } as PendingMutation\n }\n\n private serializeValue(\n value: any,\n jsonKey?: string | false,\n ancestors = new WeakSet<object>(),\n ): any {\n if (value === null || typeof value !== `object`) return value\n\n if (jsonKey !== false && value instanceof Date) {\n return { __type: `Date`, value: value.toISOString() }\n }\n\n const temporalConstructorName =\n jsonKey !== false && mayBeNativeScalar(value)\n ? getTemporalConstructorName(value[Symbol.toStringTag])\n : undefined\n if (temporalConstructorName) {\n const temporalValue = serializeTemporalValue(\n value,\n temporalConstructorName,\n )\n if (temporalValue !== undefined)\n return {\n __type: `Temporal`,\n type: `Temporal.${temporalConstructorName}`,\n value: temporalValue,\n }\n }\n\n if (ancestors.has(value))\n throw new TypeError(`Converting circular structure to JSON`)\n\n const toJSON = typeof jsonKey === `string` && value.toJSON\n if (typeof toJSON === `function`) {\n const replacement = toJSON.call(value, jsonKey)\n if (replacement === value) {\n return this.serializeValue(replacement, false, ancestors)\n }\n\n ancestors.add(value)\n try {\n return this.serializeValue(replacement, false, ancestors)\n } finally {\n ancestors.delete(value)\n }\n }\n if (\n jsonKey !== undefined &&\n (value instanceof Boolean ||\n value instanceof BigInt ||\n value instanceof Number ||\n value instanceof String)\n ) {\n return value.valueOf()\n }\n\n ancestors.add(value)\n\n const isArray = Array.isArray(value)\n const result: any = isArray ? [] : {}\n try {\n if (isArray) {\n const length = value.length\n for (let index = 0; index < length; index++) {\n const key = String(index)\n setDataProperty(\n result,\n key,\n this.serializeValue(\n value[index],\n jsonKey === undefined ? undefined : key,\n ancestors,\n ),\n )\n }\n } else {\n for (const key of Object.keys(value)) {\n setDataProperty(\n result,\n key,\n this.serializeValue(\n value[key],\n jsonKey === undefined ? undefined : key,\n ancestors,\n ),\n )\n }\n }\n } finally {\n ancestors.delete(value)\n }\n if (jsonKey === false && typeof result.toJSON === `function`)\n delete result.toJSON\n return !isArray && Object.prototype.hasOwnProperty.call(value, `__type`)\n ? { __type: `Object`, value: result }\n : result\n }\n\n private deserializeValue(value: any, valueEncoding: 2 | 3 | undefined): any {\n if (value === null || value === undefined) {\n return value\n }\n\n if (typeof value === `object` && value.__type === `Date`) {\n if (value.value === undefined || value.value === null) {\n throw new Error(`Corrupted Date marker: missing value field`)\n }\n const date = new Date(value.value)\n if (isNaN(date.getTime())) {\n throw new Error(\n `Failed to deserialize Date marker: invalid date value \"${value.value}\"`,\n )\n }\n return date\n }\n\n if (\n valueEncoding === 3 &&\n typeof value === `object` &&\n value.__type === `Temporal`\n ) {\n const constructorName = getTemporalConstructorName(value.type)\n if (!constructorName)\n throw new Error(`Corrupted Temporal marker: invalid type field`)\n if (typeof value.value !== `string`)\n throw new Error(`Corrupted Temporal marker: missing value field`)\n return requireTemporalConstructor(constructorName).from(value.value)\n }\n\n if (typeof value === `object`) {\n // Unwrap once, then decode only the fields: the object's own __type is data.\n if (valueEncoding !== undefined && value.__type === `Object`) {\n if (\n value.value === null ||\n typeof value.value !== `object` ||\n Array.isArray(value.value)\n ) {\n throw new Error(`Corrupted Object marker: expected an object value`)\n }\n value = value.value\n }\n const result: any = Array.isArray(value) ? [] : {}\n for (const key in value) {\n if (Object.prototype.hasOwnProperty.call(value, key)) {\n setDataProperty(\n result,\n key,\n this.deserializeValue(value[key], valueEncoding),\n )\n }\n }\n return result\n }\n\n return value\n }\n\n serializeError(error: Error): SerializedError {\n return {\n name: error.name,\n message: error.message,\n stack: error.stack,\n }\n }\n\n deserializeError(data: SerializedError): Error {\n const error = new Error(data.message)\n error.name = data.name\n error.stack = data.stack\n return error\n }\n}\n"],"names":[],"mappings":";;AAQA,MAAM,2BAA2B;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAQA,SAAS,2BACP,MACqC;AACrC,MAAI,OAAO,SAAS,YAAY,CAAC,KAAK,WAAW,WAAW,EAAG;AAC/D,QAAM,kBAAkB,KAAK;AAAA,IAC3B,YAAY;AAAA,EAAA;AAEd,SAAO,yBAAyB,SAAS,eAAe,IACpD,kBACA;AACN;AAEA,SAAS,2BACP,MACqB;AACrB,QAAM,cACJ,WAGA,WAAW,IAAI;AACjB,MAAI,OAAO,aAAa,SAAS;AAC/B,UAAM,IAAI;AAAA,MACR,2BAA2B,IAAI;AAAA,IAAA;AAEnC,SAAO;AACT;AAEA,SAAS,uBACP,OACA,MACoB;AACpB,QAAM,cAAc,2BAA2B,IAAI;AACnD,QAAM,WAAW,YAAY,WAAW;AACxC,MAAI,OAAO,aAAa,cAAc,aAAa,OAAO,UAAU;AAClE;AACF,MAAI;AACF,UAAM,aAAa,SAAS,KAAK,KAAK;AACtC,WAAO,OAAO,eAAe,WAAW,aAAa;AAAA,EACvD,QAAQ;AAGN;AAAA,EACF;AACF;AAEA,SAAS,kBAAkB,OAAwB;AACjD,QAAM,YAAY,OAAO,eAAe,KAAK;AAC7C,SACE,cAAc,QACd,cAAc,OAAO,aACrB,CAAC,MAAM,QAAQ,KAAK;AAExB;AAEO,MAAM,wCAAwC,MAAM;AAAC;AAE5D,SAAS,gBACP,QACA,KACA,OACM;AACN,MAAI,QAAQ,YAAa,QAAO,GAAG,IAAI;AAAA;AAErC,WAAO,eAAe,QAAQ,KAAK;AAAA,MACjC;AAAA,MACA,YAAY;AAAA,MACZ,cAAc;AAAA,MACd,UAAU;AAAA,IAAA,CACX;AACL;AAEA,SAAS,kBACP,OACuE;AACvE,MACE,UAAU,UACV,UAAU,sBACV,UAAU;AAEV,UAAM,IAAI,MAAM,sCAAsC;AAC1D;AAEO,MAAM,sBAAsB;AAAA,EAIjC,YACE,aACA;AACA,SAAK,cAAc;AAEnB,SAAK,wCAAwB,IAAA;AAC7B,eAAW,CAAC,KAAK,UAAU,KAAK,OAAO,QAAQ,WAAW,GAAG;AAC3D,WAAK,kBAAkB,IAAI,WAAW,IAAI,GAAG;AAAA,IAC/C;AAAA,EACF;AAAA,EAEA,UAAU,aAAyC;AACjD,sBAAkB,YAAY,WAAW;AACzC,UAAM,aAA2C;AAAA,MAC/C,GAAG;AAAA,MACH,eAAe;AAAA,MACf,WAAW,YAAY,UAAU,YAAA;AAAA,MACjC,UAAU,KAAK,eAAe,YAAY,UAAU,UAAU;AAAA,MAC9D,WAAW,YAAY,UAAU;AAAA,QAAI,CAAC,aACpC,KAAK,kBAAkB,QAAQ;AAAA,MAAA;AAAA,IACjC;AAEF,WAAO,KAAK,UAAU,UAAU;AAAA,EAClC;AAAA,EAEA,YAAY,MAAkC;AAG5C,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,GAAG;AAAA,IAAA,IAID,KAAK,MAAM,IAAI;AACnB,QACE,kBAAkB,UAClB,kBAAkB,KAClB,kBAAkB,GAClB;AACA,YAAM,IAAI;AAAA,QACR,2CAA2C,aAAa;AAAA,MAAA;AAAA,IAE5D;AACA,sBAAkB,WAAW;AAE7B,UAAM,YAAY,IAAI,KAAK,OAAO,SAAS;AAC3C,QAAI,MAAM,UAAU,QAAA,CAAS,GAAG;AAC9B,YAAM,IAAI;AAAA,QACR,+DAA+D,OAAO,SAAS;AAAA,MAAA;AAAA,IAEnF;AAEA,WAAO;AAAA,MACL,GAAG;AAAA,MACH,GAAI,gBAAgB,SAAY,CAAA,IAAK,EAAE,YAAA;AAAA,MACvC;AAAA,MACA,UACE,kBAAkB,IACd,KAAK,iBAAiB,OAAO,UAAU,aAAa,IACpD,OAAO;AAAA,MACb,WAAW,OAAO,UAAU;AAAA,QAAI,CAAC,iBAC/B,KAAK,oBAAoB,cAAc,aAAa;AAAA,MAAA;AAAA,IACtD;AAAA,EAEJ;AAAA,EAEQ,kBAAkB,UAA+C;AACvE,UAAM,cAAc,KAAK,kBAAkB,IAAI,SAAS,WAAW,EAAE;AACrE,QAAI,CAAC,aAAa;AAChB,YAAM,IAAI;AAAA,QACR,sBAAsB,SAAS,WAAW,EAAE;AAAA,MAAA;AAAA,IAEhD;AAEA,WAAO;AAAA,MACL,WAAW,SAAS;AAAA,MACpB,MAAM,SAAS;AAAA,MACf,UAAU,KAAK,eAAe,SAAS,QAAQ;AAAA,MAC/C,UAAU,KAAK,eAAe,SAAS,QAAQ;AAAA,MAC/C,SAAS,KAAK,eAAe,SAAS,OAAO;AAAA,MAC7C,cAAc;AAAA;AAAA,IAAA;AAAA,EAElB;AAAA,EAEQ,oBACN,MACA,eACiB;AACjB,UAAM,aAAa,KAAK,YAAY,KAAK,YAAY;AACrD,QAAI,CAAC,YAAY;AACf,YAAM,IAAI,MAAM,sBAAsB,KAAK,YAAY,YAAY;AAAA,IACrE;AAEA,UAAM,WAAW,KAAK,iBAAiB,KAAK,UAAU,aAAa;AAInE,UAAM,MAAM,WAAW,WAAW,eAAe,QAAQ,IAAI;AAI7D,WAAO;AAAA,MACL,WAAW,KAAK;AAAA,MAChB,MAAM,KAAK;AAAA,MACX;AAAA,MACA,UAAU,KAAK,iBAAiB,KAAK,UAAU,aAAa;AAAA,MAC5D,SAAS,KAAK,iBAAiB,KAAK,SAAS,aAAa,KAAK,CAAA;AAAA,MAC/D;AAAA;AAAA,MAEA,YAAY;AAAA;AAAA,MACZ;AAAA,MACA,UAAU;AAAA,MACV,cAAc,CAAA;AAAA,MACd,YAAY;AAAA,MACZ,+BAAe,KAAA;AAAA,MACf,+BAAe,KAAA;AAAA,IAAK;AAAA,EAExB;AAAA,EAEQ,eACN,OACA,SACA,YAAY,oBAAI,WACX;AACL,QAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO;AAExD,QAAI,YAAY,SAAS,iBAAiB,MAAM;AAC9C,aAAO,EAAE,QAAQ,QAAQ,OAAO,MAAM,cAAY;AAAA,IACpD;AAEA,UAAM,0BACJ,YAAY,SAAS,kBAAkB,KAAK,IACxC,2BAA2B,MAAM,OAAO,WAAW,CAAC,IACpD;AACN,QAAI,yBAAyB;AAC3B,YAAM,gBAAgB;AAAA,QACpB;AAAA,QACA;AAAA,MAAA;AAEF,UAAI,kBAAkB;AACpB,eAAO;AAAA,UACL,QAAQ;AAAA,UACR,MAAM,YAAY,uBAAuB;AAAA,UACzC,OAAO;AAAA,QAAA;AAAA,IAEb;AAEA,QAAI,UAAU,IAAI,KAAK;AACrB,YAAM,IAAI,UAAU,uCAAuC;AAE7D,UAAM,SAAS,OAAO,YAAY,YAAY,MAAM;AACpD,QAAI,OAAO,WAAW,YAAY;AAChC,YAAM,cAAc,OAAO,KAAK,OAAO,OAAO;AAC9C,UAAI,gBAAgB,OAAO;AACzB,eAAO,KAAK,eAAe,aAAa,OAAO,SAAS;AAAA,MAC1D;AAEA,gBAAU,IAAI,KAAK;AACnB,UAAI;AACF,eAAO,KAAK,eAAe,aAAa,OAAO,SAAS;AAAA,MAC1D,UAAA;AACE,kBAAU,OAAO,KAAK;AAAA,MACxB;AAAA,IACF;AACA,QACE,YAAY,WACX,iBAAiB,WAChB,iBAAiB,UACjB,iBAAiB,UACjB,iBAAiB,SACnB;AACA,aAAO,MAAM,QAAA;AAAA,IACf;AAEA,cAAU,IAAI,KAAK;AAEnB,UAAM,UAAU,MAAM,QAAQ,KAAK;AACnC,UAAM,SAAc,UAAU,CAAA,IAAK,CAAA;AACnC,QAAI;AACF,UAAI,SAAS;AACX,cAAM,SAAS,MAAM;AACrB,iBAAS,QAAQ,GAAG,QAAQ,QAAQ,SAAS;AAC3C,gBAAM,MAAM,OAAO,KAAK;AACxB;AAAA,YACE;AAAA,YACA;AAAA,YACA,KAAK;AAAA,cACH,MAAM,KAAK;AAAA,cACX,YAAY,SAAY,SAAY;AAAA,cACpC;AAAA,YAAA;AAAA,UACF;AAAA,QAEJ;AAAA,MACF,OAAO;AACL,mBAAW,OAAO,OAAO,KAAK,KAAK,GAAG;AACpC;AAAA,YACE;AAAA,YACA;AAAA,YACA,KAAK;AAAA,cACH,MAAM,GAAG;AAAA,cACT,YAAY,SAAY,SAAY;AAAA,cACpC;AAAA,YAAA;AAAA,UACF;AAAA,QAEJ;AAAA,MACF;AAAA,IACF,UAAA;AACE,gBAAU,OAAO,KAAK;AAAA,IACxB;AACA,QAAI,YAAY,SAAS,OAAO,OAAO,WAAW;AAChD,aAAO,OAAO;AAChB,WAAO,CAAC,WAAW,OAAO,UAAU,eAAe,KAAK,OAAO,QAAQ,IACnE,EAAE,QAAQ,UAAU,OAAO,WAC3B;AAAA,EACN;AAAA,EAEQ,iBAAiB,OAAY,eAAuC;AAC1E,QAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,aAAO;AAAA,IACT;AAEA,QAAI,OAAO,UAAU,YAAY,MAAM,WAAW,QAAQ;AACxD,UAAI,MAAM,UAAU,UAAa,MAAM,UAAU,MAAM;AACrD,cAAM,IAAI,MAAM,4CAA4C;AAAA,MAC9D;AACA,YAAM,OAAO,IAAI,KAAK,MAAM,KAAK;AACjC,UAAI,MAAM,KAAK,QAAA,CAAS,GAAG;AACzB,cAAM,IAAI;AAAA,UACR,0DAA0D,MAAM,KAAK;AAAA,QAAA;AAAA,MAEzE;AACA,aAAO;AAAA,IACT;AAEA,QACE,kBAAkB,KAClB,OAAO,UAAU,YACjB,MAAM,WAAW,YACjB;AACA,YAAM,kBAAkB,2BAA2B,MAAM,IAAI;AAC7D,UAAI,CAAC;AACH,cAAM,IAAI,MAAM,+CAA+C;AACjE,UAAI,OAAO,MAAM,UAAU;AACzB,cAAM,IAAI,MAAM,gDAAgD;AAClE,aAAO,2BAA2B,eAAe,EAAE,KAAK,MAAM,KAAK;AAAA,IACrE;AAEA,QAAI,OAAO,UAAU,UAAU;AAE7B,UAAI,kBAAkB,UAAa,MAAM,WAAW,UAAU;AAC5D,YACE,MAAM,UAAU,QAChB,OAAO,MAAM,UAAU,YACvB,MAAM,QAAQ,MAAM,KAAK,GACzB;AACA,gBAAM,IAAI,MAAM,mDAAmD;AAAA,QACrE;AACA,gBAAQ,MAAM;AAAA,MAChB;AACA,YAAM,SAAc,MAAM,QAAQ,KAAK,IAAI,CAAA,IAAK,CAAA;AAChD,iBAAW,OAAO,OAAO;AACvB,YAAI,OAAO,UAAU,eAAe,KAAK,OAAO,GAAG,GAAG;AACpD;AAAA,YACE;AAAA,YACA;AAAA,YACA,KAAK,iBAAiB,MAAM,GAAG,GAAG,aAAa;AAAA,UAAA;AAAA,QAEnD;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,eAAe,OAA+B;AAC5C,WAAO;AAAA,MACL,MAAM,MAAM;AAAA,MACZ,SAAS,MAAM;AAAA,MACf,OAAO,MAAM;AAAA,IAAA;AAAA,EAEjB;AAAA,EAEA,iBAAiB,MAA8B;AAC7C,UAAM,QAAQ,IAAI,MAAM,KAAK,OAAO;AACpC,UAAM,OAAO,KAAK;AAClB,UAAM,QAAQ,KAAK;AACnB,WAAO;AAAA,EACT;AACF;;;"}
@@ -1,5 +1,7 @@
1
1
  import { OfflineTransaction, SerializedError } from '../types.cjs';
2
2
  import { Collection } from '@tanstack/db';
3
+ export declare class MissingTemporalConstructorError extends Error {
4
+ }
3
5
  export declare class TransactionSerializer {
4
6
  private collections;
5
7
  private collectionIdToKey;
@@ -117,6 +117,7 @@ class IndexedDBAdapter extends StorageAdapter.BaseStorageAdapter {
117
117
  });
118
118
  } catch (error) {
119
119
  console.warn(`IndexedDB delete failed:`, error);
120
+ throw error;
120
121
  }
121
122
  }
122
123
  async keys() {
@@ -1 +1 @@
1
- {"version":3,"file":"IndexedDBAdapter.cjs","sources":["../../../src/storage/IndexedDBAdapter.ts"],"sourcesContent":["import { BaseStorageAdapter } from './StorageAdapter'\n\nexport class IndexedDBAdapter extends BaseStorageAdapter {\n private dbName: string\n private storeName: string\n private db: IDBDatabase | null = null\n\n constructor(dbName = `offline-transactions`, storeName = `transactions`) {\n super()\n this.dbName = dbName\n this.storeName = storeName\n }\n\n /**\n * Probe IndexedDB availability by attempting to open a test database.\n * This catches private mode and other restrictions that block IndexedDB.\n */\n static async probe(): Promise<{ available: boolean; error?: Error }> {\n // Check if IndexedDB exists\n if (typeof indexedDB === `undefined`) {\n return {\n available: false,\n error: new Error(`IndexedDB is not available in this environment`),\n }\n }\n\n // Try to actually open a test database to verify it works\n try {\n const testDbName = `__offline-tx-probe__`\n const request = indexedDB.open(testDbName, 1)\n\n return new Promise((resolve) => {\n request.onerror = () => {\n const error = request.error || new Error(`IndexedDB open failed`)\n resolve({ available: false, error })\n }\n\n request.onsuccess = () => {\n // Clean up test database\n const db = request.result\n db.close()\n indexedDB.deleteDatabase(testDbName)\n resolve({ available: true })\n }\n\n request.onblocked = () => {\n resolve({\n available: false,\n error: new Error(`IndexedDB is blocked`),\n })\n }\n })\n } catch (error) {\n return {\n available: false,\n error: error instanceof Error ? error : new Error(String(error)),\n }\n }\n }\n\n private async openDB(): Promise<IDBDatabase> {\n if (this.db) {\n return this.db\n }\n\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(this.dbName, 1)\n\n request.onerror = () => reject(request.error)\n request.onsuccess = () => {\n this.db = request.result\n resolve(this.db)\n }\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result\n if (!db.objectStoreNames.contains(this.storeName)) {\n db.createObjectStore(this.storeName)\n }\n }\n })\n }\n\n private async getStore(\n mode: IDBTransactionMode = `readonly`,\n ): Promise<IDBObjectStore> {\n const db = await this.openDB()\n const transaction = db.transaction([this.storeName], mode)\n return transaction.objectStore(this.storeName)\n }\n\n private waitForWrite(\n store: IDBObjectStore,\n write: () => void,\n ): Promise<void> {\n const transaction = store.transaction\n return new Promise((resolve, reject) => {\n // A successful request can still be rolled back by its transaction.\n transaction.oncomplete = () => resolve()\n transaction.onabort = () =>\n reject(\n transaction.error ??\n new DOMException(`IndexedDB transaction aborted`, `AbortError`),\n )\n write()\n })\n }\n\n async get(key: string): Promise<string | null> {\n try {\n const store = await this.getStore(`readonly`)\n return new Promise((resolve, reject) => {\n const request = store.get(key)\n request.onerror = () => reject(request.error)\n request.onsuccess = () => resolve(request.result ?? null)\n })\n } catch (error) {\n console.warn(`IndexedDB get failed:`, error)\n return null\n }\n }\n\n async set(key: string, value: string): Promise<void> {\n try {\n const store = await this.getStore(`readwrite`)\n return this.waitForWrite(store, () => {\n store.put(value, key)\n })\n } catch (error) {\n if (\n error instanceof DOMException &&\n error.name === `QuotaExceededError`\n ) {\n throw new Error(\n `Storage quota exceeded. Consider clearing old transactions.`,\n )\n }\n throw error\n }\n }\n\n async delete(key: string): Promise<void> {\n try {\n const store = await this.getStore(`readwrite`)\n return this.waitForWrite(store, () => {\n store.delete(key)\n })\n } catch (error) {\n console.warn(`IndexedDB delete failed:`, error)\n }\n }\n\n async keys(): Promise<Array<string>> {\n try {\n const store = await this.getStore(`readonly`)\n return new Promise((resolve, reject) => {\n const request = store.getAllKeys()\n request.onerror = () => reject(request.error)\n request.onsuccess = () => resolve(request.result as Array<string>)\n })\n } catch (error) {\n console.warn(`IndexedDB keys failed:`, error)\n return []\n }\n }\n\n async clear(): Promise<void> {\n try {\n const store = await this.getStore(`readwrite`)\n return this.waitForWrite(store, () => {\n store.clear()\n })\n } catch (error) {\n console.warn(`IndexedDB clear failed:`, error)\n }\n }\n}\n"],"names":["BaseStorageAdapter"],"mappings":"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"}
1
+ {"version":3,"file":"IndexedDBAdapter.cjs","sources":["../../../src/storage/IndexedDBAdapter.ts"],"sourcesContent":["import { BaseStorageAdapter } from './StorageAdapter'\n\nexport class IndexedDBAdapter extends BaseStorageAdapter {\n private dbName: string\n private storeName: string\n private db: IDBDatabase | null = null\n\n constructor(dbName = `offline-transactions`, storeName = `transactions`) {\n super()\n this.dbName = dbName\n this.storeName = storeName\n }\n\n /**\n * Probe IndexedDB availability by attempting to open a test database.\n * This catches private mode and other restrictions that block IndexedDB.\n */\n static async probe(): Promise<{ available: boolean; error?: Error }> {\n // Check if IndexedDB exists\n if (typeof indexedDB === `undefined`) {\n return {\n available: false,\n error: new Error(`IndexedDB is not available in this environment`),\n }\n }\n\n // Try to actually open a test database to verify it works\n try {\n const testDbName = `__offline-tx-probe__`\n const request = indexedDB.open(testDbName, 1)\n\n return new Promise((resolve) => {\n request.onerror = () => {\n const error = request.error || new Error(`IndexedDB open failed`)\n resolve({ available: false, error })\n }\n\n request.onsuccess = () => {\n // Clean up test database\n const db = request.result\n db.close()\n indexedDB.deleteDatabase(testDbName)\n resolve({ available: true })\n }\n\n request.onblocked = () => {\n resolve({\n available: false,\n error: new Error(`IndexedDB is blocked`),\n })\n }\n })\n } catch (error) {\n return {\n available: false,\n error: error instanceof Error ? error : new Error(String(error)),\n }\n }\n }\n\n private async openDB(): Promise<IDBDatabase> {\n if (this.db) {\n return this.db\n }\n\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(this.dbName, 1)\n\n request.onerror = () => reject(request.error)\n request.onsuccess = () => {\n this.db = request.result\n resolve(this.db)\n }\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result\n if (!db.objectStoreNames.contains(this.storeName)) {\n db.createObjectStore(this.storeName)\n }\n }\n })\n }\n\n private async getStore(\n mode: IDBTransactionMode = `readonly`,\n ): Promise<IDBObjectStore> {\n const db = await this.openDB()\n const transaction = db.transaction([this.storeName], mode)\n return transaction.objectStore(this.storeName)\n }\n\n private waitForWrite(\n store: IDBObjectStore,\n write: () => void,\n ): Promise<void> {\n const transaction = store.transaction\n return new Promise((resolve, reject) => {\n // A successful request can still be rolled back by its transaction.\n transaction.oncomplete = () => resolve()\n transaction.onabort = () =>\n reject(\n transaction.error ??\n new DOMException(`IndexedDB transaction aborted`, `AbortError`),\n )\n write()\n })\n }\n\n async get(key: string): Promise<string | null> {\n try {\n const store = await this.getStore(`readonly`)\n return new Promise((resolve, reject) => {\n const request = store.get(key)\n request.onerror = () => reject(request.error)\n request.onsuccess = () => resolve(request.result ?? null)\n })\n } catch (error) {\n console.warn(`IndexedDB get failed:`, error)\n return null\n }\n }\n\n async set(key: string, value: string): Promise<void> {\n try {\n const store = await this.getStore(`readwrite`)\n return this.waitForWrite(store, () => {\n store.put(value, key)\n })\n } catch (error) {\n if (\n error instanceof DOMException &&\n error.name === `QuotaExceededError`\n ) {\n throw new Error(\n `Storage quota exceeded. Consider clearing old transactions.`,\n )\n }\n throw error\n }\n }\n\n async delete(key: string): Promise<void> {\n try {\n const store = await this.getStore(`readwrite`)\n return this.waitForWrite(store, () => {\n store.delete(key)\n })\n } catch (error) {\n console.warn(`IndexedDB delete failed:`, error)\n throw error\n }\n }\n\n async keys(): Promise<Array<string>> {\n try {\n const store = await this.getStore(`readonly`)\n return new Promise((resolve, reject) => {\n const request = store.getAllKeys()\n request.onerror = () => reject(request.error)\n request.onsuccess = () => resolve(request.result as Array<string>)\n })\n } catch (error) {\n console.warn(`IndexedDB keys failed:`, error)\n return []\n }\n }\n\n async clear(): Promise<void> {\n try {\n const store = await this.getStore(`readwrite`)\n return this.waitForWrite(store, () => {\n store.clear()\n })\n } catch (error) {\n console.warn(`IndexedDB clear failed:`, error)\n }\n }\n}\n"],"names":["BaseStorageAdapter"],"mappings":";;;AAEO,MAAM,yBAAyBA,eAAAA,mBAAmB;AAAA,EAKvD,YAAY,SAAS,wBAAwB,YAAY,gBAAgB;AACvE,UAAA;AAHF,SAAQ,KAAyB;AAI/B,SAAK,SAAS;AACd,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,QAAwD;AAEnE,QAAI,OAAO,cAAc,aAAa;AACpC,aAAO;AAAA,QACL,WAAW;AAAA,QACX,OAAO,IAAI,MAAM,gDAAgD;AAAA,MAAA;AAAA,IAErE;AAGA,QAAI;AACF,YAAM,aAAa;AACnB,YAAM,UAAU,UAAU,KAAK,YAAY,CAAC;AAE5C,aAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,gBAAQ,UAAU,MAAM;AACtB,gBAAM,QAAQ,QAAQ,SAAS,IAAI,MAAM,uBAAuB;AAChE,kBAAQ,EAAE,WAAW,OAAO,MAAA,CAAO;AAAA,QACrC;AAEA,gBAAQ,YAAY,MAAM;AAExB,gBAAM,KAAK,QAAQ;AACnB,aAAG,MAAA;AACH,oBAAU,eAAe,UAAU;AACnC,kBAAQ,EAAE,WAAW,MAAM;AAAA,QAC7B;AAEA,gBAAQ,YAAY,MAAM;AACxB,kBAAQ;AAAA,YACN,WAAW;AAAA,YACX,OAAO,IAAI,MAAM,sBAAsB;AAAA,UAAA,CACxC;AAAA,QACH;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,aAAO;AAAA,QACL,WAAW;AAAA,QACX,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,MAAA;AAAA,IAEnE;AAAA,EACF;AAAA,EAEA,MAAc,SAA+B;AAC3C,QAAI,KAAK,IAAI;AACX,aAAO,KAAK;AAAA,IACd;AAEA,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,UAAU,UAAU,KAAK,KAAK,QAAQ,CAAC;AAE7C,cAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAC5C,cAAQ,YAAY,MAAM;AACxB,aAAK,KAAK,QAAQ;AAClB,gBAAQ,KAAK,EAAE;AAAA,MACjB;AAEA,cAAQ,kBAAkB,CAAC,UAAU;AACnC,cAAM,KAAM,MAAM,OAA4B;AAC9C,YAAI,CAAC,GAAG,iBAAiB,SAAS,KAAK,SAAS,GAAG;AACjD,aAAG,kBAAkB,KAAK,SAAS;AAAA,QACrC;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,SACZ,OAA2B,YACF;AACzB,UAAM,KAAK,MAAM,KAAK,OAAA;AACtB,UAAM,cAAc,GAAG,YAAY,CAAC,KAAK,SAAS,GAAG,IAAI;AACzD,WAAO,YAAY,YAAY,KAAK,SAAS;AAAA,EAC/C;AAAA,EAEQ,aACN,OACA,OACe;AACf,UAAM,cAAc,MAAM;AAC1B,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AAEtC,kBAAY,aAAa,MAAM,QAAA;AAC/B,kBAAY,UAAU,MACpB;AAAA,QACE,YAAY,SACV,IAAI,aAAa,iCAAiC,YAAY;AAAA,MAAA;AAEpE,YAAA;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,IAAI,KAAqC;AAC7C,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,SAAS,UAAU;AAC5C,aAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,cAAM,UAAU,MAAM,IAAI,GAAG;AAC7B,gBAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAC5C,gBAAQ,YAAY,MAAM,QAAQ,QAAQ,UAAU,IAAI;AAAA,MAC1D,CAAC;AAAA,IACH,SAAS,OAAO;AACd,cAAQ,KAAK,yBAAyB,KAAK;AAC3C,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,IAAI,KAAa,OAA8B;AACnD,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,SAAS,WAAW;AAC7C,aAAO,KAAK,aAAa,OAAO,MAAM;AACpC,cAAM,IAAI,OAAO,GAAG;AAAA,MACtB,CAAC;AAAA,IACH,SAAS,OAAO;AACd,UACE,iBAAiB,gBACjB,MAAM,SAAS,sBACf;AACA,cAAM,IAAI;AAAA,UACR;AAAA,QAAA;AAAA,MAEJ;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,KAA4B;AACvC,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,SAAS,WAAW;AAC7C,aAAO,KAAK,aAAa,OAAO,MAAM;AACpC,cAAM,OAAO,GAAG;AAAA,MAClB,CAAC;AAAA,IACH,SAAS,OAAO;AACd,cAAQ,KAAK,4BAA4B,KAAK;AAC9C,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,OAA+B;AACnC,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,SAAS,UAAU;AAC5C,aAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,cAAM,UAAU,MAAM,WAAA;AACtB,gBAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAC5C,gBAAQ,YAAY,MAAM,QAAQ,QAAQ,MAAuB;AAAA,MACnE,CAAC;AAAA,IACH,SAAS,OAAO;AACd,cAAQ,KAAK,0BAA0B,KAAK;AAC5C,aAAO,CAAA;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,SAAS,WAAW;AAC7C,aAAO,KAAK,aAAa,OAAO,MAAM;AACpC,cAAM,MAAA;AAAA,MACR,CAAC;AAAA,IACH,SAAS,OAAO;AACd,cAAQ,KAAK,2BAA2B,KAAK;AAAA,IAC/C;AAAA,EACF;AACF;;"}
@@ -69,7 +69,7 @@ class LocalStorageAdapter extends StorageAdapter.BaseStorageAdapter {
69
69
  return Promise.resolve();
70
70
  } catch (error) {
71
71
  console.warn(`localStorage delete failed:`, error);
72
- return Promise.resolve();
72
+ return Promise.reject(error);
73
73
  }
74
74
  }
75
75
  keys() {
@@ -1 +1 @@
1
- {"version":3,"file":"LocalStorageAdapter.cjs","sources":["../../../src/storage/LocalStorageAdapter.ts"],"sourcesContent":["import { BaseStorageAdapter } from './StorageAdapter'\n\nexport class LocalStorageAdapter extends BaseStorageAdapter {\n private prefix: string\n\n constructor(prefix = `offline-tx:`) {\n super()\n this.prefix = prefix\n }\n\n /**\n * Probe localStorage availability by attempting a test write.\n * This catches private mode and other restrictions that block localStorage.\n */\n static probe(): { available: boolean; error?: Error } {\n // Check if localStorage exists\n if (typeof localStorage === `undefined`) {\n return {\n available: false,\n error: new Error(`localStorage is not available in this environment`),\n }\n }\n\n // Try to actually write/read/delete to verify it works\n try {\n const testKey = `__offline-tx-probe__`\n const testValue = `test`\n\n localStorage.setItem(testKey, testValue)\n const retrieved = localStorage.getItem(testKey)\n localStorage.removeItem(testKey)\n\n if (retrieved !== testValue) {\n return {\n available: false,\n error: new Error(`localStorage read/write verification failed`),\n }\n }\n\n return { available: true }\n } catch (error) {\n return {\n available: false,\n error: error instanceof Error ? error : new Error(String(error)),\n }\n }\n }\n\n private getKey(key: string): string {\n return `${this.prefix}${key}`\n }\n\n get(key: string): Promise<string | null> {\n try {\n return Promise.resolve(localStorage.getItem(this.getKey(key)))\n } catch (error) {\n console.warn(`localStorage get failed:`, error)\n return Promise.resolve(null)\n }\n }\n\n set(key: string, value: string): Promise<void> {\n try {\n localStorage.setItem(this.getKey(key), value)\n return Promise.resolve()\n } catch (error) {\n if (\n error instanceof DOMException &&\n error.name === `QuotaExceededError`\n ) {\n return Promise.reject(\n new Error(\n `Storage quota exceeded. Consider clearing old transactions.`,\n ),\n )\n }\n return Promise.reject(error)\n }\n }\n\n delete(key: string): Promise<void> {\n try {\n localStorage.removeItem(this.getKey(key))\n return Promise.resolve()\n } catch (error) {\n console.warn(`localStorage delete failed:`, error)\n return Promise.resolve()\n }\n }\n\n keys(): Promise<Array<string>> {\n try {\n const keys: Array<string> = []\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i)\n if (key && key.startsWith(this.prefix)) {\n keys.push(key.slice(this.prefix.length))\n }\n }\n return Promise.resolve(keys)\n } catch (error) {\n console.warn(`localStorage keys failed:`, error)\n return Promise.resolve([])\n }\n }\n\n async clear(): Promise<void> {\n try {\n const keys = await this.keys()\n for (const key of keys) {\n localStorage.removeItem(this.getKey(key))\n }\n } catch (error) {\n console.warn(`localStorage clear failed:`, error)\n }\n }\n}\n"],"names":["BaseStorageAdapter"],"mappings":";;;AAEO,MAAM,4BAA4BA,eAAAA,mBAAmB;AAAA,EAG1D,YAAY,SAAS,eAAe;AAClC,UAAA;AACA,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,QAA+C;AAEpD,QAAI,OAAO,iBAAiB,aAAa;AACvC,aAAO;AAAA,QACL,WAAW;AAAA,QACX,OAAO,IAAI,MAAM,mDAAmD;AAAA,MAAA;AAAA,IAExE;AAGA,QAAI;AACF,YAAM,UAAU;AAChB,YAAM,YAAY;AAElB,mBAAa,QAAQ,SAAS,SAAS;AACvC,YAAM,YAAY,aAAa,QAAQ,OAAO;AAC9C,mBAAa,WAAW,OAAO;AAE/B,UAAI,cAAc,WAAW;AAC3B,eAAO;AAAA,UACL,WAAW;AAAA,UACX,OAAO,IAAI,MAAM,6CAA6C;AAAA,QAAA;AAAA,MAElE;AAEA,aAAO,EAAE,WAAW,KAAA;AAAA,IACtB,SAAS,OAAO;AACd,aAAO;AAAA,QACL,WAAW;AAAA,QACX,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,MAAA;AAAA,IAEnE;AAAA,EACF;AAAA,EAEQ,OAAO,KAAqB;AAClC,WAAO,GAAG,KAAK,MAAM,GAAG,GAAG;AAAA,EAC7B;AAAA,EAEA,IAAI,KAAqC;AACvC,QAAI;AACF,aAAO,QAAQ,QAAQ,aAAa,QAAQ,KAAK,OAAO,GAAG,CAAC,CAAC;AAAA,IAC/D,SAAS,OAAO;AACd,cAAQ,KAAK,4BAA4B,KAAK;AAC9C,aAAO,QAAQ,QAAQ,IAAI;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,IAAI,KAAa,OAA8B;AAC7C,QAAI;AACF,mBAAa,QAAQ,KAAK,OAAO,GAAG,GAAG,KAAK;AAC5C,aAAO,QAAQ,QAAA;AAAA,IACjB,SAAS,OAAO;AACd,UACE,iBAAiB,gBACjB,MAAM,SAAS,sBACf;AACA,eAAO,QAAQ;AAAA,UACb,IAAI;AAAA,YACF;AAAA,UAAA;AAAA,QACF;AAAA,MAEJ;AACA,aAAO,QAAQ,OAAO,KAAK;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,OAAO,KAA4B;AACjC,QAAI;AACF,mBAAa,WAAW,KAAK,OAAO,GAAG,CAAC;AACxC,aAAO,QAAQ,QAAA;AAAA,IACjB,SAAS,OAAO;AACd,cAAQ,KAAK,+BAA+B,KAAK;AACjD,aAAO,QAAQ,QAAA;AAAA,IACjB;AAAA,EACF;AAAA,EAEA,OAA+B;AAC7B,QAAI;AACF,YAAM,OAAsB,CAAA;AAC5B,eAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,cAAM,MAAM,aAAa,IAAI,CAAC;AAC9B,YAAI,OAAO,IAAI,WAAW,KAAK,MAAM,GAAG;AACtC,eAAK,KAAK,IAAI,MAAM,KAAK,OAAO,MAAM,CAAC;AAAA,QACzC;AAAA,MACF;AACA,aAAO,QAAQ,QAAQ,IAAI;AAAA,IAC7B,SAAS,OAAO;AACd,cAAQ,KAAK,6BAA6B,KAAK;AAC/C,aAAO,QAAQ,QAAQ,EAAE;AAAA,IAC3B;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI;AACF,YAAM,OAAO,MAAM,KAAK,KAAA;AACxB,iBAAW,OAAO,MAAM;AACtB,qBAAa,WAAW,KAAK,OAAO,GAAG,CAAC;AAAA,MAC1C;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,KAAK,8BAA8B,KAAK;AAAA,IAClD;AAAA,EACF;AACF;;"}
1
+ {"version":3,"file":"LocalStorageAdapter.cjs","sources":["../../../src/storage/LocalStorageAdapter.ts"],"sourcesContent":["import { BaseStorageAdapter } from './StorageAdapter'\n\nexport class LocalStorageAdapter extends BaseStorageAdapter {\n private prefix: string\n\n constructor(prefix = `offline-tx:`) {\n super()\n this.prefix = prefix\n }\n\n /**\n * Probe localStorage availability by attempting a test write.\n * This catches private mode and other restrictions that block localStorage.\n */\n static probe(): { available: boolean; error?: Error } {\n // Check if localStorage exists\n if (typeof localStorage === `undefined`) {\n return {\n available: false,\n error: new Error(`localStorage is not available in this environment`),\n }\n }\n\n // Try to actually write/read/delete to verify it works\n try {\n const testKey = `__offline-tx-probe__`\n const testValue = `test`\n\n localStorage.setItem(testKey, testValue)\n const retrieved = localStorage.getItem(testKey)\n localStorage.removeItem(testKey)\n\n if (retrieved !== testValue) {\n return {\n available: false,\n error: new Error(`localStorage read/write verification failed`),\n }\n }\n\n return { available: true }\n } catch (error) {\n return {\n available: false,\n error: error instanceof Error ? error : new Error(String(error)),\n }\n }\n }\n\n private getKey(key: string): string {\n return `${this.prefix}${key}`\n }\n\n get(key: string): Promise<string | null> {\n try {\n return Promise.resolve(localStorage.getItem(this.getKey(key)))\n } catch (error) {\n console.warn(`localStorage get failed:`, error)\n return Promise.resolve(null)\n }\n }\n\n set(key: string, value: string): Promise<void> {\n try {\n localStorage.setItem(this.getKey(key), value)\n return Promise.resolve()\n } catch (error) {\n if (\n error instanceof DOMException &&\n error.name === `QuotaExceededError`\n ) {\n return Promise.reject(\n new Error(\n `Storage quota exceeded. Consider clearing old transactions.`,\n ),\n )\n }\n return Promise.reject(error)\n }\n }\n\n delete(key: string): Promise<void> {\n try {\n localStorage.removeItem(this.getKey(key))\n return Promise.resolve()\n } catch (error) {\n console.warn(`localStorage delete failed:`, error)\n return Promise.reject(error)\n }\n }\n\n keys(): Promise<Array<string>> {\n try {\n const keys: Array<string> = []\n for (let i = 0; i < localStorage.length; i++) {\n const key = localStorage.key(i)\n if (key && key.startsWith(this.prefix)) {\n keys.push(key.slice(this.prefix.length))\n }\n }\n return Promise.resolve(keys)\n } catch (error) {\n console.warn(`localStorage keys failed:`, error)\n return Promise.resolve([])\n }\n }\n\n async clear(): Promise<void> {\n try {\n const keys = await this.keys()\n for (const key of keys) {\n localStorage.removeItem(this.getKey(key))\n }\n } catch (error) {\n console.warn(`localStorage clear failed:`, error)\n }\n }\n}\n"],"names":["BaseStorageAdapter"],"mappings":";;;AAEO,MAAM,4BAA4BA,eAAAA,mBAAmB;AAAA,EAG1D,YAAY,SAAS,eAAe;AAClC,UAAA;AACA,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,QAA+C;AAEpD,QAAI,OAAO,iBAAiB,aAAa;AACvC,aAAO;AAAA,QACL,WAAW;AAAA,QACX,OAAO,IAAI,MAAM,mDAAmD;AAAA,MAAA;AAAA,IAExE;AAGA,QAAI;AACF,YAAM,UAAU;AAChB,YAAM,YAAY;AAElB,mBAAa,QAAQ,SAAS,SAAS;AACvC,YAAM,YAAY,aAAa,QAAQ,OAAO;AAC9C,mBAAa,WAAW,OAAO;AAE/B,UAAI,cAAc,WAAW;AAC3B,eAAO;AAAA,UACL,WAAW;AAAA,UACX,OAAO,IAAI,MAAM,6CAA6C;AAAA,QAAA;AAAA,MAElE;AAEA,aAAO,EAAE,WAAW,KAAA;AAAA,IACtB,SAAS,OAAO;AACd,aAAO;AAAA,QACL,WAAW;AAAA,QACX,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,MAAA;AAAA,IAEnE;AAAA,EACF;AAAA,EAEQ,OAAO,KAAqB;AAClC,WAAO,GAAG,KAAK,MAAM,GAAG,GAAG;AAAA,EAC7B;AAAA,EAEA,IAAI,KAAqC;AACvC,QAAI;AACF,aAAO,QAAQ,QAAQ,aAAa,QAAQ,KAAK,OAAO,GAAG,CAAC,CAAC;AAAA,IAC/D,SAAS,OAAO;AACd,cAAQ,KAAK,4BAA4B,KAAK;AAC9C,aAAO,QAAQ,QAAQ,IAAI;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,IAAI,KAAa,OAA8B;AAC7C,QAAI;AACF,mBAAa,QAAQ,KAAK,OAAO,GAAG,GAAG,KAAK;AAC5C,aAAO,QAAQ,QAAA;AAAA,IACjB,SAAS,OAAO;AACd,UACE,iBAAiB,gBACjB,MAAM,SAAS,sBACf;AACA,eAAO,QAAQ;AAAA,UACb,IAAI;AAAA,YACF;AAAA,UAAA;AAAA,QACF;AAAA,MAEJ;AACA,aAAO,QAAQ,OAAO,KAAK;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,OAAO,KAA4B;AACjC,QAAI;AACF,mBAAa,WAAW,KAAK,OAAO,GAAG,CAAC;AACxC,aAAO,QAAQ,QAAA;AAAA,IACjB,SAAS,OAAO;AACd,cAAQ,KAAK,+BAA+B,KAAK;AACjD,aAAO,QAAQ,OAAO,KAAK;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,OAA+B;AAC7B,QAAI;AACF,YAAM,OAAsB,CAAA;AAC5B,eAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,cAAM,MAAM,aAAa,IAAI,CAAC;AAC9B,YAAI,OAAO,IAAI,WAAW,KAAK,MAAM,GAAG;AACtC,eAAK,KAAK,IAAI,MAAM,KAAK,OAAO,MAAM,CAAC;AAAA,QACzC;AAAA,MACF;AACA,aAAO,QAAQ,QAAQ,IAAI;AAAA,IAC7B,SAAS,OAAO;AACd,cAAQ,KAAK,6BAA6B,KAAK;AAC/C,aAAO,QAAQ,QAAQ,EAAE;AAAA,IAC3B;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI;AACF,YAAM,OAAO,MAAM,KAAK,KAAA;AACxB,iBAAW,OAAO,MAAM;AACtB,qBAAa,WAAW,KAAK,OAAO,GAAG,CAAC;AAAA,MAC1C;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,KAAK,8BAA8B,KAAK;AAAA,IAClD;AAAA,EACF;AACF;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.cjs","sources":["../../src/types.ts"],"sourcesContent":["import type {\n Collection,\n MutationFnParams,\n PendingMutation,\n Transaction,\n} from '@tanstack/db'\n\n// Extended mutation function that includes idempotency key\nexport type OfflineMutationFnParams<\n T extends object = Record<string, unknown>,\n> = MutationFnParams<T> & {\n idempotencyKey: string\n}\n\nexport type OfflineMutationFn<T extends object = Record<string, unknown>> = (\n params: OfflineMutationFnParams<T>,\n) => Promise<any>\n\n// Simplified mutation structure for serialization\nexport interface SerializedMutation {\n globalKey: string\n type: string\n modified: any\n original: any\n changes: any\n collectionId: string\n}\n\nexport interface SerializedError {\n name: string\n message: string\n stack?: string\n}\n\nexport interface SerializedSpanContext {\n traceId: string\n spanId: string\n traceFlags: number\n traceState?: string\n}\n\n// In-memory representation with full PendingMutation objects\nexport interface OfflineTransaction {\n id: string\n mutationFnName: string\n mutations: Array<PendingMutation>\n keys: Array<string>\n idempotencyKey: string\n createdAt: Date\n retryCount: number\n nextAttemptAt: number\n lastError?: SerializedError\n metadata?: Record<string, any>\n spanContext?: SerializedSpanContext\n version: 1\n}\n\n// Serialized representation for storage\nexport interface SerializedOfflineTransaction {\n /** Absent for the original Date-marker format. */\n valueEncoding?: 2\n id: string\n mutationFnName: string\n mutations: Array<SerializedMutation>\n keys: Array<string>\n idempotencyKey: string\n createdAt: string\n retryCount: number\n nextAttemptAt: number\n lastError?: SerializedError\n metadata?: Record<string, any>\n spanContext?: SerializedSpanContext\n version: 1\n}\n\n// Storage diagnostics and mode\nexport type OfflineMode = `offline` | `online-only`\n\nexport type StorageDiagnosticCode =\n | `STORAGE_AVAILABLE`\n | `INDEXEDDB_UNAVAILABLE`\n | `LOCALSTORAGE_UNAVAILABLE`\n | `STORAGE_BLOCKED`\n | `QUOTA_EXCEEDED`\n | `UNKNOWN_ERROR`\n\nexport interface StorageDiagnostic {\n code: StorageDiagnosticCode\n mode: OfflineMode\n message: string\n error?: Error\n}\n\nexport interface OfflineConfig {\n collections: Record<string, Collection<any, any, any, any, any>>\n mutationFns: Record<string, OfflineMutationFn>\n storage?: StorageAdapter\n maxConcurrency?: number\n jitter?: boolean\n beforeRetry?: (\n transactions: Array<OfflineTransaction>,\n ) => Array<OfflineTransaction>\n onUnknownMutationFn?: (name: string, tx: OfflineTransaction) => void\n onLeadershipChange?: (isLeader: boolean) => void\n onStorageFailure?: (diagnostic: StorageDiagnostic) => void\n leaderElection?: LeaderElection\n /**\n * Custom online detector implementation.\n * Defaults to WebOnlineDetector for browser environments.\n * The '@tanstack/offline-transactions/react-native' entry point uses ReactNativeOnlineDetector automatically.\n */\n onlineDetector?: OnlineDetector\n}\n\nexport interface StorageAdapter {\n get: (key: string) => Promise<string | null>\n set: (key: string, value: string) => Promise<void>\n delete: (key: string) => Promise<void>\n keys: () => Promise<Array<string>>\n clear: () => Promise<void>\n}\n\nexport interface RetryPolicy {\n calculateDelay: (retryCount: number) => number\n shouldRetry: (error: Error, retryCount: number) => boolean\n}\n\nexport interface LeaderElection {\n requestLeadership: () => Promise<boolean>\n releaseLeadership: () => void\n isLeader: () => boolean\n onLeadershipChange: (callback: (isLeader: boolean) => void) => () => void\n}\n\nexport interface TransactionSignaler {\n readonly isOfflineEnabled: boolean\n resolveTransaction: (transactionId: string, result: any) => void\n rejectTransaction: (transactionId: string, error: Error) => void\n registerRestorationTransaction: (\n offlineTransactionId: string,\n restorationTransaction: Transaction,\n ) => void\n isOnline: () => boolean\n}\n\nexport interface OnlineDetector {\n subscribe: (callback: () => void) => () => void\n notifyOnline: () => void\n isOnline: () => boolean\n dispose: () => void\n}\n\nexport interface CreateOfflineTransactionOptions {\n id?: string\n mutationFnName: string\n autoCommit?: boolean\n idempotencyKey?: string\n metadata?: Record<string, any>\n}\n\nexport interface CreateOfflineActionOptions<T> {\n mutationFnName: string\n onMutate: (variables: T) => void\n}\n\nexport class NonRetriableError extends Error {\n constructor(message: string) {\n super(message)\n this.name = `NonRetriableError`\n }\n}\n"],"names":[],"mappings":";;AAqKO,MAAM,0BAA0B,MAAM;AAAA,EAC3C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;;"}
1
+ {"version":3,"file":"types.cjs","sources":["../../src/types.ts"],"sourcesContent":["import type {\n Collection,\n MutationFnParams,\n PendingMutation,\n Transaction,\n} from '@tanstack/db'\n\n// Extended mutation function that includes idempotency key\nexport type OfflineMutationFnParams<\n T extends object = Record<string, unknown>,\n> = MutationFnParams<T> & {\n idempotencyKey: string\n}\n\nexport type OfflineMutationFn<T extends object = Record<string, unknown>> = (\n params: OfflineMutationFnParams<T>,\n) => Promise<any>\n\n// Simplified mutation structure for serialization\nexport interface SerializedMutation {\n globalKey: string\n type: string\n modified: any\n original: any\n changes: any\n collectionId: string\n}\n\nexport interface SerializedError {\n name: string\n message: string\n stack?: string\n}\n\nexport interface SerializedSpanContext {\n traceId: string\n spanId: string\n traceFlags: number\n traceState?: string\n}\n\n// In-memory representation with full PendingMutation objects\nexport interface OfflineTransaction {\n id: string\n mutationFnName: string\n mutations: Array<PendingMutation>\n keys: Array<string>\n idempotencyKey: string\n createdAt: Date\n retryCount: number\n nextAttemptAt: number\n /** Provider work has settled; only durable outbox deletion remains. */\n outboxPhase?: `deletion-pending` | `rejection-pending`\n lastError?: SerializedError\n metadata?: Record<string, any>\n spanContext?: SerializedSpanContext\n version: 1\n}\n\n// Serialized representation for storage\nexport interface SerializedOfflineTransaction {\n /** Absent for the original Date-marker format. */\n valueEncoding?: 2 | 3\n id: string\n mutationFnName: string\n mutations: Array<SerializedMutation>\n keys: Array<string>\n idempotencyKey: string\n createdAt: string\n retryCount: number\n nextAttemptAt: number\n /** Absent in older records and while mutationFn may still need to run. */\n outboxPhase?: `deletion-pending` | `rejection-pending`\n lastError?: SerializedError\n metadata?: Record<string, any>\n spanContext?: SerializedSpanContext\n version: 1\n}\n\n// Storage diagnostics and mode\nexport type OfflineMode = `offline` | `online-only`\n\nexport type StorageDiagnosticCode =\n | `STORAGE_AVAILABLE`\n | `INDEXEDDB_UNAVAILABLE`\n | `LOCALSTORAGE_UNAVAILABLE`\n | `STORAGE_BLOCKED`\n | `QUOTA_EXCEEDED`\n | `UNKNOWN_ERROR`\n\nexport interface StorageDiagnostic {\n code: StorageDiagnosticCode\n mode: OfflineMode\n message: string\n error?: Error\n}\n\nexport interface OfflineConfig {\n collections: Record<string, Collection<any, any, any, any, any>>\n mutationFns: Record<string, OfflineMutationFn>\n storage?: StorageAdapter\n maxConcurrency?: number\n jitter?: boolean\n beforeRetry?: (\n transactions: Array<OfflineTransaction>,\n ) => Array<OfflineTransaction>\n onUnknownMutationFn?: (name: string, tx: OfflineTransaction) => void\n onLeadershipChange?: (isLeader: boolean) => void\n onStorageFailure?: (diagnostic: StorageDiagnostic) => void\n leaderElection?: LeaderElection\n /**\n * Custom online detector implementation.\n * Defaults to WebOnlineDetector for browser environments.\n * The '@tanstack/offline-transactions/react-native' entry point uses ReactNativeOnlineDetector automatically.\n */\n onlineDetector?: OnlineDetector\n}\n\nexport interface StorageAdapter {\n get: (key: string) => Promise<string | null>\n set: (key: string, value: string) => Promise<void>\n delete: (key: string) => Promise<void>\n keys: () => Promise<Array<string>>\n clear: () => Promise<void>\n}\n\nexport interface RetryPolicy {\n calculateDelay: (retryCount: number) => number\n shouldRetry: (error: Error, retryCount: number) => boolean\n}\n\nexport interface LeaderElection {\n requestLeadership: () => Promise<boolean>\n releaseLeadership: () => void\n isLeader: () => boolean\n onLeadershipChange: (callback: (isLeader: boolean) => void) => () => void\n}\n\nexport interface TransactionSignaler {\n readonly isOfflineEnabled: boolean\n resolveTransaction: (transactionId: string, result: any) => void\n rejectTransaction: (transactionId: string, error: Error) => void\n registerRestorationTransaction: (\n offlineTransactionId: string,\n restorationTransaction: Transaction,\n ) => void\n isOnline: () => boolean\n}\n\nexport interface OnlineDetector {\n subscribe: (callback: () => void) => () => void\n notifyOnline: () => void\n isOnline: () => boolean\n dispose: () => void\n}\n\nexport interface CreateOfflineTransactionOptions {\n id?: string\n mutationFnName: string\n autoCommit?: boolean\n idempotencyKey?: string\n metadata?: Record<string, any>\n}\n\nexport interface CreateOfflineActionOptions<T> {\n mutationFnName: string\n onMutate: (variables: T) => void\n}\n\nexport class NonRetriableError extends Error {\n constructor(message: string) {\n super(message)\n this.name = `NonRetriableError`\n }\n}\n"],"names":[],"mappings":";;AAyKO,MAAM,0BAA0B,MAAM;AAAA,EAC3C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;;"}
@@ -31,6 +31,8 @@ export interface OfflineTransaction {
31
31
  createdAt: Date;
32
32
  retryCount: number;
33
33
  nextAttemptAt: number;
34
+ /** Provider work has settled; only durable outbox deletion remains. */
35
+ outboxPhase?: `deletion-pending` | `rejection-pending`;
34
36
  lastError?: SerializedError;
35
37
  metadata?: Record<string, any>;
36
38
  spanContext?: SerializedSpanContext;
@@ -38,7 +40,7 @@ export interface OfflineTransaction {
38
40
  }
39
41
  export interface SerializedOfflineTransaction {
40
42
  /** Absent for the original Date-marker format. */
41
- valueEncoding?: 2;
43
+ valueEncoding?: 2 | 3;
42
44
  id: string;
43
45
  mutationFnName: string;
44
46
  mutations: Array<SerializedMutation>;
@@ -47,6 +49,8 @@ export interface SerializedOfflineTransaction {
47
49
  createdAt: string;
48
50
  retryCount: number;
49
51
  nextAttemptAt: number;
52
+ /** Absent in older records and while mutationFn may still need to run. */
53
+ outboxPhase?: `deletion-pending` | `rejection-pending`;
50
54
  lastError?: SerializedError;
51
55
  metadata?: Record<string, any>;
52
56
  spanContext?: SerializedSpanContext;
@@ -10,6 +10,7 @@ import { WebOnlineDetector } from "./connectivity/OnlineDetector.js";
10
10
  import { OfflineTransaction } from "./api/OfflineTransaction.js";
11
11
  import { createOfflineAction } from "./api/OfflineAction.js";
12
12
  import { withSpan, withNestedSpan } from "./telemetry/tracer.js";
13
+ import { NonRetriableError } from "./types.js";
13
14
  let OfflineExecutor$1 = class OfflineExecutor {
14
15
  constructor(config) {
15
16
  this.isLeaderState = false;
@@ -35,6 +36,8 @@ let OfflineExecutor$1 = class OfflineExecutor {
35
36
  this.initResolve = resolve;
36
37
  this.initReject = reject;
37
38
  });
39
+ void this.initPromise.catch(() => {
40
+ });
38
41
  this.initialize();
39
42
  }
40
43
  /**
@@ -300,11 +303,11 @@ let OfflineExecutor$1 = class OfflineExecutor {
300
303
  },
301
304
  async (span) => {
302
305
  if (!this.isOfflineEnabled || !this.outbox || !this.executor) {
303
- span.setAttribute(`result`, `skipped_not_leader`);
304
- this.resolveTransaction(transaction.id, void 0);
305
- return;
306
+ span.setAttribute(`result`, `rejected_not_admitted`);
307
+ throw new NonRetriableError(`Offline transaction was not admitted`);
306
308
  }
307
309
  try {
310
+ this.executor.assertHealthy();
308
311
  await this.outbox.add(transaction);
309
312
  this.executor.execute(transaction).catch((error) => {
310
313
  console.warn(`Failed to execute transactions:`, error);