@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,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ablo/sync-engine/policy — pluggable conflict resolution.
|
|
3
|
+
*
|
|
4
|
+
* The engine detects conflicts; the policy decides. Customer code
|
|
5
|
+
* implements `ConflictPolicy` and registers it at the sync-server.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { type ConflictPolicy, defaultPolicy } from '@ablo/sync-engine/policy';
|
|
9
|
+
*
|
|
10
|
+
* export const myPolicy: ConflictPolicy = (ctx) => {
|
|
11
|
+
* if (ctx.committer.id.startsWith('linter:')) {
|
|
12
|
+
* return { action: 'allow', note: 'cosmetic writer' };
|
|
13
|
+
* }
|
|
14
|
+
* return defaultPolicy(ctx);
|
|
15
|
+
* };
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export type { Conflict, ConflictDecision, ConflictKind, ConflictOperation, ConflictPolicy, StaleContextConflict, IntentHeldConflict, } from './types.js';
|
|
19
|
+
export { defaultPolicy } from './types.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ablo/sync-engine/policy — pluggable conflict resolution.
|
|
3
|
+
*
|
|
4
|
+
* The engine detects conflicts; the policy decides. Customer code
|
|
5
|
+
* implements `ConflictPolicy` and registers it at the sync-server.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { type ConflictPolicy, defaultPolicy } from '@ablo/sync-engine/policy';
|
|
9
|
+
*
|
|
10
|
+
* export const myPolicy: ConflictPolicy = (ctx) => {
|
|
11
|
+
* if (ctx.committer.id.startsWith('linter:')) {
|
|
12
|
+
* return { action: 'allow', note: 'cosmetic writer' };
|
|
13
|
+
* }
|
|
14
|
+
* return defaultPolicy(ctx);
|
|
15
|
+
* };
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export { defaultPolicy } from './types.js';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conflict policy — the engine detects, the policy decides.
|
|
3
|
+
*
|
|
4
|
+
* Two conflict shapes today: `stale_context` (a write whose `readAt`
|
|
5
|
+
* is older than the latest delta on the target) and `intent_held`
|
|
6
|
+
* (a participant claims a target someone else is already claiming).
|
|
7
|
+
* Adding new shapes is additive on the discriminated union.
|
|
8
|
+
*/
|
|
9
|
+
import type { ParticipantRef } from '../types/streams.js';
|
|
10
|
+
export type ConflictKind = 'stale_context' | 'intent_held';
|
|
11
|
+
/** Fields shared by every conflict shape. */
|
|
12
|
+
interface ConflictBase {
|
|
13
|
+
readonly committer: ParticipantRef;
|
|
14
|
+
readonly organizationId: string;
|
|
15
|
+
/** Human at the root of the committer's delegation chain (if any). */
|
|
16
|
+
readonly delegationChainRootUserId?: string | null;
|
|
17
|
+
}
|
|
18
|
+
/** The operation whose write conflicts. */
|
|
19
|
+
export interface ConflictOperation {
|
|
20
|
+
readonly model: string;
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly type: 'CREATE' | 'UPDATE' | 'DELETE' | 'ARCHIVE' | 'UNARCHIVE';
|
|
23
|
+
readonly input?: Readonly<Record<string, unknown>>;
|
|
24
|
+
}
|
|
25
|
+
export interface StaleContextConflict extends ConflictBase {
|
|
26
|
+
readonly kind: 'stale_context';
|
|
27
|
+
readonly operation: ConflictOperation;
|
|
28
|
+
/** Watermark the committer reasoned against. */
|
|
29
|
+
readonly readAt: number;
|
|
30
|
+
/** Most recent delta id on the target. */
|
|
31
|
+
readonly observedSyncId: number;
|
|
32
|
+
}
|
|
33
|
+
export interface IntentHeldConflict extends ConflictBase {
|
|
34
|
+
readonly kind: 'intent_held';
|
|
35
|
+
readonly heldBy: ParticipantRef;
|
|
36
|
+
readonly intentId: string;
|
|
37
|
+
readonly entityType: string;
|
|
38
|
+
readonly entityId: string;
|
|
39
|
+
/** Holder's intent expiry (ms since epoch). */
|
|
40
|
+
readonly expiresAt: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The discriminated union the policy receives. Switch on `.kind` to
|
|
44
|
+
* narrow to the variant.
|
|
45
|
+
*/
|
|
46
|
+
export type Conflict = StaleContextConflict | IntentHeldConflict;
|
|
47
|
+
/** What the policy returns. */
|
|
48
|
+
export type ConflictDecision = {
|
|
49
|
+
readonly action: 'reject';
|
|
50
|
+
readonly reason?: string;
|
|
51
|
+
} | {
|
|
52
|
+
readonly action: 'allow';
|
|
53
|
+
readonly note?: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Pluggable decision function. Sync or async.
|
|
57
|
+
*
|
|
58
|
+
* ```ts
|
|
59
|
+
* const policy: ConflictPolicy = (conflict) => {
|
|
60
|
+
* if (conflict.committer.id.startsWith('linter:')) {
|
|
61
|
+
* return { action: 'allow', note: 'cosmetic writer' };
|
|
62
|
+
* }
|
|
63
|
+
* return defaultPolicy(conflict);
|
|
64
|
+
* };
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export type ConflictPolicy = (conflict: Conflict) => ConflictDecision | Promise<ConflictDecision>;
|
|
68
|
+
/**
|
|
69
|
+
* Default: reject every conflict. Safe fallback when no custom policy
|
|
70
|
+
* is wired — the engine never silently allows a stale or
|
|
71
|
+
* intent-conflicting write through.
|
|
72
|
+
*/
|
|
73
|
+
export declare const defaultPolicy: ConflictPolicy;
|
|
74
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conflict policy — the engine detects, the policy decides.
|
|
3
|
+
*
|
|
4
|
+
* Two conflict shapes today: `stale_context` (a write whose `readAt`
|
|
5
|
+
* is older than the latest delta on the target) and `intent_held`
|
|
6
|
+
* (a participant claims a target someone else is already claiming).
|
|
7
|
+
* Adding new shapes is additive on the discriminated union.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Default: reject every conflict. Safe fallback when no custom policy
|
|
11
|
+
* is wired — the engine never silently allows a stale or
|
|
12
|
+
* intent-conflicting write through.
|
|
13
|
+
*/
|
|
14
|
+
export const defaultPolicy = (conflict) => ({
|
|
15
|
+
action: 'reject',
|
|
16
|
+
reason: conflict.kind === 'stale_context' ? 'stale_context' : 'intent_conflict',
|
|
17
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Principal constructors — a thin typed façade over the raw
|
|
3
|
+
* `SessionRef` / `AgentRef` shapes so call sites don't have to memorize
|
|
4
|
+
* the discriminated-union tags.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import Ablo, { session } from '@ablo/sync-engine';
|
|
8
|
+
*
|
|
9
|
+
* const ablo = Ablo({ schema, apiKey });
|
|
10
|
+
* const participant = await ablo.participants.join({
|
|
11
|
+
* type: 'Matter',
|
|
12
|
+
* id: 'deal-1',
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Browser-human flows use `session(...)`. Agent-spawn-agent flows use
|
|
17
|
+
* `agent(...)`, but those rarely appear in customer code because the
|
|
18
|
+
* participant layer handles attenuation.
|
|
19
|
+
*
|
|
20
|
+
* These are pure — no I/O, no hidden state. If the shape ever grows a
|
|
21
|
+
* required field (say, a Biscuit scope hint), the helper is the one
|
|
22
|
+
* place to flag migrations.
|
|
23
|
+
*/
|
|
24
|
+
import type { AgentRef, SessionRef } from './types/streams.js';
|
|
25
|
+
/**
|
|
26
|
+
* Build a `SessionRef` from the identifiers your auth system already
|
|
27
|
+
* holds. Typical inputs: the Better Auth session id, the user id, and
|
|
28
|
+
* the organization the session is scoped to.
|
|
29
|
+
*/
|
|
30
|
+
export declare function session(params: {
|
|
31
|
+
id: string;
|
|
32
|
+
userId: string;
|
|
33
|
+
organizationId: string;
|
|
34
|
+
}): SessionRef;
|
|
35
|
+
/**
|
|
36
|
+
* Build an `AgentRef` from an agent id + the capability token that
|
|
37
|
+
* authenticates it. Rare in application code — the common path is
|
|
38
|
+
* `participant.join(child)` where the parent's token is attenuated
|
|
39
|
+
* automatically.
|
|
40
|
+
*/
|
|
41
|
+
export declare function agent(params: {
|
|
42
|
+
id: string;
|
|
43
|
+
capabilityToken: string;
|
|
44
|
+
}): AgentRef;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Principal constructors — a thin typed façade over the raw
|
|
3
|
+
* `SessionRef` / `AgentRef` shapes so call sites don't have to memorize
|
|
4
|
+
* the discriminated-union tags.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import Ablo, { session } from '@ablo/sync-engine';
|
|
8
|
+
*
|
|
9
|
+
* const ablo = Ablo({ schema, apiKey });
|
|
10
|
+
* const participant = await ablo.participants.join({
|
|
11
|
+
* type: 'Matter',
|
|
12
|
+
* id: 'deal-1',
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Browser-human flows use `session(...)`. Agent-spawn-agent flows use
|
|
17
|
+
* `agent(...)`, but those rarely appear in customer code because the
|
|
18
|
+
* participant layer handles attenuation.
|
|
19
|
+
*
|
|
20
|
+
* These are pure — no I/O, no hidden state. If the shape ever grows a
|
|
21
|
+
* required field (say, a Biscuit scope hint), the helper is the one
|
|
22
|
+
* place to flag migrations.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Build a `SessionRef` from the identifiers your auth system already
|
|
26
|
+
* holds. Typical inputs: the Better Auth session id, the user id, and
|
|
27
|
+
* the organization the session is scoped to.
|
|
28
|
+
*/
|
|
29
|
+
export function session(params) {
|
|
30
|
+
return {
|
|
31
|
+
kind: 'session',
|
|
32
|
+
id: params.id,
|
|
33
|
+
userId: params.userId,
|
|
34
|
+
organizationId: params.organizationId,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Build an `AgentRef` from an agent id + the capability token that
|
|
39
|
+
* authenticates it. Rare in application code — the common path is
|
|
40
|
+
* `participant.join(child)` where the parent's token is attenuated
|
|
41
|
+
* automatically.
|
|
42
|
+
*/
|
|
43
|
+
export function agent(params) {
|
|
44
|
+
return {
|
|
45
|
+
kind: 'agent',
|
|
46
|
+
id: params.id,
|
|
47
|
+
capabilityToken: params.capabilityToken,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for the generic /sync/query endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper over fetch() that:
|
|
5
|
+
* - POSTs a QueryBatch as JSON
|
|
6
|
+
* - Handles auth via `credentials: 'include'` (session cookie)
|
|
7
|
+
* - Throws on non-2xx responses
|
|
8
|
+
* - Parses the response into a typed QueryBatchResult
|
|
9
|
+
*
|
|
10
|
+
* The higher-level BootstrapHelper methods (fetchDeckSlideLayers,
|
|
11
|
+
* fetchChatMessages, etc.) use this to issue structured queries
|
|
12
|
+
* without duplicating the fetch boilerplate.
|
|
13
|
+
*/
|
|
14
|
+
import type { QueryBatch, QueryBatchResult } from './types.js';
|
|
15
|
+
export interface PostQueryOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Full base URL of the sync server including the `/api` prefix.
|
|
18
|
+
* The query endpoint is appended as `/sync/query`, so the final
|
|
19
|
+
* request hits `${baseUrl}/sync/query`.
|
|
20
|
+
*/
|
|
21
|
+
baseUrl: string;
|
|
22
|
+
/** Timeout in ms for the fetch request. Default: 30000. */
|
|
23
|
+
fetchTimeout?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Capability token (Biscuit) to attach as `Authorization: Bearer <token>`.
|
|
26
|
+
* Required for Node consumers (agent-worker, server-side tests) that
|
|
27
|
+
* have no session cookie to ride. Browser consumers can omit this and
|
|
28
|
+
* fall back to `credentials: 'include'`. When both are present the
|
|
29
|
+
* server prefers the Bearer header (see `agentTokenProvider` in
|
|
30
|
+
* `apps/sync-server/src/auth`), so passing the token in browser code
|
|
31
|
+
* is harmless.
|
|
32
|
+
*/
|
|
33
|
+
capabilityToken?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* POST a batch of queries to /sync/query. Returns the parsed
|
|
37
|
+
* QueryBatchResult. Throws a descriptive error on HTTP failure.
|
|
38
|
+
*
|
|
39
|
+
* The server guarantees results[i] corresponds to queries[i] in the
|
|
40
|
+
* request — callers can rely on index alignment to extract typed
|
|
41
|
+
* results from a multi-query batch.
|
|
42
|
+
*/
|
|
43
|
+
export declare function postQuery(options: PostQueryOptions, batch: QueryBatch): Promise<QueryBatchResult>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for the generic /sync/query endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper over fetch() that:
|
|
5
|
+
* - POSTs a QueryBatch as JSON
|
|
6
|
+
* - Handles auth via `credentials: 'include'` (session cookie)
|
|
7
|
+
* - Throws on non-2xx responses
|
|
8
|
+
* - Parses the response into a typed QueryBatchResult
|
|
9
|
+
*
|
|
10
|
+
* The higher-level BootstrapHelper methods (fetchDeckSlideLayers,
|
|
11
|
+
* fetchChatMessages, etc.) use this to issue structured queries
|
|
12
|
+
* without duplicating the fetch boilerplate.
|
|
13
|
+
*/
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
// ── Response validation ─────────────────────────────────────────────────
|
|
16
|
+
//
|
|
17
|
+
// Each result slot is an array of rows (or an object for bundled
|
|
18
|
+
// responses). Server-side per-query failures surface here as `[]`, but
|
|
19
|
+
// the server logs them via `console.error('[query.error] ...')` — alert
|
|
20
|
+
// on that prefix, not on emptiness. Parsing through Zod normalizes
|
|
21
|
+
// `null` slots into empty arrays so downstream callers never see raw
|
|
22
|
+
// null.
|
|
23
|
+
const QueryResultSchema = z
|
|
24
|
+
.union([z.array(z.unknown()), z.record(z.string(), z.unknown()), z.null()])
|
|
25
|
+
.transform((val) => {
|
|
26
|
+
if (val === null)
|
|
27
|
+
return [];
|
|
28
|
+
return val;
|
|
29
|
+
});
|
|
30
|
+
const QueryBatchResultSchema = z
|
|
31
|
+
.object({
|
|
32
|
+
results: z.array(QueryResultSchema),
|
|
33
|
+
})
|
|
34
|
+
.passthrough();
|
|
35
|
+
/**
|
|
36
|
+
* POST a batch of queries to /sync/query. Returns the parsed
|
|
37
|
+
* QueryBatchResult. Throws a descriptive error on HTTP failure.
|
|
38
|
+
*
|
|
39
|
+
* The server guarantees results[i] corresponds to queries[i] in the
|
|
40
|
+
* request — callers can rely on index alignment to extract typed
|
|
41
|
+
* results from a multi-query batch.
|
|
42
|
+
*/
|
|
43
|
+
export async function postQuery(options, batch) {
|
|
44
|
+
const url = `${options.baseUrl}/sync/query`;
|
|
45
|
+
const timeout = options.fetchTimeout ?? 30_000;
|
|
46
|
+
// Race the fetch against a timeout so hung requests don't block
|
|
47
|
+
// the calling helper indefinitely.
|
|
48
|
+
const controller = new AbortController();
|
|
49
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
50
|
+
try {
|
|
51
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
52
|
+
if (options.capabilityToken) {
|
|
53
|
+
headers.Authorization = `Bearer ${options.capabilityToken}`;
|
|
54
|
+
}
|
|
55
|
+
const response = await fetch(url, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers,
|
|
58
|
+
credentials: 'include',
|
|
59
|
+
body: JSON.stringify(batch),
|
|
60
|
+
signal: controller.signal,
|
|
61
|
+
});
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
// Direct console.error is INTENTIONAL — operators alert on the
|
|
64
|
+
// `[postQuery.error]` prefix in browser console. Routing through
|
|
65
|
+
// an injected logger here would require a coordinated change to
|
|
66
|
+
// the alerting pipeline. Tracked as future work; the dual-channel
|
|
67
|
+
// alternative (logger + observability.captureException) is the
|
|
68
|
+
// production target. Never throw — fire-and-forget callers would
|
|
69
|
+
// kill Next.js router on unhandled rejection.
|
|
70
|
+
console.error(`[postQuery.error] ${response.status} ${response.statusText} for ${batch.queries.map((q) => q.model).join(',')}`);
|
|
71
|
+
return { results: batch.queries.map(() => []) };
|
|
72
|
+
}
|
|
73
|
+
const raw = await response.json();
|
|
74
|
+
const parsed = QueryBatchResultSchema.safeParse(raw);
|
|
75
|
+
if (!parsed.success) {
|
|
76
|
+
console.error('[postQuery.error] malformed response:', parsed.error.issues);
|
|
77
|
+
return { results: batch.queries.map(() => []) };
|
|
78
|
+
}
|
|
79
|
+
return parsed.data;
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query module barrel — re-exports the public surface for convenience.
|
|
3
|
+
* See types.ts and client.ts for the actual definitions.
|
|
4
|
+
*/
|
|
5
|
+
export type { Query, QueryBatch, QueryBatchResult } from './types.js';
|
|
6
|
+
export { postQuery, type PostQueryOptions } from './client.js';
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured query types for the generic /sync/query endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Zero-shaped ZQL-ish wire format: `where` is a flat list of `[col, op, val]`
|
|
5
|
+
* tuples (AND'd together), and `related` is a list of schema-declared
|
|
6
|
+
* relation names to traverse. The server compiler reads the schema's
|
|
7
|
+
* relation metadata to turn `related: ['layers']` into the right JOIN.
|
|
8
|
+
*
|
|
9
|
+
* # Why this shape
|
|
10
|
+
*
|
|
11
|
+
* An earlier revision used equality-only `where: Record<string, unknown>`
|
|
12
|
+
* plus a PK-only `ids: string[]` batch field. That worked for simple cases
|
|
13
|
+
* but collapsed on two real workloads:
|
|
14
|
+
*
|
|
15
|
+
* 1. "Fetch all layers for these slide IDs" — the IDs are foreign keys
|
|
16
|
+
* (`SlideLayer.slideId`), not primary keys. The old `ids` field
|
|
17
|
+
* filtered on `id`, silently returning empty.
|
|
18
|
+
*
|
|
19
|
+
* 2. "Fetch all layers for this deck" — needs a JOIN through
|
|
20
|
+
* `slides.deck_id → slide_layers.slide_id`. Equality-only `where` had
|
|
21
|
+
* no way to express it, so the Go server hardcoded a dispatch case.
|
|
22
|
+
*
|
|
23
|
+
* Both are generic patterns ("batch by FK column", "filter via relation")
|
|
24
|
+
* that should be first-class in the protocol, not model-specific escape
|
|
25
|
+
* hatches on the server. This shape matches Zero's ZQL:
|
|
26
|
+
*
|
|
27
|
+
* - `where('slideId', 'IN', ids)` → `['slideId', 'IN', ids]`
|
|
28
|
+
* - `.related('layers')` → `related: ['layers']`
|
|
29
|
+
*
|
|
30
|
+
* The server's compiler stays schema-driven: given a model name, it reads
|
|
31
|
+
* the schema's declared relations to emit JOIN SQL, and given a `[col, op,
|
|
32
|
+
* val]` tuple it emits a WHERE fragment — never a switch on specific model
|
|
33
|
+
* names. Adding a new model or relation is a schema change, not a server
|
|
34
|
+
* change.
|
|
35
|
+
*/
|
|
36
|
+
/** Primitive operand types allowed in a where clause. */
|
|
37
|
+
export type WherePrimitive = string | number | boolean | null;
|
|
38
|
+
/**
|
|
39
|
+
* Comparison operators. Mirrors Zero's ZQL set so client authors can
|
|
40
|
+
* lean on familiar semantics and server compilers that already target
|
|
41
|
+
* ZQL stay portable.
|
|
42
|
+
*/
|
|
43
|
+
export type WhereOp = '=' | '!=' | '<' | '<=' | '>' | '>=' | 'IN' | 'NOT IN' | 'IS' | 'IS NOT' | 'LIKE' | 'NOT LIKE' | 'ILIKE' | 'NOT ILIKE';
|
|
44
|
+
/**
|
|
45
|
+
* A single condition. Two supported shapes:
|
|
46
|
+
*
|
|
47
|
+
* - `[col, value]` — shortcut for `[col, '=', value]`
|
|
48
|
+
* - `[col, op, value]` — explicit operator
|
|
49
|
+
*
|
|
50
|
+
* The value is a single primitive for scalar operators and an array of
|
|
51
|
+
* primitives for IN/NOT IN.
|
|
52
|
+
*/
|
|
53
|
+
export type WhereClause = readonly [col: string, value: WherePrimitive] | readonly [col: string, op: WhereOp, value: WherePrimitive | readonly WherePrimitive[]];
|
|
54
|
+
/**
|
|
55
|
+
* Client-facing where shape for `load({where})` and `deleteMany({where})`.
|
|
56
|
+
*
|
|
57
|
+
* Two shapes accepted, both AND-combined:
|
|
58
|
+
*
|
|
59
|
+
* - Object form: `{ name: 'foo', orgId: '1' }` — each entry is an `=`
|
|
60
|
+
* clause; array values become `IN`. Ergonomic for the common case.
|
|
61
|
+
* - Tuple form: `[['name', 'ILIKE', '%Goldman%'], ['orgId', '1']]` —
|
|
62
|
+
* explicit operators (LIKE/ILIKE/<=/etc.). Matches the wire
|
|
63
|
+
* `WhereClause[]` 1:1, so no translation layer.
|
|
64
|
+
*
|
|
65
|
+
* The two forms compose: pass tuple form when you need an operator,
|
|
66
|
+
* object form otherwise. For OR semantics, run two `load()` calls and
|
|
67
|
+
* union client-side — keeps the protocol AND-only.
|
|
68
|
+
*/
|
|
69
|
+
export type LoadWhere<T> = Partial<T> | {
|
|
70
|
+
[K in keyof T]?: T[K] | readonly T[K][];
|
|
71
|
+
} | readonly WhereClause[];
|
|
72
|
+
/** A single structured fetch request. */
|
|
73
|
+
export interface Query {
|
|
74
|
+
/**
|
|
75
|
+
* Client-facing model name (e.g. "File", "SlideLayer", "Message").
|
|
76
|
+
* The server's adapter maps this to the actual database table.
|
|
77
|
+
*/
|
|
78
|
+
model: string;
|
|
79
|
+
/**
|
|
80
|
+
* List of where clauses AND'd together. Empty or omitted means "no
|
|
81
|
+
* filter" (still subject to server-side org scoping).
|
|
82
|
+
*
|
|
83
|
+
* Use `['col', 'IN', values]` to batch by any column — the old
|
|
84
|
+
* primary-key-only `ids` field is subsumed by this form.
|
|
85
|
+
*/
|
|
86
|
+
where?: readonly WhereClause[];
|
|
87
|
+
/**
|
|
88
|
+
* Relation names declared in the schema for this model. The server's
|
|
89
|
+
* compiler resolves each name via the schema's relation metadata
|
|
90
|
+
* (`relation.hasMany` / `relation.belongsTo`) and emits the JOIN
|
|
91
|
+
* SQL — no model-specific dispatch on the server.
|
|
92
|
+
*
|
|
93
|
+
* Results come back as nested objects under the relation key:
|
|
94
|
+
*
|
|
95
|
+
* { __typename: 'Slide', id: '…', layers: [{ __typename: 'SlideLayer', … }] }
|
|
96
|
+
*/
|
|
97
|
+
related?: readonly string[];
|
|
98
|
+
/**
|
|
99
|
+
* Row limit. Applied after where + JOIN, before related nesting.
|
|
100
|
+
* Omit for no limit.
|
|
101
|
+
*/
|
|
102
|
+
limit?: number;
|
|
103
|
+
/**
|
|
104
|
+
* Column to order by. For stable pagination. Omit for unordered.
|
|
105
|
+
*/
|
|
106
|
+
orderBy?: string;
|
|
107
|
+
/** Order direction. Defaults to `'asc'`. */
|
|
108
|
+
order?: 'asc' | 'desc';
|
|
109
|
+
}
|
|
110
|
+
/** Request body for POST /sync/query. */
|
|
111
|
+
export interface QueryBatch {
|
|
112
|
+
/**
|
|
113
|
+
* Batch of queries to execute in one round trip. Results are
|
|
114
|
+
* returned in request order at the same indices. Keep batches
|
|
115
|
+
* small — the server caps at 16 queries per batch by default.
|
|
116
|
+
*/
|
|
117
|
+
queries: Query[];
|
|
118
|
+
}
|
|
119
|
+
/** Response body from POST /sync/query. */
|
|
120
|
+
export interface QueryBatchResult {
|
|
121
|
+
/**
|
|
122
|
+
* Per-query results in request order. `results[i]` corresponds to
|
|
123
|
+
* `queries[i]`. Each element is an array of rows for array-shaped
|
|
124
|
+
* queries, or a bundled object for providers that return multiple
|
|
125
|
+
* collections under named keys.
|
|
126
|
+
*
|
|
127
|
+
* Each row carries `__typename` for client-side model dispatch, plus
|
|
128
|
+
* any `related` keys nested under the row.
|
|
129
|
+
*
|
|
130
|
+
* Failed queries surface as empty arrays. The server logs them via
|
|
131
|
+
* `console.error('[query.error] ...')` — alert on that prefix rather
|
|
132
|
+
* than trying to infer failure from empty results. A tagged-union
|
|
133
|
+
* wire shape that forces caller acknowledgement is the right next
|
|
134
|
+
* step once every `postQuery` consumer is updated at once.
|
|
135
|
+
*/
|
|
136
|
+
results: unknown[];
|
|
137
|
+
/**
|
|
138
|
+
* Server watermark observed after the batch ran. Public resource reads
|
|
139
|
+
* expose this as `stamp` and callers thread it into `commits.create({
|
|
140
|
+
* readAt })` to reject stale writes.
|
|
141
|
+
*/
|
|
142
|
+
lastSyncId?: number;
|
|
143
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured query types for the generic /sync/query endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Zero-shaped ZQL-ish wire format: `where` is a flat list of `[col, op, val]`
|
|
5
|
+
* tuples (AND'd together), and `related` is a list of schema-declared
|
|
6
|
+
* relation names to traverse. The server compiler reads the schema's
|
|
7
|
+
* relation metadata to turn `related: ['layers']` into the right JOIN.
|
|
8
|
+
*
|
|
9
|
+
* # Why this shape
|
|
10
|
+
*
|
|
11
|
+
* An earlier revision used equality-only `where: Record<string, unknown>`
|
|
12
|
+
* plus a PK-only `ids: string[]` batch field. That worked for simple cases
|
|
13
|
+
* but collapsed on two real workloads:
|
|
14
|
+
*
|
|
15
|
+
* 1. "Fetch all layers for these slide IDs" — the IDs are foreign keys
|
|
16
|
+
* (`SlideLayer.slideId`), not primary keys. The old `ids` field
|
|
17
|
+
* filtered on `id`, silently returning empty.
|
|
18
|
+
*
|
|
19
|
+
* 2. "Fetch all layers for this deck" — needs a JOIN through
|
|
20
|
+
* `slides.deck_id → slide_layers.slide_id`. Equality-only `where` had
|
|
21
|
+
* no way to express it, so the Go server hardcoded a dispatch case.
|
|
22
|
+
*
|
|
23
|
+
* Both are generic patterns ("batch by FK column", "filter via relation")
|
|
24
|
+
* that should be first-class in the protocol, not model-specific escape
|
|
25
|
+
* hatches on the server. This shape matches Zero's ZQL:
|
|
26
|
+
*
|
|
27
|
+
* - `where('slideId', 'IN', ids)` → `['slideId', 'IN', ids]`
|
|
28
|
+
* - `.related('layers')` → `related: ['layers']`
|
|
29
|
+
*
|
|
30
|
+
* The server's compiler stays schema-driven: given a model name, it reads
|
|
31
|
+
* the schema's declared relations to emit JOIN SQL, and given a `[col, op,
|
|
32
|
+
* val]` tuple it emits a WHERE fragment — never a switch on specific model
|
|
33
|
+
* names. Adding a new model or relation is a schema change, not a server
|
|
34
|
+
* change.
|
|
35
|
+
*/
|
|
36
|
+
export {};
|