@abloatai/ablo 0.3.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 +208 -0
- package/LICENSE +201 -0
- package/NOTICE +12 -0
- package/README.md +230 -0
- package/dist/BaseSyncedStore.d.ts +709 -0
- package/dist/BaseSyncedStore.js +1843 -0
- package/dist/Database.d.ts +344 -0
- package/dist/Database.js +1259 -0
- package/dist/LazyReferenceCollection.d.ts +181 -0
- package/dist/LazyReferenceCollection.js +460 -0
- package/dist/Model.d.ts +339 -0
- package/dist/Model.js +715 -0
- package/dist/ModelRegistry.d.ts +200 -0
- package/dist/ModelRegistry.js +535 -0
- package/dist/NetworkMonitor.d.ts +27 -0
- package/dist/NetworkMonitor.js +73 -0
- package/dist/ObjectPool.d.ts +202 -0
- package/dist/ObjectPool.js +1106 -0
- package/dist/SyncClient.d.ts +489 -0
- package/dist/SyncClient.js +1555 -0
- package/dist/SyncEngineContext.d.ts +46 -0
- package/dist/SyncEngineContext.js +74 -0
- package/dist/adapters/alwaysOnline.d.ts +16 -0
- package/dist/adapters/alwaysOnline.js +19 -0
- package/dist/adapters/inMemoryStorage.d.ts +30 -0
- package/dist/adapters/inMemoryStorage.js +94 -0
- package/dist/agent/Agent.d.ts +358 -0
- package/dist/agent/Agent.js +500 -0
- package/dist/agent/index.d.ts +115 -0
- package/dist/agent/index.js +128 -0
- package/dist/agent/session.d.ts +90 -0
- package/dist/agent/session.js +156 -0
- package/dist/agent/types.d.ts +73 -0
- package/dist/agent/types.js +10 -0
- package/dist/ai-sdk/coordination-context.d.ts +51 -0
- package/dist/ai-sdk/coordination-context.js +107 -0
- package/dist/ai-sdk/index.d.ts +68 -0
- package/dist/ai-sdk/index.js +68 -0
- package/dist/ai-sdk/intent-broadcast.d.ts +77 -0
- package/dist/ai-sdk/intent-broadcast.js +72 -0
- package/dist/ai-sdk/wrap.d.ts +67 -0
- package/dist/ai-sdk/wrap.js +45 -0
- package/dist/api/index.d.ts +10 -0
- package/dist/api/index.js +9 -0
- package/dist/auth/index.d.ts +137 -0
- package/dist/auth/index.js +246 -0
- package/dist/client/Ablo.d.ts +835 -0
- package/dist/client/Ablo.js +1440 -0
- package/dist/client/ApiClient.d.ts +200 -0
- package/dist/client/ApiClient.js +659 -0
- package/dist/client/auth.d.ts +79 -0
- package/dist/client/auth.js +81 -0
- package/dist/client/createInternalComponents.d.ts +44 -0
- package/dist/client/createInternalComponents.js +88 -0
- package/dist/client/createModelProxy.d.ts +152 -0
- package/dist/client/createModelProxy.js +199 -0
- package/dist/client/identity.d.ts +63 -0
- package/dist/client/identity.js +156 -0
- package/dist/client/index.d.ts +36 -0
- package/dist/client/index.js +33 -0
- package/dist/client/persistence.d.ts +7 -0
- package/dist/client/persistence.js +11 -0
- package/dist/client/validateAbloOptions.d.ts +42 -0
- package/dist/client/validateAbloOptions.js +43 -0
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.js +12 -0
- package/dist/context.d.ts +27 -0
- package/dist/context.js +58 -0
- package/dist/core/DatabaseManager.d.ts +108 -0
- package/dist/core/DatabaseManager.js +361 -0
- package/dist/core/QueryProcessor.d.ts +77 -0
- package/dist/core/QueryProcessor.js +262 -0
- package/dist/core/QueryView.d.ts +64 -0
- package/dist/core/QueryView.js +219 -0
- package/dist/core/StoreManager.d.ts +131 -0
- package/dist/core/StoreManager.js +334 -0
- package/dist/core/ViewRegistry.d.ts +20 -0
- package/dist/core/ViewRegistry.js +55 -0
- package/dist/core/index.d.ts +34 -0
- package/dist/core/index.js +59 -0
- package/dist/core/openIDBWithTimeout.d.ts +27 -0
- package/dist/core/openIDBWithTimeout.js +63 -0
- package/dist/core/query-utils.d.ts +37 -0
- package/dist/core/query-utils.js +60 -0
- package/dist/errors.d.ts +235 -0
- package/dist/errors.js +243 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +82 -0
- package/dist/interfaces/headless.d.ts +95 -0
- package/dist/interfaces/headless.js +41 -0
- package/dist/interfaces/index.d.ts +321 -0
- package/dist/interfaces/index.js +8 -0
- package/dist/mutators/RecordingTransaction.d.ts +36 -0
- package/dist/mutators/RecordingTransaction.js +216 -0
- package/dist/mutators/Transaction.d.ts +48 -0
- package/dist/mutators/Transaction.js +64 -0
- package/dist/mutators/UndoManager.d.ts +114 -0
- package/dist/mutators/UndoManager.js +143 -0
- package/dist/mutators/defineMutators.d.ts +55 -0
- package/dist/mutators/defineMutators.js +28 -0
- package/dist/policy/index.d.ts +19 -0
- package/dist/policy/index.js +18 -0
- package/dist/policy/types.d.ts +74 -0
- package/dist/policy/types.js +17 -0
- package/dist/principal.d.ts +44 -0
- package/dist/principal.js +49 -0
- package/dist/query/client.d.ts +43 -0
- package/dist/query/client.js +84 -0
- package/dist/query/index.d.ts +6 -0
- package/dist/query/index.js +5 -0
- package/dist/query/types.d.ts +143 -0
- package/dist/query/types.js +36 -0
- package/dist/react/AbloProvider.d.ts +205 -0
- package/dist/react/AbloProvider.js +398 -0
- package/dist/react/ClientSideSuspense.d.ts +36 -0
- package/dist/react/ClientSideSuspense.js +17 -0
- package/dist/react/DefaultFallback.d.ts +24 -0
- package/dist/react/DefaultFallback.js +43 -0
- package/dist/react/SyncGroupProvider.d.ts +19 -0
- package/dist/react/SyncGroupProvider.js +44 -0
- package/dist/react/context.d.ts +161 -0
- package/dist/react/context.js +35 -0
- package/dist/react/index.d.ts +64 -0
- package/dist/react/index.js +73 -0
- package/dist/react/internalContext.d.ts +35 -0
- package/dist/react/internalContext.js +3 -0
- package/dist/react/useAblo.d.ts +72 -0
- package/dist/react/useAblo.js +63 -0
- package/dist/react/useCurrentUserId.d.ts +21 -0
- package/dist/react/useCurrentUserId.js +33 -0
- package/dist/react/useErrorListener.d.ts +20 -0
- package/dist/react/useErrorListener.js +39 -0
- package/dist/react/useIntent.d.ts +29 -0
- package/dist/react/useIntent.js +42 -0
- package/dist/react/useMutate.d.ts +83 -0
- package/dist/react/useMutate.js +122 -0
- package/dist/react/useMutationFailureListener.d.ts +26 -0
- package/dist/react/useMutationFailureListener.js +38 -0
- package/dist/react/useMutators.d.ts +56 -0
- package/dist/react/useMutators.js +66 -0
- package/dist/react/usePresence.d.ts +32 -0
- package/dist/react/usePresence.js +41 -0
- package/dist/react/useQuery.d.ts +123 -0
- package/dist/react/useQuery.js +145 -0
- package/dist/react/useReactive.d.ts +35 -0
- package/dist/react/useReactive.js +111 -0
- package/dist/react/useReader.d.ts +69 -0
- package/dist/react/useReader.js +73 -0
- package/dist/react/useSyncStatus.d.ts +61 -0
- package/dist/react/useSyncStatus.js +76 -0
- package/dist/react/useUndoScope.d.ts +36 -0
- package/dist/react/useUndoScope.js +73 -0
- package/dist/realtime/index.d.ts +10 -0
- package/dist/realtime/index.js +9 -0
- package/dist/schema/field.d.ts +134 -0
- package/dist/schema/field.js +264 -0
- package/dist/schema/index.d.ts +29 -0
- package/dist/schema/index.js +38 -0
- package/dist/schema/model.d.ts +326 -0
- package/dist/schema/model.js +89 -0
- package/dist/schema/queries.d.ts +203 -0
- package/dist/schema/queries.js +145 -0
- package/dist/schema/relation.d.ts +172 -0
- package/dist/schema/relation.js +104 -0
- package/dist/schema/schema.d.ts +259 -0
- package/dist/schema/schema.js +188 -0
- package/dist/schema/sugar.d.ts +129 -0
- package/dist/schema/sugar.js +94 -0
- package/dist/source/index.d.ts +423 -0
- package/dist/source/index.js +320 -0
- package/dist/source/pushQueue.d.ts +112 -0
- package/dist/source/pushQueue.js +249 -0
- package/dist/stores/ObjectStore.d.ts +103 -0
- package/dist/stores/ObjectStore.js +371 -0
- package/dist/stores/ObjectStoreContract.d.ts +39 -0
- package/dist/stores/ObjectStoreContract.js +1 -0
- package/dist/stores/SyncActionStore.d.ts +101 -0
- package/dist/stores/SyncActionStore.js +481 -0
- package/dist/sync/BootstrapHelper.d.ts +127 -0
- package/dist/sync/BootstrapHelper.js +434 -0
- package/dist/sync/ConnectionManager.d.ts +136 -0
- package/dist/sync/ConnectionManager.js +465 -0
- package/dist/sync/HydrationCoordinator.d.ts +137 -0
- package/dist/sync/HydrationCoordinator.js +468 -0
- package/dist/sync/NetworkProbe.d.ts +43 -0
- package/dist/sync/NetworkProbe.js +113 -0
- package/dist/sync/OfflineFlush.d.ts +9 -0
- package/dist/sync/OfflineFlush.js +22 -0
- package/dist/sync/OfflineTransactionStore.d.ts +37 -0
- package/dist/sync/OfflineTransactionStore.js +263 -0
- package/dist/sync/SyncWebSocket.d.ts +663 -0
- package/dist/sync/SyncWebSocket.js +1336 -0
- package/dist/sync/createIntentStream.d.ts +33 -0
- package/dist/sync/createIntentStream.js +243 -0
- package/dist/sync/createPresenceStream.d.ts +46 -0
- package/dist/sync/createPresenceStream.js +192 -0
- package/dist/sync/createSnapshot.d.ts +33 -0
- package/dist/sync/createSnapshot.js +124 -0
- package/dist/sync/participants.d.ts +114 -0
- package/dist/sync/participants.js +336 -0
- package/dist/sync/schemas.d.ts +79 -0
- package/dist/sync/schemas.js +78 -0
- package/dist/testing/fixtures/bootstrap.d.ts +45 -0
- package/dist/testing/fixtures/bootstrap.js +53 -0
- package/dist/testing/fixtures/deltas.d.ts +86 -0
- package/dist/testing/fixtures/deltas.js +139 -0
- package/dist/testing/fixtures/models.d.ts +82 -0
- package/dist/testing/fixtures/models.js +270 -0
- package/dist/testing/helpers/react-wrapper.d.ts +66 -0
- package/dist/testing/helpers/react-wrapper.js +64 -0
- package/dist/testing/helpers/sync-engine-harness.d.ts +55 -0
- package/dist/testing/helpers/sync-engine-harness.js +70 -0
- package/dist/testing/helpers/wait.d.ts +25 -0
- package/dist/testing/helpers/wait.js +44 -0
- package/dist/testing/index.d.ts +21 -0
- package/dist/testing/index.js +32 -0
- package/dist/testing/mocks/MockMutationExecutor.d.ts +65 -0
- package/dist/testing/mocks/MockMutationExecutor.js +139 -0
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +20 -0
- package/dist/testing/mocks/MockNetworkMonitor.js +46 -0
- package/dist/testing/mocks/MockSyncContext.d.ts +64 -0
- package/dist/testing/mocks/MockSyncContext.js +100 -0
- package/dist/testing/mocks/MockSyncStore.d.ts +88 -0
- package/dist/testing/mocks/MockSyncStore.js +171 -0
- package/dist/testing/mocks/MockWebSocket.d.ts +66 -0
- package/dist/testing/mocks/MockWebSocket.js +117 -0
- package/dist/transactions/OptimisticEchoTracker.d.ts +82 -0
- package/dist/transactions/OptimisticEchoTracker.js +104 -0
- package/dist/transactions/TransactionQueue.d.ts +499 -0
- package/dist/transactions/TransactionQueue.js +1895 -0
- package/dist/transactions/index.d.ts +16 -0
- package/dist/transactions/index.js +7 -0
- package/dist/transactions/mutation-error-handler.d.ts +5 -0
- package/dist/transactions/mutation-error-handler.js +39 -0
- package/dist/types/global.d.ts +107 -0
- package/dist/types/global.js +38 -0
- package/dist/types/index.d.ts +241 -0
- package/dist/types/index.js +70 -0
- package/dist/types/streams.d.ts +495 -0
- package/dist/types/streams.js +11 -0
- package/dist/utils/asyncIterator.d.ts +41 -0
- package/dist/utils/asyncIterator.js +142 -0
- package/dist/utils/duration.d.ts +28 -0
- package/dist/utils/duration.js +47 -0
- package/dist/utils/mobx-setup.d.ts +42 -0
- package/dist/utils/mobx-setup.js +381 -0
- package/docs/api-keys.md +24 -0
- package/docs/api.md +230 -0
- package/docs/audit.md +81 -0
- package/docs/capabilities.md +163 -0
- package/docs/client-behavior.md +202 -0
- package/docs/data-sources.md +214 -0
- package/docs/examples/agent-human.md +84 -0
- package/docs/examples/ai-sdk-tool.md +92 -0
- package/docs/examples/existing-python-backend.md +249 -0
- package/docs/examples/nextjs.md +88 -0
- package/docs/examples/server-agent.md +86 -0
- package/docs/guarantees.md +148 -0
- package/docs/index.md +97 -0
- package/docs/integration-guide.md +493 -0
- package/docs/interaction-model.md +140 -0
- package/docs/mcp/claude-code.md +43 -0
- package/docs/mcp/cursor.md +53 -0
- package/docs/mcp/windsurf.md +46 -0
- package/docs/mcp.md +59 -0
- package/docs/quickstart.md +152 -0
- package/docs/react.md +115 -0
- package/docs/roadmap.md +45 -0
- package/examples/README.md +54 -0
- package/examples/data-source/README.md +102 -0
- package/examples/data-source/ablo-driver.ts +89 -0
- package/examples/data-source/customer-server.ts +208 -0
- package/examples/data-source/run.ts +101 -0
- package/examples/data-source/schema.ts +25 -0
- package/examples/quickstart.ts +54 -0
- package/examples/tsconfig.json +16 -0
- package/llms.txt +143 -0
- package/package.json +147 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueryView<T> — Incrementally maintained materialized view for a single query.
|
|
3
|
+
*
|
|
4
|
+
* Instead of scanning the full collection on every render, a QueryView
|
|
5
|
+
* builds the result set once, then maintains it via handleAdded /
|
|
6
|
+
* handleUpdated / handleRemoved calls from the ViewRegistry.
|
|
7
|
+
*
|
|
8
|
+
* `results` is a stable MobX observable array — components observe it
|
|
9
|
+
* directly and receive granular updates (splice/push, never replacement).
|
|
10
|
+
*/
|
|
11
|
+
import { type IObservableArray } from 'mobx';
|
|
12
|
+
import { ModelScope } from '../types/index.js';
|
|
13
|
+
import type { ObjectPool } from '../ObjectPool.js';
|
|
14
|
+
import type { ViewRegistry } from './ViewRegistry.js';
|
|
15
|
+
import type { IncrementalView } from './query-utils.js';
|
|
16
|
+
export interface QueryViewOptions<T> {
|
|
17
|
+
where?: Partial<T>;
|
|
18
|
+
filter?: (entity: T) => boolean;
|
|
19
|
+
orderBy?: keyof T & string;
|
|
20
|
+
order?: 'asc' | 'desc';
|
|
21
|
+
limit?: number;
|
|
22
|
+
offset?: number;
|
|
23
|
+
scope?: ModelScope;
|
|
24
|
+
}
|
|
25
|
+
export declare class QueryView<T extends Record<string, unknown>> implements IncrementalView {
|
|
26
|
+
/** The full (unlimited) internal result set, kept sorted. */
|
|
27
|
+
private _internal;
|
|
28
|
+
/**
|
|
29
|
+
* Public observable result set — windowed by offset/limit.
|
|
30
|
+
* Components observe this directly.
|
|
31
|
+
*/
|
|
32
|
+
readonly results: IObservableArray<T>;
|
|
33
|
+
private readonly typename;
|
|
34
|
+
private readonly pool;
|
|
35
|
+
private readonly registry;
|
|
36
|
+
private readonly whereEntries;
|
|
37
|
+
private readonly filterFn;
|
|
38
|
+
private readonly sortKey;
|
|
39
|
+
private readonly sortDir;
|
|
40
|
+
private readonly limitN;
|
|
41
|
+
private readonly offsetN;
|
|
42
|
+
private readonly scope;
|
|
43
|
+
/** FK-index optimization: if the where clause targets a single FK-indexed field. */
|
|
44
|
+
private readonly fkField;
|
|
45
|
+
private readonly fkValue;
|
|
46
|
+
private disposed;
|
|
47
|
+
constructor(typename: string, pool: ObjectPool, registry: ViewRegistry, options?: QueryViewOptions<T>);
|
|
48
|
+
private initialScan;
|
|
49
|
+
handleAdded(model: Record<string, unknown>): void;
|
|
50
|
+
handleUpdated(model: Record<string, unknown>): void;
|
|
51
|
+
handleRemoved(modelId: string): void;
|
|
52
|
+
dispose(): void;
|
|
53
|
+
/** Check whether an entity passes both `where` and `filter`. */
|
|
54
|
+
private matchesFilter;
|
|
55
|
+
/** Insert entity into _internal at the correct sorted position. */
|
|
56
|
+
private insertSorted;
|
|
57
|
+
/** Find index of entity by id in _internal. */
|
|
58
|
+
private findIndexById;
|
|
59
|
+
/**
|
|
60
|
+
* Synchronize the public `results` array with the windowed slice of
|
|
61
|
+
* `_internal`. Mutates in place to keep the reference stable.
|
|
62
|
+
*/
|
|
63
|
+
private syncWindow;
|
|
64
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueryView<T> — Incrementally maintained materialized view for a single query.
|
|
3
|
+
*
|
|
4
|
+
* Instead of scanning the full collection on every render, a QueryView
|
|
5
|
+
* builds the result set once, then maintains it via handleAdded /
|
|
6
|
+
* handleUpdated / handleRemoved calls from the ViewRegistry.
|
|
7
|
+
*
|
|
8
|
+
* `results` is a stable MobX observable array — components observe it
|
|
9
|
+
* directly and receive granular updates (splice/push, never replacement).
|
|
10
|
+
*/
|
|
11
|
+
import { observable, runInAction } from 'mobx';
|
|
12
|
+
import { modelAsRow } from '../Model.js';
|
|
13
|
+
import { ModelScope } from '../types/index.js';
|
|
14
|
+
import { compareValues, binaryInsertionIndex, findIndexById, } from './query-utils.js';
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// QueryView
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
export class QueryView {
|
|
19
|
+
/** The full (unlimited) internal result set, kept sorted. */
|
|
20
|
+
_internal;
|
|
21
|
+
/**
|
|
22
|
+
* Public observable result set — windowed by offset/limit.
|
|
23
|
+
* Components observe this directly.
|
|
24
|
+
*/
|
|
25
|
+
results;
|
|
26
|
+
typename;
|
|
27
|
+
pool;
|
|
28
|
+
registry;
|
|
29
|
+
whereEntries;
|
|
30
|
+
filterFn;
|
|
31
|
+
sortKey;
|
|
32
|
+
sortDir;
|
|
33
|
+
limitN;
|
|
34
|
+
offsetN;
|
|
35
|
+
scope;
|
|
36
|
+
/** FK-index optimization: if the where clause targets a single FK-indexed field. */
|
|
37
|
+
fkField;
|
|
38
|
+
fkValue;
|
|
39
|
+
disposed = false;
|
|
40
|
+
constructor(typename, pool, registry, options = {}) {
|
|
41
|
+
this.typename = typename;
|
|
42
|
+
this.pool = pool;
|
|
43
|
+
this.registry = registry;
|
|
44
|
+
// Parse options
|
|
45
|
+
this.whereEntries = options.where
|
|
46
|
+
? Object.entries(options.where).filter(([, v]) => v !== undefined)
|
|
47
|
+
: null;
|
|
48
|
+
this.filterFn = options.filter ?? null;
|
|
49
|
+
this.sortKey = options.orderBy ?? null;
|
|
50
|
+
this.sortDir = options.order === 'desc' ? -1 : 1;
|
|
51
|
+
this.limitN = options.limit;
|
|
52
|
+
this.offsetN = options.offset ?? 0;
|
|
53
|
+
this.scope = options.scope ?? ModelScope.live;
|
|
54
|
+
// Check for FK-index optimization: single-field where with an indexed FK
|
|
55
|
+
this.fkField = null;
|
|
56
|
+
this.fkValue = null;
|
|
57
|
+
if (this.whereEntries &&
|
|
58
|
+
this.whereEntries.length === 1 &&
|
|
59
|
+
typeof this.whereEntries[0][1] === 'string') {
|
|
60
|
+
const [field, value] = this.whereEntries[0];
|
|
61
|
+
if (pool.hasForeignKeyIndex(typename, field)) {
|
|
62
|
+
this.fkField = field;
|
|
63
|
+
this.fkValue = value;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Create observable arrays (shallow — models are already observable)
|
|
67
|
+
this._internal = observable.array([], { deep: false });
|
|
68
|
+
this.results = observable.array([], { deep: false });
|
|
69
|
+
// Perform initial scan
|
|
70
|
+
this.initialScan();
|
|
71
|
+
// Register for incremental updates
|
|
72
|
+
this.registry.register(typename, this);
|
|
73
|
+
}
|
|
74
|
+
// -----------------------------------------------------------------------
|
|
75
|
+
// Initial scan
|
|
76
|
+
// -----------------------------------------------------------------------
|
|
77
|
+
initialScan() {
|
|
78
|
+
let candidates;
|
|
79
|
+
if (this.fkField && this.fkValue) {
|
|
80
|
+
// O(1) FK-index lookup
|
|
81
|
+
candidates = this.pool.getByForeignKey(this.typename, this.fkField, this.fkValue);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
candidates = this.pool.getByTypeName(this.typename, this.scope);
|
|
85
|
+
}
|
|
86
|
+
const matching = [];
|
|
87
|
+
for (const model of candidates) {
|
|
88
|
+
const entity = modelAsRow(model);
|
|
89
|
+
if (this.matchesFilter(entity)) {
|
|
90
|
+
matching.push(entity);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Sort
|
|
94
|
+
if (this.sortKey) {
|
|
95
|
+
const key = this.sortKey;
|
|
96
|
+
const dir = this.sortDir;
|
|
97
|
+
matching.sort((a, b) => compareValues(a[key], b[key], dir));
|
|
98
|
+
}
|
|
99
|
+
runInAction(() => {
|
|
100
|
+
this._internal.replace(matching);
|
|
101
|
+
this.syncWindow();
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
// -----------------------------------------------------------------------
|
|
105
|
+
// Incremental update handlers (called by ViewRegistry)
|
|
106
|
+
// -----------------------------------------------------------------------
|
|
107
|
+
handleAdded(model) {
|
|
108
|
+
if (this.disposed)
|
|
109
|
+
return;
|
|
110
|
+
const entity = model;
|
|
111
|
+
const passesFilter = this.matchesFilter(entity);
|
|
112
|
+
if (!passesFilter)
|
|
113
|
+
return;
|
|
114
|
+
runInAction(() => {
|
|
115
|
+
this.insertSorted(entity);
|
|
116
|
+
this.syncWindow();
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
handleUpdated(model) {
|
|
120
|
+
if (this.disposed)
|
|
121
|
+
return;
|
|
122
|
+
const entity = model;
|
|
123
|
+
const id = entity['id'];
|
|
124
|
+
const idx = id !== undefined ? this.findIndexById(id) : -1;
|
|
125
|
+
const matchesNow = this.matchesFilter(entity);
|
|
126
|
+
runInAction(() => {
|
|
127
|
+
if (idx >= 0 && matchesNow) {
|
|
128
|
+
// Was in view and still matches.
|
|
129
|
+
// Models are plain objects (not MobX-observable), so we must notify
|
|
130
|
+
// observers that the data changed. Splice-in-place triggers the
|
|
131
|
+
// observable array to fire, which causes useQuery consumers to
|
|
132
|
+
// re-render with fresh property values.
|
|
133
|
+
this._internal.splice(idx, 1);
|
|
134
|
+
this.insertSorted(entity);
|
|
135
|
+
}
|
|
136
|
+
else if (idx >= 0 && !matchesNow) {
|
|
137
|
+
// Was in view but no longer matches — remove
|
|
138
|
+
this._internal.splice(idx, 1);
|
|
139
|
+
}
|
|
140
|
+
else if (idx < 0 && matchesNow) {
|
|
141
|
+
// Wasn't in view but now matches — add
|
|
142
|
+
this.insertSorted(entity);
|
|
143
|
+
}
|
|
144
|
+
this.syncWindow();
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
handleRemoved(modelId) {
|
|
148
|
+
if (this.disposed)
|
|
149
|
+
return;
|
|
150
|
+
const idx = this.findIndexById(modelId);
|
|
151
|
+
if (idx < 0)
|
|
152
|
+
return;
|
|
153
|
+
runInAction(() => {
|
|
154
|
+
this._internal.splice(idx, 1);
|
|
155
|
+
this.syncWindow();
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
// -----------------------------------------------------------------------
|
|
159
|
+
// Dispose
|
|
160
|
+
// -----------------------------------------------------------------------
|
|
161
|
+
dispose() {
|
|
162
|
+
if (this.disposed)
|
|
163
|
+
return;
|
|
164
|
+
this.disposed = true;
|
|
165
|
+
this.registry.unregister(this.typename, this);
|
|
166
|
+
}
|
|
167
|
+
// -----------------------------------------------------------------------
|
|
168
|
+
// Private helpers
|
|
169
|
+
// -----------------------------------------------------------------------
|
|
170
|
+
/** Check whether an entity passes both `where` and `filter`. */
|
|
171
|
+
matchesFilter(entity) {
|
|
172
|
+
// Note: scope is tracked per-entry in the ObjectPool, not on the model.
|
|
173
|
+
// The initial scan handles scope via getByTypeName(typename, scope).
|
|
174
|
+
// Incremental notifications from the pool are scope-appropriate since
|
|
175
|
+
// add/upsert/remove reflect the pool's authoritative scope tracking.
|
|
176
|
+
// Where clause: declarative field matching
|
|
177
|
+
if (this.whereEntries) {
|
|
178
|
+
for (const [key, value] of this.whereEntries) {
|
|
179
|
+
if (entity[key] !== value)
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Arbitrary predicate
|
|
184
|
+
if (this.filterFn) {
|
|
185
|
+
if (!this.filterFn(entity))
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
/** Insert entity into _internal at the correct sorted position. */
|
|
191
|
+
insertSorted(entity) {
|
|
192
|
+
if (this.sortKey) {
|
|
193
|
+
const idx = binaryInsertionIndex(this._internal, entity, this.sortKey, this.sortDir);
|
|
194
|
+
this._internal.splice(idx, 0, entity);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
this._internal.push(entity);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/** Find index of entity by id in _internal. */
|
|
201
|
+
findIndexById(id) {
|
|
202
|
+
return findIndexById(this._internal, id);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Synchronize the public `results` array with the windowed slice of
|
|
206
|
+
* `_internal`. Mutates in place to keep the reference stable.
|
|
207
|
+
*/
|
|
208
|
+
syncWindow() {
|
|
209
|
+
const start = this.offsetN;
|
|
210
|
+
const end = this.limitN !== undefined ? start + this.limitN : this._internal.length;
|
|
211
|
+
const windowed = this._internal.slice(start, end);
|
|
212
|
+
// Minimal diff: only splice if contents differ
|
|
213
|
+
if (windowed.length === this.results.length &&
|
|
214
|
+
windowed.every((item, i) => this.results[i] === item)) {
|
|
215
|
+
return; // no change
|
|
216
|
+
}
|
|
217
|
+
this.results.replace(windowed);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linear Sync Engine - Store Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages all ObjectStore instances for registered models.
|
|
5
|
+
* Creates appropriate store types based on model load strategies.
|
|
6
|
+
* Follows Linear's architecture with 80+ ObjectStore instances.
|
|
7
|
+
*/
|
|
8
|
+
import { ModelRegistry } from '../ModelRegistry.js';
|
|
9
|
+
import { ObjectStore } from '../stores/ObjectStore.js';
|
|
10
|
+
import { SyncActionStore } from '../stores/SyncActionStore.js';
|
|
11
|
+
import { LoadStrategy } from '../types/index.js';
|
|
12
|
+
/**
|
|
13
|
+
* StoreManager - Central manager for all ObjectStore instances
|
|
14
|
+
*
|
|
15
|
+
* Key responsibilities:
|
|
16
|
+
* - Creates ObjectStore instances for each registered model
|
|
17
|
+
* - Manages store lifecycle and readiness
|
|
18
|
+
* - Provides unified interface for database operations
|
|
19
|
+
* - Handles store-specific optimizations based on load strategies
|
|
20
|
+
*/
|
|
21
|
+
export declare class StoreManager {
|
|
22
|
+
private stores;
|
|
23
|
+
private syncactionStore;
|
|
24
|
+
private db;
|
|
25
|
+
private isInitialized;
|
|
26
|
+
private modelRegistry;
|
|
27
|
+
constructor(modelRegistry: ModelRegistry);
|
|
28
|
+
/**
|
|
29
|
+
* Initialize all stores for registered models
|
|
30
|
+
*/
|
|
31
|
+
initializeStores(db: IDBDatabase): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Create ObjectStore for a specific model
|
|
34
|
+
*/
|
|
35
|
+
private createStoreForModel;
|
|
36
|
+
/**
|
|
37
|
+
* Create stores (tables) in IndexedDB
|
|
38
|
+
*/
|
|
39
|
+
createStores(db: IDBDatabase, transaction: IDBTransaction): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Create special tables (sync_action_table, model_table, model_table_partial, __meta, __transactions)
|
|
42
|
+
*/
|
|
43
|
+
private createSpecialTables;
|
|
44
|
+
/**
|
|
45
|
+
* Get ObjectStore for a model
|
|
46
|
+
*/
|
|
47
|
+
getStore(modelName: string): ObjectStore | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Get SyncactionStore instance
|
|
50
|
+
*/
|
|
51
|
+
getSyncactionStore(): SyncActionStore | null;
|
|
52
|
+
/**
|
|
53
|
+
* Get all stores
|
|
54
|
+
*/
|
|
55
|
+
getAllStores(): Map<string, ObjectStore>;
|
|
56
|
+
/**
|
|
57
|
+
* Check readiness of all stores
|
|
58
|
+
*/
|
|
59
|
+
checkReadinessOfStores(): Promise<{
|
|
60
|
+
ready: boolean;
|
|
61
|
+
readyStores: string[];
|
|
62
|
+
notReadyStores: string[];
|
|
63
|
+
totalStores: number;
|
|
64
|
+
}>;
|
|
65
|
+
/**
|
|
66
|
+
* Check if ANY data store has at least one record.
|
|
67
|
+
*
|
|
68
|
+
* This is the Zero-style cache-validity check: if the stores are empty,
|
|
69
|
+
* the sync cursor (lastSyncId) is invalid regardless of what the metadata
|
|
70
|
+
* says. The cursor and the data must be co-located — no data means no
|
|
71
|
+
* cursor, which means full bootstrap.
|
|
72
|
+
*
|
|
73
|
+
* Samples up to 3 stores to avoid a full scan. If any store has records,
|
|
74
|
+
* returns true (we have cached data worth preserving).
|
|
75
|
+
*/
|
|
76
|
+
hasAnyData(): Promise<boolean>;
|
|
77
|
+
/**
|
|
78
|
+
* Get store type distribution for debugging
|
|
79
|
+
*/
|
|
80
|
+
getStoreTypeDistribution(): {
|
|
81
|
+
full: number;
|
|
82
|
+
partial: number;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Get stores by load strategy
|
|
86
|
+
*/
|
|
87
|
+
getStoresByStrategy(strategy: LoadStrategy): ObjectStore[];
|
|
88
|
+
/**
|
|
89
|
+
* Get models to load for bootstrapping
|
|
90
|
+
*/
|
|
91
|
+
getModelsToLoad(): {
|
|
92
|
+
instant: string[];
|
|
93
|
+
lazy: string[];
|
|
94
|
+
partial: string[];
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Perform maintenance on all stores
|
|
98
|
+
*/
|
|
99
|
+
performMaintenance(): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Clear all stores
|
|
102
|
+
*/
|
|
103
|
+
clearAllStores(): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Mark all stores as closing to prevent new operations
|
|
106
|
+
* Called before database connection is closed
|
|
107
|
+
*/
|
|
108
|
+
markAllStoresAsClosing(): void;
|
|
109
|
+
/**
|
|
110
|
+
* Get comprehensive statistics
|
|
111
|
+
*/
|
|
112
|
+
getComprehensiveStats(): Promise<{
|
|
113
|
+
totalStores: number;
|
|
114
|
+
storeTypes: {
|
|
115
|
+
full: number;
|
|
116
|
+
partial: number;
|
|
117
|
+
};
|
|
118
|
+
readiness: {
|
|
119
|
+
ready: number;
|
|
120
|
+
notReady: number;
|
|
121
|
+
};
|
|
122
|
+
totalRecords: number;
|
|
123
|
+
storeDetails: Array<{
|
|
124
|
+
modelName: string;
|
|
125
|
+
storeName: string;
|
|
126
|
+
strategy: LoadStrategy;
|
|
127
|
+
ready: boolean;
|
|
128
|
+
count: number;
|
|
129
|
+
}>;
|
|
130
|
+
}>;
|
|
131
|
+
}
|