@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/Database.d.ts
CHANGED
|
@@ -77,12 +77,12 @@ export interface BootstrapResult {
|
|
|
77
77
|
* was disconnected — without this, DELETE deltas persist to IDB but
|
|
78
78
|
* ghost entities linger in the pool until a full reload.
|
|
79
79
|
*/
|
|
80
|
-
deltaResults?:
|
|
80
|
+
deltaResults?: {
|
|
81
81
|
action: 'add' | 'update' | 'remove' | 'archive' | 'verify';
|
|
82
82
|
modelName: string;
|
|
83
83
|
modelId: string;
|
|
84
84
|
data?: ModelData | null;
|
|
85
|
-
}
|
|
85
|
+
}[];
|
|
86
86
|
}
|
|
87
87
|
export declare class Database {
|
|
88
88
|
private databaseManager;
|
|
@@ -236,7 +236,7 @@ export declare class Database {
|
|
|
236
236
|
* - Skips stale updates for entities that will be/were deleted
|
|
237
237
|
* - Prevents 404 errors from fetching already-deleted entities
|
|
238
238
|
*/
|
|
239
|
-
processDeltaBatch(deltas:
|
|
239
|
+
processDeltaBatch(deltas: {
|
|
240
240
|
syncId?: number;
|
|
241
241
|
/**
|
|
242
242
|
* Includes 'G' and 'S' defensively — they're routed upstream and
|
|
@@ -257,14 +257,14 @@ export declare class Database {
|
|
|
257
257
|
* have a client transaction.
|
|
258
258
|
*/
|
|
259
259
|
transactionId?: string;
|
|
260
|
-
}
|
|
261
|
-
results:
|
|
260
|
+
}[]): Promise<{
|
|
261
|
+
results: {
|
|
262
262
|
action: 'add' | 'update' | 'remove' | 'archive' | 'verify';
|
|
263
263
|
modelName: string;
|
|
264
264
|
modelId: string;
|
|
265
265
|
data?: ModelData | null;
|
|
266
266
|
transactionId?: string;
|
|
267
|
-
}
|
|
267
|
+
}[];
|
|
268
268
|
/**
|
|
269
269
|
* Highest syncId whose IDB store transaction actually committed in this
|
|
270
270
|
* batch. The runtime delta cursor (WS `lastSyncId`, server-side
|
|
@@ -292,7 +292,6 @@ export declare class Database {
|
|
|
292
292
|
/** Read workspace metadata from IDB (returns null if db not open). */
|
|
293
293
|
getWorkspaceMetadata(): Promise<WorkspaceMetadata | null>;
|
|
294
294
|
getLastSyncId(): Promise<number>;
|
|
295
|
-
getVersionVector(): Promise<Record<string, number> | null>;
|
|
296
295
|
updateWorkspaceMetadata(metadata: Partial<WorkspaceMetadata>): Promise<void>;
|
|
297
296
|
/** Transaction persistence for offline/retry support.
|
|
298
297
|
* Returns either the IDB-backed ObjectStore or its in-memory twin
|
|
@@ -332,13 +331,13 @@ export declare class Database {
|
|
|
332
331
|
notReady: number;
|
|
333
332
|
};
|
|
334
333
|
totalRecords: number;
|
|
335
|
-
storeDetails:
|
|
334
|
+
storeDetails: {
|
|
336
335
|
modelName: string;
|
|
337
336
|
storeName: string;
|
|
338
337
|
strategy: LoadStrategy;
|
|
339
338
|
ready: boolean;
|
|
340
339
|
count: number;
|
|
341
|
-
}
|
|
340
|
+
}[];
|
|
342
341
|
};
|
|
343
342
|
metadata: WorkspaceMetadata | null;
|
|
344
343
|
}>;
|
package/dist/Database.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { DatabaseManager } from './core/DatabaseManager.js';
|
|
6
6
|
import { StoreManager } from './core/StoreManager.js';
|
|
7
|
+
import { ModelRegistry } from './ModelRegistry.js';
|
|
7
8
|
import { LoadStrategy } from './types/index.js';
|
|
8
9
|
import { getContext } from './context.js';
|
|
9
10
|
import { AbloConnectionError, AbloValidationError } from './errors.js';
|
|
@@ -105,7 +106,7 @@ export class Database {
|
|
|
105
106
|
logPreservedFields(modelName, modelId, existing, delta) {
|
|
106
107
|
if (modelName === 'Activity')
|
|
107
108
|
return;
|
|
108
|
-
const requiredFields = this.essentialFields[modelName]
|
|
109
|
+
const requiredFields = this.essentialFields[modelName] ?? [];
|
|
109
110
|
const preserved = requiredFields.filter((field) => existing[field] !== undefined && delta[field] === undefined);
|
|
110
111
|
if (preserved.length > 0) {
|
|
111
112
|
getContext().logger.debug('[Database] UPDATE merged - preserved fields', {
|
|
@@ -324,7 +325,7 @@ export class Database {
|
|
|
324
325
|
type,
|
|
325
326
|
modelsToLoad,
|
|
326
327
|
lastSyncId,
|
|
327
|
-
syncGroups: metadata?.syncGroups
|
|
328
|
+
syncGroups: metadata?.syncGroups ?? [],
|
|
328
329
|
};
|
|
329
330
|
}
|
|
330
331
|
/**
|
|
@@ -360,7 +361,7 @@ export class Database {
|
|
|
360
361
|
lastSyncId: bootstrapData.lastSyncId,
|
|
361
362
|
hasModels: !!bootstrapData.models,
|
|
362
363
|
hasDeltas: !!bootstrapData.deltas,
|
|
363
|
-
deltaCount: bootstrapData.deltaCount
|
|
364
|
+
deltaCount: bootstrapData.deltaCount ?? 0,
|
|
364
365
|
});
|
|
365
366
|
// ✅ Only clear AFTER successful fetch (transactional safety)
|
|
366
367
|
// IMPORTANT: Clear if the SERVER says it's a full snapshot, regardless of what we asked.
|
|
@@ -369,7 +370,7 @@ export class Database {
|
|
|
369
370
|
}
|
|
370
371
|
// Handle partial bootstrap (delta batch)
|
|
371
372
|
if (bootstrapData.type === 'partial') {
|
|
372
|
-
const deltas = bootstrapData.deltas
|
|
373
|
+
const deltas = bootstrapData.deltas ?? [];
|
|
373
374
|
getContext().logger.info('Processing partial bootstrap with delta batch', {
|
|
374
375
|
deltaCount: deltas.length,
|
|
375
376
|
fromSyncId: requirements.lastSyncId,
|
|
@@ -783,7 +784,7 @@ export class Database {
|
|
|
783
784
|
const deleteSyncId = deleteSyncIds.get(key);
|
|
784
785
|
if (deleteSyncId !== undefined) {
|
|
785
786
|
// DELETE exists for this entity
|
|
786
|
-
const deltaSyncId = delta.syncId
|
|
787
|
+
const deltaSyncId = delta.syncId ?? 0;
|
|
787
788
|
if (deleteSyncId >= deltaSyncId) {
|
|
788
789
|
// DELETE has equal or higher syncId - skip this UPDATE/INSERT
|
|
789
790
|
getContext().logger.debug('[Database.processDeltaBatch] Skipping stale delta (DELETE wins)', {
|
|
@@ -989,8 +990,8 @@ export class Database {
|
|
|
989
990
|
}
|
|
990
991
|
// Wait for transaction to complete
|
|
991
992
|
await new Promise((resolve, reject) => {
|
|
992
|
-
tx.oncomplete = () => resolve();
|
|
993
|
-
tx.onerror = () => reject(tx.error);
|
|
993
|
+
tx.oncomplete = () => { resolve(); };
|
|
994
|
+
tx.onerror = () => { reject(tx.error); };
|
|
994
995
|
});
|
|
995
996
|
// Only commit staged results to the global results if the transaction succeeded.
|
|
996
997
|
// Also advance `highestPersistedSyncId` ONLY for deltas in this successful tx
|
|
@@ -1122,13 +1123,7 @@ export class Database {
|
|
|
1122
1123
|
return 0;
|
|
1123
1124
|
}
|
|
1124
1125
|
const metadata = await this.databaseManager.getWorkspaceMetadata(this.workspaceDb);
|
|
1125
|
-
return metadata?.lastSyncId
|
|
1126
|
-
}
|
|
1127
|
-
async getVersionVector() {
|
|
1128
|
-
if (!this.workspaceDb)
|
|
1129
|
-
return null;
|
|
1130
|
-
const metadata = await this.databaseManager.getWorkspaceMetadata(this.workspaceDb);
|
|
1131
|
-
return (metadata?.versions || null);
|
|
1126
|
+
return metadata?.lastSyncId ?? 0;
|
|
1132
1127
|
}
|
|
1133
1128
|
async updateWorkspaceMetadata(metadata) {
|
|
1134
1129
|
// In-memory mode: store in local variable
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* data on-demand with intelligent caching and batching.
|
|
6
6
|
*/
|
|
7
7
|
import { makeObservable, observable, action, computed, onBecomeObserved, onBecomeUnobserved, } from 'mobx';
|
|
8
|
+
import { Database } from './Database.js';
|
|
9
|
+
import { ObjectPool } from './ObjectPool.js';
|
|
8
10
|
import { getActiveRegistry } from './ModelRegistry.js';
|
|
9
11
|
import { AbloValidationError } from './errors.js';
|
|
10
12
|
/**
|
|
@@ -289,6 +291,9 @@ export class LazyReferenceCollection {
|
|
|
289
291
|
this.objectPool.add(model);
|
|
290
292
|
}
|
|
291
293
|
if (model) {
|
|
294
|
+
// The by-name registry lookup types instances as the base
|
|
295
|
+
// `Model`; this collection's `modelName` IS `T`'s registered
|
|
296
|
+
// name (fixed at construction), so the instance is a `T`.
|
|
292
297
|
models.push(model);
|
|
293
298
|
}
|
|
294
299
|
}
|
package/dist/Model.js
CHANGED
|
@@ -70,7 +70,7 @@ export class Model {
|
|
|
70
70
|
_observedCollections = new Set();
|
|
71
71
|
constructor(data = {}) {
|
|
72
72
|
// Always generate permanent UUID on client
|
|
73
|
-
this.id = data.id
|
|
73
|
+
this.id = data.id ?? Model.generateId();
|
|
74
74
|
this.clientId = this.id; // No more temp IDs!
|
|
75
75
|
// Ensure dates are Date objects, not strings
|
|
76
76
|
this.createdAt = data.createdAt
|
|
@@ -91,7 +91,7 @@ export class Model {
|
|
|
91
91
|
: data.createdAt
|
|
92
92
|
? new Date(this.createdAt)
|
|
93
93
|
: new Date();
|
|
94
|
-
this.syncStatus = data.syncStatus
|
|
94
|
+
this.syncStatus = data.syncStatus ?? 'pending';
|
|
95
95
|
}
|
|
96
96
|
/**
|
|
97
97
|
* Generate unique ID
|
|
@@ -340,9 +340,7 @@ export class Model {
|
|
|
340
340
|
* Add validation rule
|
|
341
341
|
*/
|
|
342
342
|
addValidationRule(propName, rule) {
|
|
343
|
-
|
|
344
|
-
this.validationRules[propName] = [];
|
|
345
|
-
}
|
|
343
|
+
this.validationRules[propName] ??= [];
|
|
346
344
|
this.validationRules[propName].push(rule);
|
|
347
345
|
}
|
|
348
346
|
/**
|
|
@@ -575,7 +573,7 @@ export class Model {
|
|
|
575
573
|
syncStatus: this.syncStatus,
|
|
576
574
|
};
|
|
577
575
|
if (this.archivedAt !== undefined) {
|
|
578
|
-
result.archivedAt = this.archivedAt?.toISOString()
|
|
576
|
+
result.archivedAt = this.archivedAt?.toISOString() ?? null;
|
|
579
577
|
}
|
|
580
578
|
if (properties) {
|
|
581
579
|
const self = this;
|
|
@@ -616,7 +614,7 @@ export class Model {
|
|
|
616
614
|
const className = this.constructor.name;
|
|
617
615
|
// Use consumer-provided fallback map from config (replaces hardcoded Prisma name map)
|
|
618
616
|
const fallbackMap = getContext().config.classNameFallbackMap;
|
|
619
|
-
return fallbackMap[className]
|
|
617
|
+
return fallbackMap[className] ?? className.replace(/Model$/, '');
|
|
620
618
|
}
|
|
621
619
|
/**
|
|
622
620
|
* Read a field value by name. Runtime-safe dynamic field access —
|
|
@@ -809,7 +807,7 @@ export class Model {
|
|
|
809
807
|
*/
|
|
810
808
|
static fromJSON(data) {
|
|
811
809
|
// Support both __class and __typename, and handle both old and new naming
|
|
812
|
-
const modelIdentifier = data.__typename
|
|
810
|
+
const modelIdentifier = data.__typename ?? data.__class ?? data.modelName;
|
|
813
811
|
if (!modelIdentifier) {
|
|
814
812
|
throw new AbloValidationError('Model identifier (__typename, __class, or modelName) not found in data', { code: 'model_identifier_missing' });
|
|
815
813
|
}
|
package/dist/ModelRegistry.d.ts
CHANGED
|
@@ -9,7 +9,26 @@
|
|
|
9
9
|
* - Comprehensive error reporting
|
|
10
10
|
* - Best practices from Linear Sync Engine
|
|
11
11
|
*/
|
|
12
|
-
import { ModelMetadata, PropertyMetadata, ReferenceMetadata, LoadStrategy } from './types/index.js';
|
|
12
|
+
import { type ModelMetadata, type PropertyMetadata, type ReferenceMetadata, LoadStrategy } from './types/index.js';
|
|
13
|
+
import type { Model } from './Model.js';
|
|
14
|
+
import type { ConcreteModelConstructor } from './BaseSyncedStore.js';
|
|
15
|
+
/**
|
|
16
|
+
* What callers may hand to {@link ModelRegistry.registerModel}: any concrete
|
|
17
|
+
* `Model` subclass constructor. `never[]` params make every subclass
|
|
18
|
+
* constructor assignable (construct-signature params are contravariant).
|
|
19
|
+
*/
|
|
20
|
+
export type ModelClassInput = new (...args: never[]) => Model;
|
|
21
|
+
/**
|
|
22
|
+
* What the registry hands BACK: a registered model class — concretely
|
|
23
|
+
* constructible with an optional data row (`ConcreteModelConstructor`, the
|
|
24
|
+
* SDK's existing Model-vs-row construction seam) and carrying `Model`'s
|
|
25
|
+
* statics (`fromJSON`, …). `Omit<typeof Model, never>` keeps the statics
|
|
26
|
+
* while stripping the ABSTRACT construct signature (mapped types drop
|
|
27
|
+
* construct signatures), so `new registry.getModelByName(n)!(...)` is legal.
|
|
28
|
+
* The one cast from {@link ModelClassInput} lives at the validated
|
|
29
|
+
* registration boundary below.
|
|
30
|
+
*/
|
|
31
|
+
export type RegisteredModelClass = Omit<typeof Model, never> & ConcreteModelConstructor<Model>;
|
|
13
32
|
/**
|
|
14
33
|
* Extended ReferenceMetadata with additional Linear-style options
|
|
15
34
|
*/
|
|
@@ -73,7 +92,7 @@ export declare class ModelRegistry {
|
|
|
73
92
|
/**
|
|
74
93
|
* Register a model with validation
|
|
75
94
|
*/
|
|
76
|
-
registerModel(name: string, constructor:
|
|
95
|
+
registerModel(name: string, constructor: ModelClassInput, metadata?: ModelMetadata): void;
|
|
77
96
|
/**
|
|
78
97
|
* Register property with validation
|
|
79
98
|
*/
|
|
@@ -95,7 +114,7 @@ export declare class ModelRegistry {
|
|
|
95
114
|
/** Get all models with specific load strategy. */
|
|
96
115
|
getModelsByLoadStrategy(strategy: LoadStrategy): string[];
|
|
97
116
|
/** Get model name from constructor (production-safe). */
|
|
98
|
-
getModelNameFromConstructor(constructor:
|
|
117
|
+
getModelNameFromConstructor(constructor: unknown): string | undefined;
|
|
99
118
|
/** Get properties for a model. */
|
|
100
119
|
getPropertiesForModel(modelName: string): Map<string, PropertyMetadata>;
|
|
101
120
|
/**
|
|
@@ -103,7 +122,7 @@ export declare class ModelRegistry {
|
|
|
103
122
|
*/
|
|
104
123
|
getRegisteredModelNames(): string[];
|
|
105
124
|
/** Get model constructor by name */
|
|
106
|
-
getModelByName(name: string):
|
|
125
|
+
getModelByName(name: string): RegisteredModelClass | undefined;
|
|
107
126
|
/** Check if model is registered */
|
|
108
127
|
hasModel(name: string): boolean;
|
|
109
128
|
/** Get model metadata by name */
|
|
@@ -117,10 +136,10 @@ export declare class ModelRegistry {
|
|
|
117
136
|
/** Get back-references for a child model */
|
|
118
137
|
getBackReferences(childModelName: string): BackReferenceMetadata[];
|
|
119
138
|
/** Get child models for a parent */
|
|
120
|
-
getChildModels(parentModelName: string):
|
|
139
|
+
getChildModels(parentModelName: string): {
|
|
121
140
|
childModel: string;
|
|
122
141
|
foreignKey: string;
|
|
123
|
-
}
|
|
142
|
+
}[];
|
|
124
143
|
/**
|
|
125
144
|
* Calculate schema hash using crypto
|
|
126
145
|
*/
|
package/dist/ModelRegistry.js
CHANGED
|
@@ -43,7 +43,9 @@ export class ModelRegistry {
|
|
|
43
43
|
properties = new Map();
|
|
44
44
|
references = new Map();
|
|
45
45
|
pendingReferences = new Map();
|
|
46
|
-
// 🔧 PROPER FIX: Static mapping from constructor to model name
|
|
46
|
+
// 🔧 PROPER FIX: Static mapping from constructor to model name.
|
|
47
|
+
// Keyed `unknown` on purpose: lookups arrive as `this.constructor`
|
|
48
|
+
// (Function) and as typed model classes — both accepted without casts.
|
|
47
49
|
constructorToModelName = new Map();
|
|
48
50
|
// LINEAR PATTERN: BackReferences for cascade-aware transaction handling.
|
|
49
51
|
// Maps childModelName → BackReferenceMetadata[] (which parent models
|
|
@@ -65,13 +67,14 @@ export class ModelRegistry {
|
|
|
65
67
|
if (typeof constructor !== 'function') {
|
|
66
68
|
throw new AbloValidationError(`Model ${name} constructor must be a function`, { code: 'registry_invalid_constructor' });
|
|
67
69
|
}
|
|
68
|
-
|
|
70
|
+
const prototype = constructor.prototype;
|
|
71
|
+
if (!prototype || typeof prototype !== 'object') {
|
|
69
72
|
throw new AbloValidationError(`Model ${name} constructor must have a prototype`, { code: 'registry_invalid_constructor' });
|
|
70
73
|
}
|
|
71
74
|
// Check for required methods
|
|
72
75
|
const required = ['updateFromData', 'toJSON', 'getModelName'];
|
|
73
76
|
for (const method of required) {
|
|
74
|
-
if (typeof
|
|
77
|
+
if (typeof prototype[method] !== 'function') {
|
|
75
78
|
getContext().logger.debug('Model missing required method', name, { method });
|
|
76
79
|
}
|
|
77
80
|
}
|
|
@@ -159,8 +162,12 @@ export class ModelRegistry {
|
|
|
159
162
|
return;
|
|
160
163
|
}
|
|
161
164
|
getContext().logger.debug('Registering model', name);
|
|
162
|
-
// Register
|
|
163
|
-
|
|
165
|
+
// Register. The one cast in this file: input is any Model-subclass
|
|
166
|
+
// constructor (validated above); registered classes are concrete
|
|
167
|
+
// subclasses constructible with `data?` and carrying Model's statics —
|
|
168
|
+
// the shape `RegisteredModelClass` names for every read site.
|
|
169
|
+
const modelClass = constructor;
|
|
170
|
+
this.models.set(name, modelClass);
|
|
164
171
|
this.modelMetadata.set(name, metadata);
|
|
165
172
|
// 🔧 PROPER FIX: Create reverse mapping from constructor to model name
|
|
166
173
|
this.constructorToModelName.set(constructor, name);
|
|
@@ -291,7 +298,6 @@ export class ModelRegistry {
|
|
|
291
298
|
return Array.from(this.models.keys());
|
|
292
299
|
}
|
|
293
300
|
/** Get model constructor by name */
|
|
294
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
295
301
|
getModelByName(name) {
|
|
296
302
|
return this.models.get(name);
|
|
297
303
|
}
|
package/dist/NetworkMonitor.js
CHANGED
package/dist/ObjectPool.js
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* Pure memory management without database or registry dependencies.
|
|
5
5
|
* Uses static ModelRegistry for model class lookup only.
|
|
6
6
|
*/
|
|
7
|
-
import { makeObservable, observable, action, computed, runInAction } from 'mobx';
|
|
7
|
+
import { makeObservable, observable, action, computed, runInAction, set } from 'mobx';
|
|
8
|
+
import { Model } from './Model.js';
|
|
9
|
+
import { ModelRegistry } from './ModelRegistry.js';
|
|
8
10
|
import { getContext } from './context.js';
|
|
9
11
|
import { AbloValidationError } from './errors.js';
|
|
10
12
|
import { ModelScope } from './types/index.js';
|
|
@@ -203,7 +205,7 @@ export class ObjectPool {
|
|
|
203
205
|
}
|
|
204
206
|
// Check if model already exists to prevent duplicates
|
|
205
207
|
const existingEntry = this.entries.get(id);
|
|
206
|
-
if (existingEntry
|
|
208
|
+
if (existingEntry?.model && !existingEntry.model.disposed) {
|
|
207
209
|
// Model already exists and is valid, update its scope if needed
|
|
208
210
|
if (existingEntry.scope !== scope) {
|
|
209
211
|
runInAction(() => {
|
|
@@ -320,7 +322,7 @@ export class ObjectPool {
|
|
|
320
322
|
}
|
|
321
323
|
// Skip if model already exists and is valid
|
|
322
324
|
const existingEntry = this.entries.get(id);
|
|
323
|
-
if (existingEntry
|
|
325
|
+
if (existingEntry?.model && !existingEntry.model.disposed) {
|
|
324
326
|
if (existingEntry.scope !== scope) {
|
|
325
327
|
this.entries.set(id, { ...existingEntry, scope });
|
|
326
328
|
this.accessTimes.set(id, now);
|
|
@@ -881,6 +883,10 @@ export class ObjectPool {
|
|
|
881
883
|
if (this.gcTimer)
|
|
882
884
|
return;
|
|
883
885
|
this.gcTimer = setInterval(() => this.gc(), this.config.gcInterval);
|
|
886
|
+
// Don't hold a headless Node process open just for pool GC — without
|
|
887
|
+
// this, an agent that never calls disconnect() can never exit. No-op in
|
|
888
|
+
// browsers (where setInterval returns a number without `unref`).
|
|
889
|
+
this.gcTimer.unref?.();
|
|
884
890
|
}
|
|
885
891
|
stopGC() {
|
|
886
892
|
if (this.gcTimer) {
|
package/dist/SyncClient.d.ts
CHANGED
|
@@ -117,6 +117,10 @@ export declare class SyncClient extends EventEmitter {
|
|
|
117
117
|
* will deliver the missing deltas and naturally confirm the transaction.
|
|
118
118
|
*/
|
|
119
119
|
private setupAwaitingTransactionPersistence;
|
|
120
|
+
/** Persist an unconfirmed transaction to IDB (never rejects — failures are captured). */
|
|
121
|
+
private persistAwaitingTransaction;
|
|
122
|
+
/** Drop the persisted awaiting-row once confirmed (never rejects). */
|
|
123
|
+
private removeAwaitingTransaction;
|
|
120
124
|
/**
|
|
121
125
|
* Initialize sync client with authentication
|
|
122
126
|
*/
|
|
@@ -254,7 +258,12 @@ export declare class SyncClient extends EventEmitter {
|
|
|
254
258
|
*/
|
|
255
259
|
private persistMutationQueue;
|
|
256
260
|
/**
|
|
257
|
-
* Restore mutation queue from IndexedDB
|
|
261
|
+
* Restore mutation queue from IndexedDB.
|
|
262
|
+
*
|
|
263
|
+
* The persisted record was written by a PREVIOUS session (possibly an older
|
|
264
|
+
* SDK build), so each entry is validated at this replay boundary (T1.8):
|
|
265
|
+
* corrupt entries are dropped + logged at debug, and a failure never
|
|
266
|
+
* vanishes into an empty catch — offline write survival must be observable.
|
|
258
267
|
*/
|
|
259
268
|
private restoreMutationQueue;
|
|
260
269
|
/**
|
|
@@ -278,10 +287,11 @@ export declare class SyncClient extends EventEmitter {
|
|
|
278
287
|
* CRITICAL: Always respects certain server states (deletes, deactivations)
|
|
279
288
|
* even when there are local changes, to maintain data consistency.
|
|
280
289
|
*/
|
|
281
|
-
resolveConflicts(localModel: Model, serverData:
|
|
290
|
+
resolveConflicts(localModel: Model, serverData: Record<string, unknown>): Model;
|
|
282
291
|
/**
|
|
283
292
|
* Extract critical state fields from server data
|
|
284
|
-
* These are states that must always be respected, even with local changes
|
|
293
|
+
* These are states that must always be respected, even with local changes.
|
|
294
|
+
* The conflict brain reads exactly these known fields — nothing else.
|
|
285
295
|
*/
|
|
286
296
|
private extractCriticalState;
|
|
287
297
|
/**
|
|
@@ -482,7 +492,7 @@ export declare class SyncClient extends EventEmitter {
|
|
|
482
492
|
* consumers — `Ablo` itself is the public surface.
|
|
483
493
|
*/
|
|
484
494
|
getTransactionQueue(): TransactionQueue;
|
|
485
|
-
applyDeltaBatchToPool(dbResults:
|
|
495
|
+
applyDeltaBatchToPool(dbResults: {
|
|
486
496
|
action: string;
|
|
487
497
|
modelName: string;
|
|
488
498
|
modelId: string;
|
|
@@ -495,7 +505,7 @@ export declare class SyncClient extends EventEmitter {
|
|
|
495
505
|
* schema-derived deltas, etc.) don't have a client transaction.
|
|
496
506
|
*/
|
|
497
507
|
transactionId?: string;
|
|
498
|
-
}
|
|
508
|
+
}[], enrichRelations: (modelName: string, data: Record<string, unknown>) => Record<string, unknown>): void;
|
|
499
509
|
/**
|
|
500
510
|
* Apply bootstrap data to the ObjectPool with ghost removal.
|
|
501
511
|
* Owns: model creation, batch upsert, ghost detection + removal.
|