@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,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Duration parser — `'3m'`, `'24h'`, `500` (ms), `'15s'`, etc.
|
|
3
|
+
*
|
|
4
|
+
* The same TTL flavor every config-driven tool uses (Vercel's `ms`,
|
|
5
|
+
* Zod's `.duration()`, a hundred CLIs). Zero deps, one regex, three
|
|
6
|
+
* units that cover everything the SDK needs:
|
|
7
|
+
*
|
|
8
|
+
* - `'500ms'` → 500 ms
|
|
9
|
+
* - `'30s'` → 30 000 ms
|
|
10
|
+
* - `'3m'` → 180 000 ms
|
|
11
|
+
* - `'24h'` → 86 400 000 ms
|
|
12
|
+
*
|
|
13
|
+
* Back-compat escape hatch: plain numbers are kept as-is and
|
|
14
|
+
* interpreted in the caller's existing unit (seconds for TTL APIs).
|
|
15
|
+
* This lets us retrofit the string form to every `ttlSeconds` field
|
|
16
|
+
* without breaking numeric callers — the wrapper below branches on
|
|
17
|
+
* the input type.
|
|
18
|
+
*/
|
|
19
|
+
export type Duration = number | `${number}ms` | `${number}s` | `${number}m` | `${number}h`;
|
|
20
|
+
/**
|
|
21
|
+
* Parse a duration expressed as a number-of-seconds OR a unit-suffixed
|
|
22
|
+
* string. Returns milliseconds. A bare number is interpreted as
|
|
23
|
+
* **seconds** (matches the existing `ttlSeconds` semantics — prevents
|
|
24
|
+
* silent breakage when a caller migrates from numeric to string).
|
|
25
|
+
*/
|
|
26
|
+
export declare function toMs(input: Duration): number;
|
|
27
|
+
/** Convenience: same as `toMs` but divides out to seconds. */
|
|
28
|
+
export declare function toSeconds(input: Duration): number;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Duration parser — `'3m'`, `'24h'`, `500` (ms), `'15s'`, etc.
|
|
3
|
+
*
|
|
4
|
+
* The same TTL flavor every config-driven tool uses (Vercel's `ms`,
|
|
5
|
+
* Zod's `.duration()`, a hundred CLIs). Zero deps, one regex, three
|
|
6
|
+
* units that cover everything the SDK needs:
|
|
7
|
+
*
|
|
8
|
+
* - `'500ms'` → 500 ms
|
|
9
|
+
* - `'30s'` → 30 000 ms
|
|
10
|
+
* - `'3m'` → 180 000 ms
|
|
11
|
+
* - `'24h'` → 86 400 000 ms
|
|
12
|
+
*
|
|
13
|
+
* Back-compat escape hatch: plain numbers are kept as-is and
|
|
14
|
+
* interpreted in the caller's existing unit (seconds for TTL APIs).
|
|
15
|
+
* This lets us retrofit the string form to every `ttlSeconds` field
|
|
16
|
+
* without breaking numeric callers — the wrapper below branches on
|
|
17
|
+
* the input type.
|
|
18
|
+
*/
|
|
19
|
+
const PATTERN = /^(\d+(?:\.\d+)?)(ms|s|m|h)$/;
|
|
20
|
+
const UNIT_MS = {
|
|
21
|
+
ms: 1,
|
|
22
|
+
s: 1_000,
|
|
23
|
+
m: 60_000,
|
|
24
|
+
h: 3_600_000,
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Parse a duration expressed as a number-of-seconds OR a unit-suffixed
|
|
28
|
+
* string. Returns milliseconds. A bare number is interpreted as
|
|
29
|
+
* **seconds** (matches the existing `ttlSeconds` semantics — prevents
|
|
30
|
+
* silent breakage when a caller migrates from numeric to string).
|
|
31
|
+
*/
|
|
32
|
+
export function toMs(input) {
|
|
33
|
+
if (typeof input === 'number')
|
|
34
|
+
return input * 1_000;
|
|
35
|
+
const match = PATTERN.exec(input);
|
|
36
|
+
if (!match) {
|
|
37
|
+
throw new Error(`Invalid duration "${input}" — expected number (seconds) or ` +
|
|
38
|
+
`a string like "500ms" | "30s" | "3m" | "24h".`);
|
|
39
|
+
}
|
|
40
|
+
const value = Number(match[1]);
|
|
41
|
+
const unit = match[2];
|
|
42
|
+
return value * UNIT_MS[unit];
|
|
43
|
+
}
|
|
44
|
+
/** Convenience: same as `toMs` but divides out to seconds. */
|
|
45
|
+
export function toSeconds(input) {
|
|
46
|
+
return Math.floor(toMs(input) / 1_000);
|
|
47
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M1 Helper - Simplified MobX Setup
|
|
3
|
+
*
|
|
4
|
+
* Fixed version that doesn't conflict with existing getters/setters
|
|
5
|
+
*/
|
|
6
|
+
import { type AnnotationMapEntry } from 'mobx';
|
|
7
|
+
import { PropertyMetadata, ReferenceMetadata } from '../types/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* The internal contract M1 relies on. Models invoke M1 from inside
|
|
10
|
+
* their constructor, where these fields are guaranteed to exist on
|
|
11
|
+
* `this`. Declared here as the bound on M1's generic so the body can
|
|
12
|
+
* reference them without `as any` casts. Each field is optional so a
|
|
13
|
+
* partial Model implementation (e.g., a test fixture) still satisfies
|
|
14
|
+
* the bound.
|
|
15
|
+
*/
|
|
16
|
+
interface M1Target {
|
|
17
|
+
_hasCustomObservability?: boolean;
|
|
18
|
+
_isConstructing?: boolean;
|
|
19
|
+
_extraMobxAnnotations?: Record<string, AnnotationMapEntry>;
|
|
20
|
+
setupObservability?(): void;
|
|
21
|
+
propertyChanged?(name: string, oldValue: unknown, newValue: unknown): void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* M1 - Make properties observable with proper MobX setup
|
|
25
|
+
*
|
|
26
|
+
* Simplified version that respects existing getters/setters
|
|
27
|
+
*/
|
|
28
|
+
export declare function M1<T extends M1Target>(target: T, propertyMetadata: Map<string, PropertyMetadata>, referenceMetadata?: Map<string, ReferenceMetadata>): void;
|
|
29
|
+
/**
|
|
30
|
+
* Helper to make a class observable
|
|
31
|
+
* For classes that don't have custom observability
|
|
32
|
+
*/
|
|
33
|
+
export declare function makeModelObservable(modelClass: any, propertyMetadata: Map<string, PropertyMetadata>, referenceMetadata?: Map<string, any>): any;
|
|
34
|
+
/**
|
|
35
|
+
* Utility to check if a property is observable
|
|
36
|
+
*/
|
|
37
|
+
export declare function isObservableProperty(target: any, propName: string, propertyMetadata: Map<string, PropertyMetadata>): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Utility to get computed properties
|
|
40
|
+
*/
|
|
41
|
+
export declare function getComputedProperties(propertyMetadata: Map<string, PropertyMetadata>): string[];
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M1 Helper - Simplified MobX Setup
|
|
3
|
+
*
|
|
4
|
+
* Fixed version that doesn't conflict with existing getters/setters
|
|
5
|
+
*/
|
|
6
|
+
import { observable, makeObservable, action, computed, observe, } from 'mobx';
|
|
7
|
+
import { PropertyType } from '../types/index.js';
|
|
8
|
+
import { getContext } from '../context.js';
|
|
9
|
+
/**
|
|
10
|
+
* M1 - Make properties observable with proper MobX setup
|
|
11
|
+
*
|
|
12
|
+
* Simplified version that respects existing getters/setters
|
|
13
|
+
*/
|
|
14
|
+
export function M1(target, propertyMetadata, referenceMetadata) {
|
|
15
|
+
// MobX accepts an annotations map keyed by PropertyKey. We build it
|
|
16
|
+
// from the runtime metadata, so the keys are strings — not
|
|
17
|
+
// statically derivable from `keyof T`.
|
|
18
|
+
const annotations = {};
|
|
19
|
+
// Helper to check if property has a getter
|
|
20
|
+
const hasGetter = (propName) => {
|
|
21
|
+
let obj = target;
|
|
22
|
+
while (obj) {
|
|
23
|
+
const descriptor = Object.getOwnPropertyDescriptor(obj, propName);
|
|
24
|
+
if (descriptor && descriptor.get)
|
|
25
|
+
return true;
|
|
26
|
+
obj = Object.getPrototypeOf(obj);
|
|
27
|
+
if (obj === Object.prototype)
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
};
|
|
32
|
+
// Helper to check if property has a setter
|
|
33
|
+
const hasSetter = (propName) => {
|
|
34
|
+
let obj = target;
|
|
35
|
+
while (obj) {
|
|
36
|
+
const descriptor = Object.getOwnPropertyDescriptor(obj, propName);
|
|
37
|
+
if (descriptor && descriptor.set)
|
|
38
|
+
return true;
|
|
39
|
+
obj = Object.getPrototypeOf(obj);
|
|
40
|
+
if (obj === Object.prototype)
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
};
|
|
45
|
+
// Skip if target has its own observability setup
|
|
46
|
+
// This allows models like Task to handle their own MobX setup
|
|
47
|
+
if (target.setupObservability || target._hasCustomObservability) {
|
|
48
|
+
getContext().modelDebugLogger?.logDebug(`${target.constructor.name} has custom observability, skipping M1`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Process each property based on its type
|
|
52
|
+
for (const [propName, metadata] of propertyMetadata) {
|
|
53
|
+
const hasCustomGetter = hasGetter(propName);
|
|
54
|
+
const hasCustomSetter = hasSetter(propName);
|
|
55
|
+
// If property has custom getter/setter, respect it
|
|
56
|
+
if (hasCustomGetter && hasCustomSetter) {
|
|
57
|
+
// Property is fully managed, skip it
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
switch (metadata.type) {
|
|
61
|
+
case PropertyType.property:
|
|
62
|
+
case PropertyType.ephemeralProperty:
|
|
63
|
+
if (hasCustomGetter) {
|
|
64
|
+
// Has getter but no setter - mark as computed
|
|
65
|
+
annotations[propName] = computed;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Pick the cheapest observability that still captures
|
|
69
|
+
// reactivity at the granularity consumers subscribe to. Default
|
|
70
|
+
// `observable` is deep, which on JSON-blob fields produces a
|
|
71
|
+
// recursive atom tree (catastrophic on chart specs, ProseMirror
|
|
72
|
+
// docs, style maps) — see PropertyMetadata.observability docs.
|
|
73
|
+
switch (metadata.observability) {
|
|
74
|
+
case 'ref':
|
|
75
|
+
annotations[propName] = observable.ref;
|
|
76
|
+
break;
|
|
77
|
+
case 'shallow':
|
|
78
|
+
annotations[propName] = observable.shallow;
|
|
79
|
+
break;
|
|
80
|
+
case 'deep':
|
|
81
|
+
case undefined:
|
|
82
|
+
default:
|
|
83
|
+
annotations[propName] = observable;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
case PropertyType.reference:
|
|
89
|
+
// Foreign key ID property
|
|
90
|
+
const idPropName = propName.endsWith('Id') ? propName : `${propName}Id`;
|
|
91
|
+
if (!hasGetter(idPropName) && !hasSetter(idPropName)) {
|
|
92
|
+
annotations[idPropName] = observable;
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
case PropertyType.referenceModel:
|
|
96
|
+
// Computed getter for referenced model
|
|
97
|
+
if (!hasCustomGetter && !hasCustomSetter) {
|
|
98
|
+
annotations[propName] = computed;
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
case PropertyType.referenceCollection:
|
|
102
|
+
// Observable collection
|
|
103
|
+
if (!hasCustomGetter && !hasCustomSetter) {
|
|
104
|
+
annotations[propName] = observable;
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
case PropertyType.backReference:
|
|
108
|
+
// Computed back-reference
|
|
109
|
+
if (!hasCustomGetter && !hasCustomSetter) {
|
|
110
|
+
annotations[propName] = computed;
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
case PropertyType.referenceArray:
|
|
114
|
+
// Observable array of IDs
|
|
115
|
+
if (!propName.endsWith('Ids')) {
|
|
116
|
+
const idsPropName = `${propName}Ids`;
|
|
117
|
+
if (!hasGetter(idsPropName) && !hasSetter(idsPropName)) {
|
|
118
|
+
annotations[idsPropName] = observable;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (!hasCustomGetter && !hasCustomSetter) {
|
|
122
|
+
annotations[propName] = observable;
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Add standard model properties only if they don't exist
|
|
128
|
+
if (!hasGetter('id') && !hasSetter('id')) {
|
|
129
|
+
annotations.id = observable;
|
|
130
|
+
}
|
|
131
|
+
if (!hasGetter('createdAt') && !hasSetter('createdAt')) {
|
|
132
|
+
annotations.createdAt = observable;
|
|
133
|
+
}
|
|
134
|
+
if (!hasGetter('updatedAt') && !hasSetter('updatedAt')) {
|
|
135
|
+
annotations.updatedAt = observable;
|
|
136
|
+
}
|
|
137
|
+
if (!hasGetter('modifiedProperties') && !hasSetter('modifiedProperties')) {
|
|
138
|
+
annotations.modifiedProperties = observable;
|
|
139
|
+
}
|
|
140
|
+
// Add actions only if methods exist and aren't already actions.
|
|
141
|
+
// `Reflect.get` keeps the read typed without an index-signature
|
|
142
|
+
// cast — `target` is `M1Target`, which deliberately doesn't index
|
|
143
|
+
// by arbitrary string (we don't want to sneak random fields in).
|
|
144
|
+
const actionMethods = [
|
|
145
|
+
'propertyChanged',
|
|
146
|
+
'markAsPersisted',
|
|
147
|
+
'clearChanges',
|
|
148
|
+
'updateFromData',
|
|
149
|
+
];
|
|
150
|
+
for (const methodName of actionMethods) {
|
|
151
|
+
if (typeof Reflect.get(target, methodName) === 'function') {
|
|
152
|
+
annotations[methodName] = action;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Log setup for debugging
|
|
156
|
+
const modelName = target.constructor.name;
|
|
157
|
+
const observableProps = Object.keys(annotations).filter((k) => annotations[k] === observable);
|
|
158
|
+
const computedProps = Object.keys(annotations).filter((k) => annotations[k] === computed);
|
|
159
|
+
getContext().modelDebugLogger?.logObservableSetup(modelName, observableProps, computedProps);
|
|
160
|
+
// Merge any extra annotations declared by the model (e.g., computed for query getters)
|
|
161
|
+
if (target._extraMobxAnnotations && typeof target._extraMobxAnnotations === 'object') {
|
|
162
|
+
Object.assign(annotations, target._extraMobxAnnotations);
|
|
163
|
+
}
|
|
164
|
+
// Apply MobX decorators
|
|
165
|
+
try {
|
|
166
|
+
// Only apply if we have annotations to apply
|
|
167
|
+
if (Object.keys(annotations).length > 0) {
|
|
168
|
+
makeObservable(target, annotations);
|
|
169
|
+
// Bridge MobX's observable setter to `propertyChanged()` so the
|
|
170
|
+
// dynamic-class mutation path sees direct assignments like
|
|
171
|
+
// `layer.position = newPos` — i.e., the transaction queue gets an
|
|
172
|
+
// update and the server eventually sees it.
|
|
173
|
+
//
|
|
174
|
+
// History: the old hand-coded models wired setters via
|
|
175
|
+
// `setupSimplePropertyTracking` which overrode MobX's accessors and
|
|
176
|
+
// broke reactivity — that function was correctly kept off the
|
|
177
|
+
// schema-driven dynamic-class path. The intended replacement was
|
|
178
|
+
// "use `store.mutate.slideLayers.update(...)` from callers," but a
|
|
179
|
+
// large amount of existing product code (drag, resize, formatting,
|
|
180
|
+
// keyboard nudge, AI tools, etc.) still assigns properties directly,
|
|
181
|
+
// and making that silently not sync was the regression that broke
|
|
182
|
+
// all slide-layer edits.
|
|
183
|
+
//
|
|
184
|
+
// `observe()` attaches a post-set listener WITHOUT replacing MobX's
|
|
185
|
+
// accessors — so the observable keeps its normal reactivity and we
|
|
186
|
+
// get a synchronous change event we can forward to
|
|
187
|
+
// `propertyChanged()`. We scope it to `PropertyType.property`
|
|
188
|
+
// (persisted fields) so ephemeral UI state and computed/reference
|
|
189
|
+
// virtual fields don't leak into `modifiedProperties`.
|
|
190
|
+
//
|
|
191
|
+
// Construction-time writes (the constructor's initial field
|
|
192
|
+
// population from wire data) also fire `observe` — so we gate with
|
|
193
|
+
// `target._isNew` and a transient `_isConstructing` flag: any change
|
|
194
|
+
// that happens before the model is marked persisted is an initial
|
|
195
|
+
// hydration, not a user edit.
|
|
196
|
+
if (!target._hasCustomObservability) {
|
|
197
|
+
for (const [propName, metadata] of propertyMetadata) {
|
|
198
|
+
if (metadata.type !== PropertyType.property)
|
|
199
|
+
continue;
|
|
200
|
+
// Only `annotations[propName] === observable` entries are
|
|
201
|
+
// safe to `observe()`. DON'T gate on
|
|
202
|
+
// `Object.getOwnPropertyDescriptor(target, propName).get/set` —
|
|
203
|
+
// `makeObservable(target, annotations)` has ALREADY installed
|
|
204
|
+
// its own getter/setter by this point, so that descriptor
|
|
205
|
+
// check flags every field as "custom" and silently skips
|
|
206
|
+
// every observer. That was the root cause of `input: {}` on
|
|
207
|
+
// the wire: `modifiedProperties` stayed empty for dynamic
|
|
208
|
+
// models, the transaction queue couldn't find any changes to
|
|
209
|
+
// send, and the server acked a no-op mutation.
|
|
210
|
+
if (!(propName in annotations))
|
|
211
|
+
continue;
|
|
212
|
+
// Accept any flavor of `observable` (deep, ref, shallow). `observe()`
|
|
213
|
+
// works on all three — the listener fires on the property
|
|
214
|
+
// reassignment, regardless of how the value is enhanced
|
|
215
|
+
// internally. Crucially, this lets `propertyChanged()` (and the
|
|
216
|
+
// transaction queue) still see writes to JSON-blob fields
|
|
217
|
+
// annotated as `observable.ref`. Gating on the bare `observable`
|
|
218
|
+
// constant alone would silently drop those writes — see the
|
|
219
|
+
// `input: {}` regression captured in the comment above for the
|
|
220
|
+
// same failure mode.
|
|
221
|
+
const ann = annotations[propName];
|
|
222
|
+
if (ann !== observable && ann !== observable.ref && ann !== observable.shallow)
|
|
223
|
+
continue;
|
|
224
|
+
try {
|
|
225
|
+
// Cross the runtime/static boundary: propName is a string
|
|
226
|
+
// from the propertyMetadata Map iteration. At runtime it's
|
|
227
|
+
// guaranteed to be a key on `target` (we just wrote the
|
|
228
|
+
// annotation entry for it via makeObservable above). The
|
|
229
|
+
// cast to `keyof T` reflects that runtime invariant —
|
|
230
|
+
// it's not "I don't know," it's "I know but TS can't see
|
|
231
|
+
// the proof from here." MobX's IValueDidChange<T[K]>
|
|
232
|
+
// gives the change parameter a concrete typed value.
|
|
233
|
+
const key = propName;
|
|
234
|
+
observe(target, key, (change) => {
|
|
235
|
+
// Only track updates, not add/delete. For scalar observables,
|
|
236
|
+
// MobX emits `{ type: 'update', oldValue, newValue }`.
|
|
237
|
+
if (change.type !== 'update')
|
|
238
|
+
return;
|
|
239
|
+
// Skip initial hydration writes. `_isNew` stays true until
|
|
240
|
+
// the model is `markAsPersisted()`, and the first wave of
|
|
241
|
+
// setters runs during construction BEFORE this observer
|
|
242
|
+
// would exist (observers are installed now, on this line);
|
|
243
|
+
// but defensively still gate, because callers that
|
|
244
|
+
// pre-construct models with partial data then bulk-assign
|
|
245
|
+
// would otherwise spuriously fill `modifiedProperties`.
|
|
246
|
+
if (target._isConstructing)
|
|
247
|
+
return;
|
|
248
|
+
if (typeof target.propertyChanged === 'function') {
|
|
249
|
+
target.propertyChanged(propName, change.oldValue, change.newValue);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
// If a property isn't observable for any reason (e.g. it
|
|
255
|
+
// was filtered out by the annotations logic but still shows
|
|
256
|
+
// up in metadata), silently skip — propertyChanged tracking
|
|
257
|
+
// is best-effort, not a correctness guarantee.
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
265
|
+
getContext().modelDebugLogger?.logError(modelName, 'OBSERVABLE_SETUP', errorMessage, {
|
|
266
|
+
annotations: Object.keys(annotations),
|
|
267
|
+
target: Object.keys(target),
|
|
268
|
+
});
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Setup simple property tracking for change detection
|
|
274
|
+
* Only for properties without existing getters/setters
|
|
275
|
+
*/
|
|
276
|
+
function setupSimplePropertyTracking(target, propertyMetadata) {
|
|
277
|
+
for (const [propName, metadata] of propertyMetadata) {
|
|
278
|
+
// Only track regular properties
|
|
279
|
+
if (metadata.type !== PropertyType.property &&
|
|
280
|
+
metadata.type !== PropertyType.ephemeralProperty) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
// Check if property already has custom getter/setter
|
|
284
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, propName);
|
|
285
|
+
if (descriptor && (descriptor.get || descriptor.set)) {
|
|
286
|
+
// Property already managed, skip
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
// Check prototype chain
|
|
290
|
+
let proto = Object.getPrototypeOf(target);
|
|
291
|
+
let hasCustomAccessor = false;
|
|
292
|
+
while (proto && proto !== Object.prototype) {
|
|
293
|
+
const protoDescriptor = Object.getOwnPropertyDescriptor(proto, propName);
|
|
294
|
+
if (protoDescriptor && (protoDescriptor.get || protoDescriptor.set)) {
|
|
295
|
+
hasCustomAccessor = true;
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
proto = Object.getPrototypeOf(proto);
|
|
299
|
+
}
|
|
300
|
+
if (hasCustomAccessor) {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
// Only add tracking if property exists and isn't already tracked
|
|
304
|
+
if (propName in target) {
|
|
305
|
+
const currentValue = target[propName];
|
|
306
|
+
// Store value in a private field
|
|
307
|
+
const privateField = `_tracked_${propName}`;
|
|
308
|
+
target[privateField] = currentValue;
|
|
309
|
+
// Create simple getter/setter for tracking
|
|
310
|
+
Object.defineProperty(target, propName, {
|
|
311
|
+
get() {
|
|
312
|
+
return this[privateField];
|
|
313
|
+
},
|
|
314
|
+
set(newValue) {
|
|
315
|
+
const oldValue = this[privateField];
|
|
316
|
+
if (oldValue !== newValue) {
|
|
317
|
+
this[privateField] = newValue;
|
|
318
|
+
// Only track changes for non-ephemeral properties
|
|
319
|
+
if (metadata.type === PropertyType.property && this.propertyChanged) {
|
|
320
|
+
this.propertyChanged(propName, oldValue, newValue);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
enumerable: true,
|
|
325
|
+
configurable: true,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Helper to make a class observable
|
|
332
|
+
* For classes that don't have custom observability
|
|
333
|
+
*/
|
|
334
|
+
export function makeModelObservable(modelClass, propertyMetadata, referenceMetadata) {
|
|
335
|
+
// Check if class already handles observability
|
|
336
|
+
if (modelClass.prototype.setupObservability || modelClass.prototype._hasCustomObservability) {
|
|
337
|
+
return modelClass;
|
|
338
|
+
}
|
|
339
|
+
// Create wrapper class
|
|
340
|
+
const WrappedClass = class extends modelClass {
|
|
341
|
+
constructor(...args) {
|
|
342
|
+
super(...args);
|
|
343
|
+
M1(this, propertyMetadata, referenceMetadata);
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
// Preserve class name
|
|
347
|
+
Object.defineProperty(WrappedClass, 'name', {
|
|
348
|
+
value: modelClass.name,
|
|
349
|
+
configurable: true,
|
|
350
|
+
});
|
|
351
|
+
// Copy static properties
|
|
352
|
+
Object.setPrototypeOf(WrappedClass, modelClass);
|
|
353
|
+
return WrappedClass;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Utility to check if a property is observable
|
|
357
|
+
*/
|
|
358
|
+
export function isObservableProperty(target, propName, propertyMetadata) {
|
|
359
|
+
const metadata = propertyMetadata.get(propName);
|
|
360
|
+
if (!metadata)
|
|
361
|
+
return false;
|
|
362
|
+
return [
|
|
363
|
+
PropertyType.property,
|
|
364
|
+
PropertyType.ephemeralProperty,
|
|
365
|
+
PropertyType.referenceCollection,
|
|
366
|
+
PropertyType.referenceArray,
|
|
367
|
+
].includes(metadata.type);
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Utility to get computed properties
|
|
371
|
+
*/
|
|
372
|
+
export function getComputedProperties(propertyMetadata) {
|
|
373
|
+
const computed = [];
|
|
374
|
+
for (const [propName, metadata] of propertyMetadata) {
|
|
375
|
+
if (metadata.type === PropertyType.referenceModel ||
|
|
376
|
+
metadata.type === PropertyType.backReference) {
|
|
377
|
+
computed.push(propName);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return computed;
|
|
381
|
+
}
|
package/docs/api-keys.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# API Keys
|
|
2
|
+
|
|
3
|
+
Trusted runtimes authenticate with an API key.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import Ablo from '@ablo/sync-engine';
|
|
7
|
+
|
|
8
|
+
const ablo = Ablo({ apiKey: process.env.ABLO_API_KEY });
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The key identifies the Ablo account. Application code does not pass an organization id; Ablo derives scope from the credential.
|
|
12
|
+
|
|
13
|
+
Use the root `@ablo/sync-engine` import with a schema for app clients.
|
|
14
|
+
|
|
15
|
+
## Server-Side API Keys
|
|
16
|
+
|
|
17
|
+
Use API keys from trusted runtimes:
|
|
18
|
+
|
|
19
|
+
- backend route handlers
|
|
20
|
+
- workers and agents
|
|
21
|
+
- CLI tools
|
|
22
|
+
- webhooks
|
|
23
|
+
|
|
24
|
+
Never ship a secret API key to a browser bundle.
|