@abloatai/ablo 0.25.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -3
- package/CHANGELOG.md +20 -0
- package/README.md +3 -3
- package/dist/BaseSyncedStore.d.ts +72 -195
- package/dist/BaseSyncedStore.js +240 -625
- package/dist/Database.d.ts +8 -9
- package/dist/Database.js +9 -14
- package/dist/LazyReferenceCollection.js +5 -0
- package/dist/Model.js +6 -8
- package/dist/ModelRegistry.d.ts +25 -6
- package/dist/ModelRegistry.js +12 -6
- package/dist/NetworkMonitor.js +1 -1
- package/dist/ObjectPool.js +9 -3
- package/dist/SyncClient.d.ts +15 -5
- package/dist/SyncClient.js +152 -66
- package/dist/SyncEngineContext.d.ts +1 -3
- package/dist/SyncEngineContext.js +1 -2
- package/dist/agent/Agent.d.ts +21 -8
- package/dist/agent/Agent.js +26 -13
- package/dist/ai-sdk/coordination-context.js +9 -3
- package/dist/ai-sdk/wrap.d.ts +1 -1
- package/dist/ai-sdk/wrap.js +1 -1
- package/dist/auth/credentialPolicy.d.ts +10 -2
- package/dist/auth/credentialSource.d.ts +1 -13
- package/dist/auth/credentialSource.js +6 -13
- package/dist/auth/index.js +3 -3
- package/dist/cli.cjs +328 -262
- package/dist/client/Ablo.d.ts +35 -764
- package/dist/client/Ablo.js +64 -688
- package/dist/client/ApiClient.d.ts +14 -1
- package/dist/client/ApiClient.js +63 -9
- package/dist/client/auth.d.ts +8 -17
- package/dist/client/auth.js +42 -6
- package/dist/client/consoleLogger.d.ts +36 -0
- package/dist/client/consoleLogger.js +45 -0
- package/dist/client/createInternalComponents.js +1 -1
- package/dist/client/createModelProxy.js +10 -6
- package/dist/client/credentialEndpoint.d.ts +63 -0
- package/dist/client/credentialEndpoint.js +87 -0
- package/dist/client/hostedEndpoints.d.ts +24 -0
- package/dist/client/hostedEndpoints.js +24 -0
- package/dist/client/httpClient.d.ts +1 -1
- package/dist/client/identity.js +2 -1
- package/dist/client/modelRegistration.d.ts +14 -0
- package/dist/client/modelRegistration.js +321 -0
- package/dist/client/options.d.ts +396 -0
- package/dist/client/options.js +10 -0
- package/dist/client/resourceTypes.d.ts +344 -0
- package/dist/client/resourceTypes.js +13 -0
- package/dist/client/schemaConfig.d.ts +56 -0
- package/dist/client/schemaConfig.js +188 -0
- package/dist/client/sessionMint.d.ts +1 -1
- package/dist/client/validateAbloOptions.js +1 -1
- package/dist/client/wsMutationExecutor.d.ts +33 -0
- package/dist/client/wsMutationExecutor.js +77 -0
- package/dist/context.js +0 -3
- package/dist/coordination/index.d.ts +8 -1
- package/dist/coordination/index.js +24 -1
- package/dist/coordination/trace.d.ts +0 -1
- package/dist/coordination/trace.js +0 -1
- package/dist/core/DatabaseManager.d.ts +0 -1
- package/dist/core/DatabaseManager.js +26 -24
- package/dist/core/QueryProcessor.js +7 -8
- package/dist/core/QueryView.d.ts +14 -2
- package/dist/core/QueryView.js +4 -5
- package/dist/core/StoreManager.d.ts +2 -2
- package/dist/core/StoreManager.js +5 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/openIDBWithTimeout.js +15 -11
- package/dist/core/query-utils.js +1 -1
- package/dist/core/storeContract.d.ts +143 -0
- package/dist/core/storeContract.js +16 -0
- package/dist/errorCodes.d.ts +11 -2
- package/dist/errorCodes.js +155 -137
- package/dist/errors.d.ts +18 -7
- package/dist/errors.js +14 -1
- package/dist/index.js +5 -0
- package/dist/interfaces/index.d.ts +6 -20
- package/dist/keys/index.js +18 -9
- package/dist/mutators/UndoManager.js +6 -1
- package/dist/mutators/defineMutators.d.ts +1 -3
- package/dist/policy/types.d.ts +1 -1
- package/dist/query/client.d.ts +11 -0
- package/dist/query/client.js +81 -52
- package/dist/react/AbloProvider.d.ts +6 -10
- package/dist/react/AbloProvider.js +22 -15
- package/dist/react/context.d.ts +2 -118
- package/dist/react/useAblo.js +1 -1
- package/dist/react/useErrorListener.js +1 -1
- package/dist/react/useMutationFailureListener.js +1 -1
- package/dist/react/useMutators.js +7 -1
- package/dist/react/useReactive.js +0 -1
- package/dist/react/useUndoScope.js +2 -2
- package/dist/schema/ddlLock.d.ts +39 -0
- package/dist/schema/ddlLock.js +51 -0
- package/dist/schema/field.js +1 -1
- package/dist/schema/index.d.ts +4 -3
- package/dist/schema/index.js +15 -5
- package/dist/schema/model.d.ts +3 -3
- package/dist/schema/model.js +2 -2
- package/dist/schema/openapi.js +5 -1
- package/dist/schema/queries.d.ts +1 -1
- package/dist/schema/queries.js +1 -0
- package/dist/schema/residency.d.ts +35 -0
- package/dist/schema/residency.js +27 -0
- package/dist/schema/roles.d.ts +25 -0
- package/dist/schema/roles.js +37 -0
- package/dist/schema/schema.d.ts +3 -3
- package/dist/schema/schema.js +11 -7
- package/dist/schema/serialize.d.ts +8 -8
- package/dist/schema/sync-delta-row.d.ts +2 -2
- package/dist/schema/sync-delta-row.js +2 -2
- package/dist/server/storage-mode.d.ts +8 -1
- package/dist/server/storage-mode.js +14 -4
- package/dist/source/adapter.d.ts +1 -1
- package/dist/source/adapters/drizzle.js +4 -3
- package/dist/source/adapters/kysely.js +4 -3
- package/dist/source/adapters/memory.js +1 -1
- package/dist/source/adapters/prisma.js +5 -4
- package/dist/source/conformance.js +10 -8
- package/dist/source/connector.js +4 -2
- package/dist/source/factory.d.ts +106 -0
- package/dist/source/factory.js +273 -0
- package/dist/source/index.d.ts +11 -461
- package/dist/source/index.js +14 -416
- package/dist/source/next.d.ts +1 -1
- package/dist/source/next.js +1 -1
- package/dist/source/pushQueue.d.ts +1 -1
- package/dist/source/pushQueue.js +6 -3
- package/dist/source/signing.d.ts +59 -0
- package/dist/source/signing.js +142 -0
- package/dist/source/types.d.ts +334 -0
- package/dist/source/types.js +44 -0
- package/dist/stores/ObjectStore.d.ts +1 -1
- package/dist/stores/ObjectStore.js +23 -23
- package/dist/stores/SyncActionStore.d.ts +1 -1
- package/dist/stores/SyncActionStore.js +70 -35
- package/dist/surface.d.ts +1 -1
- package/dist/surface.js +1 -0
- package/dist/sync/AreaOfInterestManager.d.ts +2 -2
- package/dist/sync/AreaOfInterestManager.js +1 -1
- package/dist/sync/BootstrapHelper.d.ts +1 -3
- package/dist/sync/BootstrapHelper.js +29 -8
- package/dist/sync/ConnectionManager.d.ts +8 -0
- package/dist/sync/ConnectionManager.js +19 -4
- package/dist/sync/HydrationCoordinator.d.ts +12 -0
- package/dist/sync/HydrationCoordinator.js +25 -5
- package/dist/sync/NetworkProbe.d.ts +3 -2
- package/dist/sync/NetworkProbe.js +7 -6
- package/dist/sync/SyncWebSocket.d.ts +71 -120
- package/dist/sync/SyncWebSocket.js +324 -630
- package/dist/sync/awaitClaimGrant.js +27 -19
- package/dist/sync/bootstrapApply.d.ts +60 -0
- package/dist/sync/bootstrapApply.js +65 -0
- package/dist/sync/commitFrames.d.ts +43 -0
- package/dist/sync/commitFrames.js +94 -0
- package/dist/sync/createClaimStream.js +3 -2
- package/dist/sync/createSnapshot.d.ts +1 -3
- package/dist/sync/credentialLifecycle.d.ts +175 -0
- package/dist/sync/credentialLifecycle.js +329 -0
- package/dist/sync/deltaPipeline.d.ts +111 -0
- package/dist/sync/deltaPipeline.js +262 -0
- package/dist/sync/groupChange.d.ts +111 -0
- package/dist/sync/groupChange.js +246 -0
- package/dist/sync/heartbeat.d.ts +62 -0
- package/dist/sync/heartbeat.js +91 -0
- package/dist/sync/participants.d.ts +8 -8
- package/dist/sync/syncCursor.d.ts +44 -0
- package/dist/sync/syncCursor.js +59 -0
- package/dist/sync/syncPlan.d.ts +62 -0
- package/dist/sync/syncPlan.js +51 -0
- package/dist/sync/wsFrameHandlers.d.ts +117 -0
- package/dist/sync/wsFrameHandlers.js +378 -0
- package/dist/testing/fixtures/bootstrap.d.ts +9 -11
- package/dist/testing/fixtures/deltas.d.ts +1 -1
- package/dist/testing/fixtures/models.js +1 -0
- package/dist/testing/helpers/sync-engine-harness.d.ts +0 -3
- package/dist/testing/helpers/sync-engine-harness.js +2 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +1 -1
- package/dist/testing/mocks/MockSyncContext.d.ts +1 -17
- package/dist/testing/mocks/MockSyncContext.js +1 -32
- package/dist/testing/mocks/MockSyncStore.d.ts +4 -4
- package/dist/testing/mocks/MockWebSocket.d.ts +2 -2
- package/dist/transactions/TransactionQueue.d.ts +20 -56
- package/dist/transactions/TransactionQueue.js +101 -487
- package/dist/transactions/TransactionStore.d.ts +18 -0
- package/dist/transactions/TransactionStore.js +51 -0
- package/dist/transactions/coalesceRules.d.ts +34 -0
- package/dist/transactions/coalesceRules.js +117 -0
- package/dist/transactions/commitPayload.d.ts +134 -0
- package/dist/transactions/commitPayload.js +152 -0
- package/dist/transactions/deltaConfirmation.d.ts +60 -0
- package/dist/transactions/deltaConfirmation.js +223 -0
- package/dist/transactions/optimistic.d.ts +24 -0
- package/dist/transactions/optimistic.js +45 -0
- package/dist/transactions/persistedReplay.d.ts +93 -0
- package/dist/transactions/persistedReplay.js +105 -0
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.js +0 -5
- package/dist/types/modelData.d.ts +12 -0
- package/dist/types/modelData.js +11 -0
- package/dist/types/participant.d.ts +21 -0
- package/dist/types/participant.js +12 -0
- package/dist/types/streams.d.ts +8 -14
- package/dist/utils/mobx-setup.d.ts +1 -1
- package/dist/utils/mobx-setup.js +2 -2
- package/dist/wire/errorEnvelope.d.ts +11 -2
- package/dist/wire/errorEnvelope.js +14 -2
- package/dist/wire/frames.d.ts +90 -0
- package/dist/wire/frames.js +55 -1
- package/dist/wire/index.d.ts +4 -1
- package/dist/wire/index.js +20 -1
- package/dist/wire/protocol.d.ts +45 -0
- package/dist/wire/protocol.js +45 -0
- package/dist/wire/protocolVersion.d.ts +56 -0
- package/dist/wire/protocolVersion.js +63 -0
- package/docs/api-keys.md +4 -3
- package/docs/examples/existing-python-backend.md +3 -3
- package/docs/identity.md +4 -4
- package/docs/integration-guide.md +1 -1
- package/docs/react.md +1 -1
- package/docs/sessions.md +5 -7
- package/package.json +15 -13
- package/dist/client/index.d.ts +0 -36
- package/dist/client/index.js +0 -33
- package/dist/config/index.d.ts +0 -10
- package/dist/config/index.js +0 -12
- package/dist/interfaces/headless.d.ts +0 -95
- package/dist/interfaces/headless.js +0 -41
- package/dist/query/index.d.ts +0 -6
- package/dist/query/index.js +0 -5
- package/dist/realtime/index.d.ts +0 -10
- package/dist/realtime/index.js +0 -9
- package/dist/schema/plane.d.ts +0 -23
- package/dist/schema/plane.js +0 -19
- package/dist/server/next.d.ts +0 -51
- package/dist/server/next.js +0 -47
- package/dist/sync/OfflineFlush.d.ts +0 -9
- package/dist/sync/OfflineFlush.js +0 -22
- package/dist/sync/OfflineTransactionStore.d.ts +0 -37
- package/dist/sync/OfflineTransactionStore.js +0 -263
- package/dist/transactions/index.d.ts +0 -16
- package/dist/transactions/index.js +0 -7
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory transaction index — id map plus a by-status secondary index so
|
|
3
|
+
* the queue's hot paths (`getByStatus('pending')` on every batch/coalesce
|
|
4
|
+
* decision) stay O(status bucket) instead of O(all transactions). Lifted out
|
|
5
|
+
* of `TransactionQueue.ts` as a stateful leaf with no queue coupling.
|
|
6
|
+
*/
|
|
7
|
+
import type { Transaction } from './commitPayload.js';
|
|
8
|
+
export declare class TransactionStore {
|
|
9
|
+
private transactions;
|
|
10
|
+
private byStatus;
|
|
11
|
+
add(transaction: Transaction): void;
|
|
12
|
+
get(id: string): Transaction | undefined;
|
|
13
|
+
updateStatus(id: string, newStatus: Transaction['status']): void;
|
|
14
|
+
getByStatus(status: Transaction['status']): Transaction[];
|
|
15
|
+
remove(id: string): void;
|
|
16
|
+
clear(): void;
|
|
17
|
+
getAll(): Transaction[];
|
|
18
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory transaction index — id map plus a by-status secondary index so
|
|
3
|
+
* the queue's hot paths (`getByStatus('pending')` on every batch/coalesce
|
|
4
|
+
* decision) stay O(status bucket) instead of O(all transactions). Lifted out
|
|
5
|
+
* of `TransactionQueue.ts` as a stateful leaf with no queue coupling.
|
|
6
|
+
*/
|
|
7
|
+
export class TransactionStore {
|
|
8
|
+
transactions = new Map();
|
|
9
|
+
byStatus = new Map();
|
|
10
|
+
add(transaction) {
|
|
11
|
+
this.transactions.set(transaction.id, transaction);
|
|
12
|
+
if (!this.byStatus.has(transaction.status)) {
|
|
13
|
+
this.byStatus.set(transaction.status, new Set());
|
|
14
|
+
}
|
|
15
|
+
this.byStatus.get(transaction.status).add(transaction.id);
|
|
16
|
+
}
|
|
17
|
+
get(id) {
|
|
18
|
+
return this.transactions.get(id);
|
|
19
|
+
}
|
|
20
|
+
updateStatus(id, newStatus) {
|
|
21
|
+
const tx = this.transactions.get(id);
|
|
22
|
+
if (!tx)
|
|
23
|
+
return;
|
|
24
|
+
this.byStatus.get(tx.status)?.delete(id);
|
|
25
|
+
tx.status = newStatus;
|
|
26
|
+
if (!this.byStatus.has(newStatus)) {
|
|
27
|
+
this.byStatus.set(newStatus, new Set());
|
|
28
|
+
}
|
|
29
|
+
this.byStatus.get(newStatus).add(id);
|
|
30
|
+
}
|
|
31
|
+
getByStatus(status) {
|
|
32
|
+
const ids = this.byStatus.get(status) || new Set();
|
|
33
|
+
return Array.from(ids)
|
|
34
|
+
.map((id) => this.transactions.get(id))
|
|
35
|
+
.filter(Boolean);
|
|
36
|
+
}
|
|
37
|
+
remove(id) {
|
|
38
|
+
const tx = this.transactions.get(id);
|
|
39
|
+
if (!tx)
|
|
40
|
+
return;
|
|
41
|
+
this.transactions.delete(id);
|
|
42
|
+
this.byStatus.get(tx.status)?.delete(id);
|
|
43
|
+
}
|
|
44
|
+
clear() {
|
|
45
|
+
this.transactions.clear();
|
|
46
|
+
this.byStatus.clear();
|
|
47
|
+
}
|
|
48
|
+
getAll() {
|
|
49
|
+
return Array.from(this.transactions.values());
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coalescing rules — the queue's same-entity causality guards, lifted out of
|
|
3
|
+
* `TransactionQueue.ts` as a leaf over a minimal store-shaped interface:
|
|
4
|
+
*
|
|
5
|
+
* • create-then-delete cancellation (`takeUnsentCreateForModel`): a delete
|
|
6
|
+
* of a row whose create never left the client cancels both locally;
|
|
7
|
+
* • the create barrier (`findCreateBarrierForDelete`) + deferred-delete
|
|
8
|
+
* parking (`deferDeleteUntilCreateSettles` / `release…`): once a create
|
|
9
|
+
* HAS been sent, a delete must wait for it to settle or the server could
|
|
10
|
+
* see DELETE-before-CREATE;
|
|
11
|
+
* • update-payload merging (`mergeUpdateData`): collapses rapid same-entity
|
|
12
|
+
* updates into one wire op.
|
|
13
|
+
*
|
|
14
|
+
* The queue's `enqueue`/`delete` verbs stay in the host (they orchestrate
|
|
15
|
+
* optimistic state + events); the RULES live here.
|
|
16
|
+
*/
|
|
17
|
+
import type { MutationInput, Transaction } from './commitPayload.js';
|
|
18
|
+
/** The slice of `TransactionStore` the coalescing rules read. */
|
|
19
|
+
export interface TransactionStoreLike {
|
|
20
|
+
get(id: string): Transaction | undefined;
|
|
21
|
+
getByStatus(status: Transaction['status']): Transaction[];
|
|
22
|
+
}
|
|
23
|
+
export declare const entityKey: (modelName: string, modelId: string) => string;
|
|
24
|
+
/**
|
|
25
|
+
* Find-and-remove a create for (model, id) that has never been sent —
|
|
26
|
+
* checking the microtask staging area, the execution queue, and the store's
|
|
27
|
+
* pending bucket, in that order. Splices the winner out of whichever queue
|
|
28
|
+
* held it so the caller can cancel it instead of shipping create+delete.
|
|
29
|
+
*/
|
|
30
|
+
export declare function takeUnsentCreateForModel(staged: Transaction[], queued: Transaction[], store: Pick<TransactionStoreLike, 'getByStatus'>, modelName: string, modelId: string): Transaction | undefined;
|
|
31
|
+
export declare function findCreateBarrierForDelete(store: Pick<TransactionStoreLike, 'getByStatus'>, modelName: string, modelId: string): Transaction | undefined;
|
|
32
|
+
export declare function deferDeleteUntilCreateSettles(deferredDeletesByCreate: Map<string, Transaction[]>, createTransaction: Transaction, deleteTransaction: Transaction): void;
|
|
33
|
+
export declare function releaseDeferredDeletesForCreate(deferredDeletesByCreate: Map<string, Transaction[]>, store: Pick<TransactionStoreLike, 'get'>, enqueue: (transaction: Transaction) => void, createTransaction: Transaction): void;
|
|
34
|
+
export declare function mergeUpdateData(left: MutationInput | undefined, right: MutationInput | undefined, _modelName?: string): MutationInput;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coalescing rules — the queue's same-entity causality guards, lifted out of
|
|
3
|
+
* `TransactionQueue.ts` as a leaf over a minimal store-shaped interface:
|
|
4
|
+
*
|
|
5
|
+
* • create-then-delete cancellation (`takeUnsentCreateForModel`): a delete
|
|
6
|
+
* of a row whose create never left the client cancels both locally;
|
|
7
|
+
* • the create barrier (`findCreateBarrierForDelete`) + deferred-delete
|
|
8
|
+
* parking (`deferDeleteUntilCreateSettles` / `release…`): once a create
|
|
9
|
+
* HAS been sent, a delete must wait for it to settle or the server could
|
|
10
|
+
* see DELETE-before-CREATE;
|
|
11
|
+
* • update-payload merging (`mergeUpdateData`): collapses rapid same-entity
|
|
12
|
+
* updates into one wire op.
|
|
13
|
+
*
|
|
14
|
+
* The queue's `enqueue`/`delete` verbs stay in the host (they orchestrate
|
|
15
|
+
* optimistic state + events); the RULES live here.
|
|
16
|
+
*/
|
|
17
|
+
export const entityKey = (modelName, modelId) => `${modelName}:${modelId}`;
|
|
18
|
+
const isTransactionForModel = (transaction, modelName, modelId) => transaction.modelName === modelName && transaction.modelId === modelId;
|
|
19
|
+
/**
|
|
20
|
+
* Find-and-remove a create for (model, id) that has never been sent —
|
|
21
|
+
* checking the microtask staging area, the execution queue, and the store's
|
|
22
|
+
* pending bucket, in that order. Splices the winner out of whichever queue
|
|
23
|
+
* held it so the caller can cancel it instead of shipping create+delete.
|
|
24
|
+
*/
|
|
25
|
+
export function takeUnsentCreateForModel(staged, queued, store, modelName, modelId) {
|
|
26
|
+
const isUnsentCreate = (tx) => tx.type === 'create' &&
|
|
27
|
+
tx.status === 'pending' &&
|
|
28
|
+
tx.attempts === 0 &&
|
|
29
|
+
isTransactionForModel(tx, modelName, modelId);
|
|
30
|
+
const stagedIndex = staged.findIndex(isUnsentCreate);
|
|
31
|
+
if (stagedIndex >= 0) {
|
|
32
|
+
return staged.splice(stagedIndex, 1)[0];
|
|
33
|
+
}
|
|
34
|
+
const queuedIndex = queued.findIndex(isUnsentCreate);
|
|
35
|
+
if (queuedIndex >= 0) {
|
|
36
|
+
return queued.splice(queuedIndex, 1)[0];
|
|
37
|
+
}
|
|
38
|
+
return store.getByStatus('pending').find(isUnsentCreate);
|
|
39
|
+
}
|
|
40
|
+
export function findCreateBarrierForDelete(store, modelName, modelId) {
|
|
41
|
+
const liveCreates = [
|
|
42
|
+
...store.getByStatus('pending'),
|
|
43
|
+
...store.getByStatus('executing'),
|
|
44
|
+
...store.getByStatus('awaiting_delta'),
|
|
45
|
+
].filter((tx) => tx.type === 'create' &&
|
|
46
|
+
isTransactionForModel(tx, modelName, modelId) &&
|
|
47
|
+
// A never-attempted pending create can be cancelled instead. Once the
|
|
48
|
+
// create has been sent, even a retry-pending state is a causal barrier:
|
|
49
|
+
// the server may already have applied it and only the response was lost.
|
|
50
|
+
(tx.status !== 'pending' || tx.attempts > 0));
|
|
51
|
+
return liveCreates.sort((a, b) => b.createdAt - a.createdAt)[0];
|
|
52
|
+
}
|
|
53
|
+
export function deferDeleteUntilCreateSettles(deferredDeletesByCreate, createTransaction, deleteTransaction) {
|
|
54
|
+
const key = entityKey(createTransaction.modelName, createTransaction.modelId);
|
|
55
|
+
const deferred = deferredDeletesByCreate.get(key) ?? [];
|
|
56
|
+
deferred.push(deleteTransaction);
|
|
57
|
+
deferredDeletesByCreate.set(key, deferred);
|
|
58
|
+
}
|
|
59
|
+
export function releaseDeferredDeletesForCreate(deferredDeletesByCreate, store, enqueue, createTransaction) {
|
|
60
|
+
const key = entityKey(createTransaction.modelName, createTransaction.modelId);
|
|
61
|
+
const deferred = deferredDeletesByCreate.get(key);
|
|
62
|
+
if (!deferred || deferred.length === 0)
|
|
63
|
+
return;
|
|
64
|
+
deferredDeletesByCreate.delete(key);
|
|
65
|
+
for (const deleteTransaction of deferred) {
|
|
66
|
+
if (store.get(deleteTransaction.id)?.status !== 'pending')
|
|
67
|
+
continue;
|
|
68
|
+
enqueue(deleteTransaction);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Merge two GraphQL update payloads with special handling for metadata fields
|
|
72
|
+
export function mergeUpdateData(left, right, _modelName) {
|
|
73
|
+
const out = { ...(left || {}) };
|
|
74
|
+
const src = right || {};
|
|
75
|
+
for (const key of Object.keys(src)) {
|
|
76
|
+
// Special case: metadata payloads may be JSON strings; merge objects instead of clobbering
|
|
77
|
+
if (key === 'metadata') {
|
|
78
|
+
const l = out.metadata;
|
|
79
|
+
const r = src.metadata;
|
|
80
|
+
// If both sides undefined/null, continue
|
|
81
|
+
if (l == null && r == null) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
// Normalize to objects
|
|
85
|
+
const toObj = (v) => {
|
|
86
|
+
if (v == null)
|
|
87
|
+
return {};
|
|
88
|
+
if (typeof v === 'string') {
|
|
89
|
+
try {
|
|
90
|
+
return JSON.parse(v);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (typeof v === 'object')
|
|
97
|
+
return v;
|
|
98
|
+
return {};
|
|
99
|
+
};
|
|
100
|
+
const lobj = toObj(l);
|
|
101
|
+
const robj = toObj(r);
|
|
102
|
+
const merged = { ...lobj, ...robj };
|
|
103
|
+
// Re-stringify to match schema input type
|
|
104
|
+
try {
|
|
105
|
+
out.metadata = JSON.stringify(merged);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// Fallback to right-hand side if stringify fails
|
|
109
|
+
out.metadata = typeof r === 'string' ? r : JSON.stringify(robj || {});
|
|
110
|
+
}
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
// Default: shallow overwrite with right-hand value
|
|
114
|
+
out[key] = src[key];
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commit-payload projection — the queue's wire vocabulary, lifted out of
|
|
3
|
+
* `TransactionQueue.ts` as a pure leaf (no queue state, no timers).
|
|
4
|
+
*
|
|
5
|
+
* Owns the `Transaction` record shape plus every helper that turns a local
|
|
6
|
+
* transaction into a wire-safe commit operation: schema-field projection
|
|
7
|
+
* (`projectCommitPayload`), write-option projection (`applyWriteOptions`),
|
|
8
|
+
* FK-aware priority scoring, and the transport-error duck-typing used to
|
|
9
|
+
* classify server rejections. Because nothing here touches the queue's
|
|
10
|
+
* runtime, the HTTP commit path can share this projection too.
|
|
11
|
+
*/
|
|
12
|
+
import { MutationOperationType } from '../types/index.js';
|
|
13
|
+
import type { MutationOptions, WriteOptions } from '../interfaces/index.js';
|
|
14
|
+
export interface UserContext {
|
|
15
|
+
userId: string;
|
|
16
|
+
organizationId: string;
|
|
17
|
+
role?: string;
|
|
18
|
+
teamIds?: string[];
|
|
19
|
+
}
|
|
20
|
+
/** Wire-format mutation payload (post-projection). */
|
|
21
|
+
export type MutationInput = Record<string, unknown>;
|
|
22
|
+
/**
|
|
23
|
+
* Project a Model's serialized data onto its schema-declared fields
|
|
24
|
+
* and return a wire-safe commit payload. Two jobs:
|
|
25
|
+
*
|
|
26
|
+
* 1. Drop framework internals (`__class`, `__typename`, `clientId`,
|
|
27
|
+
* `syncStatus`) and anything not declared on the model's schema.
|
|
28
|
+
* 2. JSON.stringify values typed as `field.json()` — TEXT columns
|
|
29
|
+
* storing JSON need explicit stringification; postgres.js won't
|
|
30
|
+
* auto-serialize for non-JSONB columns.
|
|
31
|
+
*
|
|
32
|
+
* For updates (`dropUndefined: true`), `undefined` values are also
|
|
33
|
+
* stripped so they don't translate to `SET column = NULL` on the
|
|
34
|
+
* server side.
|
|
35
|
+
*
|
|
36
|
+
* Fields are read from `ModelRegistry`, populated by
|
|
37
|
+
* `registerModelsFromSchema` at SDK initialization. If the model
|
|
38
|
+
* isn't registered with field metadata (edge case — e.g., tests or
|
|
39
|
+
* manually registered models), projection falls back to identity and
|
|
40
|
+
* the caller gets whatever the Model serialized.
|
|
41
|
+
*/
|
|
42
|
+
export declare function projectCommitPayload(modelName: string, source: Record<string, unknown>, opts: {
|
|
43
|
+
dropUndefined: boolean;
|
|
44
|
+
}): MutationInput;
|
|
45
|
+
export interface Transaction {
|
|
46
|
+
id: string;
|
|
47
|
+
type: 'create' | 'update' | 'delete' | 'archive' | 'unarchive';
|
|
48
|
+
modelName: string;
|
|
49
|
+
modelId: string;
|
|
50
|
+
modelKey: string;
|
|
51
|
+
data?: MutationInput;
|
|
52
|
+
previousData?: MutationInput | null;
|
|
53
|
+
context: UserContext;
|
|
54
|
+
status: 'pending' | 'executing' | 'awaiting_delta' | 'completed' | 'failed' | 'rolled_back';
|
|
55
|
+
createdAt: number;
|
|
56
|
+
attempts: number;
|
|
57
|
+
priority: 'normal' | 'high';
|
|
58
|
+
priorityScore: number;
|
|
59
|
+
writeOptions?: WriteOptions;
|
|
60
|
+
batchId?: string;
|
|
61
|
+
/** Completed locally without a server operation; no sync echo will arrive. */
|
|
62
|
+
localOnly?: boolean;
|
|
63
|
+
/** LINEAR PATTERN: syncId threshold - transaction confirms when delta.id >= this value */
|
|
64
|
+
syncIdNeededForCompletion?: number;
|
|
65
|
+
/**
|
|
66
|
+
* Resolves when the server has confirmed this transaction (delta arrived
|
|
67
|
+
* or HTTP ack). Rejects with the originating error if the transaction is
|
|
68
|
+
* permanently rolled back. Name matches the queue's existing `'confirmed'`
|
|
69
|
+
* status vocabulary (`commits.create({wait:'confirmed'})`,
|
|
70
|
+
* `waitForConfirmation`) — gives call sites a single `await` point for
|
|
71
|
+
* "did my write land?", so failures surface at the source instead of
|
|
72
|
+
* leaking via silent pool rollback. The rejection error is the same
|
|
73
|
+
* `AbloError` recorded on the queue's `transaction:failed` event.
|
|
74
|
+
*/
|
|
75
|
+
confirmation?: Promise<void>;
|
|
76
|
+
}
|
|
77
|
+
export declare const normalizeModelKey: (modelName: string) => string;
|
|
78
|
+
export declare const stripModelSuffix: (modelName: string) => string;
|
|
79
|
+
/**
|
|
80
|
+
* FK-ordered create priority.
|
|
81
|
+
*
|
|
82
|
+
* Reads `config.modelCreatePriority` out of the runtime SyncEngineContext —
|
|
83
|
+
* this map is populated once at `createSyncEngine(...)` time by walking the
|
|
84
|
+
* schema's `belongsTo` graph (see `computeFKDepthPriority` in
|
|
85
|
+
* `client/createSyncEngine.ts`). The queue stays schema-agnostic: no model
|
|
86
|
+
* names appear here, and consumer applications can override specific
|
|
87
|
+
* priorities via `configOverrides.modelCreatePriority` without touching the
|
|
88
|
+
* SDK.
|
|
89
|
+
*
|
|
90
|
+
* Non-create ops (update/delete/archive/unarchive) don't need FK ordering
|
|
91
|
+
* because the row already exists, so they all share
|
|
92
|
+
* `config.defaultNonCreatePriority`.
|
|
93
|
+
*/
|
|
94
|
+
export declare const computePriorityScore: (type: Transaction["type"], modelName: string) => number;
|
|
95
|
+
export declare const TX_TYPE_TO_MUTATION_OP: Record<Transaction['type'], MutationOperationType>;
|
|
96
|
+
export declare function hasStaleWriteOptions(options?: WriteOptions): boolean;
|
|
97
|
+
export interface WriteOperationFields {
|
|
98
|
+
readAt?: number | null;
|
|
99
|
+
onStale?: 'reject' | 'overwrite' | 'notify' | null;
|
|
100
|
+
options?: Pick<MutationOptions, 'idempotencyKey' | 'label'>;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Project a transaction's `writeOptions` onto the wire operation. Stale
|
|
104
|
+
* guards (`readAt`/`onStale`) ride at the op root; `idempotencyKey`/`label`
|
|
105
|
+
* ride in the op's `options` slot (`MutationOperation.options` — the
|
|
106
|
+
* mutation_log cache key + audit tag). This is the single place the
|
|
107
|
+
* caller-supplied write vocabulary crosses onto the wire.
|
|
108
|
+
*/
|
|
109
|
+
export declare function applyWriteOptions<T extends object>(op: T, transaction: Transaction): T & WriteOperationFields;
|
|
110
|
+
/**
|
|
111
|
+
* Structural shape we duck-type against for transport-layer errors.
|
|
112
|
+
* Captures the union of GraphQL-style and HTTP-style error shapes the
|
|
113
|
+
* mutation executor surfaces — kept narrow on purpose so we don't
|
|
114
|
+
* pretend to know fields the runtime won't always supply.
|
|
115
|
+
*/
|
|
116
|
+
export interface TransportError {
|
|
117
|
+
message?: string;
|
|
118
|
+
code?: string | number;
|
|
119
|
+
extensions?: Record<string, unknown>;
|
|
120
|
+
locations?: readonly unknown[];
|
|
121
|
+
path?: readonly (string | number)[];
|
|
122
|
+
response?: {
|
|
123
|
+
status?: number;
|
|
124
|
+
errors?: readonly {
|
|
125
|
+
extensions?: {
|
|
126
|
+
code?: string;
|
|
127
|
+
};
|
|
128
|
+
message?: string;
|
|
129
|
+
}[];
|
|
130
|
+
};
|
|
131
|
+
error?: string;
|
|
132
|
+
}
|
|
133
|
+
export declare function asTransportError(value: unknown): TransportError;
|
|
134
|
+
export declare function extractStatusCode(error: unknown): number | undefined;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commit-payload projection — the queue's wire vocabulary, lifted out of
|
|
3
|
+
* `TransactionQueue.ts` as a pure leaf (no queue state, no timers).
|
|
4
|
+
*
|
|
5
|
+
* Owns the `Transaction` record shape plus every helper that turns a local
|
|
6
|
+
* transaction into a wire-safe commit operation: schema-field projection
|
|
7
|
+
* (`projectCommitPayload`), write-option projection (`applyWriteOptions`),
|
|
8
|
+
* FK-aware priority scoring, and the transport-error duck-typing used to
|
|
9
|
+
* classify server rejections. Because nothing here touches the queue's
|
|
10
|
+
* runtime, the HTTP commit path can share this projection too.
|
|
11
|
+
*/
|
|
12
|
+
import { getContext } from '../context.js';
|
|
13
|
+
import { getActiveRegistry } from '../ModelRegistry.js';
|
|
14
|
+
import { MutationOperationType } from '../types/index.js';
|
|
15
|
+
/**
|
|
16
|
+
* Framework-internal keys added by `Model.toJSON()` that must never
|
|
17
|
+
* reach the wire. The server treats each top-level key as a target
|
|
18
|
+
* column, so shipping these would blow up the INSERT/UPDATE.
|
|
19
|
+
*/
|
|
20
|
+
const FRAMEWORK_KEYS = new Set(['__class', '__typename', 'clientId', 'syncStatus']);
|
|
21
|
+
/**
|
|
22
|
+
* Project a Model's serialized data onto its schema-declared fields
|
|
23
|
+
* and return a wire-safe commit payload. Two jobs:
|
|
24
|
+
*
|
|
25
|
+
* 1. Drop framework internals (`__class`, `__typename`, `clientId`,
|
|
26
|
+
* `syncStatus`) and anything not declared on the model's schema.
|
|
27
|
+
* 2. JSON.stringify values typed as `field.json()` — TEXT columns
|
|
28
|
+
* storing JSON need explicit stringification; postgres.js won't
|
|
29
|
+
* auto-serialize for non-JSONB columns.
|
|
30
|
+
*
|
|
31
|
+
* For updates (`dropUndefined: true`), `undefined` values are also
|
|
32
|
+
* stripped so they don't translate to `SET column = NULL` on the
|
|
33
|
+
* server side.
|
|
34
|
+
*
|
|
35
|
+
* Fields are read from `ModelRegistry`, populated by
|
|
36
|
+
* `registerModelsFromSchema` at SDK initialization. If the model
|
|
37
|
+
* isn't registered with field metadata (edge case — e.g., tests or
|
|
38
|
+
* manually registered models), projection falls back to identity and
|
|
39
|
+
* the caller gets whatever the Model serialized.
|
|
40
|
+
*/
|
|
41
|
+
export function projectCommitPayload(modelName, source, opts) {
|
|
42
|
+
const metadata = getActiveRegistry().getMetadata(modelName);
|
|
43
|
+
const fields = metadata?.fields;
|
|
44
|
+
const out = {};
|
|
45
|
+
if (!fields) {
|
|
46
|
+
// Unknown registration — strip framework keys and ship the rest.
|
|
47
|
+
for (const [k, v] of Object.entries(source)) {
|
|
48
|
+
if (FRAMEWORK_KEYS.has(k))
|
|
49
|
+
continue;
|
|
50
|
+
if (opts.dropUndefined && v === undefined)
|
|
51
|
+
continue;
|
|
52
|
+
out[k] = v;
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
for (const [key, meta] of Object.entries(fields)) {
|
|
57
|
+
if (!(key in source))
|
|
58
|
+
continue;
|
|
59
|
+
const value = source[key];
|
|
60
|
+
if (opts.dropUndefined && value === undefined)
|
|
61
|
+
continue;
|
|
62
|
+
// JSON-typed fields (`jsonb` on the server): ship as OBJECTS over
|
|
63
|
+
// the wire, not pre-stringified strings. Previously we stringified
|
|
64
|
+
// here, which round-tripped incorrectly:
|
|
65
|
+
//
|
|
66
|
+
// 1. Client stringifies `position: {x, y}` → `'{"x":...}'`
|
|
67
|
+
// 2. Server writes to jsonb column (parses string → jsonb object, fine)
|
|
68
|
+
// 3. Server's delta echoes `data: JSON.stringify(op.input)` where
|
|
69
|
+
// `op.input.position` is still the STRING from step 1
|
|
70
|
+
// 4. Client merges delta → `model.position = "{...}"` (STRING)
|
|
71
|
+
// 5. Next drag: `{ ...layer.position, x, y }` spreads the STRING
|
|
72
|
+
// char-by-char, producing corrupted char-indexed objects like
|
|
73
|
+
// `{"0":"{","1":"\"","2":"x",...,"x":null,"y":null,...}`
|
|
74
|
+
// 6. That corrupt object lands in the next commit, stored in jsonb.
|
|
75
|
+
//
|
|
76
|
+
// Sending objects avoids the round-trip mismatch: the wire carries
|
|
77
|
+
// the object through delta + commit unchanged, and `postgres-js`
|
|
78
|
+
// serializes JS objects to jsonb correctly via its own
|
|
79
|
+
// `json.serialize` (triggered by Postgres's ParameterDescription
|
|
80
|
+
// response identifying the column as type 3802 / jsonb).
|
|
81
|
+
out[key] = value;
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
export const normalizeModelKey = (modelName) => modelName.replace('Model', '').toLowerCase();
|
|
86
|
+
export const stripModelSuffix = (modelName) => modelName.replace('Model', '');
|
|
87
|
+
/**
|
|
88
|
+
* FK-ordered create priority.
|
|
89
|
+
*
|
|
90
|
+
* Reads `config.modelCreatePriority` out of the runtime SyncEngineContext —
|
|
91
|
+
* this map is populated once at `createSyncEngine(...)` time by walking the
|
|
92
|
+
* schema's `belongsTo` graph (see `computeFKDepthPriority` in
|
|
93
|
+
* `client/createSyncEngine.ts`). The queue stays schema-agnostic: no model
|
|
94
|
+
* names appear here, and consumer applications can override specific
|
|
95
|
+
* priorities via `configOverrides.modelCreatePriority` without touching the
|
|
96
|
+
* SDK.
|
|
97
|
+
*
|
|
98
|
+
* Non-create ops (update/delete/archive/unarchive) don't need FK ordering
|
|
99
|
+
* because the row already exists, so they all share
|
|
100
|
+
* `config.defaultNonCreatePriority`.
|
|
101
|
+
*/
|
|
102
|
+
export const computePriorityScore = (type, modelName) => {
|
|
103
|
+
const { modelCreatePriority, defaultCreatePriority, defaultNonCreatePriority } = getContext().config;
|
|
104
|
+
if (type !== 'create')
|
|
105
|
+
return defaultNonCreatePriority;
|
|
106
|
+
return modelCreatePriority.get(modelName) ?? defaultCreatePriority;
|
|
107
|
+
};
|
|
108
|
+
export const TX_TYPE_TO_MUTATION_OP = {
|
|
109
|
+
create: MutationOperationType.CREATE,
|
|
110
|
+
update: MutationOperationType.UPDATE,
|
|
111
|
+
delete: MutationOperationType.DELETE,
|
|
112
|
+
archive: MutationOperationType.ARCHIVE,
|
|
113
|
+
unarchive: MutationOperationType.UNARCHIVE,
|
|
114
|
+
};
|
|
115
|
+
export function hasStaleWriteOptions(options) {
|
|
116
|
+
return (options?.readAt !== undefined ||
|
|
117
|
+
options?.onStale !== undefined);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Project a transaction's `writeOptions` onto the wire operation. Stale
|
|
121
|
+
* guards (`readAt`/`onStale`) ride at the op root; `idempotencyKey`/`label`
|
|
122
|
+
* ride in the op's `options` slot (`MutationOperation.options` — the
|
|
123
|
+
* mutation_log cache key + audit tag). This is the single place the
|
|
124
|
+
* caller-supplied write vocabulary crosses onto the wire.
|
|
125
|
+
*/
|
|
126
|
+
export function applyWriteOptions(op, transaction) {
|
|
127
|
+
const operation = op;
|
|
128
|
+
const writeOptions = transaction.writeOptions;
|
|
129
|
+
if (!writeOptions)
|
|
130
|
+
return operation;
|
|
131
|
+
if (writeOptions.readAt !== undefined) {
|
|
132
|
+
operation.readAt = writeOptions.readAt;
|
|
133
|
+
}
|
|
134
|
+
if (writeOptions.onStale !== undefined) {
|
|
135
|
+
operation.onStale = writeOptions.onStale;
|
|
136
|
+
}
|
|
137
|
+
if (writeOptions.idempotencyKey != null || writeOptions.label !== undefined) {
|
|
138
|
+
operation.options = {
|
|
139
|
+
...(writeOptions.idempotencyKey != null
|
|
140
|
+
? { idempotencyKey: writeOptions.idempotencyKey }
|
|
141
|
+
: {}),
|
|
142
|
+
...(writeOptions.label !== undefined ? { label: writeOptions.label } : {}),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return operation;
|
|
146
|
+
}
|
|
147
|
+
export function asTransportError(value) {
|
|
148
|
+
return (value && typeof value === 'object' ? value : {});
|
|
149
|
+
}
|
|
150
|
+
export function extractStatusCode(error) {
|
|
151
|
+
return asTransportError(error).response?.status;
|
|
152
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delta-confirmation tracking — the queue's "did my write's echo arrive?"
|
|
3
|
+
* machinery, lifted out of `TransactionQueue.ts` as a stateful leaf.
|
|
4
|
+
*
|
|
5
|
+
* Owns the ack watermark (via the shared `SyncPosition`), the per-transaction
|
|
6
|
+
* confirmation timeout map, and the retry-with-backoff/reconciliation policy
|
|
7
|
+
* for `awaiting_delta` transactions. Talks back to the queue through the
|
|
8
|
+
* minimal `DeltaConfirmationContext` interface (never the host class type),
|
|
9
|
+
* so the leaf stays cycle-free and testable in isolation.
|
|
10
|
+
*/
|
|
11
|
+
import type { SyncPosition } from '../sync/syncPosition.js';
|
|
12
|
+
import type { Transaction } from './commitPayload.js';
|
|
13
|
+
/**
|
|
14
|
+
* The slice of the queue a confirmation tracker needs: store lookups +
|
|
15
|
+
* status flips, dropping optimistic entries on confirm, the host's event
|
|
16
|
+
* surface (`transaction:completed`, `reconciliation:needed`, …), the
|
|
17
|
+
* connection check (timeouts re-schedule instead of escalating while
|
|
18
|
+
* offline), and the shared client position (`noteAck` advances `acked`;
|
|
19
|
+
* diagnostics read `applied`).
|
|
20
|
+
*/
|
|
21
|
+
export interface DeltaConfirmationContext {
|
|
22
|
+
store: {
|
|
23
|
+
get(id: string): Transaction | undefined;
|
|
24
|
+
getByStatus(status: Transaction['status']): Transaction[];
|
|
25
|
+
updateStatus(id: string, status: Transaction['status']): void;
|
|
26
|
+
};
|
|
27
|
+
optimisticUpdates: {
|
|
28
|
+
delete(id: string): boolean;
|
|
29
|
+
};
|
|
30
|
+
emit(event: string, payload?: unknown): void;
|
|
31
|
+
isConnected(): boolean;
|
|
32
|
+
position: SyncPosition;
|
|
33
|
+
}
|
|
34
|
+
export declare class DeltaConfirmationTracker {
|
|
35
|
+
private readonly ctx;
|
|
36
|
+
private static readonly DELTA_MAX_RETRIES;
|
|
37
|
+
private static readonly DELTA_INITIAL_TIMEOUT_MS;
|
|
38
|
+
private static readonly DELTA_MAX_TIMEOUT_MS;
|
|
39
|
+
private deltaConfirmationTimeouts;
|
|
40
|
+
private deltaConfirmationRetries;
|
|
41
|
+
constructor(ctx: DeltaConfirmationContext);
|
|
42
|
+
/** Applied-cursor alias, kept so the read sites below stay legible. */
|
|
43
|
+
private get lastSeenSyncId();
|
|
44
|
+
noteAck(lastSyncId: number | undefined): void;
|
|
45
|
+
/**
|
|
46
|
+
* LINEAR PATTERN: Confirm all awaiting transactions when delta with syncId >= threshold arrives.
|
|
47
|
+
* This replaces clientMutationId echoing - transactions are confirmed by sync ID threshold.
|
|
48
|
+
* @param syncId - The sync ID of the received delta
|
|
49
|
+
*/
|
|
50
|
+
onDeltaReceived(syncId: number): void;
|
|
51
|
+
scheduleDeltaConfirmationTimeout(tx: Transaction, timeoutMs: number): void;
|
|
52
|
+
private cancelDeltaConfirmationTimeout;
|
|
53
|
+
/**
|
|
54
|
+
* Tear down every armed confirmation timer (30–120s each, one per
|
|
55
|
+
* in-flight transaction). Called from `TransactionQueue.dispose()` —
|
|
56
|
+
* without it a disposed queue kept the Node process alive and fired
|
|
57
|
+
* callbacks against an already-cleared store (T1.19).
|
|
58
|
+
*/
|
|
59
|
+
dispose(): void;
|
|
60
|
+
}
|