@tanstack/offline-transactions 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/README.md +219 -0
  2. package/dist/cjs/OfflineExecutor.cjs +266 -0
  3. package/dist/cjs/OfflineExecutor.cjs.map +1 -0
  4. package/dist/cjs/OfflineExecutor.d.cts +39 -0
  5. package/dist/cjs/api/OfflineAction.cjs +47 -0
  6. package/dist/cjs/api/OfflineAction.cjs.map +1 -0
  7. package/dist/cjs/api/OfflineAction.d.cts +3 -0
  8. package/dist/cjs/api/OfflineTransaction.cjs +96 -0
  9. package/dist/cjs/api/OfflineTransaction.cjs.map +1 -0
  10. package/dist/cjs/api/OfflineTransaction.d.cts +18 -0
  11. package/dist/cjs/connectivity/OnlineDetector.cjs +73 -0
  12. package/dist/cjs/connectivity/OnlineDetector.cjs.map +1 -0
  13. package/dist/cjs/connectivity/OnlineDetector.d.cts +15 -0
  14. package/dist/cjs/coordination/BroadcastChannelLeader.cjs +146 -0
  15. package/dist/cjs/coordination/BroadcastChannelLeader.cjs.map +1 -0
  16. package/dist/cjs/coordination/BroadcastChannelLeader.d.cts +26 -0
  17. package/dist/cjs/coordination/LeaderElection.cjs +31 -0
  18. package/dist/cjs/coordination/LeaderElection.cjs.map +1 -0
  19. package/dist/cjs/coordination/LeaderElection.d.cts +10 -0
  20. package/dist/cjs/coordination/WebLocksLeader.cjs +71 -0
  21. package/dist/cjs/coordination/WebLocksLeader.cjs.map +1 -0
  22. package/dist/cjs/coordination/WebLocksLeader.d.cts +10 -0
  23. package/dist/cjs/executor/KeyScheduler.cjs +106 -0
  24. package/dist/cjs/executor/KeyScheduler.cjs.map +1 -0
  25. package/dist/cjs/executor/KeyScheduler.d.cts +18 -0
  26. package/dist/cjs/executor/TransactionExecutor.cjs +236 -0
  27. package/dist/cjs/executor/TransactionExecutor.cjs.map +1 -0
  28. package/dist/cjs/executor/TransactionExecutor.d.cts +28 -0
  29. package/dist/cjs/index.cjs +34 -0
  30. package/dist/cjs/index.cjs.map +1 -0
  31. package/dist/cjs/index.d.cts +16 -0
  32. package/dist/cjs/outbox/OutboxManager.cjs +114 -0
  33. package/dist/cjs/outbox/OutboxManager.cjs.map +1 -0
  34. package/dist/cjs/outbox/OutboxManager.d.cts +18 -0
  35. package/dist/cjs/outbox/TransactionSerializer.cjs +135 -0
  36. package/dist/cjs/outbox/TransactionSerializer.cjs.map +1 -0
  37. package/dist/cjs/outbox/TransactionSerializer.d.cts +15 -0
  38. package/dist/cjs/retry/BackoffCalculator.cjs +14 -0
  39. package/dist/cjs/retry/BackoffCalculator.cjs.map +1 -0
  40. package/dist/cjs/retry/BackoffCalculator.d.cts +5 -0
  41. package/dist/cjs/retry/NonRetriableError.d.cts +1 -0
  42. package/dist/cjs/retry/RetryPolicy.cjs +33 -0
  43. package/dist/cjs/retry/RetryPolicy.cjs.map +1 -0
  44. package/dist/cjs/retry/RetryPolicy.d.cts +8 -0
  45. package/dist/cjs/storage/IndexedDBAdapter.cjs +104 -0
  46. package/dist/cjs/storage/IndexedDBAdapter.cjs.map +1 -0
  47. package/dist/cjs/storage/IndexedDBAdapter.d.cts +14 -0
  48. package/dist/cjs/storage/LocalStorageAdapter.cjs +71 -0
  49. package/dist/cjs/storage/LocalStorageAdapter.cjs.map +1 -0
  50. package/dist/cjs/storage/LocalStorageAdapter.d.cts +11 -0
  51. package/dist/cjs/storage/StorageAdapter.cjs +6 -0
  52. package/dist/cjs/storage/StorageAdapter.cjs.map +1 -0
  53. package/dist/cjs/storage/StorageAdapter.d.cts +9 -0
  54. package/dist/cjs/telemetry/tracer.cjs +91 -0
  55. package/dist/cjs/telemetry/tracer.cjs.map +1 -0
  56. package/dist/cjs/telemetry/tracer.d.cts +29 -0
  57. package/dist/cjs/types.cjs +10 -0
  58. package/dist/cjs/types.cjs.map +1 -0
  59. package/dist/cjs/types.d.cts +101 -0
  60. package/dist/esm/OfflineExecutor.d.ts +39 -0
  61. package/dist/esm/OfflineExecutor.js +266 -0
  62. package/dist/esm/OfflineExecutor.js.map +1 -0
  63. package/dist/esm/api/OfflineAction.d.ts +3 -0
  64. package/dist/esm/api/OfflineAction.js +47 -0
  65. package/dist/esm/api/OfflineAction.js.map +1 -0
  66. package/dist/esm/api/OfflineTransaction.d.ts +18 -0
  67. package/dist/esm/api/OfflineTransaction.js +96 -0
  68. package/dist/esm/api/OfflineTransaction.js.map +1 -0
  69. package/dist/esm/connectivity/OnlineDetector.d.ts +15 -0
  70. package/dist/esm/connectivity/OnlineDetector.js +73 -0
  71. package/dist/esm/connectivity/OnlineDetector.js.map +1 -0
  72. package/dist/esm/coordination/BroadcastChannelLeader.d.ts +26 -0
  73. package/dist/esm/coordination/BroadcastChannelLeader.js +146 -0
  74. package/dist/esm/coordination/BroadcastChannelLeader.js.map +1 -0
  75. package/dist/esm/coordination/LeaderElection.d.ts +10 -0
  76. package/dist/esm/coordination/LeaderElection.js +31 -0
  77. package/dist/esm/coordination/LeaderElection.js.map +1 -0
  78. package/dist/esm/coordination/WebLocksLeader.d.ts +10 -0
  79. package/dist/esm/coordination/WebLocksLeader.js +71 -0
  80. package/dist/esm/coordination/WebLocksLeader.js.map +1 -0
  81. package/dist/esm/executor/KeyScheduler.d.ts +18 -0
  82. package/dist/esm/executor/KeyScheduler.js +106 -0
  83. package/dist/esm/executor/KeyScheduler.js.map +1 -0
  84. package/dist/esm/executor/TransactionExecutor.d.ts +28 -0
  85. package/dist/esm/executor/TransactionExecutor.js +236 -0
  86. package/dist/esm/executor/TransactionExecutor.js.map +1 -0
  87. package/dist/esm/index.d.ts +16 -0
  88. package/dist/esm/index.js +34 -0
  89. package/dist/esm/index.js.map +1 -0
  90. package/dist/esm/outbox/OutboxManager.d.ts +18 -0
  91. package/dist/esm/outbox/OutboxManager.js +114 -0
  92. package/dist/esm/outbox/OutboxManager.js.map +1 -0
  93. package/dist/esm/outbox/TransactionSerializer.d.ts +15 -0
  94. package/dist/esm/outbox/TransactionSerializer.js +135 -0
  95. package/dist/esm/outbox/TransactionSerializer.js.map +1 -0
  96. package/dist/esm/retry/BackoffCalculator.d.ts +5 -0
  97. package/dist/esm/retry/BackoffCalculator.js +14 -0
  98. package/dist/esm/retry/BackoffCalculator.js.map +1 -0
  99. package/dist/esm/retry/NonRetriableError.d.ts +1 -0
  100. package/dist/esm/retry/RetryPolicy.d.ts +8 -0
  101. package/dist/esm/retry/RetryPolicy.js +33 -0
  102. package/dist/esm/retry/RetryPolicy.js.map +1 -0
  103. package/dist/esm/storage/IndexedDBAdapter.d.ts +14 -0
  104. package/dist/esm/storage/IndexedDBAdapter.js +104 -0
  105. package/dist/esm/storage/IndexedDBAdapter.js.map +1 -0
  106. package/dist/esm/storage/LocalStorageAdapter.d.ts +11 -0
  107. package/dist/esm/storage/LocalStorageAdapter.js +71 -0
  108. package/dist/esm/storage/LocalStorageAdapter.js.map +1 -0
  109. package/dist/esm/storage/StorageAdapter.d.ts +9 -0
  110. package/dist/esm/storage/StorageAdapter.js +6 -0
  111. package/dist/esm/storage/StorageAdapter.js.map +1 -0
  112. package/dist/esm/telemetry/tracer.d.ts +29 -0
  113. package/dist/esm/telemetry/tracer.js +91 -0
  114. package/dist/esm/telemetry/tracer.js.map +1 -0
  115. package/dist/esm/types.d.ts +101 -0
  116. package/dist/esm/types.js +10 -0
  117. package/dist/esm/types.js.map +1 -0
  118. package/package.json +66 -0
  119. package/src/OfflineExecutor.ts +360 -0
  120. package/src/api/OfflineAction.ts +68 -0
  121. package/src/api/OfflineTransaction.ts +134 -0
  122. package/src/connectivity/OnlineDetector.ts +87 -0
  123. package/src/coordination/BroadcastChannelLeader.ts +181 -0
  124. package/src/coordination/LeaderElection.ts +35 -0
  125. package/src/coordination/WebLocksLeader.ts +82 -0
  126. package/src/executor/KeyScheduler.ts +123 -0
  127. package/src/executor/TransactionExecutor.ts +330 -0
  128. package/src/index.ts +47 -0
  129. package/src/outbox/OutboxManager.ts +141 -0
  130. package/src/outbox/TransactionSerializer.ts +163 -0
  131. package/src/retry/BackoffCalculator.ts +13 -0
  132. package/src/retry/NonRetriableError.ts +1 -0
  133. package/src/retry/RetryPolicy.ts +41 -0
  134. package/src/storage/IndexedDBAdapter.ts +119 -0
  135. package/src/storage/LocalStorageAdapter.ts +79 -0
  136. package/src/storage/StorageAdapter.ts +11 -0
  137. package/src/telemetry/tracer.ts +156 -0
  138. package/src/types.ts +133 -0
