@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
package/dist/SyncClient.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* - Handle conflict resolution for local changes
|
|
9
9
|
*/
|
|
10
10
|
import { runInAction } from 'mobx';
|
|
11
|
-
import { ModelScope } from './ObjectPool.js';
|
|
11
|
+
import { ObjectPool, ModelScope } from './ObjectPool.js';
|
|
12
|
+
import { Model } from './Model.js';
|
|
12
13
|
// ModelRegistry instance accessed via this.objectPool.registry
|
|
13
14
|
import { LoadStrategy } from './types/index.js';
|
|
14
15
|
import { getContext } from './context.js';
|
|
@@ -16,6 +17,7 @@ import { AbloAuthenticationError, AbloError, AbloValidationError } from './error
|
|
|
16
17
|
import { EventEmitter } from 'events';
|
|
17
18
|
import { NetworkMonitor } from './NetworkMonitor.js';
|
|
18
19
|
import { TransactionQueue } from './transactions/TransactionQueue.js';
|
|
20
|
+
import { persistedMutationSchema } from './transactions/persistedReplay.js';
|
|
19
21
|
import { OptimisticEchoTracker, } from './transactions/OptimisticEchoTracker.js';
|
|
20
22
|
import { SyncPosition } from './sync/syncPosition.js';
|
|
21
23
|
/**
|
|
@@ -43,6 +45,22 @@ function rawRecordIsNewer(data, existing) {
|
|
|
43
45
|
return true;
|
|
44
46
|
return inMs > exMs;
|
|
45
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Narrow an untyped server `updatedAt` (ISO string / epoch / Date, off a
|
|
50
|
+
* `Record<string, unknown>` row) to epoch ms for LWW comparison. Falsy /
|
|
51
|
+
* non-date values → 0, matching the conflict resolver's historical
|
|
52
|
+
* "no timestamp = epoch" behavior.
|
|
53
|
+
*/
|
|
54
|
+
function toEpochMs(value) {
|
|
55
|
+
if (!value)
|
|
56
|
+
return 0;
|
|
57
|
+
if (value instanceof Date)
|
|
58
|
+
return value.getTime();
|
|
59
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
60
|
+
return new Date(value).getTime();
|
|
61
|
+
}
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
46
64
|
export class SyncClient extends EventEmitter {
|
|
47
65
|
objectPool;
|
|
48
66
|
database;
|
|
@@ -122,8 +140,25 @@ export class SyncClient extends EventEmitter {
|
|
|
122
140
|
* Setup network monitoring handlers
|
|
123
141
|
*/
|
|
124
142
|
setupNetworkMonitoring() {
|
|
125
|
-
|
|
126
|
-
|
|
143
|
+
// Both handlers emit to external listeners (which can throw) before/around
|
|
144
|
+
// their own try/catch — route rejections into observability rather than
|
|
145
|
+
// losing a failed reconnect flush silently.
|
|
146
|
+
this.networkMonitor.on('online', () => {
|
|
147
|
+
void this.handleReconnection().catch((error) => {
|
|
148
|
+
getContext().observability.captureTransactionFailure({
|
|
149
|
+
context: 'network-online-reconnection',
|
|
150
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
this.networkMonitor.on('offline', () => {
|
|
155
|
+
void this.handleDisconnection().catch((error) => {
|
|
156
|
+
getContext().observability.captureTransactionFailure({
|
|
157
|
+
context: 'network-offline-handler',
|
|
158
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
127
162
|
}
|
|
128
163
|
/**
|
|
129
164
|
* Handle transaction rollback. Two distinct shapes flow through this
|
|
@@ -281,46 +316,14 @@ export class SyncClient extends EventEmitter {
|
|
|
281
316
|
* will deliver the missing deltas and naturally confirm the transaction.
|
|
282
317
|
*/
|
|
283
318
|
setupAwaitingTransactionPersistence() {
|
|
284
|
-
this.transactionQueue.on('transaction:persist_awaiting',
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
try {
|
|
288
|
-
await this.database.saveTransaction({
|
|
289
|
-
id: `awaiting_${event.txId}`,
|
|
290
|
-
type: 'awaiting_delta',
|
|
291
|
-
timestamp: Date.now(),
|
|
292
|
-
awaitingDelta: {
|
|
293
|
-
syncIdNeeded: event.syncIdNeeded ?? 0,
|
|
294
|
-
modelName: event.model,
|
|
295
|
-
modelId: event.modelId,
|
|
296
|
-
operationType: event.operationType,
|
|
297
|
-
},
|
|
298
|
-
});
|
|
299
|
-
getContext().observability.breadcrumb('Persisted unconfirmed transaction to IDB', 'sync.transaction', 'info', {
|
|
300
|
-
txId: event.txId,
|
|
301
|
-
model: event.model,
|
|
302
|
-
modelId: event.modelId,
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
catch (error) {
|
|
306
|
-
getContext().observability.captureTransactionFailure({
|
|
307
|
-
context: 'persist-awaiting-transaction',
|
|
308
|
-
modelName: event.model,
|
|
309
|
-
modelId: event.modelId,
|
|
310
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
311
|
-
});
|
|
312
|
-
}
|
|
319
|
+
this.transactionQueue.on('transaction:persist_awaiting', (event) => {
|
|
320
|
+
// void is safe: the handler's body is fully try/catch'd.
|
|
321
|
+
void this.persistAwaitingTransaction(event);
|
|
313
322
|
});
|
|
314
323
|
// Clean up persisted awaiting transactions when they're finally confirmed
|
|
315
|
-
this.transactionQueue.on('transaction:completed',
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
try {
|
|
319
|
-
await this.database.removeTransaction(`awaiting_${tx.id}`);
|
|
320
|
-
}
|
|
321
|
-
catch {
|
|
322
|
-
// Ignore — might not have been persisted
|
|
323
|
-
}
|
|
324
|
+
this.transactionQueue.on('transaction:completed', (tx) => {
|
|
325
|
+
// void is safe: the handler's body is fully try/catch'd.
|
|
326
|
+
void this.removeAwaitingTransaction(tx.id);
|
|
324
327
|
});
|
|
325
328
|
// Echo detection bridge. When the queue stages a transaction, the
|
|
326
329
|
// client has already optimistically applied the change to the
|
|
@@ -338,6 +341,48 @@ export class SyncClient extends EventEmitter {
|
|
|
338
341
|
this.echoTracker.drainOnRollback(event.transaction.id);
|
|
339
342
|
});
|
|
340
343
|
}
|
|
344
|
+
/** Persist an unconfirmed transaction to IDB (never rejects — failures are captured). */
|
|
345
|
+
async persistAwaitingTransaction(event) {
|
|
346
|
+
if (!this.database)
|
|
347
|
+
return;
|
|
348
|
+
try {
|
|
349
|
+
await this.database.saveTransaction({
|
|
350
|
+
id: `awaiting_${event.txId}`,
|
|
351
|
+
type: 'awaiting_delta',
|
|
352
|
+
timestamp: Date.now(),
|
|
353
|
+
awaitingDelta: {
|
|
354
|
+
syncIdNeeded: event.syncIdNeeded ?? 0,
|
|
355
|
+
modelName: event.model,
|
|
356
|
+
modelId: event.modelId,
|
|
357
|
+
operationType: event.operationType,
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
getContext().observability.breadcrumb('Persisted unconfirmed transaction to IDB', 'sync.transaction', 'info', {
|
|
361
|
+
txId: event.txId,
|
|
362
|
+
model: event.model,
|
|
363
|
+
modelId: event.modelId,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
getContext().observability.captureTransactionFailure({
|
|
368
|
+
context: 'persist-awaiting-transaction',
|
|
369
|
+
modelName: event.model,
|
|
370
|
+
modelId: event.modelId,
|
|
371
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/** Drop the persisted awaiting-row once confirmed (never rejects). */
|
|
376
|
+
async removeAwaitingTransaction(txId) {
|
|
377
|
+
if (!this.database)
|
|
378
|
+
return;
|
|
379
|
+
try {
|
|
380
|
+
await this.database.removeTransaction(`awaiting_${txId}`);
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
// Ignore — might not have been persisted
|
|
384
|
+
}
|
|
385
|
+
}
|
|
341
386
|
/**
|
|
342
387
|
* Initialize sync client with authentication
|
|
343
388
|
*/
|
|
@@ -483,8 +528,9 @@ export class SyncClient extends EventEmitter {
|
|
|
483
528
|
// Persist healed records back to IndexedDB (fire-and-forget, non-blocking)
|
|
484
529
|
if (recordsToHeal.length > 0 && this.database) {
|
|
485
530
|
getContext().logger.info(`[SyncClient.hydrate] Persisting ${recordsToHeal.length} healed ${modelType} records to IndexedDB`);
|
|
486
|
-
// Use fire-and-forget to not block hydration
|
|
487
|
-
|
|
531
|
+
// Use fire-and-forget to not block hydration.
|
|
532
|
+
// void is safe: the handler's body is fully try/catch'd.
|
|
533
|
+
void Promise.resolve().then(async () => {
|
|
488
534
|
try {
|
|
489
535
|
for (const { id, data } of recordsToHeal) {
|
|
490
536
|
await this.database.putRecord(modelType, id, data);
|
|
@@ -501,13 +547,6 @@ export class SyncClient extends EventEmitter {
|
|
|
501
547
|
});
|
|
502
548
|
}
|
|
503
549
|
const typeEnd = typeof performance !== 'undefined' ? performance.now() : Date.now();
|
|
504
|
-
// Dev-only hydration summary
|
|
505
|
-
if (modelType === 'InboxItem' && process.env.NODE_ENV !== 'production') {
|
|
506
|
-
getContext().logger.debug('[SyncClient] InboxItem hydration summary', {
|
|
507
|
-
fetched: rawData.length,
|
|
508
|
-
added: modelsForType.length,
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
550
|
perTypePerfLogs.push({
|
|
512
551
|
type: modelType,
|
|
513
552
|
fetched: rawData.length,
|
|
@@ -600,7 +639,8 @@ export class SyncClient extends EventEmitter {
|
|
|
600
639
|
if (this.database) {
|
|
601
640
|
const id = healResult.data.id;
|
|
602
641
|
const healedData = healResult.data;
|
|
603
|
-
|
|
642
|
+
// void is safe: the handler's body is fully try/catch'd.
|
|
643
|
+
void Promise.resolve().then(async () => {
|
|
604
644
|
try {
|
|
605
645
|
await this.database.putRecord(modelType, id, healedData);
|
|
606
646
|
}
|
|
@@ -755,11 +795,11 @@ export class SyncClient extends EventEmitter {
|
|
|
755
795
|
}
|
|
756
796
|
/** Add new model (CREATE) - works offline */
|
|
757
797
|
add(model, options) {
|
|
758
|
-
this.mutate('create', model, () => this.objectPool.add(model, ModelScope.live), options);
|
|
798
|
+
this.mutate('create', model, () => { this.objectPool.add(model, ModelScope.live); }, options);
|
|
759
799
|
}
|
|
760
800
|
/** Update existing model (UPDATE) - works offline */
|
|
761
801
|
update(model, options) {
|
|
762
|
-
this.mutate('update', model, () => this.objectPool.upsert(model, ModelScope.live), options);
|
|
802
|
+
this.mutate('update', model, () => { this.objectPool.upsert(model, ModelScope.live); }, options);
|
|
763
803
|
}
|
|
764
804
|
/**
|
|
765
805
|
* Update existing model with pre-computed changes.
|
|
@@ -912,7 +952,7 @@ export class SyncClient extends EventEmitter {
|
|
|
912
952
|
}
|
|
913
953
|
/** Archive model (ARCHIVE) - works offline */
|
|
914
954
|
archive(model) {
|
|
915
|
-
this.mutate('archive', model, () => this.objectPool.updateScope(model.id, ModelScope.archived));
|
|
955
|
+
this.mutate('archive', model, () => { this.objectPool.updateScope(model.id, ModelScope.archived); });
|
|
916
956
|
}
|
|
917
957
|
/**
|
|
918
958
|
* Append a mutation and schedule its sync work.
|
|
@@ -969,10 +1009,21 @@ export class SyncClient extends EventEmitter {
|
|
|
969
1009
|
timestamp: Date.now(),
|
|
970
1010
|
});
|
|
971
1011
|
}
|
|
972
|
-
catch (error) {
|
|
1012
|
+
catch (error) {
|
|
1013
|
+
// Best-effort persistence — the in-memory queue still processes; only
|
|
1014
|
+
// a tab close before reconnect loses these. Forensic → debug.
|
|
1015
|
+
getContext().logger.debug('[SyncClient] Failed to persist offline mutation queue', {
|
|
1016
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
973
1019
|
}
|
|
974
1020
|
/**
|
|
975
|
-
* Restore mutation queue from IndexedDB
|
|
1021
|
+
* Restore mutation queue from IndexedDB.
|
|
1022
|
+
*
|
|
1023
|
+
* The persisted record was written by a PREVIOUS session (possibly an older
|
|
1024
|
+
* SDK build), so each entry is validated at this replay boundary (T1.8):
|
|
1025
|
+
* corrupt entries are dropped + logged at debug, and a failure never
|
|
1026
|
+
* vanishes into an empty catch — offline write survival must be observable.
|
|
976
1027
|
*/
|
|
977
1028
|
async restoreMutationQueue() {
|
|
978
1029
|
if (!this.database || !this.userId)
|
|
@@ -982,19 +1033,39 @@ export class SyncClient extends EventEmitter {
|
|
|
982
1033
|
const queue = stored.find((t) => t.id === 'mutation-queue');
|
|
983
1034
|
if (queue?.mutations) {
|
|
984
1035
|
for (const mutation of queue.mutations) {
|
|
985
|
-
const
|
|
1036
|
+
const parsed = persistedMutationSchema.safeParse(mutation);
|
|
1037
|
+
if (!parsed.success) {
|
|
1038
|
+
getContext().logger.debug('[SyncClient] Dropping malformed persisted mutation', {
|
|
1039
|
+
issues: parsed.error.issues.map((i) => i.path.join('.')).join(', '),
|
|
1040
|
+
});
|
|
1041
|
+
continue;
|
|
1042
|
+
}
|
|
1043
|
+
const model = this.objectPool.createFromData(parsed.data.modelData);
|
|
986
1044
|
if (model) {
|
|
987
1045
|
this.pendingMutations.push({
|
|
988
|
-
type:
|
|
1046
|
+
type: parsed.data.type,
|
|
989
1047
|
model,
|
|
990
|
-
timestamp: new Date(
|
|
991
|
-
writeOptions
|
|
1048
|
+
timestamp: new Date(parsed.data.timestamp),
|
|
1049
|
+
...(parsed.data.writeOptions !== undefined
|
|
1050
|
+
? { writeOptions: parsed.data.writeOptions }
|
|
1051
|
+
: {}),
|
|
992
1052
|
});
|
|
993
1053
|
}
|
|
994
1054
|
}
|
|
995
1055
|
}
|
|
996
1056
|
}
|
|
997
|
-
catch (error) {
|
|
1057
|
+
catch (error) {
|
|
1058
|
+
// A restore failure means queued offline writes did NOT rehydrate.
|
|
1059
|
+
// Self-healing is impossible here (the record may be unreadable), but
|
|
1060
|
+
// the failure must be visible for diagnosis instead of silent loss.
|
|
1061
|
+
getContext().logger.debug('[SyncClient] Failed to restore offline mutation queue', {
|
|
1062
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1063
|
+
});
|
|
1064
|
+
getContext().observability.captureTransactionFailure({
|
|
1065
|
+
context: 'restore-mutation-queue',
|
|
1066
|
+
error: error instanceof Error ? error : String(error),
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
998
1069
|
}
|
|
999
1070
|
/**
|
|
1000
1071
|
* Process pending mutations - can be called by SyncedStore when online
|
|
@@ -1055,12 +1126,26 @@ export class SyncClient extends EventEmitter {
|
|
|
1055
1126
|
if (!this.userId || !this.organizationId)
|
|
1056
1127
|
return;
|
|
1057
1128
|
const ctx = { userId: this.userId, organizationId: this.organizationId };
|
|
1129
|
+
// Settlement is delivered via transaction.confirmation, not this promise —
|
|
1130
|
+
// it only rejects when staging itself throws (change extraction, optimistic
|
|
1131
|
+
// apply, store add). That means the write never entered the queue, so
|
|
1132
|
+
// capture it instead of dropping it silently.
|
|
1133
|
+
const captureStagingFailure = (error) => {
|
|
1134
|
+
getContext().observability.captureTransactionFailure({
|
|
1135
|
+
context: `stage-mutation-${mutation.type}`,
|
|
1136
|
+
modelName: mutation.model.getModelName(),
|
|
1137
|
+
modelId: mutation.model.id,
|
|
1138
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
1139
|
+
});
|
|
1140
|
+
};
|
|
1058
1141
|
if (mutation.type === 'update') {
|
|
1059
|
-
this.transactionQueue
|
|
1142
|
+
this.transactionQueue
|
|
1143
|
+
.update(mutation.model, ctx, mutation.capturedChanges, mutation.writeOptions)
|
|
1144
|
+
.catch(captureStagingFailure);
|
|
1060
1145
|
}
|
|
1061
1146
|
else {
|
|
1062
1147
|
const handler = this.transactionQueue[mutation.type].bind(this.transactionQueue);
|
|
1063
|
-
handler(mutation.model, ctx, mutation.writeOptions);
|
|
1148
|
+
handler(mutation.model, ctx, mutation.writeOptions).catch(captureStagingFailure);
|
|
1064
1149
|
}
|
|
1065
1150
|
}
|
|
1066
1151
|
/**
|
|
@@ -1078,7 +1163,7 @@ export class SyncClient extends EventEmitter {
|
|
|
1078
1163
|
? localModel.updatedAt.getTime()
|
|
1079
1164
|
: new Date(localModel.updatedAt).getTime()
|
|
1080
1165
|
: 0;
|
|
1081
|
-
const serverUpdatedAt =
|
|
1166
|
+
const serverUpdatedAt = toEpochMs(serverData.updatedAt);
|
|
1082
1167
|
getContext().logger.debug('Conflict resolution', {
|
|
1083
1168
|
modelId: localModel.id,
|
|
1084
1169
|
modelType: localModel.getModelName(),
|
|
@@ -1114,7 +1199,7 @@ export class SyncClient extends EventEmitter {
|
|
|
1114
1199
|
// Merge: server baseline + local dirty fields win
|
|
1115
1200
|
const merged = { ...serverData, ...(localChanges || {}) };
|
|
1116
1201
|
// Preserve the most recent updatedAt without clearing dirty flags
|
|
1117
|
-
if (serverData
|
|
1202
|
+
if (serverData.updatedAt || localModel.updatedAt) {
|
|
1118
1203
|
const mergedUpdatedAt = new Date(Math.max(localUpdatedAt, serverUpdatedAt));
|
|
1119
1204
|
// updateFromData accepts Date or ISO string for dates
|
|
1120
1205
|
merged.updatedAt = mergedUpdatedAt;
|
|
@@ -1134,7 +1219,8 @@ export class SyncClient extends EventEmitter {
|
|
|
1134
1219
|
}
|
|
1135
1220
|
/**
|
|
1136
1221
|
* Extract critical state fields from server data
|
|
1137
|
-
* These are states that must always be respected, even with local changes
|
|
1222
|
+
* These are states that must always be respected, even with local changes.
|
|
1223
|
+
* The conflict brain reads exactly these known fields — nothing else.
|
|
1138
1224
|
*/
|
|
1139
1225
|
extractCriticalState(serverData) {
|
|
1140
1226
|
const critical = {};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* All SDK classes receive this context at construction time.
|
|
5
5
|
* It bundles every injectable dependency so constructors stay clean.
|
|
6
6
|
*/
|
|
7
|
-
import type { SyncLogger, SyncObservabilityProvider, SyncAnalytics, SessionErrorDetector, OnlineStatusProvider, ModelDebugLoggerContract, MutationExecutor,
|
|
7
|
+
import type { SyncLogger, SyncObservabilityProvider, SyncAnalytics, SessionErrorDetector, OnlineStatusProvider, ModelDebugLoggerContract, MutationExecutor, SyncEngineConfig } from './interfaces/index.js';
|
|
8
8
|
export interface SyncEngineContext {
|
|
9
9
|
/** Structured logger */
|
|
10
10
|
logger: SyncLogger;
|
|
@@ -20,8 +20,6 @@ export interface SyncEngineContext {
|
|
|
20
20
|
modelDebugLogger?: ModelDebugLoggerContract;
|
|
21
21
|
/** Backend mutation transport (GraphQL, REST, etc.) */
|
|
22
22
|
mutationExecutor: MutationExecutor;
|
|
23
|
-
/** Offline mutation replay dispatcher */
|
|
24
|
-
mutationDispatcher: MutationDispatcher;
|
|
25
23
|
/** Application-specific sync configuration */
|
|
26
24
|
config: SyncEngineConfig;
|
|
27
25
|
}
|
|
@@ -26,7 +26,6 @@ export const noopObservability = {
|
|
|
26
26
|
captureReconciliation() { },
|
|
27
27
|
captureDeltaRetryExhausted() { },
|
|
28
28
|
captureWebSocketError() { },
|
|
29
|
-
captureOfflineFlushFailure() { },
|
|
30
29
|
captureSelfHealing() { },
|
|
31
30
|
captureClaim() { },
|
|
32
31
|
captureConflict() { },
|
|
@@ -57,7 +56,7 @@ export const browserOnlineStatus = {
|
|
|
57
56
|
export const defaultSessionErrorDetector = {
|
|
58
57
|
isSessionError(error) {
|
|
59
58
|
if (error && typeof error === 'object' && 'isSessionError' in error) {
|
|
60
|
-
return error.isSessionError
|
|
59
|
+
return error.isSessionError;
|
|
61
60
|
}
|
|
62
61
|
return false;
|
|
63
62
|
},
|
package/dist/agent/Agent.d.ts
CHANGED
|
@@ -141,14 +141,14 @@ export interface AgentMessage {
|
|
|
141
141
|
/** Subset of AI SDK's prepareStep context. */
|
|
142
142
|
export interface PrepareStepContext<M extends AgentMessage = AgentMessage> {
|
|
143
143
|
stepNumber: number;
|
|
144
|
-
steps:
|
|
145
|
-
toolCalls?:
|
|
144
|
+
steps: readonly {
|
|
145
|
+
toolCalls?: readonly {
|
|
146
146
|
toolName: string;
|
|
147
147
|
input?: unknown;
|
|
148
148
|
args?: unknown;
|
|
149
|
-
}
|
|
150
|
-
toolResults?:
|
|
151
|
-
}
|
|
149
|
+
}[];
|
|
150
|
+
toolResults?: readonly unknown[];
|
|
151
|
+
}[];
|
|
152
152
|
messages: M[];
|
|
153
153
|
model?: unknown;
|
|
154
154
|
}
|
|
@@ -164,12 +164,12 @@ export interface StepFinishContext {
|
|
|
164
164
|
stepType?: 'initial' | 'continue' | 'tool-result';
|
|
165
165
|
finishReason?: string;
|
|
166
166
|
text?: string;
|
|
167
|
-
toolCalls?:
|
|
167
|
+
toolCalls?: readonly {
|
|
168
168
|
toolName: string;
|
|
169
169
|
input?: unknown;
|
|
170
170
|
args?: unknown;
|
|
171
|
-
}
|
|
172
|
-
toolResults?:
|
|
171
|
+
}[];
|
|
172
|
+
toolResults?: readonly unknown[];
|
|
173
173
|
usage?: {
|
|
174
174
|
inputTokens?: number;
|
|
175
175
|
outputTokens?: number;
|
|
@@ -230,6 +230,19 @@ export interface WrapToolOptions<TArgs> {
|
|
|
230
230
|
*/
|
|
231
231
|
announceOnExecute?: boolean;
|
|
232
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Console-backed default logger — the SAME gated factory the `Ablo()` client
|
|
235
|
+
* uses (`createConsoleLogger`, threshold from `ABLO_LOG_LEVEL`, default
|
|
236
|
+
* `warn`), tagged `[agent]` so agent-runtime lines are distinguishable. The
|
|
237
|
+
* previous hand-rolled shim ignored the level gate entirely and printed
|
|
238
|
+
* `[perception]` engine internals at debug/info by default. Level is resolved
|
|
239
|
+
* per construction (not at module load) so `ABLO_LOG_LEVEL` set by the host
|
|
240
|
+
* process before building an Agent is honored.
|
|
241
|
+
*
|
|
242
|
+
* Exported for the unit test that pins the gating; consumers pass their own
|
|
243
|
+
* `logger` option instead.
|
|
244
|
+
*/
|
|
245
|
+
export declare function defaultAgentLogger(): SyncLogger;
|
|
233
246
|
export declare class Agent implements PresenceAnnouncer {
|
|
234
247
|
private readonly opts;
|
|
235
248
|
constructor(options: AgentOptions);
|
package/dist/agent/Agent.js
CHANGED
|
@@ -40,17 +40,30 @@
|
|
|
40
40
|
* for custom integrations outside the AI SDK.
|
|
41
41
|
*/
|
|
42
42
|
import { createAgentSession } from './session.js';
|
|
43
|
+
import { createConsoleLogger, resolveLogLevel } from '../client/consoleLogger.js';
|
|
43
44
|
import { AbloValidationError } from '../errors.js';
|
|
44
45
|
// ── Agent ───────────────────────────────────────────────────────
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Console-backed default logger — the SAME gated factory the `Ablo()` client
|
|
48
|
+
* uses (`createConsoleLogger`, threshold from `ABLO_LOG_LEVEL`, default
|
|
49
|
+
* `warn`), tagged `[agent]` so agent-runtime lines are distinguishable. The
|
|
50
|
+
* previous hand-rolled shim ignored the level gate entirely and printed
|
|
51
|
+
* `[perception]` engine internals at debug/info by default. Level is resolved
|
|
52
|
+
* per construction (not at module load) so `ABLO_LOG_LEVEL` set by the host
|
|
53
|
+
* process before building an Agent is honored.
|
|
54
|
+
*
|
|
55
|
+
* Exported for the unit test that pins the gating; consumers pass their own
|
|
56
|
+
* `logger` option instead.
|
|
57
|
+
*/
|
|
58
|
+
export function defaultAgentLogger() {
|
|
59
|
+
const gated = createConsoleLogger(resolveLogLevel());
|
|
60
|
+
return {
|
|
61
|
+
debug: (msg, ...args) => { gated.debug('[agent]', msg, ...args); },
|
|
62
|
+
info: (msg, ...args) => { gated.info('[agent]', msg, ...args); },
|
|
63
|
+
warn: (msg, ...args) => { gated.warn('[agent]', msg, ...args); },
|
|
64
|
+
error: (msg, ...args) => { gated.error('[agent]', msg, ...args); },
|
|
65
|
+
};
|
|
66
|
+
}
|
|
54
67
|
export class Agent {
|
|
55
68
|
opts;
|
|
56
69
|
constructor(options) {
|
|
@@ -63,7 +76,7 @@ export class Agent {
|
|
|
63
76
|
syncGroups: options.syncGroups,
|
|
64
77
|
fetch: options.fetch ?? globalThis.fetch.bind(globalThis),
|
|
65
78
|
timeoutMs: options.timeoutMs ?? 5_000,
|
|
66
|
-
logger: options.logger ??
|
|
79
|
+
logger: options.logger ?? defaultAgentLogger(),
|
|
67
80
|
};
|
|
68
81
|
}
|
|
69
82
|
/**
|
|
@@ -222,9 +235,10 @@ export class Agent {
|
|
|
222
235
|
pendingClaims,
|
|
223
236
|
};
|
|
224
237
|
}
|
|
225
|
-
const body = (await res.json());
|
|
238
|
+
const body = (await (res).json());
|
|
226
239
|
const rows = body.results?.[0];
|
|
227
|
-
|
|
240
|
+
const entity = rows?.[0];
|
|
241
|
+
if (!entity) {
|
|
228
242
|
return {
|
|
229
243
|
stale: true,
|
|
230
244
|
reason: 'not_found',
|
|
@@ -232,7 +246,6 @@ export class Agent {
|
|
|
232
246
|
pendingClaims,
|
|
233
247
|
};
|
|
234
248
|
}
|
|
235
|
-
const entity = rows[0];
|
|
236
249
|
const updatedAtRaw = entity.updated_at ?? entity.updatedAt;
|
|
237
250
|
const lastModifiedBy = entity.updated_by ??
|
|
238
251
|
entity.updatedBy ??
|
|
@@ -38,7 +38,13 @@ export function coordinationContextMiddleware(options) {
|
|
|
38
38
|
return params;
|
|
39
39
|
// Read peer claims on the same target. Synchronous lookup
|
|
40
40
|
// against the engine's reactive claims.others array — no I/O.
|
|
41
|
-
|
|
41
|
+
// Type compares case-insensitively: observed claims carry the WIRE
|
|
42
|
+
// dialect (lowercased typename, `slidedeck`), while callers naturally
|
|
43
|
+
// write the schema typename (`SlideDeck`) — the same normalization the
|
|
44
|
+
// commit plane applies (`modelMap` lookups lowercase, and
|
|
45
|
+
// `targetsOverlap` below already lowercases field/path).
|
|
46
|
+
const wantedType = target.type.toLowerCase();
|
|
47
|
+
const peerClaims = agent.claims.others.filter((claim) => claim.target.type.toLowerCase() === wantedType &&
|
|
42
48
|
claim.target.id === target.id &&
|
|
43
49
|
targetsOverlap(claim.target, target) &&
|
|
44
50
|
!excludeClaimIds.has(claim.id));
|
|
@@ -78,8 +84,8 @@ function targetsOverlap(claimTarget, target) {
|
|
|
78
84
|
*/
|
|
79
85
|
function formatCoordinationNote(claims, target) {
|
|
80
86
|
const entityLabel = target.type.toLowerCase();
|
|
81
|
-
|
|
82
|
-
|
|
87
|
+
const c = claims.length === 1 ? claims[0] : undefined;
|
|
88
|
+
if (c) {
|
|
83
89
|
const details = c.description ? `Declared work: ${c.description}. ` : '';
|
|
84
90
|
return (`<multiplayer_context>\n` +
|
|
85
91
|
`Another participant is currently editing this ${entityLabel}. ` +
|
package/dist/ai-sdk/wrap.d.ts
CHANGED
package/dist/ai-sdk/wrap.js
CHANGED
|
@@ -22,7 +22,14 @@
|
|
|
22
22
|
* prefix lookup so it can ship in the client bundle.
|
|
23
23
|
*/
|
|
24
24
|
import type { exchangeApiKey, mintUserSessionKey, resolveIdentity } from './index.js';
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Structural signature of `client/auth.ts`'s `resolveApiKeyValue` — declared
|
|
27
|
+
* locally (not `typeof`-imported) because `client/auth.ts` runtime-imports
|
|
28
|
+
* {@link classifyCredentialKind} from THIS module; a back-edge here was the
|
|
29
|
+
* SDK's last auth-layer import cycle. The setter shape matches `ApiKeySetter`.
|
|
30
|
+
* `identity.ts` passes the real function; structural typing keeps them bound.
|
|
31
|
+
*/
|
|
32
|
+
type ResolveApiKeyValueFn = (apiKey: string | (() => Promise<string | null>) | null) => Promise<string | null>;
|
|
26
33
|
/**
|
|
27
34
|
* The four Ablo API-key kinds (Stripe-style). Prefix contract — kept in lockstep
|
|
28
35
|
* with `keys/index.ts` `API_KEY_KINDS` / `KIND_BY_PREFIX`, but declared locally
|
|
@@ -52,7 +59,7 @@ export interface CredentialPrimitives {
|
|
|
52
59
|
readonly exchangeApiKey: typeof exchangeApiKey;
|
|
53
60
|
readonly mintUserSessionKey: typeof mintUserSessionKey;
|
|
54
61
|
readonly resolveIdentity: typeof resolveIdentity;
|
|
55
|
-
readonly resolveApiKeyValue:
|
|
62
|
+
readonly resolveApiKeyValue: ResolveApiKeyValueFn;
|
|
56
63
|
}
|
|
57
64
|
export interface ResolveCredentialContext {
|
|
58
65
|
readonly primitives: CredentialPrimitives;
|
|
@@ -143,3 +150,4 @@ export type ResolvedCredential =
|
|
|
143
150
|
* 3. otherwise (identity known) → `explicit` (legacy self-hosted, no round-trip).
|
|
144
151
|
*/
|
|
145
152
|
export declare function resolveCredential(input: ResolveCredentialInput, ctx: ResolveCredentialContext): Promise<ResolvedCredential>;
|
|
153
|
+
export {};
|
|
@@ -6,19 +6,7 @@
|
|
|
6
6
|
* auth. Token refresh writes here once; consumers observe the new value
|
|
7
7
|
* through their getter without being manually patched one by one.
|
|
8
8
|
*/
|
|
9
|
-
|
|
10
|
-
* WebSocket subprotocols used to carry the bearer credential OUT of the URL.
|
|
11
|
-
*
|
|
12
|
-
* Browsers cannot set an `Authorization` header on a WebSocket, so the SDK
|
|
13
|
-
* offers the token as a `Sec-WebSocket-Protocol` value — `ablo.bearer.<token>` —
|
|
14
|
-
* alongside the real `ablo.sync.v1` protocol the server selects. This keeps the
|
|
15
|
-
* credential out of the query string, which ALB access logs, proxies, and
|
|
16
|
-
* browser history capture. The server reads the token from the subprotocol and
|
|
17
|
-
* echoes back ONLY `ablo.sync.v1`, never the token-bearing value. Shared with
|
|
18
|
-
* the sync-server so client and server can never drift on the wire format.
|
|
19
|
-
*/
|
|
20
|
-
export declare const WS_BEARER_SUBPROTOCOL_PREFIX = "ablo.bearer.";
|
|
21
|
-
export declare const WS_SYNC_SUBPROTOCOL = "ablo.sync.v1";
|
|
9
|
+
export { WS_BEARER_SUBPROTOCOL_PREFIX, WS_SYNC_SUBPROTOCOL } from '../wire/protocol.js';
|
|
22
10
|
export interface AuthCredentialSource {
|
|
23
11
|
getAuthToken(): string | null;
|
|
24
12
|
setAuthToken(token: string | null | undefined): void;
|
|
@@ -6,19 +6,12 @@
|
|
|
6
6
|
* auth. Token refresh writes here once; consumers observe the new value
|
|
7
7
|
* through their getter without being manually patched one by one.
|
|
8
8
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* credential out of the query string, which ALB access logs, proxies, and
|
|
16
|
-
* browser history capture. The server reads the token from the subprotocol and
|
|
17
|
-
* echoes back ONLY `ablo.sync.v1`, never the token-bearing value. Shared with
|
|
18
|
-
* the sync-server so client and server can never drift on the wire format.
|
|
19
|
-
*/
|
|
20
|
-
export const WS_BEARER_SUBPROTOCOL_PREFIX = 'ablo.bearer.';
|
|
21
|
-
export const WS_SYNC_SUBPROTOCOL = 'ablo.sync.v1';
|
|
9
|
+
// The WS bearer-subprotocol constants moved to `../wire/protocol.js` — they are
|
|
10
|
+
// the wire contract shared with the sync-server (which imports them via
|
|
11
|
+
// `@abloatai/ablo/wire`, never the root barrel). Re-exported here so
|
|
12
|
+
// existing SDK-side importers (`SyncWebSocket`, `source/connector-protocol`)
|
|
13
|
+
// keep their import path.
|
|
14
|
+
export { WS_BEARER_SUBPROTOCOL_PREFIX, WS_SYNC_SUBPROTOCOL } from '../wire/protocol.js';
|
|
22
15
|
export function createAuthCredentialSource(initialToken) {
|
|
23
16
|
let authToken = normalizeToken(initialToken);
|
|
24
17
|
return {
|