@abloatai/ablo 0.35.0 → 0.36.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/CHANGELOG.md +713 -629
- package/README.md +56 -519
- package/bin/ablo.cjs +39 -0
- package/dist/BaseSyncedStore.d.ts +24 -4
- package/dist/BaseSyncedStore.js +53 -37
- package/dist/Database.d.ts +8 -20
- package/dist/Database.js +61 -59
- package/dist/InstanceCache.d.ts +6 -2
- package/dist/InstanceCache.js +18 -16
- package/dist/Model.d.ts +17 -7
- package/dist/Model.js +17 -7
- package/dist/ModelRegistry.d.ts +4 -0
- package/dist/ModelRegistry.js +17 -15
- package/dist/NetworkMonitor.d.ts +3 -1
- package/dist/NetworkMonitor.js +7 -5
- package/dist/SyncClient.d.ts +5 -15
- package/dist/SyncClient.js +60 -57
- package/dist/client/Ablo.js +30 -19
- package/dist/client/createInternalComponents.d.ts +4 -0
- package/dist/client/createInternalComponents.js +8 -2
- package/dist/client/createModelProxy.d.ts +21 -1
- package/dist/client/createModelProxy.js +123 -57
- package/dist/client/humans.d.ts +30 -9
- package/dist/client/humans.js +45 -19
- package/dist/client/reactiveEngine.d.ts +16 -11
- package/dist/client/reactiveEngine.js +113 -335
- package/dist/client/storeCluster.d.ts +47 -0
- package/dist/client/storeCluster.js +118 -0
- package/dist/client/storeLifecycle.d.ts +61 -0
- package/dist/client/storeLifecycle.js +231 -0
- package/dist/context.d.ts +13 -0
- package/dist/context.js +23 -0
- package/dist/core/index.d.ts +1 -1
- package/dist/docs/catalog.js +6 -3
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/query/client.d.ts +3 -0
- package/dist/query/client.js +6 -5
- package/dist/react/AbloProvider.d.ts +13 -1
- package/dist/react/AbloProvider.js +5 -2
- package/dist/react/context.d.ts +2 -2
- package/dist/react/createAbloReact.d.ts +56 -0
- package/dist/react/createAbloReact.js +51 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +3 -0
- package/dist/react/useAblo.d.ts +9 -2
- package/dist/react/useAblo.js +25 -7
- package/dist/schema/coordination.js +5 -1
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +4 -0
- package/dist/schema/select.js +3 -0
- package/dist/schema/serialize.js +3 -0
- package/dist/source/adapter.d.ts +7 -5
- package/dist/source/adapter.js +7 -5
- package/dist/source/index.d.ts +1 -1
- package/dist/source/index.js +1 -1
- package/dist/{core/storeContract.d.ts → storeContract.d.ts} +5 -5
- package/dist/{core → stores}/DatabaseManager.d.ts +3 -1
- package/dist/{core → stores}/DatabaseManager.js +13 -12
- package/dist/{core → stores}/StoreManager.d.ts +5 -3
- package/dist/{core → stores}/StoreManager.js +24 -22
- package/dist/stores/SyncActionStore.d.ts +3 -1
- package/dist/stores/SyncActionStore.js +9 -7
- package/dist/sync/BootstrapFetcher.d.ts +4 -0
- package/dist/sync/BootstrapFetcher.js +28 -26
- package/dist/sync/OnDemandLoader.d.ts +3 -0
- package/dist/sync/OnDemandLoader.js +1 -0
- package/dist/sync/bootstrapApply.d.ts +3 -0
- package/dist/sync/bootstrapApply.js +2 -2
- package/dist/sync/deltaPipeline.d.ts +13 -12
- package/dist/sync/deltaPipeline.js +21 -4
- package/dist/sync/groupChange.d.ts +3 -0
- package/dist/sync/groupChange.js +16 -14
- package/dist/sync/participants.d.ts +19 -2
- package/dist/sync/participants.js +3 -1
- package/dist/sync/schemas.d.ts +2 -1
- package/dist/sync/schemas.js +3 -3
- package/dist/syncLog/contract.d.ts +20 -0
- package/dist/syncLog/contract.js +19 -0
- package/dist/syncLog/index.d.ts +1 -0
- package/dist/syncLog/index.js +1 -0
- package/dist/transaction/auth/capability.d.ts +35 -0
- package/dist/transaction/auth/capability.js +25 -0
- package/dist/transaction/coordination/awaitClaimGrant.d.ts +7 -0
- package/dist/transaction/coordination/awaitClaimGrant.js +12 -0
- package/dist/transaction/coordination/claimHeartbeatLoop.d.ts +34 -0
- package/dist/transaction/coordination/claimHeartbeatLoop.js +20 -0
- package/dist/transaction/coordination/index.d.ts +4 -4
- package/dist/transaction/coordination/index.js +4 -3
- package/dist/transaction/coordination/locator.d.ts +23 -2
- package/dist/transaction/coordination/locator.js +22 -2
- package/dist/transaction/coordination/schema.d.ts +125 -62
- package/dist/transaction/coordination/schema.js +228 -64
- package/dist/transaction/coordination/targetConflict.js +32 -28
- package/dist/transaction/errorCodes.d.ts +2 -2
- package/dist/transaction/errorCodes.js +13 -9
- package/dist/transaction/plugin.d.ts +95 -2
- package/dist/transaction/plugin.js +21 -2
- package/dist/transaction/resources/httpResources.d.ts +57 -2
- package/dist/transaction/resources/modelOperations.d.ts +148 -40
- package/dist/transaction/resources/where.d.ts +16 -0
- package/dist/transaction/resources/where.js +45 -0
- package/dist/transaction/schema/field.d.ts +12 -18
- package/dist/transaction/schema/fieldRef.d.ts +38 -0
- package/dist/transaction/schema/fieldRef.js +11 -0
- package/dist/transaction/schema/openapi.d.ts +16 -15
- package/dist/transaction/schema/openapi.js +186 -25
- package/dist/transaction/schema/relation.d.ts +7 -2
- package/dist/transaction/schema/schema.d.ts +27 -0
- package/dist/transaction/schema/schema.js +20 -0
- package/dist/transaction/transactions/settlement/commitEnvelope.d.ts +1 -1
- package/dist/transaction/transactions/settlement/pendingWrite.d.ts +1 -1
- package/dist/transaction/transport/httpClient.d.ts +9 -1
- package/dist/transaction/transport/httpClient.js +1 -0
- package/dist/transaction/transport/httpTransport.js +156 -44
- package/dist/transaction/transport/wsTransport.d.ts +2 -4
- package/dist/transaction/transport/wsTransport.js +16 -10
- package/dist/transaction/types/streams.d.ts +10 -0
- package/dist/transaction/utils/duration.d.ts +25 -0
- package/dist/transaction/utils/duration.js +32 -0
- package/dist/transaction/wire/accountResponses.d.ts +69 -0
- package/dist/transaction/wire/accountResponses.js +36 -1
- package/dist/transaction/wire/auth.d.ts +9 -2
- package/dist/transaction/wire/auth.js +7 -1
- package/dist/transaction/wire/claims.d.ts +164 -97
- package/dist/transaction/wire/claims.js +126 -28
- package/dist/transaction/wire/commit.d.ts +1 -1
- package/dist/transaction/wire/feedEvent.d.ts +27 -0
- package/dist/transaction/wire/feedEvent.js +27 -1
- package/dist/transaction/wire/frames.d.ts +2 -2
- package/dist/transaction/wire/inboundFrames.d.ts +12 -2
- package/dist/transaction/wire/index.d.ts +10 -6
- package/dist/transaction/wire/index.js +12 -3
- package/dist/transaction/wire/modelMutations.d.ts +31 -0
- package/dist/transaction/wire/modelMutations.js +52 -0
- package/dist/transaction/wire/modelShape.d.ts +78 -0
- package/dist/transaction/wire/modelShape.js +74 -0
- package/dist/transactions/mutations/MutationQueue.d.ts +6 -0
- package/dist/transactions/mutations/MutationQueue.js +55 -45
- package/dist/transactions/mutations/commitPayload.d.ts +3 -2
- package/dist/transactions/mutations/commitPayload.js +5 -5
- package/dist/transactions/mutations/deltaConfirmation.d.ts +4 -0
- package/dist/transactions/mutations/deltaConfirmation.js +10 -8
- package/dist/transactions/mutations/replayValidation.d.ts +2 -1
- package/dist/transactions/mutations/replayValidation.js +3 -2
- package/dist/{core → views}/QueryView.d.ts +1 -1
- package/dist/{core → views}/QueryView.js +1 -1
- package/dist/{core → views}/ViewRegistry.d.ts +1 -1
- package/dist/{core/queryUtils.d.ts → views/incrementalView.d.ts} +6 -6
- package/dist/{core/queryUtils.js → views/incrementalView.js} +6 -6
- package/docs/agents.md +1 -1
- package/docs/api-keys.md +6 -6
- package/docs/api.md +5 -43
- package/docs/audit.md +4 -3
- package/docs/cli.md +11 -11
- package/docs/client-behavior.md +4 -4
- package/docs/concurrency-convention.md +28 -42
- package/docs/coordination.md +235 -83
- package/docs/data-sources.md +4 -4
- package/docs/debugging.md +34 -12
- package/docs/deployment.md +8 -8
- package/docs/examples/scoped-agent.md +3 -3
- package/docs/groups.md +57 -3
- package/docs/guarantees.md +37 -10
- package/docs/how-it-works.md +29 -5
- package/docs/idempotency.md +6 -6
- package/docs/identity.md +22 -23
- package/docs/index.md +8 -8
- package/docs/integration-guide.md +14 -3
- package/docs/mcp.md +7 -7
- package/docs/migration.md +34 -15
- package/docs/projects.md +1 -1
- package/docs/react.md +19 -8
- package/docs/sessions.md +1 -1
- package/docs/webhooks.md +9 -9
- package/llms.txt +4 -4
- package/package.json +13 -20
- package/dist/cli.cjs +0 -288600
- package/dist/testing/fixtures/bootstrap.d.ts +0 -49
- package/dist/testing/fixtures/bootstrap.js +0 -59
- package/dist/testing/fixtures/deltas.d.ts +0 -83
- package/dist/testing/fixtures/deltas.js +0 -136
- package/dist/testing/fixtures/httpResponses.d.ts +0 -70
- package/dist/testing/fixtures/httpResponses.js +0 -90
- package/dist/testing/fixtures/models.d.ts +0 -83
- package/dist/testing/fixtures/models.js +0 -272
- package/dist/testing/helpers/reactWrapper.d.ts +0 -69
- package/dist/testing/helpers/reactWrapper.js +0 -67
- package/dist/testing/helpers/syncEngineHarness.d.ts +0 -54
- package/dist/testing/helpers/syncEngineHarness.js +0 -73
- package/dist/testing/helpers/wait.d.ts +0 -30
- package/dist/testing/helpers/wait.js +0 -49
- package/dist/testing/index.d.ts +0 -23
- package/dist/testing/index.js +0 -33
- package/dist/testing/mocks/FakeDatabase.d.ts +0 -18
- package/dist/testing/mocks/FakeDatabase.js +0 -10
- package/dist/testing/mocks/MockMutationExecutor.d.ts +0 -87
- package/dist/testing/mocks/MockMutationExecutor.js +0 -186
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +0 -20
- package/dist/testing/mocks/MockNetworkMonitor.js +0 -46
- package/dist/testing/mocks/MockSyncContext.d.ts +0 -51
- package/dist/testing/mocks/MockSyncContext.js +0 -72
- package/dist/testing/mocks/MockSyncStore.d.ts +0 -88
- package/dist/testing/mocks/MockSyncStore.js +0 -171
- package/dist/testing/mocks/MockWebSocket.d.ts +0 -71
- package/dist/testing/mocks/MockWebSocket.js +0 -118
- package/docs/interaction-model.md +0 -99
- /package/dist/{core → query}/QueryProcessor.d.ts +0 -0
- /package/dist/{core → query}/QueryProcessor.js +0 -0
- /package/dist/{core/storeContract.js → storeContract.js} +0 -0
- /package/dist/{core → stores}/openIDBWithTimeout.d.ts +0 -0
- /package/dist/{core → stores}/openIDBWithTimeout.js +0 -0
- /package/dist/{source → transaction}/footprint.d.ts +0 -0
- /package/dist/{source → transaction}/footprint.js +0 -0
- /package/dist/{core → views}/ViewRegistry.js +0 -0
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* when its data is loaded.
|
|
8
8
|
*/
|
|
9
9
|
import { ModelRegistry } from '../ModelRegistry.js';
|
|
10
|
-
import { ObjectStore } from '
|
|
11
|
-
import { SyncActionStore } from '
|
|
10
|
+
import { ObjectStore } from './ObjectStore.js';
|
|
11
|
+
import { SyncActionStore } from './SyncActionStore.js';
|
|
12
12
|
import { LoadStrategy } from '../transaction/types/index.js';
|
|
13
13
|
import { AbloValidationError } from '../transaction/errors.js';
|
|
14
14
|
/**
|
|
@@ -21,6 +21,7 @@ import { AbloValidationError } from '../transaction/errors.js';
|
|
|
21
21
|
* - Handles store-specific optimizations based on load strategies
|
|
22
22
|
*/
|
|
23
23
|
export class StoreManager {
|
|
24
|
+
runtime;
|
|
24
25
|
stores = new Map();
|
|
25
26
|
/** Strict-durability wrapper for the client write journal and commit outbox. */
|
|
26
27
|
transactionStore = null;
|
|
@@ -28,7 +29,8 @@ export class StoreManager {
|
|
|
28
29
|
db = null;
|
|
29
30
|
isInitialized = false;
|
|
30
31
|
modelRegistry;
|
|
31
|
-
constructor(modelRegistry) {
|
|
32
|
+
constructor(modelRegistry, runtime = globalRuntime) {
|
|
33
|
+
this.runtime = runtime;
|
|
32
34
|
this.modelRegistry = modelRegistry;
|
|
33
35
|
}
|
|
34
36
|
/**
|
|
@@ -38,10 +40,10 @@ export class StoreManager {
|
|
|
38
40
|
this.db = db;
|
|
39
41
|
if (this.isInitialized) {
|
|
40
42
|
// Idempotent re-entry — harmless, nothing for the consumer to act on → debug.
|
|
41
|
-
|
|
43
|
+
this.runtime.logger.debug('StoreManager already initialized');
|
|
42
44
|
return;
|
|
43
45
|
}
|
|
44
|
-
|
|
46
|
+
this.runtime.logger.info('Initializing ObjectStore instances for all models');
|
|
45
47
|
const startTime = performance.now();
|
|
46
48
|
// Get all registered models
|
|
47
49
|
const allModels = this.modelRegistry.getRegisteredModelNames();
|
|
@@ -55,11 +57,11 @@ export class StoreManager {
|
|
|
55
57
|
// mode.
|
|
56
58
|
this.transactionStore = new ObjectStore(this.db, '__transactions', '__transactions', { loadStrategy: LoadStrategy.instant }, 'strict');
|
|
57
59
|
// Initialize SyncactionStore
|
|
58
|
-
this.syncactionStore = new SyncActionStore(this.db);
|
|
60
|
+
this.syncactionStore = new SyncActionStore(this.db, this.runtime);
|
|
59
61
|
await this.syncactionStore.initialize();
|
|
60
62
|
this.isInitialized = true;
|
|
61
63
|
const duration = performance.now() - startTime;
|
|
62
|
-
|
|
64
|
+
this.runtime.logger.info('Initialized ObjectStores and SyncactionStore', {
|
|
63
65
|
count: this.stores.size,
|
|
64
66
|
ms: duration.toFixed(2),
|
|
65
67
|
});
|
|
@@ -84,14 +86,14 @@ export class StoreManager {
|
|
|
84
86
|
* Create stores (tables) in IndexedDB
|
|
85
87
|
*/
|
|
86
88
|
async createStores(db) {
|
|
87
|
-
|
|
89
|
+
this.runtime.logger.info('Creating tables for all registered models');
|
|
88
90
|
for (const modelName of this.modelRegistry.getRegisteredModelNames()) {
|
|
89
91
|
const storeName = modelName;
|
|
90
92
|
// Skip if store already exists
|
|
91
93
|
if (db.objectStoreNames.contains(storeName)) {
|
|
92
94
|
continue;
|
|
93
95
|
}
|
|
94
|
-
|
|
96
|
+
this.runtime.logger.debug('Creating table', { storeName, modelName });
|
|
95
97
|
// Create object store with id as keyPath
|
|
96
98
|
const store = db.createObjectStore(storeName, { keyPath: 'id' });
|
|
97
99
|
// Create indexes for indexed properties
|
|
@@ -99,12 +101,12 @@ export class StoreManager {
|
|
|
99
101
|
for (const propName of indexedProperties) {
|
|
100
102
|
try {
|
|
101
103
|
store.createIndex(propName, propName, { unique: false });
|
|
102
|
-
|
|
104
|
+
this.runtime.logger.debug('Created index', { store: storeName, prop: propName });
|
|
103
105
|
}
|
|
104
106
|
catch (error) {
|
|
105
107
|
// Internal IndexedDB index setup — a miss only affects local query
|
|
106
108
|
// speed and isn't consumer-actionable → debug.
|
|
107
|
-
|
|
109
|
+
this.runtime.logger.debug('Failed to create index', { store: storeName, prop: propName, error });
|
|
108
110
|
}
|
|
109
111
|
}
|
|
110
112
|
}
|
|
@@ -119,12 +121,12 @@ export class StoreManager {
|
|
|
119
121
|
if (!db.objectStoreNames.contains('sync_action_table')) {
|
|
120
122
|
const syncActionStore = db.createObjectStore('sync_action_table', { keyPath: 'id' });
|
|
121
123
|
syncActionStore.createIndex('syncId', 'id');
|
|
122
|
-
|
|
124
|
+
this.runtime.logger.debug('Created sync_action_table');
|
|
123
125
|
}
|
|
124
126
|
// Create __meta table for model persistence state and database metadata
|
|
125
127
|
if (!db.objectStoreNames.contains('__meta')) {
|
|
126
128
|
db.createObjectStore('__meta');
|
|
127
|
-
|
|
129
|
+
this.runtime.logger.debug('Created __meta table');
|
|
128
130
|
}
|
|
129
131
|
// Create __transactions table for unsent transactions
|
|
130
132
|
if (!db.objectStoreNames.contains('__transactions')) {
|
|
@@ -135,7 +137,7 @@ export class StoreManager {
|
|
|
135
137
|
// Create indexes for transaction queries
|
|
136
138
|
transactionStore.createIndex('timestamp', 'timestamp');
|
|
137
139
|
transactionStore.createIndex('status', 'status');
|
|
138
|
-
|
|
140
|
+
this.runtime.logger.debug('Created __transactions table');
|
|
139
141
|
}
|
|
140
142
|
}
|
|
141
143
|
/**
|
|
@@ -175,7 +177,7 @@ export class StoreManager {
|
|
|
175
177
|
}
|
|
176
178
|
}
|
|
177
179
|
const allReady = notReadyStores.length === 0;
|
|
178
|
-
|
|
180
|
+
this.runtime.logger.debug('Store readiness', {
|
|
179
181
|
ready: readyStores.length,
|
|
180
182
|
total: this.stores.size,
|
|
181
183
|
notReady: notReadyStores,
|
|
@@ -232,10 +234,10 @@ export class StoreManager {
|
|
|
232
234
|
* Perform maintenance on all stores
|
|
233
235
|
*/
|
|
234
236
|
async performMaintenance() {
|
|
235
|
-
|
|
237
|
+
this.runtime.logger.info('Performing maintenance on all stores');
|
|
236
238
|
const promises = Array.from(this.stores.values()).map((store) => store.performMaintenance());
|
|
237
239
|
await Promise.all(promises);
|
|
238
|
-
|
|
240
|
+
this.runtime.logger.info('Store maintenance completed');
|
|
239
241
|
}
|
|
240
242
|
/**
|
|
241
243
|
* Clear all stores
|
|
@@ -243,17 +245,17 @@ export class StoreManager {
|
|
|
243
245
|
async clearAllStores() {
|
|
244
246
|
// Lifecycle chatter (logout / identity switch / reset), not a warning.
|
|
245
247
|
// Logged at `debug` so it stays silent under the default `warn` threshold.
|
|
246
|
-
|
|
248
|
+
this.runtime.logger.debug('Clearing all stores');
|
|
247
249
|
const promises = Array.from(this.stores.values()).map((store) => store.clear());
|
|
248
250
|
await Promise.all(promises);
|
|
249
|
-
|
|
251
|
+
this.runtime.logger.info('All stores cleared');
|
|
250
252
|
}
|
|
251
253
|
/**
|
|
252
254
|
* Mark all stores as closing to prevent new operations
|
|
253
255
|
* Called before database connection is closed
|
|
254
256
|
*/
|
|
255
257
|
markAllStoresAsClosing() {
|
|
256
|
-
|
|
258
|
+
this.runtime.logger.debug('Marking all stores as closing');
|
|
257
259
|
for (const store of this.stores.values()) {
|
|
258
260
|
store.markAsClosing();
|
|
259
261
|
}
|
|
@@ -263,7 +265,7 @@ export class StoreManager {
|
|
|
263
265
|
// If closing-state coordination is ever needed for sync actions, add it
|
|
264
266
|
// explicitly to SyncActionStore rather than casting to reach a method that
|
|
265
267
|
// may not exist.
|
|
266
|
-
|
|
268
|
+
this.runtime.logger.debug('All stores marked as closing');
|
|
267
269
|
}
|
|
268
270
|
/**
|
|
269
271
|
* Get comprehensive statistics
|
|
@@ -299,4 +301,4 @@ export class StoreManager {
|
|
|
299
301
|
};
|
|
300
302
|
}
|
|
301
303
|
}
|
|
302
|
-
import {
|
|
304
|
+
import { globalRuntime } from '../context.js';
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* A local, IndexedDB-backed store for the sync actions (change deltas) the
|
|
3
3
|
* server sends. See {@link SyncActionStore}.
|
|
4
4
|
*/
|
|
5
|
+
import type { RuntimeContext } from '../RuntimeContext.js';
|
|
5
6
|
import { type SyncAction } from './syncAction.js';
|
|
6
7
|
/**
|
|
7
8
|
* Stores the sync actions (the change deltas) the server sends, keyed by
|
|
@@ -11,11 +12,12 @@ import { type SyncAction } from './syncAction.js';
|
|
|
11
12
|
* replayed while resolving a conflict.
|
|
12
13
|
*/
|
|
13
14
|
export declare class SyncActionStore {
|
|
15
|
+
private readonly runtime;
|
|
14
16
|
private db;
|
|
15
17
|
private storeName;
|
|
16
18
|
private lastAppliedSyncId;
|
|
17
19
|
private pendingActions;
|
|
18
|
-
constructor(db: IDBDatabase);
|
|
20
|
+
constructor(db: IDBDatabase, runtime?: RuntimeContext);
|
|
19
21
|
/**
|
|
20
22
|
* Initialize store (create if needed)
|
|
21
23
|
*/
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* server sends. See {@link SyncActionStore}.
|
|
4
4
|
*/
|
|
5
5
|
// Uses native IndexedDB for maximum performance
|
|
6
|
-
import {
|
|
6
|
+
import { globalRuntime } from '../context.js';
|
|
7
7
|
import { syncActionSchema } from './syncAction.js';
|
|
8
8
|
/**
|
|
9
9
|
* The validation boundary for rows read back from the store. A row may have
|
|
@@ -14,10 +14,10 @@ import { syncActionSchema } from './syncAction.js';
|
|
|
14
14
|
* than replayed as a malformed delta.
|
|
15
15
|
*/
|
|
16
16
|
/** Parse one stored row into a SyncAction, or `null` (dropped + logged). */
|
|
17
|
-
function toSyncAction(row) {
|
|
17
|
+
function toSyncAction(row, runtime) {
|
|
18
18
|
const parsed = syncActionSchema.safeParse(row);
|
|
19
19
|
if (!parsed.success) {
|
|
20
|
-
|
|
20
|
+
runtime.logger.debug('[SyncActionStore] Dropping malformed stored sync action', {
|
|
21
21
|
issues: parsed.error.issues.map((i) => i.path.join('.')).join(', '),
|
|
22
22
|
});
|
|
23
23
|
return null;
|
|
@@ -32,11 +32,13 @@ function toSyncAction(row) {
|
|
|
32
32
|
* replayed while resolving a conflict.
|
|
33
33
|
*/
|
|
34
34
|
export class SyncActionStore {
|
|
35
|
+
runtime;
|
|
35
36
|
db;
|
|
36
37
|
storeName = 'sync_action_table';
|
|
37
38
|
lastAppliedSyncId = 0;
|
|
38
39
|
pendingActions = new Map();
|
|
39
|
-
constructor(db) {
|
|
40
|
+
constructor(db, runtime = globalRuntime) {
|
|
41
|
+
this.runtime = runtime;
|
|
40
42
|
this.db = db;
|
|
41
43
|
}
|
|
42
44
|
/**
|
|
@@ -121,7 +123,7 @@ export class SyncActionStore {
|
|
|
121
123
|
resolve(undefined);
|
|
122
124
|
return;
|
|
123
125
|
}
|
|
124
|
-
resolve(toSyncAction(data) ?? undefined);
|
|
126
|
+
resolve(toSyncAction(data, this.runtime) ?? undefined);
|
|
125
127
|
};
|
|
126
128
|
request.onerror = () => { reject(request.error); };
|
|
127
129
|
});
|
|
@@ -139,7 +141,7 @@ export class SyncActionStore {
|
|
|
139
141
|
request.onsuccess = () => {
|
|
140
142
|
const allData = request.result;
|
|
141
143
|
const actions = allData
|
|
142
|
-
.map(toSyncAction)
|
|
144
|
+
.map((row) => toSyncAction(row, this.runtime))
|
|
143
145
|
.filter((action) => action !== null);
|
|
144
146
|
resolve(actions);
|
|
145
147
|
};
|
|
@@ -488,7 +490,7 @@ export class SyncActionStore {
|
|
|
488
490
|
try {
|
|
489
491
|
await this.updateLastSyncId(this.lastAppliedSyncId);
|
|
490
492
|
const result = actionsToRewind
|
|
491
|
-
.map(toSyncAction)
|
|
493
|
+
.map((row) => toSyncAction(row, this.runtime))
|
|
492
494
|
.filter((action) => action !== null);
|
|
493
495
|
resolve(result);
|
|
494
496
|
}
|
|
@@ -94,11 +94,15 @@ export interface BootstrapOptions {
|
|
|
94
94
|
* {@link BootstrapFetcher.setAuthToken}.
|
|
95
95
|
*/
|
|
96
96
|
getAuthToken?: AuthTokenGetter;
|
|
97
|
+
/** The owning client's runtime. Defaults to the module-global bridge. */
|
|
98
|
+
runtime?: RuntimeContext;
|
|
97
99
|
}
|
|
100
|
+
import type { RuntimeContext } from '../RuntimeContext.js';
|
|
98
101
|
import { type AuthTokenGetter } from '../transaction/auth/credentialSource.js';
|
|
99
102
|
import { type ValidatedServerDelta } from './schemas.js';
|
|
100
103
|
export declare class BootstrapFetcher {
|
|
101
104
|
private options;
|
|
105
|
+
private readonly runtime;
|
|
102
106
|
/**
|
|
103
107
|
* Every in-flight request's controller, tagged with the lane it belongs to. A
|
|
104
108
|
* registry rather than a single field because a chunked cold start runs
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* cached snapshot when the device is offline. {@link BootstrapData} is the
|
|
7
7
|
* shape it returns; {@link BootstrapOptions} configures it.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import { globalRuntime } from '../context.js';
|
|
10
10
|
import { AbloError, AbloSessionError, AbloConnectionError, translateHttpError, toAbloError, isRetryableCode } from '../transaction/errors.js';
|
|
11
11
|
import { withAuthHeaders } from '../transaction/auth/credentialSource.js';
|
|
12
12
|
import { classifySchemaDrift, describeSchemaDrift, } from './schemaDrift.js';
|
|
13
|
-
// SyncObservability replaced by
|
|
13
|
+
// SyncObservability replaced by this.runtime.observability
|
|
14
14
|
import { parseBootstrapResponse } from './schemas.js';
|
|
15
15
|
/**
|
|
16
16
|
* Rows per page for the chunked cold-start bootstrap. Matches the server's
|
|
@@ -67,6 +67,7 @@ function classifyRequestFailure(error, controller, diedMessage) {
|
|
|
67
67
|
}
|
|
68
68
|
export class BootstrapFetcher {
|
|
69
69
|
options;
|
|
70
|
+
runtime;
|
|
70
71
|
/**
|
|
71
72
|
* Every in-flight request's controller, tagged with the lane it belongs to. A
|
|
72
73
|
* registry rather than a single field because a chunked cold start runs
|
|
@@ -139,14 +140,14 @@ export class BootstrapFetcher {
|
|
|
139
140
|
warnOnSchemaDrift(serverHash) {
|
|
140
141
|
if (this.schemaDriftWarned || !serverHash)
|
|
141
142
|
return;
|
|
142
|
-
const clientHash =
|
|
143
|
+
const clientHash = this.runtime.config.expectedSchemaHash;
|
|
143
144
|
if (!clientHash || clientHash === serverHash)
|
|
144
145
|
return;
|
|
145
146
|
// A projection (`selectModels`/`omitModels`) hashes its subset, which never
|
|
146
147
|
// equals the full schema a server runs — so it also carries the source
|
|
147
148
|
// schema's hash. Matching that means the client is a faithful subset of the
|
|
148
149
|
// deployed schema: current, not drifted. Only warn when neither matches.
|
|
149
|
-
const sourceHash =
|
|
150
|
+
const sourceHash = this.runtime.config.expectedSourceSchemaHash;
|
|
150
151
|
if (sourceHash && sourceHash === serverHash)
|
|
151
152
|
return;
|
|
152
153
|
this.schemaDriftWarned = true;
|
|
@@ -158,7 +159,7 @@ export class BootstrapFetcher {
|
|
|
158
159
|
// from the server's per-model surface before speaking; fall back to the
|
|
159
160
|
// hash message only when that surface is unavailable (older server,
|
|
160
161
|
// network hiccup). Fire-and-forget: never blocks or fails the bootstrap.
|
|
161
|
-
const clientModels =
|
|
162
|
+
const clientModels = this.runtime.config.expectedModelHashes;
|
|
162
163
|
if (clientModels && Object.keys(clientModels).length > 0) {
|
|
163
164
|
void this.resolveSemanticDrift(clientModels, clientHash, serverHash, where);
|
|
164
165
|
return;
|
|
@@ -188,7 +189,7 @@ export class BootstrapFetcher {
|
|
|
188
189
|
if (finding.kind === 'aligned')
|
|
189
190
|
return; // additive server lead — not this client's concern
|
|
190
191
|
if (finding.kind !== 'unknown') {
|
|
191
|
-
|
|
192
|
+
this.runtime.logger.warn(describeSchemaDrift(finding, where), {
|
|
192
193
|
clientSchemaHash: clientHash,
|
|
193
194
|
serverSchemaHash: serverHash,
|
|
194
195
|
serverUrl: this.baseUrl,
|
|
@@ -210,7 +211,7 @@ export class BootstrapFetcher {
|
|
|
210
211
|
// `[Ablo]` namespace — consumers wiring their own logger (pino, etc.) lose
|
|
211
212
|
// that prefix, and a drift warning that reads like the app's own log is
|
|
212
213
|
// worse than none. The brand tells them at a glance who is talking.
|
|
213
|
-
|
|
214
|
+
this.runtime.logger.warn(`Ablo: Schema drift — the schema this client was built with (${clientHash}) is not the ` +
|
|
214
215
|
`one active on the server it connected to (${serverHash} at ${where}). Until they match, ` +
|
|
215
216
|
`operations that depend on the difference will fail later with an opaque database error. ` +
|
|
216
217
|
`This is usually one of three things. The schema may have been pushed to a different ` +
|
|
@@ -227,6 +228,7 @@ export class BootstrapFetcher {
|
|
|
227
228
|
});
|
|
228
229
|
}
|
|
229
230
|
constructor(options) {
|
|
231
|
+
this.runtime = options.runtime ?? globalRuntime;
|
|
230
232
|
// Defaults are spread first; the explicit `baseUrl` then takes precedence,
|
|
231
233
|
// resolved from `options.baseUrl` or the localhost fallback. Callers pass
|
|
232
234
|
// the full base URL, including the `/api` prefix.
|
|
@@ -299,7 +301,7 @@ export class BootstrapFetcher {
|
|
|
299
301
|
const key = this.flightKey(lastSyncId);
|
|
300
302
|
const joined = this.flights.get(key);
|
|
301
303
|
if (joined) {
|
|
302
|
-
|
|
304
|
+
this.runtime.logger.debug('Joining the bootstrap already in flight', { key });
|
|
303
305
|
return joined;
|
|
304
306
|
}
|
|
305
307
|
// A request for something else genuinely does supersede: the running one is
|
|
@@ -372,14 +374,14 @@ export class BootstrapFetcher {
|
|
|
372
374
|
? this.loadCachedBootstrap(this.options.cacheScope)
|
|
373
375
|
: null;
|
|
374
376
|
if (cached) {
|
|
375
|
-
|
|
377
|
+
this.runtime.logger.info('Using cached bootstrap (offline)');
|
|
376
378
|
return cached;
|
|
377
379
|
}
|
|
378
380
|
throw new AbloConnectionError('Offline and no cached bootstrap available', {
|
|
379
381
|
code: 'bootstrap_offline_no_cache',
|
|
380
382
|
});
|
|
381
383
|
}
|
|
382
|
-
|
|
384
|
+
this.runtime.logger.info('Fetching fresh bootstrap data', { url });
|
|
383
385
|
const lane = syncGroupsOverride ? 'scoped' : 'bootstrap';
|
|
384
386
|
// Chunk a COLD start by model: each instant model is its own request, so
|
|
385
387
|
// one giant model can't make the whole snapshot undeliverable, and a
|
|
@@ -397,7 +399,7 @@ export class BootstrapFetcher {
|
|
|
397
399
|
const data = chunked
|
|
398
400
|
? await this.fetchChunkedBootstrap(instantModels, this.options.syncGroups)
|
|
399
401
|
: await this.fetchWithRetries(url, lane);
|
|
400
|
-
|
|
402
|
+
this.runtime.logger.info('Bootstrap data fetched', {
|
|
401
403
|
type: data.type,
|
|
402
404
|
lastSyncId: data.lastSyncId,
|
|
403
405
|
chunked,
|
|
@@ -429,7 +431,7 @@ export class BootstrapFetcher {
|
|
|
429
431
|
? this.loadCachedBootstrap(this.options.cacheScope)
|
|
430
432
|
: null;
|
|
431
433
|
if (cached) {
|
|
432
|
-
|
|
434
|
+
this.runtime.observability.breadcrumb('Bootstrap cache fallback', 'sync.bootstrap', 'warning', {
|
|
433
435
|
error: ablo.message,
|
|
434
436
|
});
|
|
435
437
|
return cached;
|
|
@@ -452,7 +454,7 @@ export class BootstrapFetcher {
|
|
|
452
454
|
catch (error) {
|
|
453
455
|
// SessionError should NOT be retried - the session is invalid and needs re-authentication
|
|
454
456
|
if (AbloSessionError.isSessionError(error)) {
|
|
455
|
-
|
|
457
|
+
this.runtime.observability.breadcrumb('Bootstrap session error - redirecting to sign-in', 'sync.bootstrap', 'warning', {
|
|
456
458
|
statusCode: (error).statusCode,
|
|
457
459
|
});
|
|
458
460
|
throw error;
|
|
@@ -465,11 +467,11 @@ export class BootstrapFetcher {
|
|
|
465
467
|
// unclassified error with no code) flow through to the retry/backoff.
|
|
466
468
|
const ablo = toAbloError(error);
|
|
467
469
|
if (ablo.code && !isRetryableCode(ablo.code)) {
|
|
468
|
-
|
|
470
|
+
this.runtime.observability.breadcrumb('Bootstrap non-retryable error — failing fast', 'sync.bootstrap', 'warning', { code: ablo.code, httpStatus: ablo.httpStatus });
|
|
469
471
|
throw ablo;
|
|
470
472
|
}
|
|
471
473
|
lastError = error;
|
|
472
|
-
|
|
474
|
+
this.runtime.observability.breadcrumb('Bootstrap fetch failed', 'sync.bootstrap', 'warning', {
|
|
473
475
|
attempt: attempt + 1,
|
|
474
476
|
});
|
|
475
477
|
if (attempt < this.options.maxRetries - 1) {
|
|
@@ -490,7 +492,7 @@ export class BootstrapFetcher {
|
|
|
490
492
|
* and cancels its siblings.
|
|
491
493
|
*/
|
|
492
494
|
async fetchChunkedBootstrap(models, syncGroups) {
|
|
493
|
-
|
|
495
|
+
this.runtime.logger.info('Bootstrap chunked by model', {
|
|
494
496
|
models: models.length,
|
|
495
497
|
});
|
|
496
498
|
const queue = [...models];
|
|
@@ -587,7 +589,7 @@ export class BootstrapFetcher {
|
|
|
587
589
|
const etag = res.headers.get('ETag');
|
|
588
590
|
if (res.status === 304) {
|
|
589
591
|
// Log for telemetry
|
|
590
|
-
|
|
592
|
+
this.runtime.logger.info('[Bootstrap] 304 Not Modified - using cached data');
|
|
591
593
|
return { notModified: true, etag };
|
|
592
594
|
}
|
|
593
595
|
if (!res.ok) {
|
|
@@ -621,7 +623,7 @@ export class BootstrapFetcher {
|
|
|
621
623
|
}
|
|
622
624
|
throw translated;
|
|
623
625
|
}
|
|
624
|
-
const data = parseBootstrapResponse(await this.readJsonWithStallGuard(res, controller));
|
|
626
|
+
const data = parseBootstrapResponse(await this.readJsonWithStallGuard(res, controller), this.runtime);
|
|
625
627
|
this.warnOnSchemaDrift(data.schemaHash);
|
|
626
628
|
// Persist payload for offline
|
|
627
629
|
try {
|
|
@@ -633,7 +635,7 @@ export class BootstrapFetcher {
|
|
|
633
635
|
// Offline persistence is best-effort; a failed cache write must not
|
|
634
636
|
// block returning the freshly fetched data.
|
|
635
637
|
}
|
|
636
|
-
|
|
638
|
+
this.runtime.logger.info('[Bootstrap] 200 OK - received new data');
|
|
637
639
|
return { notModified: false, data, etag };
|
|
638
640
|
}
|
|
639
641
|
/**
|
|
@@ -667,7 +669,7 @@ export class BootstrapFetcher {
|
|
|
667
669
|
const armStallTimer = () => {
|
|
668
670
|
clearTimeout(stallTimer);
|
|
669
671
|
stallTimer = setTimeout(() => {
|
|
670
|
-
|
|
672
|
+
this.runtime.observability.breadcrumb('Bootstrap download stalled', 'sync.bootstrap', 'warning', { receivedBytes, stallTimeoutMs: this.options.stallTimeout });
|
|
671
673
|
const stallError = new AbloConnectionError(`Bootstrap download stalled: no data received for ${this.options.stallTimeout}ms (${receivedBytes} bytes arrived before the stream went quiet)`, { code: 'bootstrap_fetch_timeout' });
|
|
672
674
|
stallReject?.(stallError);
|
|
673
675
|
// Then tear the transfer down: abort frees the socket under real
|
|
@@ -723,7 +725,7 @@ export class BootstrapFetcher {
|
|
|
723
725
|
}
|
|
724
726
|
async fetchOnceWith(url, controller) {
|
|
725
727
|
const timeoutId = setTimeout(() => {
|
|
726
|
-
|
|
728
|
+
this.runtime.observability.breadcrumb('Bootstrap fetch timeout', 'sync.bootstrap', 'warning', {
|
|
727
729
|
timeoutMs: this.options.fetchTimeout,
|
|
728
730
|
});
|
|
729
731
|
controller.abort(new AbloConnectionError(`Bootstrap fetch timed out after ${this.options.fetchTimeout}ms waiting for the server to respond`, { code: 'bootstrap_fetch_timeout' }));
|
|
@@ -771,7 +773,7 @@ export class BootstrapFetcher {
|
|
|
771
773
|
}
|
|
772
774
|
throw translated;
|
|
773
775
|
}
|
|
774
|
-
const data = parseBootstrapResponse(await this.readJsonWithStallGuard(response, controller));
|
|
776
|
+
const data = parseBootstrapResponse(await this.readJsonWithStallGuard(response, controller), this.runtime);
|
|
775
777
|
this.warnOnSchemaDrift(data.schemaHash);
|
|
776
778
|
// Offline caching happens in `fetchBootstrap` on the assembled result —
|
|
777
779
|
// caching here would let a single-model chunk overwrite the full snapshot.
|
|
@@ -848,11 +850,11 @@ export class BootstrapFetcher {
|
|
|
848
850
|
}
|
|
849
851
|
keysToRemove.forEach((key) => {
|
|
850
852
|
localStorage.removeItem(key);
|
|
851
|
-
|
|
853
|
+
this.runtime.logger.debug('Cleared cache key', { key });
|
|
852
854
|
});
|
|
853
855
|
}
|
|
854
856
|
catch (error) {
|
|
855
|
-
|
|
857
|
+
this.runtime.logger.debug('Failed to clear cache', { error });
|
|
856
858
|
}
|
|
857
859
|
}
|
|
858
860
|
// Cache helpers for offline bootstrap
|
|
@@ -866,7 +868,7 @@ export class BootstrapFetcher {
|
|
|
866
868
|
localStorage.setItem(this.getBootstrapCacheKey(orgId), JSON.stringify(data));
|
|
867
869
|
}
|
|
868
870
|
catch (e) {
|
|
869
|
-
|
|
871
|
+
this.runtime.logger.debug('Failed to cache bootstrap payload', {
|
|
870
872
|
error: e instanceof Error ? e.message : String(e),
|
|
871
873
|
});
|
|
872
874
|
}
|
|
@@ -918,7 +920,7 @@ export class BootstrapFetcher {
|
|
|
918
920
|
return body.status === 'healthy';
|
|
919
921
|
}
|
|
920
922
|
catch {
|
|
921
|
-
|
|
923
|
+
this.runtime.observability.breadcrumb('Health check failed', 'sync.bootstrap', 'warning');
|
|
922
924
|
return false;
|
|
923
925
|
}
|
|
924
926
|
}
|
|
@@ -24,6 +24,7 @@ import type { InstanceCache } from '../InstanceCache.js';
|
|
|
24
24
|
import type { Database } from '../Database.js';
|
|
25
25
|
import type { Model } from '../Model.js';
|
|
26
26
|
import type { ModelRegistry } from '../ModelRegistry.js';
|
|
27
|
+
import type { RuntimeContext } from '../RuntimeContext.js';
|
|
27
28
|
import type { RecoveryClass } from '../transaction/errorCodes.js';
|
|
28
29
|
import type { LoadWhere, WhereClause } from '../query/types.js';
|
|
29
30
|
import type { Schema } from '../transaction/schema/schema.js';
|
|
@@ -41,6 +42,8 @@ export interface OnDemandLoaderOptions {
|
|
|
41
42
|
readonly getAuthToken?: () => string | null;
|
|
42
43
|
/** @deprecated Use `getAuthToken`. */
|
|
43
44
|
readonly getCapabilityToken?: () => string | null;
|
|
45
|
+
/** The owning client's runtime. Defaults to the module-global bridge. */
|
|
46
|
+
readonly runtime?: RuntimeContext;
|
|
44
47
|
}
|
|
45
48
|
export interface FetchOptions<T> {
|
|
46
49
|
/**
|
|
@@ -432,6 +432,7 @@ export class OnDemandLoader {
|
|
|
432
432
|
baseUrl: this.opts.baseUrl,
|
|
433
433
|
getAuthToken: this.authTokenProvider ?? undefined,
|
|
434
434
|
recoverCredential: this.credentialRecovery ?? undefined,
|
|
435
|
+
runtime: this.opts.runtime,
|
|
435
436
|
}, { queries: [query] });
|
|
436
437
|
const rows = Array.isArray(result.results[0]) ? result.results[0] : [];
|
|
437
438
|
// Normalize: wire rows lack `__typename` when the server elides it.
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* stale local copies the server no longer reports — are performed by the sync
|
|
14
14
|
* client behind that interface.
|
|
15
15
|
*/
|
|
16
|
+
import type { RuntimeContext } from '../RuntimeContext.js';
|
|
16
17
|
import type { BootstrapResult } from '../Database.js';
|
|
17
18
|
import type { SyncDelta } from './SyncWebSocket.js';
|
|
18
19
|
/** Counts describing what applying a bootstrap changed in the pool: entities
|
|
@@ -32,6 +33,8 @@ export interface RehydrationStats {
|
|
|
32
33
|
* takes effect through this interface.
|
|
33
34
|
*/
|
|
34
35
|
export interface PoolContext {
|
|
36
|
+
/** The owning client's runtime. Defaults to the module-global bridge. */
|
|
37
|
+
readonly runtime?: RuntimeContext;
|
|
35
38
|
/** Applies persisted delta results to the in-memory pool, with the host's relation enrichment bound. */
|
|
36
39
|
applyDeltaBatchToPool(results: NonNullable<BootstrapResult['deltaResults']>): void;
|
|
37
40
|
/** Writes bootstrap data into the pool: creates models, heals partial rows, upserts, and removes stale local copies the server no longer reports. */
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* stale local copies the server no longer reports — are performed by the sync
|
|
14
14
|
* client behind that interface.
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
16
|
+
import { globalRuntime } from '../context.js';
|
|
17
17
|
/**
|
|
18
18
|
* Applies a bootstrap result to the in-memory pool and returns what changed.
|
|
19
19
|
* A full bootstrap creates, heals, and upserts models and removes stale local
|
|
@@ -42,7 +42,7 @@ export function applyBootstrapToPool(ctx, bootstrapResult, protectedIds) {
|
|
|
42
42
|
// Creates models, heals partial rows, upserts, and removes stale local copies.
|
|
43
43
|
const stats = ctx.applyBootstrapDataToPool(bootstrapData, protectedIds);
|
|
44
44
|
const elapsedMs = Math.round((typeof performance !== 'undefined' ? performance.now() : Date.now()) - start);
|
|
45
|
-
|
|
45
|
+
(ctx.runtime ?? globalRuntime).logger.info('[BaseSyncedStore] Bootstrap applied', {
|
|
46
46
|
...stats, elapsedMs, poolSize: ctx.getPoolSize(),
|
|
47
47
|
});
|
|
48
48
|
return { ...stats, elapsedMs };
|
|
@@ -10,18 +10,12 @@
|
|
|
10
10
|
* point lives on the store and drives this pipeline through {@link enqueueDelta}
|
|
11
11
|
* and {@link flushPendingDeltas}.
|
|
12
12
|
*/
|
|
13
|
+
import type { RuntimeContext } from '../RuntimeContext.js';
|
|
13
14
|
import { ModelScope } from '../InstanceCache.js';
|
|
14
15
|
import type { Model } from '../Model.js';
|
|
15
|
-
import type { ModelData } from '../transaction/types/modelData.js';
|
|
16
16
|
import type { SyncDelta } from './SyncWebSocket.js';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
action: 'add' | 'update' | 'remove' | 'archive' | 'verify';
|
|
20
|
-
modelName: string;
|
|
21
|
-
modelId: string;
|
|
22
|
-
data?: ModelData | null;
|
|
23
|
-
transactionId?: string;
|
|
24
|
-
}
|
|
17
|
+
import type { ModelData } from '../transaction/types/modelData.js';
|
|
18
|
+
import { type AbloPlugin, type AppliedChange } from '../transaction/plugin.js';
|
|
25
19
|
/**
|
|
26
20
|
* What the pipeline needs back from the surrounding store: the shared mutable
|
|
27
21
|
* pipeline state (backed by the store's own fields through accessors), narrow
|
|
@@ -29,6 +23,14 @@ interface DeltaDbResult {
|
|
|
29
23
|
* subclass's overrides continue to take effect.
|
|
30
24
|
*/
|
|
31
25
|
export interface DeltaPipelineContext {
|
|
26
|
+
/** The owning client's runtime. Defaults to the module-global bridge. */
|
|
27
|
+
readonly runtime?: RuntimeContext;
|
|
28
|
+
/**
|
|
29
|
+
* The installed plugins, whose declared stage handlers this pipeline
|
|
30
|
+
* dispatches at each boundary. Empty (or absent) on directly-constructed
|
|
31
|
+
* stores, where the store's own apply is the whole pipeline.
|
|
32
|
+
*/
|
|
33
|
+
readonly stagePlugins?: readonly AbloPlugin[];
|
|
32
34
|
pendingDeltas: SyncDelta[];
|
|
33
35
|
batchTimer: ReturnType<typeof setTimeout> | null;
|
|
34
36
|
/** Queue for deltas arriving during an active bootstrap; null when none. */
|
|
@@ -52,11 +54,11 @@ export interface DeltaPipelineContext {
|
|
|
52
54
|
data: ModelData | null;
|
|
53
55
|
transactionId?: string;
|
|
54
56
|
}[]): Promise<{
|
|
55
|
-
results:
|
|
57
|
+
results: AppliedChange[];
|
|
56
58
|
persistedSyncId: number;
|
|
57
59
|
}>;
|
|
58
60
|
/** Applies persisted delta results to the in-memory pool, with the host's relation enrichment bound. */
|
|
59
|
-
applyDeltaBatchToPool(results:
|
|
61
|
+
applyDeltaBatchToPool(results: AppliedChange[]): void;
|
|
60
62
|
/** Acknowledges a sync id back to the server; a no-op when the socket is down. */
|
|
61
63
|
acknowledge(syncId: number): void;
|
|
62
64
|
readonly objectPool: {
|
|
@@ -110,4 +112,3 @@ export declare function scheduleDeltaFlush(ctx: DeltaPipelineContext): void;
|
|
|
110
112
|
* and advances the acknowledgement cursor once the store write has committed.
|
|
111
113
|
*/
|
|
112
114
|
export declare function flushPendingDeltas(ctx: DeltaPipelineContext): Promise<void>;
|
|
113
|
-
export {};
|