@tanstack/offline-transactions 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -14
- package/dist/cjs/OfflineExecutor.cjs.map +1 -1
- package/dist/cjs/api/OfflineAction.cjs.map +1 -1
- package/dist/cjs/api/OfflineTransaction.cjs.map +1 -1
- package/dist/cjs/connectivity/OnlineDetector.cjs.map +1 -1
- package/dist/cjs/coordination/BroadcastChannelLeader.cjs.map +1 -1
- package/dist/cjs/coordination/LeaderElection.cjs.map +1 -1
- package/dist/cjs/coordination/WebLocksLeader.cjs.map +1 -1
- package/dist/cjs/executor/KeyScheduler.cjs.map +1 -1
- package/dist/cjs/executor/TransactionExecutor.cjs.map +1 -1
- package/dist/cjs/outbox/OutboxManager.cjs.map +1 -1
- package/dist/cjs/outbox/TransactionSerializer.cjs.map +1 -1
- package/dist/cjs/retry/RetryPolicy.cjs.map +1 -1
- package/dist/cjs/storage/IndexedDBAdapter.cjs.map +1 -1
- package/dist/cjs/storage/LocalStorageAdapter.cjs.map +1 -1
- package/dist/cjs/storage/StorageAdapter.cjs.map +1 -1
- package/dist/cjs/telemetry/tracer.cjs.map +1 -1
- package/dist/cjs/types.cjs.map +1 -1
- package/dist/esm/OfflineExecutor.js.map +1 -1
- package/dist/esm/api/OfflineAction.js.map +1 -1
- package/dist/esm/api/OfflineTransaction.js.map +1 -1
- package/dist/esm/connectivity/OnlineDetector.js.map +1 -1
- package/dist/esm/coordination/BroadcastChannelLeader.js.map +1 -1
- package/dist/esm/coordination/LeaderElection.js.map +1 -1
- package/dist/esm/coordination/WebLocksLeader.js.map +1 -1
- package/dist/esm/executor/KeyScheduler.js.map +1 -1
- package/dist/esm/executor/TransactionExecutor.js.map +1 -1
- package/dist/esm/outbox/OutboxManager.js.map +1 -1
- package/dist/esm/outbox/TransactionSerializer.js.map +1 -1
- package/dist/esm/retry/RetryPolicy.js.map +1 -1
- package/dist/esm/storage/IndexedDBAdapter.js.map +1 -1
- package/dist/esm/storage/LocalStorageAdapter.js.map +1 -1
- package/dist/esm/storage/StorageAdapter.js.map +1 -1
- package/dist/esm/telemetry/tracer.js.map +1 -1
- package/dist/esm/types.js.map +1 -1
- package/package.json +10 -13
- package/src/OfflineExecutor.ts +26 -26
- package/src/api/OfflineAction.ts +6 -6
- package/src/api/OfflineTransaction.ts +6 -6
- package/src/connectivity/OnlineDetector.ts +2 -2
- package/src/coordination/BroadcastChannelLeader.ts +1 -1
- package/src/coordination/LeaderElection.ts +1 -1
- package/src/coordination/WebLocksLeader.ts +3 -3
- package/src/executor/KeyScheduler.ts +12 -12
- package/src/executor/TransactionExecutor.ts +22 -22
- package/src/index.ts +16 -16
- package/src/outbox/OutboxManager.ts +17 -17
- package/src/outbox/TransactionSerializer.ts +7 -7
- package/src/retry/NonRetriableError.ts +1 -1
- package/src/retry/RetryPolicy.ts +3 -3
- package/src/storage/IndexedDBAdapter.ts +3 -3
- package/src/storage/LocalStorageAdapter.ts +3 -3
- package/src/storage/StorageAdapter.ts +1 -1
- package/src/telemetry/tracer.ts +3 -3
- package/src/types.ts +3 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { withSpan } from
|
|
2
|
-
import { TransactionSerializer } from
|
|
3
|
-
import type { OfflineTransaction, StorageAdapter } from
|
|
4
|
-
import type { Collection } from
|
|
1
|
+
import { withSpan } from '../telemetry/tracer'
|
|
2
|
+
import { TransactionSerializer } from './TransactionSerializer'
|
|
3
|
+
import type { OfflineTransaction, StorageAdapter } from '../types'
|
|
4
|
+
import type { Collection } from '@tanstack/db'
|
|
5
5
|
|
|
6
6
|
export class OutboxManager {
|
|
7
7
|
private storage: StorageAdapter
|
|
@@ -11,7 +11,7 @@ export class OutboxManager {
|
|
|
11
11
|
constructor(
|
|
12
12
|
storage: StorageAdapter,
|
|
13
13
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
-
collections: Record<string, Collection<any, any, any, any, any
|
|
14
|
+
collections: Record<string, Collection<any, any, any, any, any>>,
|
|
15
15
|
) {
|
|
16
16
|
this.storage = storage
|
|
17
17
|
this.serializer = new TransactionSerializer(collections)
|
|
@@ -25,20 +25,20 @@ export class OutboxManager {
|
|
|
25
25
|
return withSpan(
|
|
26
26
|
`outbox.add`,
|
|
27
27
|
{
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
'transaction.id': transaction.id,
|
|
29
|
+
'transaction.mutationFnName': transaction.mutationFnName,
|
|
30
|
+
'transaction.keyCount': transaction.keys.length,
|
|
31
31
|
},
|
|
32
32
|
async () => {
|
|
33
33
|
const key = this.getStorageKey(transaction.id)
|
|
34
34
|
const serialized = this.serializer.serialize(transaction)
|
|
35
35
|
await this.storage.set(key, serialized)
|
|
36
|
-
}
|
|
36
|
+
},
|
|
37
37
|
)
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
async get(id: string): Promise<OfflineTransaction | null> {
|
|
41
|
-
return withSpan(`outbox.get`, {
|
|
41
|
+
return withSpan(`outbox.get`, { 'transaction.id': id }, async (span) => {
|
|
42
42
|
const key = this.getStorageKey(id)
|
|
43
43
|
const data = await this.storage.get(key)
|
|
44
44
|
|
|
@@ -63,7 +63,7 @@ export class OutboxManager {
|
|
|
63
63
|
return withSpan(`outbox.getAll`, {}, async (span) => {
|
|
64
64
|
const keys = await this.storage.keys()
|
|
65
65
|
const transactionKeys = keys.filter((key) =>
|
|
66
|
-
key.startsWith(this.keyPrefix)
|
|
66
|
+
key.startsWith(this.keyPrefix),
|
|
67
67
|
)
|
|
68
68
|
|
|
69
69
|
span.setAttribute(`transactionCount`, transactionKeys.length)
|
|
@@ -79,14 +79,14 @@ export class OutboxManager {
|
|
|
79
79
|
} catch (error) {
|
|
80
80
|
console.warn(
|
|
81
81
|
`Failed to deserialize transaction from key ${key}:`,
|
|
82
|
-
error
|
|
82
|
+
error,
|
|
83
83
|
)
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
return transactions.sort(
|
|
89
|
-
(a, b) => a.createdAt.getTime() - b.createdAt.getTime()
|
|
89
|
+
(a, b) => a.createdAt.getTime() - b.createdAt.getTime(),
|
|
90
90
|
)
|
|
91
91
|
})
|
|
92
92
|
}
|
|
@@ -96,15 +96,15 @@ export class OutboxManager {
|
|
|
96
96
|
const keySet = new Set(keys)
|
|
97
97
|
|
|
98
98
|
return allTransactions.filter((transaction) =>
|
|
99
|
-
transaction.keys.some((key) => keySet.has(key))
|
|
99
|
+
transaction.keys.some((key) => keySet.has(key)),
|
|
100
100
|
)
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
async update(
|
|
104
104
|
id: string,
|
|
105
|
-
updates: Partial<OfflineTransaction
|
|
105
|
+
updates: Partial<OfflineTransaction>,
|
|
106
106
|
): Promise<void> {
|
|
107
|
-
return withSpan(`outbox.update`, {
|
|
107
|
+
return withSpan(`outbox.update`, { 'transaction.id': id }, async () => {
|
|
108
108
|
const existing = await this.get(id)
|
|
109
109
|
if (!existing) {
|
|
110
110
|
throw new Error(`Transaction ${id} not found`)
|
|
@@ -116,7 +116,7 @@ export class OutboxManager {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
async remove(id: string): Promise<void> {
|
|
119
|
-
return withSpan(`outbox.remove`, {
|
|
119
|
+
return withSpan(`outbox.remove`, { 'transaction.id': id }, async () => {
|
|
120
120
|
const key = this.getStorageKey(id)
|
|
121
121
|
await this.storage.delete(key)
|
|
122
122
|
})
|
|
@@ -3,8 +3,8 @@ import type {
|
|
|
3
3
|
SerializedError,
|
|
4
4
|
SerializedMutation,
|
|
5
5
|
SerializedOfflineTransaction,
|
|
6
|
-
} from
|
|
7
|
-
import type { Collection, PendingMutation } from
|
|
6
|
+
} from '../types'
|
|
7
|
+
import type { Collection, PendingMutation } from '@tanstack/db'
|
|
8
8
|
|
|
9
9
|
export class TransactionSerializer {
|
|
10
10
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -13,7 +13,7 @@ export class TransactionSerializer {
|
|
|
13
13
|
|
|
14
14
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
15
|
constructor(
|
|
16
|
-
collections: Record<string, Collection<any, any, any, any, any
|
|
16
|
+
collections: Record<string, Collection<any, any, any, any, any>>,
|
|
17
17
|
) {
|
|
18
18
|
this.collections = collections
|
|
19
19
|
// Create reverse lookup from collection.id to registry key
|
|
@@ -28,7 +28,7 @@ export class TransactionSerializer {
|
|
|
28
28
|
...transaction,
|
|
29
29
|
createdAt: transaction.createdAt,
|
|
30
30
|
mutations: transaction.mutations.map((mutation) =>
|
|
31
|
-
this.serializeMutation(mutation)
|
|
31
|
+
this.serializeMutation(mutation),
|
|
32
32
|
),
|
|
33
33
|
}
|
|
34
34
|
// Convert the whole object to JSON, handling dates
|
|
@@ -52,13 +52,13 @@ export class TransactionSerializer {
|
|
|
52
52
|
return new Date(value)
|
|
53
53
|
}
|
|
54
54
|
return value
|
|
55
|
-
}
|
|
55
|
+
},
|
|
56
56
|
)
|
|
57
57
|
|
|
58
58
|
return {
|
|
59
59
|
...parsed,
|
|
60
60
|
mutations: parsed.mutations.map((mutationData) =>
|
|
61
|
-
this.deserializeMutation(mutationData)
|
|
61
|
+
this.deserializeMutation(mutationData),
|
|
62
62
|
),
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -67,7 +67,7 @@ export class TransactionSerializer {
|
|
|
67
67
|
const registryKey = this.collectionIdToKey.get(mutation.collection.id)
|
|
68
68
|
if (!registryKey) {
|
|
69
69
|
throw new Error(
|
|
70
|
-
`Collection with id ${mutation.collection.id} not found in registry
|
|
70
|
+
`Collection with id ${mutation.collection.id} not found in registry`,
|
|
71
71
|
)
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { NonRetriableError } from
|
|
1
|
+
export { NonRetriableError } from '../types'
|
package/src/retry/RetryPolicy.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { NonRetriableError } from
|
|
2
|
-
import { BackoffCalculator } from
|
|
3
|
-
import type { RetryPolicy } from
|
|
1
|
+
import { NonRetriableError } from '../types'
|
|
2
|
+
import { BackoffCalculator } from './BackoffCalculator'
|
|
3
|
+
import type { RetryPolicy } from '../types'
|
|
4
4
|
|
|
5
5
|
export class DefaultRetryPolicy implements RetryPolicy {
|
|
6
6
|
private backoffCalculator: BackoffCalculator
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseStorageAdapter } from
|
|
1
|
+
import { BaseStorageAdapter } from './StorageAdapter'
|
|
2
2
|
|
|
3
3
|
export class IndexedDBAdapter extends BaseStorageAdapter {
|
|
4
4
|
private dbName: string
|
|
@@ -82,7 +82,7 @@ export class IndexedDBAdapter extends BaseStorageAdapter {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
private async getStore(
|
|
85
|
-
mode: IDBTransactionMode = `readonly
|
|
85
|
+
mode: IDBTransactionMode = `readonly`,
|
|
86
86
|
): Promise<IDBObjectStore> {
|
|
87
87
|
const db = await this.openDB()
|
|
88
88
|
const transaction = db.transaction([this.storeName], mode)
|
|
@@ -117,7 +117,7 @@ export class IndexedDBAdapter extends BaseStorageAdapter {
|
|
|
117
117
|
error.name === `QuotaExceededError`
|
|
118
118
|
) {
|
|
119
119
|
throw new Error(
|
|
120
|
-
`Storage quota exceeded. Consider clearing old transactions
|
|
120
|
+
`Storage quota exceeded. Consider clearing old transactions.`,
|
|
121
121
|
)
|
|
122
122
|
}
|
|
123
123
|
throw error
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseStorageAdapter } from
|
|
1
|
+
import { BaseStorageAdapter } from './StorageAdapter'
|
|
2
2
|
|
|
3
3
|
export class LocalStorageAdapter extends BaseStorageAdapter {
|
|
4
4
|
private prefix: string
|
|
@@ -70,8 +70,8 @@ export class LocalStorageAdapter extends BaseStorageAdapter {
|
|
|
70
70
|
) {
|
|
71
71
|
return Promise.reject(
|
|
72
72
|
new Error(
|
|
73
|
-
`Storage quota exceeded. Consider clearing old transactions
|
|
74
|
-
)
|
|
73
|
+
`Storage quota exceeded. Consider clearing old transactions.`,
|
|
74
|
+
),
|
|
75
75
|
)
|
|
76
76
|
}
|
|
77
77
|
return Promise.reject(error)
|
package/src/telemetry/tracer.ts
CHANGED
|
@@ -26,7 +26,7 @@ export async function withSpan<T>(
|
|
|
26
26
|
name: string,
|
|
27
27
|
attrs: SpanAttrs,
|
|
28
28
|
fn: (span: any) => Promise<T>,
|
|
29
|
-
_options?: WithSpanOptions
|
|
29
|
+
_options?: WithSpanOptions,
|
|
30
30
|
): Promise<T> {
|
|
31
31
|
return await fn(noopSpan)
|
|
32
32
|
}
|
|
@@ -39,7 +39,7 @@ export async function withNestedSpan<T>(
|
|
|
39
39
|
name: string,
|
|
40
40
|
attrs: SpanAttrs,
|
|
41
41
|
fn: (span: any) => Promise<T>,
|
|
42
|
-
_options?: WithSpanOptions
|
|
42
|
+
_options?: WithSpanOptions,
|
|
43
43
|
): Promise<T> {
|
|
44
44
|
return await fn(noopSpan)
|
|
45
45
|
}
|
|
@@ -52,7 +52,7 @@ export function withSyncSpan<T>(
|
|
|
52
52
|
name: string,
|
|
53
53
|
attrs: SpanAttrs,
|
|
54
54
|
fn: (span: any) => T,
|
|
55
|
-
_options?: WithSpanOptions
|
|
55
|
+
_options?: WithSpanOptions,
|
|
56
56
|
): T {
|
|
57
57
|
return fn(noopSpan)
|
|
58
58
|
}
|
package/src/types.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type {
|
|
|
2
2
|
Collection,
|
|
3
3
|
MutationFnParams,
|
|
4
4
|
PendingMutation,
|
|
5
|
-
} from
|
|
5
|
+
} from '@tanstack/db'
|
|
6
6
|
|
|
7
7
|
// Extended mutation function that includes idempotency key
|
|
8
8
|
export type OfflineMutationFnParams<
|
|
@@ -12,7 +12,7 @@ export type OfflineMutationFnParams<
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export type OfflineMutationFn<T extends object = Record<string, unknown>> = (
|
|
15
|
-
params: OfflineMutationFnParams<T
|
|
15
|
+
params: OfflineMutationFnParams<T>,
|
|
16
16
|
) => Promise<any>
|
|
17
17
|
|
|
18
18
|
// Simplified mutation structure for serialization
|
|
@@ -95,7 +95,7 @@ export interface OfflineConfig {
|
|
|
95
95
|
maxConcurrency?: number
|
|
96
96
|
jitter?: boolean
|
|
97
97
|
beforeRetry?: (
|
|
98
|
-
transactions: Array<OfflineTransaction
|
|
98
|
+
transactions: Array<OfflineTransaction>,
|
|
99
99
|
) => Array<OfflineTransaction>
|
|
100
100
|
onUnknownMutationFn?: (name: string, tx: OfflineTransaction) => void
|
|
101
101
|
onLeadershipChange?: (isLeader: boolean) => void
|