@@ -0,0 +1 @@
1
+ export { NonRetriableError } from '../types.js';
@@ -0,0 +1,8 @@
1
+ import { RetryPolicy } from '../types.js';
2
+ export declare class DefaultRetryPolicy implements RetryPolicy {
3
+ private backoffCalculator;
4
+ private maxRetries;
5
+ constructor(maxRetries?: number, jitter?: boolean);
6
+ calculateDelay(retryCount: number): number;
7
+ shouldRetry(error: Error, retryCount: number): boolean;
8
+ }
@@ -0,0 +1,33 @@
1
+ import { NonRetriableError } from "../types.js";
2
+ import { BackoffCalculator } from "./BackoffCalculator.js";
3
+ class DefaultRetryPolicy {
4
+ constructor(maxRetries = 10, jitter = true) {
5
+ this.backoffCalculator = new BackoffCalculator(jitter);
6
+ this.maxRetries = maxRetries;
7
+ }
8
+ calculateDelay(retryCount) {
9
+ return this.backoffCalculator.calculate(retryCount);
10
+ }
11
+ shouldRetry(error, retryCount) {
12
+ if (retryCount >= this.maxRetries) {
13
+ return false;
14
+ }
15
+ if (error instanceof NonRetriableError) {
16
+ return false;
17
+ }
18
+ if (error.name === `AbortError`) {
19
+ return false;
20
+ }
21
+ if (error.message.includes(`401`) || error.message.includes(`403`)) {
22
+ return false;
23
+ }
24
+ if (error.message.includes(`422`) || error.message.includes(`400`)) {
25
+ return false;
26
+ }
27
+ return true;
28
+ }
29
+ }
30
+ export {
31
+ DefaultRetryPolicy
32
+ };
33
+ //# sourceMappingURL=RetryPolicy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RetryPolicy.js","sources":["../../../src/retry/RetryPolicy.ts"],"sourcesContent":["import { NonRetriableError } from \"../types\"\nimport { BackoffCalculator } from \"./BackoffCalculator\"\nimport type { RetryPolicy } from \"../types\"\n\nexport class DefaultRetryPolicy implements RetryPolicy {\n private backoffCalculator: BackoffCalculator\n private maxRetries: number\n\n constructor(maxRetries = 10, jitter = true) {\n this.backoffCalculator = new BackoffCalculator(jitter)\n this.maxRetries = maxRetries\n }\n\n calculateDelay(retryCount: number): number {\n return this.backoffCalculator.calculate(retryCount)\n }\n\n shouldRetry(error: Error, retryCount: number): boolean {\n if (retryCount >= this.maxRetries) {\n return false\n }\n\n if (error instanceof NonRetriableError) {\n return false\n }\n\n if (error.name === `AbortError`) {\n return false\n }\n\n if (error.message.includes(`401`) || error.message.includes(`403`)) {\n return false\n }\n\n if (error.message.includes(`422`) || error.message.includes(`400`)) {\n return false\n }\n\n return true\n }\n}\n"],"names":[],"mappings":";;AAIO,MAAM,mBAA0C;AAAA,EAIrD,YAAY,aAAa,IAAI,SAAS,MAAM;AAC1C,SAAK,oBAAoB,IAAI,kBAAkB,MAAM;AACrD,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,eAAe,YAA4B;AACzC,WAAO,KAAK,kBAAkB,UAAU,UAAU;AAAA,EACpD;AAAA,EAEA,YAAY,OAAc,YAA6B;AACrD,QAAI,cAAc,KAAK,YAAY;AACjC,aAAO;AAAA,IACT;AAEA,QAAI,iBAAiB,mBAAmB;AACtC,aAAO;AAAA,IACT;AAEA,QAAI,MAAM,SAAS,cAAc;AAC/B,aAAO;AAAA,IACT;AAEA,QAAI,MAAM,QAAQ,SAAS,KAAK,KAAK,MAAM,QAAQ,SAAS,KAAK,GAAG;AAClE,aAAO;AAAA,IACT;AAEA,QAAI,MAAM,QAAQ,SAAS,KAAK,KAAK,MAAM,QAAQ,SAAS,KAAK,GAAG;AAClE,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AACF;"}
@@ -0,0 +1,14 @@
1
+ import { BaseStorageAdapter } from './StorageAdapter.js';
2
+ export declare class IndexedDBAdapter extends BaseStorageAdapter {
3
+ private dbName;
4
+ private storeName;
5
+ private db;
6
+ constructor(dbName?: string, storeName?: string);
7
+ private openDB;
8
+ private getStore;
9
+ get(key: string): Promise<string | null>;
10
+ set(key: string, value: string): Promise<void>;
11
+ delete(key: string): Promise<void>;
12
+ keys(): Promise<Array<string>>;
13
+ clear(): Promise<void>;
14
+ }
@@ -0,0 +1,104 @@
1
+ import { BaseStorageAdapter } from "./StorageAdapter.js";
2
+ class IndexedDBAdapter extends BaseStorageAdapter {
3
+ constructor(dbName = `offline-transactions`, storeName = `transactions`) {
4
+ super();
5
+ this.db = null;
6
+ this.dbName = dbName;
7
+ this.storeName = storeName;
8
+ }
9
+ async openDB() {
10
+ if (this.db) {
11
+ return this.db;
12
+ }
13
+ return new Promise((resolve, reject) => {
14
+ const request = indexedDB.open(this.dbName, 1);
15
+ request.onerror = () => reject(request.error);
16
+ request.onsuccess = () => {
17
+ this.db = request.result;
18
+ resolve(this.db);
19
+ };
20
+ request.onupgradeneeded = (event) => {
21
+ const db = event.target.result;
22
+ if (!db.objectStoreNames.contains(this.storeName)) {
23
+ db.createObjectStore(this.storeName);
24
+ }
25
+ };
26
+ });
27
+ }
28
+ async getStore(mode = `readonly`) {
29
+ const db = await this.openDB();
30
+ const transaction = db.transaction([this.storeName], mode);
31
+ return transaction.objectStore(this.storeName);
32
+ }
33
+ async get(key) {
34
+ try {
35
+ const store = await this.getStore(`readonly`);
36
+ return new Promise((resolve, reject) => {
37
+ const request = store.get(key);
38
+ request.onerror = () => reject(request.error);
39
+ request.onsuccess = () => resolve(request.result ?? null);
40
+ });
41
+ } catch (error) {
42
+ console.warn(`IndexedDB get failed:`, error);
43
+ return null;
44
+ }
45
+ }
46
+ async set(key, value) {
47
+ try {
48
+ const store = await this.getStore(`readwrite`);
49
+ return new Promise((resolve, reject) => {
50
+ const request = store.put(value, key);
51
+ request.onerror = () => reject(request.error);
52
+ request.onsuccess = () => resolve();
53
+ });
54
+ } catch (error) {
55
+ if (error instanceof DOMException && error.name === `QuotaExceededError`) {
56
+ throw new Error(
57
+ `Storage quota exceeded. Consider clearing old transactions.`
58
+ );
59
+ }
60
+ throw error;
61
+ }
62
+ }
63
+ async delete(key) {
64
+ try {
65
+ const store = await this.getStore(`readwrite`);
66
+ return new Promise((resolve, reject) => {
67
+ const request = store.delete(key);
68
+ request.onerror = () => reject(request.error);
69
+ request.onsuccess = () => resolve();
70
+ });
71
+ } catch (error) {
72
+ console.warn(`IndexedDB delete failed:`, error);
73
+ }
74
+ }
75
+ async keys() {
76
+ try {
77
+ const store = await this.getStore(`readonly`);
78
+ return new Promise((resolve, reject) => {
79
+ const request = store.getAllKeys();
80
+ request.onerror = () => reject(request.error);
81
+ request.onsuccess = () => resolve(request.result);
82
+ });
83
+ } catch (error) {
84
+ console.warn(`IndexedDB keys failed:`, error);
85
+ return [];
86
+ }
87
+ }
88
+ async clear() {
89
+ try {
90
+ const store = await this.getStore(`readwrite`);
91
+ return new Promise((resolve, reject) => {
92
+ const request = store.clear();
93
+ request.onerror = () => reject(request.error);
94
+ request.onsuccess = () => resolve();
95
+ });
96
+ } catch (error) {
97
+ console.warn(`IndexedDB clear failed:`, error);
98
+ }
99
+ }
100
+ }
101
+ export {
102
+ IndexedDBAdapter
103
+ };
104
+ //# sourceMappingURL=IndexedDBAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IndexedDBAdapter.js","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 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 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 new Promise((resolve, reject) => {\n const request = store.put(value, key)\n request.onerror = () => reject(request.error)\n request.onsuccess = () => resolve()\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 new Promise((resolve, reject) => {\n const request = store.delete(key)\n request.onerror = () => reject(request.error)\n request.onsuccess = () => resolve()\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 new Promise((resolve, reject) => {\n const request = store.clear()\n request.onerror = () => reject(request.error)\n request.onsuccess = () => resolve()\n })\n } catch (error) {\n console.warn(`IndexedDB clear failed:`, error)\n }\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { BaseStorageAdapter } from './StorageAdapter.js';
2
+ export declare class LocalStorageAdapter extends BaseStorageAdapter {
3
+ private prefix;
4
+ constructor(prefix?: string);
5
+ private getKey;
6
+ get(key: string): Promise<string | null>;
7
+ set(key: string, value: string): Promise<void>;
8
+ delete(key: string): Promise<void>;
9
+ keys(): Promise<Array<string>>;
10
+ clear(): Promise<void>;
11
+ }
@@ -0,0 +1,71 @@
1
+ import { BaseStorageAdapter } from "./StorageAdapter.js";
2
+ class LocalStorageAdapter extends BaseStorageAdapter {
3
+ constructor(prefix = `offline-tx:`) {
4
+ super();
5
+ this.prefix = prefix;
6
+ }
7
+ getKey(key) {
8
+ return `${this.prefix}${key}`;
9
+ }
10
+ get(key) {
11
+ try {
12
+ return Promise.resolve(localStorage.getItem(this.getKey(key)));
13
+ } catch (error) {
14
+ console.warn(`localStorage get failed:`, error);
15
+ return Promise.resolve(null);
16
+ }
17
+ }
18
+ set(key, value) {
19
+ try {
20
+ localStorage.setItem(this.getKey(key), value);
21
+ return Promise.resolve();
22
+ } catch (error) {
23
+ if (error instanceof DOMException && error.name === `QuotaExceededError`) {
24
+ return Promise.reject(
25
+ new Error(
26
+ `Storage quota exceeded. Consider clearing old transactions.`
27
+ )
28
+ );
29
+ }
30
+ return Promise.reject(error);
31
+ }
32
+ }
33
+ delete(key) {
34
+ try {
35
+ localStorage.removeItem(this.getKey(key));
36
+ return Promise.resolve();
37
+ } catch (error) {
38
+ console.warn(`localStorage delete failed:`, error);
39
+ return Promise.resolve();
40
+ }
41
+ }
42
+ keys() {
43
+ try {
44
+ const keys = [];
45
+ for (let i = 0; i < localStorage.length; i++) {
46
+ const key = localStorage.key(i);
47
+ if (key && key.startsWith(this.prefix)) {
48
+ keys.push(key.slice(this.prefix.length));
49
+ }
50
+ }
51
+ return Promise.resolve(keys);
52
+ } catch (error) {
53
+ console.warn(`localStorage keys failed:`, error);
54
+ return Promise.resolve([]);
55
+ }
56
+ }
57
+ async clear() {
58
+ try {
59
+ const keys = await this.keys();
60
+ for (const key of keys) {
61
+ localStorage.removeItem(this.getKey(key));
62
+ }
63
+ } catch (error) {
64
+ console.warn(`localStorage clear failed:`, error);
65
+ }
66
+ }
67
+ }
68
+ export {
69
+ LocalStorageAdapter
70
+ };
71
+ //# sourceMappingURL=LocalStorageAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocalStorageAdapter.js","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 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":[],"mappings":";AAEO,MAAM,4BAA4B,mBAAmB;AAAA,EAG1D,YAAY,SAAS,eAAe;AAClC,UAAA;AACA,SAAK,SAAS;AAAA,EAChB;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;"}
@@ -0,0 +1,9 @@
1
+ import { StorageAdapter } from '../types.js';
2
+ export declare abstract class BaseStorageAdapter implements StorageAdapter {
3
+ abstract get(key: string): Promise<string | null>;
4
+ abstract set(key: string, value: string): Promise<void>;
5
+ abstract delete(key: string): Promise<void>;
6
+ abstract keys(): Promise<Array<string>>;
7
+ abstract clear(): Promise<void>;
8
+ }
9
+ export { type StorageAdapter };
@@ -0,0 +1,6 @@
1
+ class BaseStorageAdapter {
2
+ }
3
+ export {
4
+ BaseStorageAdapter
5
+ };
6
+ //# sourceMappingURL=StorageAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StorageAdapter.js","sources":["../../../src/storage/StorageAdapter.ts"],"sourcesContent":["import type { StorageAdapter } from \"../types\"\n\nexport abstract class BaseStorageAdapter implements StorageAdapter {\n abstract get(key: string): Promise<string | null>\n abstract set(key: string, value: string): Promise<void>\n abstract delete(key: string): Promise<void>\n abstract keys(): Promise<Array<string>>\n abstract clear(): Promise<void>\n}\n\nexport { type StorageAdapter }\n"],"names":[],"mappings":"AAEO,MAAe,mBAA6C;AAMnE;"}
@@ -0,0 +1,29 @@
1
+ import { Span, SpanContext } from '@opentelemetry/api';
2
+ export interface SpanAttrs {
3
+ [key: string]: string | number | boolean | undefined;
4
+ }
5
+ interface WithSpanOptions {
6
+ parentContext?: SpanContext;
7
+ }
8
+ /**
9
+ * Lightweight span wrapper with error handling.
10
+ * Uses OpenTelemetry API which is no-op when tracing is disabled.
11
+ *
12
+ * By default, creates spans at the current context level (siblings).
13
+ * Use withNestedSpan if you want parent-child relationships.
14
+ */
15
+ export declare function withSpan<T>(name: string, attrs: SpanAttrs, fn: (span: Span) => Promise<T>, options?: WithSpanOptions): Promise<T>;
16
+ /**
17
+ * Like withSpan but propagates context so child spans nest properly.
18
+ * Use this when you want operations inside fn to be child spans.
19
+ */
20
+ export declare function withNestedSpan<T>(name: string, attrs: SpanAttrs, fn: (span: Span) => Promise<T>, options?: WithSpanOptions): Promise<T>;
21
+ /**
22
+ * Creates a synchronous span for non-async operations
23
+ */
24
+ export declare function withSyncSpan<T>(name: string, attrs: SpanAttrs, fn: (span: Span) => T, options?: WithSpanOptions): T;
25
+ /**
26
+ * Get the current tracer instance
27
+ */
28
+ export declare function getTracer(): import('@opentelemetry/api').Tracer;
29
+ export {};
@@ -0,0 +1,91 @@
1
+ import { trace, SpanStatusCode, context } from "@opentelemetry/api";
2
+ const TRACER = trace.getTracer("@tanstack/offline-transactions", "0.0.1");
3
+ function getParentContext(options) {
4
+ if (options?.parentContext) {
5
+ const parentSpan = trace.wrapSpanContext(options.parentContext);
6
+ return trace.setSpan(context.active(), parentSpan);
7
+ }
8
+ return context.active();
9
+ }
10
+ async function withSpan(name, attrs, fn, options) {
11
+ const parentCtx = getParentContext(options);
12
+ const span = TRACER.startSpan(name, void 0, parentCtx);
13
+ const filteredAttrs = {};
14
+ for (const [key, value] of Object.entries(attrs)) {
15
+ if (value !== void 0) {
16
+ filteredAttrs[key] = value;
17
+ }
18
+ }
19
+ span.setAttributes(filteredAttrs);
20
+ try {
21
+ const result = await fn(span);
22
+ span.setStatus({ code: SpanStatusCode.OK });
23
+ return result;
24
+ } catch (error) {
25
+ span.setStatus({
26
+ code: SpanStatusCode.ERROR,
27
+ message: error instanceof Error ? error.message : String(error)
28
+ });
29
+ span.recordException(error);
30
+ throw error;
31
+ } finally {
32
+ span.end();
33
+ }
34
+ }
35
+ async function withNestedSpan(name, attrs, fn, options) {
36
+ const parentCtx = getParentContext(options);
37
+ const span = TRACER.startSpan(name, void 0, parentCtx);
38
+ const filteredAttrs = {};
39
+ for (const [key, value] of Object.entries(attrs)) {
40
+ if (value !== void 0) {
41
+ filteredAttrs[key] = value;
42
+ }
43
+ }
44
+ span.setAttributes(filteredAttrs);
45
+ const ctx = trace.setSpan(parentCtx, span);
46
+ try {
47
+ const result = await context.with(ctx, () => fn(span));
48
+ span.setStatus({ code: SpanStatusCode.OK });
49
+ return result;
50
+ } catch (error) {
51
+ span.setStatus({
52
+ code: SpanStatusCode.ERROR,
53
+ message: error instanceof Error ? error.message : String(error)
54
+ });
55
+ span.recordException(error);
56
+ throw error;
57
+ } finally {
58
+ span.end();
59
+ }
60
+ }
61
+ function withSyncSpan(name, attrs, fn, options) {
62
+ const parentCtx = getParentContext(options);
63
+ const span = TRACER.startSpan(name, void 0, parentCtx);
64
+ const filteredAttrs = {};
65
+ for (const [key, value] of Object.entries(attrs)) {
66
+ if (value !== void 0) {
67
+ filteredAttrs[key] = value;
68
+ }
69
+ }
70
+ span.setAttributes(filteredAttrs);
71
+ try {
72
+ const result = fn(span);
73
+ span.setStatus({ code: SpanStatusCode.OK });
74
+ return result;
75
+ } catch (error) {
76
+ span.setStatus({
77
+ code: SpanStatusCode.ERROR,
78
+ message: error instanceof Error ? error.message : String(error)
79
+ });
80
+ span.recordException(error);
81
+ throw error;
82
+ } finally {
83
+ span.end();
84
+ }
85
+ }
86
+ export {
87
+ withNestedSpan,
88
+ withSpan,
89
+ withSyncSpan
90
+ };
91
+ //# sourceMappingURL=tracer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracer.js","sources":["../../../src/telemetry/tracer.ts"],"sourcesContent":["import {\n trace,\n type Span,\n SpanStatusCode,\n context,\n type SpanContext,\n} 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":[],"mappings":";AAQA,MAAM,SAAS,MAAM,UAAU,kCAAkC,OAAO;AAUxE,SAAS,iBAAiB,SAA2B;AACnD,MAAI,SAAS,eAAe;AAC1B,UAAM,aAAa,MAAM,gBAAgB,QAAQ,aAAa;AAC9D,WAAO,MAAM,QAAQ,QAAQ,OAAA,GAAU,UAAU;AAAA,EACnD;AAEA,SAAO,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,MAAM,eAAe,IAAI;AAC1C,WAAO;AAAA,EACT,SAAS,OAAO;AACd,SAAK,UAAU;AAAA,MACb,MAAM,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,MAAM,MAAM,QAAQ,WAAW,IAAI;AAEzC,MAAI;AAEF,UAAM,SAAS,MAAM,QAAQ,KAAK,KAAK,MAAM,GAAG,IAAI,CAAC;AACrD,SAAK,UAAU,EAAE,MAAM,eAAe,IAAI;AAC1C,WAAO;AAAA,EACT,SAAS,OAAO;AACd,SAAK,UAAU;AAAA,MACb,MAAM,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,MAAM,eAAe,IAAI;AAC1C,WAAO;AAAA,EACT,SAAS,OAAO;AACd,SAAK,UAAU;AAAA,MACb,MAAM,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;"}
@@ -0,0 +1,101 @@
1
+ import { Collection, MutationFnParams, PendingMutation } from '@tanstack/db';
2
+ export type OfflineMutationFnParams<T extends object = Record<string, unknown>> = MutationFnParams<T> & {
3
+ idempotencyKey: string;
4
+ };
5
+ export type OfflineMutationFn<T extends object = Record<string, unknown>> = (params: OfflineMutationFnParams<T>) => Promise<any>;
6
+ export interface SerializedMutation {
7
+ globalKey: string;
8
+ type: string;
9
+ modified: any;
10
+ original: any;
11
+ collectionId: string;
12
+ }
13
+ export interface SerializedError {
14
+ name: string;
15
+ message: string;
16
+ stack?: string;
17
+ }
18
+ export interface SerializedSpanContext {
19
+ traceId: string;
20
+ spanId: string;
21
+ traceFlags: number;
22
+ traceState?: string;
23
+ }
24
+ export interface OfflineTransaction {
25
+ id: string;
26
+ mutationFnName: string;
27
+ mutations: Array<PendingMutation>;
28
+ keys: Array<string>;
29
+ idempotencyKey: string;
30
+ createdAt: Date;
31
+ retryCount: number;
32
+ nextAttemptAt: number;
33
+ lastError?: SerializedError;
34
+ metadata?: Record<string, any>;
35
+ spanContext?: SerializedSpanContext;
36
+ version: 1;
37
+ }
38
+ export interface SerializedOfflineTransaction {
39
+ id: string;
40
+ mutationFnName: string;
41
+ mutations: Array<SerializedMutation>;
42
+ keys: Array<string>;
43
+ idempotencyKey: string;
44
+ createdAt: Date;
45
+ retryCount: number;
46
+ nextAttemptAt: number;
47
+ lastError?: SerializedError;
48
+ metadata?: Record<string, any>;
49
+ spanContext?: SerializedSpanContext;
50
+ version: 1;
51
+ }
52
+ export interface OfflineConfig {
53
+ collections: Record<string, Collection>;
54
+ mutationFns: Record<string, OfflineMutationFn>;
55
+ storage?: StorageAdapter;
56
+ maxConcurrency?: number;
57
+ jitter?: boolean;
58
+ beforeRetry?: (transactions: Array<OfflineTransaction>) => Array<OfflineTransaction>;
59
+ onUnknownMutationFn?: (name: string, tx: OfflineTransaction) => void;
60
+ onLeadershipChange?: (isLeader: boolean) => void;
61
+ leaderElection?: LeaderElection;
62
+ otel?: {
63
+ endpoint: string;
64
+ headers?: Record<string, string>;
65
+ };
66
+ }
67
+ export interface StorageAdapter {
68
+ get: (key: string) => Promise<string | null>;
69
+ set: (key: string, value: string) => Promise<void>;
70
+ delete: (key: string) => Promise<void>;
71
+ keys: () => Promise<Array<string>>;
72
+ clear: () => Promise<void>;
73
+ }
74
+ export interface RetryPolicy {
75
+ calculateDelay: (retryCount: number) => number;
76
+ shouldRetry: (error: Error, retryCount: number) => boolean;
77
+ }
78
+ export interface LeaderElection {
79
+ requestLeadership: () => Promise<boolean>;
80
+ releaseLeadership: () => void;
81
+ isLeader: () => boolean;
82
+ onLeadershipChange: (callback: (isLeader: boolean) => void) => () => void;
83
+ }
84
+ export interface OnlineDetector {
85
+ subscribe: (callback: () => void) => () => void;
86
+ notifyOnline: () => void;
87
+ }
88
+ export interface CreateOfflineTransactionOptions {
89
+ id?: string;
90
+ mutationFnName: string;
91
+ autoCommit?: boolean;
92
+ idempotencyKey?: string;
93
+ metadata?: Record<string, any>;
94
+ }
95
+ export interface CreateOfflineActionOptions<T> {
96
+ mutationFnName: string;
97
+ onMutate: (variables: T) => void;
98
+ }
99
+ export declare class NonRetriableError extends Error {
100
+ constructor(message: string);
101
+ }
@@ -0,0 +1,10 @@
1
+ class NonRetriableError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = `NonRetriableError`;
5
+ }
6
+ }
7
+ export {
8
+ NonRetriableError
9
+ };
10
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sources":["../../src/types.ts"],"sourcesContent":["import type {\n Collection,\n MutationFnParams,\n PendingMutation,\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 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 id: string\n mutationFnName: string\n mutations: Array<SerializedMutation>\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\nexport interface OfflineConfig {\n collections: Record<string, Collection>\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 leaderElection?: LeaderElection\n otel?: {\n endpoint: string\n headers?: Record<string, string>\n }\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 OnlineDetector {\n subscribe: (callback: () => void) => () => void\n notifyOnline: () => 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":"AA+HO,MAAM,0BAA0B,MAAM;AAAA,EAC3C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;"}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@tanstack/offline-transactions",
3
+ "version": "0.0.0",
4
+ "description": "Offline-first transaction capabilities for TanStack DB",
5
+ "author": "TanStack",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/TanStack/db.git",
10
+ "directory": "packages/offline-transactions"
11
+ },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "keywords": [
16
+ "tanstack",
17
+ "database",
18
+ "offline",
19
+ "transactions",
20
+ "persistence",
21
+ "sync"
22
+ ],
23
+ "type": "module",
24
+ "sideEffects": false,
25
+ "exports": {
26
+ ".": {
27
+ "import": {
28
+ "types": "./dist/esm/index.d.ts",
29
+ "default": "./dist/esm/index.js"
30
+ },
31
+ "require": {
32
+ "types": "./dist/cjs/index.d.cts",
33
+ "default": "./dist/cjs/index.cjs"
34
+ }
35
+ },
36
+ "./package.json": "./package.json"
37
+ },
38
+ "main": "dist/cjs/index.cjs",
39
+ "module": "dist/esm/index.js",
40
+ "types": "dist/esm/index.d.ts",
41
+ "files": [
42
+ "dist",
43
+ "src"
44
+ ],
45
+ "scripts": {
46
+ "build": "vite build",
47
+ "dev": "vite build --watch",
48
+ "test": "vitest",
49
+ "test:watch": "vitest --watch",
50
+ "typecheck": "tsc --noEmit",
51
+ "lint": "eslint src"
52
+ },
53
+ "dependencies": {
54
+ "@opentelemetry/api": "^1.9.0",
55
+ "@tanstack/db": "workspace:*"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "^20.0.0",
59
+ "eslint": "^8.57.0",
60
+ "typescript": "^5.5.4",
61
+ "vitest": "^3.2.4"
62
+ },
63
+ "peerDependencies": {
64
+ "@tanstack/db": "workspace:*"
65
+ }
66
+ }