@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,43 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Neutral loading placeholder — the default value of `<AbloProvider>`'s
|
|
5
|
+
* `fallback` prop. Rendered during the first bootstrap pass when the
|
|
6
|
+
* consumer hasn't supplied their own skeleton.
|
|
7
|
+
*
|
|
8
|
+
* Design goals:
|
|
9
|
+
* - Zero design-system dependency. Inline styles only; no CSS file,
|
|
10
|
+
* no UI-lib imports, no Tailwind assumptions.
|
|
11
|
+
* - Theme-adaptive. Uses `currentColor` for the ring so the spinner
|
|
12
|
+
* inherits the text color from whichever ancestor defines it —
|
|
13
|
+
* works in light + dark contexts without a prop.
|
|
14
|
+
* - Self-centering. Flex-centered in a full-parent container so the
|
|
15
|
+
* common case (provider at the layout root) renders a spinner in
|
|
16
|
+
* the middle of the viewport. Consumers who need different
|
|
17
|
+
* positioning compose their own fallback and pass it explicitly.
|
|
18
|
+
* - Minimal bundle footprint. The whole component + keyframe is ~50
|
|
19
|
+
* bytes gzipped.
|
|
20
|
+
*
|
|
21
|
+
* Consumers wanting a branded loader should pass `fallback={<YourSkeleton />}`
|
|
22
|
+
* on `<AbloProvider>`. Consumers wanting NO visual during bootstrap
|
|
23
|
+
* pass `fallback={null}`. Consumers who want to skip the gate entirely
|
|
24
|
+
* pass `fallback="passthrough"`.
|
|
25
|
+
*/
|
|
26
|
+
export function DefaultFallback() {
|
|
27
|
+
return (_jsxs("div", { role: "status", "aria-live": "polite", "aria-label": "Loading", style: {
|
|
28
|
+
display: 'flex',
|
|
29
|
+
alignItems: 'center',
|
|
30
|
+
justifyContent: 'center',
|
|
31
|
+
width: '100%',
|
|
32
|
+
minHeight: '100vh',
|
|
33
|
+
color: 'currentColor',
|
|
34
|
+
}, children: [_jsx("div", { style: {
|
|
35
|
+
width: 24,
|
|
36
|
+
height: 24,
|
|
37
|
+
border: '2px solid currentColor',
|
|
38
|
+
borderTopColor: 'transparent',
|
|
39
|
+
borderRadius: '50%',
|
|
40
|
+
opacity: 0.6,
|
|
41
|
+
animation: 'ablo-default-fallback-spin 0.8s linear infinite',
|
|
42
|
+
} }), _jsx("style", { children: `@keyframes ablo-default-fallback-spin { to { transform: rotate(360deg); } }` })] }));
|
|
43
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export interface SyncGroupProviderProps {
|
|
3
|
+
/** The sync-group identifier — e.g., `matter:abc-123`, `deck:xyz`. */
|
|
4
|
+
id: string;
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
export declare function SyncGroupProvider({ id, children }: SyncGroupProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
/**
|
|
9
|
+
* Returns the ID of the nearest `<SyncGroupProvider>`. Throws if
|
|
10
|
+
* called outside one — sync-group awareness is mandatory by design,
|
|
11
|
+
* so the error points the consumer at the provider instead of
|
|
12
|
+
* returning undefined and letting downstream code silently miss scope.
|
|
13
|
+
*
|
|
14
|
+
* If a component legitimately renders both inside and outside a
|
|
15
|
+
* group, structure the tree so the hook is only called on the
|
|
16
|
+
* inside path (e.g., split into two components). Silent nulls are
|
|
17
|
+
* never the right answer.
|
|
18
|
+
*/
|
|
19
|
+
export declare function useSyncGroup(): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext, useMemo } from 'react';
|
|
4
|
+
import { AbloValidationError } from '../errors.js';
|
|
5
|
+
/**
|
|
6
|
+
* Narrow context for a per-entity sync-group scope. Maps directly onto
|
|
7
|
+
* Liveblocks' `<RoomProvider id="...">`: wrap a subtree, and any hooks
|
|
8
|
+
* inside can read `useSyncGroup()` to discover "which entity am I
|
|
9
|
+
* scoped to?" without threading the ID through props.
|
|
10
|
+
*
|
|
11
|
+
* Typical IDs follow the multiplayer sync-group convention: `matter:<id>`,
|
|
12
|
+
* `deck:<id>`, `project:<id>`. The ID is an opaque string — the
|
|
13
|
+
* provider doesn't parse it.
|
|
14
|
+
*
|
|
15
|
+
* v0.3.0 scope: this is a thin passthrough. Future versions will
|
|
16
|
+
* scope `useQuery` / `useOne` results to the group automatically.
|
|
17
|
+
*/
|
|
18
|
+
const SyncGroupContext = createContext(null);
|
|
19
|
+
export function SyncGroupProvider({ id, children }) {
|
|
20
|
+
// Stabilize the context value so consumers memoized on it don't
|
|
21
|
+
// re-render when the provider re-renders for unrelated reasons.
|
|
22
|
+
const value = useMemo(() => id, [id]);
|
|
23
|
+
return _jsx(SyncGroupContext.Provider, { value: value, children: children });
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Returns the ID of the nearest `<SyncGroupProvider>`. Throws if
|
|
27
|
+
* called outside one — sync-group awareness is mandatory by design,
|
|
28
|
+
* so the error points the consumer at the provider instead of
|
|
29
|
+
* returning undefined and letting downstream code silently miss scope.
|
|
30
|
+
*
|
|
31
|
+
* If a component legitimately renders both inside and outside a
|
|
32
|
+
* group, structure the tree so the hook is only called on the
|
|
33
|
+
* inside path (e.g., split into two components). Silent nulls are
|
|
34
|
+
* never the right answer.
|
|
35
|
+
*/
|
|
36
|
+
export function useSyncGroup() {
|
|
37
|
+
const id = useContext(SyncGroupContext);
|
|
38
|
+
if (!id) {
|
|
39
|
+
throw new AbloValidationError('useSyncGroup: no <SyncGroupProvider> mounted above this component. ' +
|
|
40
|
+
'Wrap your tree with <SyncGroupProvider id="matter:..."> from ' +
|
|
41
|
+
'@ablo/sync-engine/react.', { code: 'no_sync_group_provider' });
|
|
42
|
+
}
|
|
43
|
+
return id;
|
|
44
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { Model } from '../Model.js';
|
|
3
|
+
import type { ModelScope } from '../types/index.js';
|
|
4
|
+
import type { QueryView, QueryViewOptions } from '../core/QueryView.js';
|
|
5
|
+
import type { ViewRegistry } from '../core/ViewRegistry.js';
|
|
6
|
+
import type { Schema } from '../schema/schema.js';
|
|
7
|
+
import type { SyncStatus } from '../BaseSyncedStore.js';
|
|
8
|
+
/**
|
|
9
|
+
* Minimal store interface that the SDK hooks need.
|
|
10
|
+
* Consumers provide their concrete store (e.g., SyncedStore) that implements this.
|
|
11
|
+
*/
|
|
12
|
+
export interface SyncStoreContract {
|
|
13
|
+
retrieve(modelClass: abstract new (...args: never[]) => Model, id: string): Model | undefined;
|
|
14
|
+
queryByClass(modelClass: abstract new (...args: never[]) => Model, options?: {
|
|
15
|
+
predicate?: (model: Model) => boolean;
|
|
16
|
+
scope?: ModelScope;
|
|
17
|
+
orderBy?: keyof Model;
|
|
18
|
+
order?: 'asc' | 'desc';
|
|
19
|
+
limit?: number;
|
|
20
|
+
offset?: number;
|
|
21
|
+
}): {
|
|
22
|
+
data: Model[];
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Save (create or update) one entity. Calling `save` in a tight loop
|
|
26
|
+
* produces a single wire commit with one `batchIndex`: the SyncClient
|
|
27
|
+
* debounces IDB persistence and the server push to one microtask, and
|
|
28
|
+
* TransactionQueue coalesces every transaction staged in the tick into
|
|
29
|
+
* one batch. There is intentionally no `saveMany` — Zero, Replicache,
|
|
30
|
+
* and the rest of the local-first lineage all expose one-row writes
|
|
31
|
+
* and rely on the implicit tick boundary.
|
|
32
|
+
*
|
|
33
|
+
* `skipValidation` exists for trusted bulk paths (AI sandbox layer
|
|
34
|
+
* generation, PPTX import, hydration) where the producer has already
|
|
35
|
+
* type-checked and per-row Zod is a measurable cost.
|
|
36
|
+
*/
|
|
37
|
+
save(model: Model, options?: {
|
|
38
|
+
skipValidation?: boolean;
|
|
39
|
+
}): Promise<void>;
|
|
40
|
+
delete(model: Model): Promise<void>;
|
|
41
|
+
archive(model: Model): Promise<void>;
|
|
42
|
+
unarchive(model: Model): Promise<void>;
|
|
43
|
+
/** The ObjectPool — for entity/collection lookups by ID or typename. */
|
|
44
|
+
pool: {
|
|
45
|
+
get(id: string): Model | undefined;
|
|
46
|
+
getByTypeName(typename: string, scope?: ModelScope): Model[];
|
|
47
|
+
getByForeignKey(modelName: string, fieldName: string, fieldValue: string): Model[];
|
|
48
|
+
createFromData(data: Record<string, unknown>): Model | null;
|
|
49
|
+
hasForeignKeyIndex(typename: string, fieldName: string): boolean;
|
|
50
|
+
createView<T extends Record<string, unknown>>(typename: string, options?: QueryViewOptions<T>): QueryView<T>;
|
|
51
|
+
viewRegistry: ViewRegistry;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Reactive sync-status getters. Powered by MobX `computed` inside
|
|
55
|
+
* `BaseSyncedStore`, so they're safe to read in `observer` components
|
|
56
|
+
* and inside `reaction(() => store.isReady, ...)`. Consumers that
|
|
57
|
+
* don't want to touch MobX should prefer the `useSyncStatus()` hook.
|
|
58
|
+
*/
|
|
59
|
+
readonly isReady: boolean;
|
|
60
|
+
readonly isSyncing: boolean;
|
|
61
|
+
readonly isOffline: boolean;
|
|
62
|
+
readonly isReconnecting: boolean;
|
|
63
|
+
readonly isError: boolean;
|
|
64
|
+
readonly hasUnsyncedChanges: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Raw MobX-observable `SyncStatus` record. `useSyncStatus()` reads
|
|
67
|
+
* `state`, `progress`, `pendingChanges`, `isSessionError`, `error`
|
|
68
|
+
* from this to build its tagged union. Exposed on the contract so
|
|
69
|
+
* consumer-facing hooks and test doubles can manipulate it directly.
|
|
70
|
+
*/
|
|
71
|
+
readonly syncStatus: SyncStatus;
|
|
72
|
+
}
|
|
73
|
+
export interface SyncReactContext {
|
|
74
|
+
store: SyncStoreContract;
|
|
75
|
+
/** Current organization ID for default entity context */
|
|
76
|
+
organizationId: string;
|
|
77
|
+
/**
|
|
78
|
+
* Optional schema reference. When set, compatibility hook overloads
|
|
79
|
+
* (`useQuery('tasks')`, `useOne('tasks', id)`, etc.) resolve their
|
|
80
|
+
* model metadata from this schema — consumers don't pass `schema` at
|
|
81
|
+
* every call site. When absent, hooks fall back to the legacy
|
|
82
|
+
* `(schema, modelKey, …)` signatures so non-opting consumers keep
|
|
83
|
+
* working unchanged.
|
|
84
|
+
*
|
|
85
|
+
* The stored reference is untyped here (`Schema` with default
|
|
86
|
+
* parameters) because the React context is a single runtime value
|
|
87
|
+
* shared by every hook. The compile-time types flow from the
|
|
88
|
+
* consumer's `declare global { interface AbloSync { Schema: ... } }`
|
|
89
|
+
* augmentation — see `src/types/global.ts`.
|
|
90
|
+
*/
|
|
91
|
+
schema?: Schema;
|
|
92
|
+
/**
|
|
93
|
+
* Optional presence source. When set, `usePresence()` returns this
|
|
94
|
+
* value cast to the consumer's `ResolvePresence` type (declared via
|
|
95
|
+
* `interface AbloSync { Presence: ... }`). The SDK doesn't own a
|
|
96
|
+
* presence wire format — consumers plug whatever backs their cursors,
|
|
97
|
+
* status, or activity state (a MobX store, a Zustand slice, a custom
|
|
98
|
+
* subscription). The typed-global gives it a call-site-ergonomic
|
|
99
|
+
* type without the SDK dictating the transport.
|
|
100
|
+
*/
|
|
101
|
+
presence?: unknown;
|
|
102
|
+
/**
|
|
103
|
+
* Optional intent initiator. Same pattern as presence — consumers
|
|
104
|
+
* plug a function that turns an intent claim into a handle they
|
|
105
|
+
* control (WebSocket send, optimistic local update, whatever).
|
|
106
|
+
* `useIntent(name)` returns a typed invoker for the named intent
|
|
107
|
+
* from `interface AbloSync { Intents: ... }`.
|
|
108
|
+
*/
|
|
109
|
+
beginIntent?: (intentName: string, claim: unknown) => unknown;
|
|
110
|
+
}
|
|
111
|
+
export declare const SyncContext: import("react").Context<SyncReactContext | null>;
|
|
112
|
+
/**
|
|
113
|
+
* Access the sync store from React components.
|
|
114
|
+
* Must be used within a SyncProvider.
|
|
115
|
+
*/
|
|
116
|
+
export declare function useSyncContext(): SyncReactContext;
|
|
117
|
+
/**
|
|
118
|
+
* Props for SyncProvider.
|
|
119
|
+
*/
|
|
120
|
+
export interface SyncProviderProps {
|
|
121
|
+
/** The sync store (must implement SyncStoreContract). */
|
|
122
|
+
store: SyncStoreContract;
|
|
123
|
+
/** Current organization ID for default entity context. */
|
|
124
|
+
organizationId: string;
|
|
125
|
+
/**
|
|
126
|
+
* Optional schema. Wire this when you want compatibility string-keyed hooks
|
|
127
|
+
* (`useQuery('tasks')`) — the schema type also narrows via the
|
|
128
|
+
* consumer's global `AbloSync` declaration. Omit to keep hooks on
|
|
129
|
+
* their legacy `(schema, modelKey, …)` signatures.
|
|
130
|
+
*/
|
|
131
|
+
schema?: Schema;
|
|
132
|
+
/**
|
|
133
|
+
* Optional presence source for `usePresence()`. See
|
|
134
|
+
* {@link SyncReactContext.presence} — the consumer plugs whatever
|
|
135
|
+
* backs their presence state; the hook returns it with
|
|
136
|
+
* `ResolvePresence` typing.
|
|
137
|
+
*/
|
|
138
|
+
presence?: unknown;
|
|
139
|
+
/**
|
|
140
|
+
* Optional intent initiator for `useIntent()`. See
|
|
141
|
+
* {@link SyncReactContext.beginIntent}.
|
|
142
|
+
*/
|
|
143
|
+
beginIntent?: (intentName: string, claim: unknown) => unknown;
|
|
144
|
+
children?: ReactNode;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* SyncProvider wires the sync store into React so SDK hooks
|
|
148
|
+
* (useModel, useModels, useMutations) can access it.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* import { SyncProvider } from '@ablo/sync-engine/react';
|
|
152
|
+
*
|
|
153
|
+
* function App() {
|
|
154
|
+
* return (
|
|
155
|
+
* <SyncProvider store={syncStore} organizationId={orgId}>
|
|
156
|
+
* <YourApp />
|
|
157
|
+
* </SyncProvider>
|
|
158
|
+
* );
|
|
159
|
+
* }
|
|
160
|
+
*/
|
|
161
|
+
export declare function SyncProvider({ store, organizationId, schema, presence, beginIntent, children, }: SyncProviderProps): import("react").FunctionComponentElement<import("react").ProviderProps<SyncReactContext | null>>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { createContext, createElement, useContext } from 'react';
|
|
3
|
+
import { AbloValidationError } from '../errors.js';
|
|
4
|
+
export const SyncContext = createContext(null);
|
|
5
|
+
/**
|
|
6
|
+
* Access the sync store from React components.
|
|
7
|
+
* Must be used within a SyncProvider.
|
|
8
|
+
*/
|
|
9
|
+
export function useSyncContext() {
|
|
10
|
+
const ctx = useContext(SyncContext);
|
|
11
|
+
if (!ctx) {
|
|
12
|
+
throw new AbloValidationError('useSyncContext must be used within a SyncProvider', {
|
|
13
|
+
code: 'sync_context_missing_provider',
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return ctx;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* SyncProvider wires the sync store into React so SDK hooks
|
|
20
|
+
* (useModel, useModels, useMutations) can access it.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* import { SyncProvider } from '@ablo/sync-engine/react';
|
|
24
|
+
*
|
|
25
|
+
* function App() {
|
|
26
|
+
* return (
|
|
27
|
+
* <SyncProvider store={syncStore} organizationId={orgId}>
|
|
28
|
+
* <YourApp />
|
|
29
|
+
* </SyncProvider>
|
|
30
|
+
* );
|
|
31
|
+
* }
|
|
32
|
+
*/
|
|
33
|
+
export function SyncProvider({ store, organizationId, schema, presence, beginIntent, children, }) {
|
|
34
|
+
return createElement(SyncContext.Provider, { value: { store, organizationId, schema, presence, beginIntent } }, children);
|
|
35
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ablo/sync-engine/react — React bindings (v0.3.0)
|
|
3
|
+
*
|
|
4
|
+
* Umbrella provider:
|
|
5
|
+
* <AbloProvider schema={...} userId={...} orgId={...} fallback={<Skeleton/>}>
|
|
6
|
+
* — owns sync engine + multiplayer lifecycle; the `fallback` prop
|
|
7
|
+
* gates children on first bootstrap. Pass `fallback="passthrough"`
|
|
8
|
+
* to disable the gate.
|
|
9
|
+
* <SyncGroupProvider id="matter:..."> — per-entity scope
|
|
10
|
+
* <ClientSideSuspense fallback={<Skeleton/>}> — NESTED gate inside an
|
|
11
|
+
* already-ready provider. Use only when you need a separate gate
|
|
12
|
+
* for a heavy subtree (e.g. a canvas) while app chrome renders
|
|
13
|
+
* immediately. The provider-level `fallback` is the default path.
|
|
14
|
+
*
|
|
15
|
+
* Data hooks:
|
|
16
|
+
* useAblo((ablo) => ablo.tasks.retrieve(id)) — primary React read API
|
|
17
|
+
* useAblo() — typed client for callbacks/effects
|
|
18
|
+
* useQuery/useOne/useReader/useMutate — compatibility helpers for older
|
|
19
|
+
* string-keyed integrations
|
|
20
|
+
* useMutators(defs, opts?) — Zero-style custom mutators
|
|
21
|
+
* useUndoScope(name) — per-surface undo/redo
|
|
22
|
+
*
|
|
23
|
+
* Status + errors:
|
|
24
|
+
* useSyncStatus() — tagged-union lifecycle snapshot
|
|
25
|
+
* useErrorListener(cb) — imperative error callback (Sentry/Datadog)
|
|
26
|
+
* useCurrentUserId() — the provider's userId prop
|
|
27
|
+
*
|
|
28
|
+
* Multiplayer (always available — `<AbloProvider>` always constructs a client):
|
|
29
|
+
* useAblo((ablo) => ablo.intents.list(...)) — reactive coordination reads
|
|
30
|
+
* useParticipant({ scope }) — join multiplayer for a scope, get peers/claims
|
|
31
|
+
* usePresence() — typed presence view
|
|
32
|
+
* useIntent(name) — typed intent dispatcher
|
|
33
|
+
*
|
|
34
|
+
* ── Breaking changes from v0.2.x ───────────────────────────────────
|
|
35
|
+
* Removed: <SyncProvider>, SyncContext, useSyncContext — folded into
|
|
36
|
+
* <AbloProvider>. Access the raw engine with `useSync()`.
|
|
37
|
+
* Removed: createAbloContext() factory + its returned AbloProvider —
|
|
38
|
+
* multiplayer is now always-on inside <AbloProvider>. Schema-typed
|
|
39
|
+
* participant hooks ship in a follow-up release.
|
|
40
|
+
* Removed: withSync (no-op alias of observer). Import observer
|
|
41
|
+
* from mobx-react-lite directly if you still need it.
|
|
42
|
+
* Changed: useSyncStatus() now returns a discriminated union. See the
|
|
43
|
+
* migration notes in CHANGELOG.md.
|
|
44
|
+
*/
|
|
45
|
+
export type { DefaultSyncShape, ResolveSchema, ResolvePresence, ResolveIntents, ResolveUserMeta, ResolveModelKey, } from '../types/global.js';
|
|
46
|
+
export { AbloProvider, useParticipant, useSync, useSyncStore, type AbloProviderProps, type ParticipantScope, type ParticipantStatus, type UseParticipantOptions, type UseParticipantReturn, type MeshParticipantStatus, } from './AbloProvider.js';
|
|
47
|
+
export { SyncGroupProvider, useSyncGroup, type SyncGroupProviderProps, } from './SyncGroupProvider.js';
|
|
48
|
+
export { ClientSideSuspense, type ClientSideSuspenseProps, } from './ClientSideSuspense.js';
|
|
49
|
+
export { DefaultFallback } from './DefaultFallback.js';
|
|
50
|
+
export type { SyncStoreContract } from './context.js';
|
|
51
|
+
export { useSyncStatus, type SyncStatusSnapshot, } from './useSyncStatus.js';
|
|
52
|
+
export { useErrorListener } from './useErrorListener.js';
|
|
53
|
+
export { useMutationFailureListener, type MutationFailurePayload, } from './useMutationFailureListener.js';
|
|
54
|
+
export { useCurrentUserId } from './useCurrentUserId.js';
|
|
55
|
+
export { useReactive } from './useReactive.js';
|
|
56
|
+
export { useQuery, useOne, type QueryOptions } from './useQuery.js';
|
|
57
|
+
export { useMutate, type MutateActions } from './useMutate.js';
|
|
58
|
+
export { useReader, type ReaderActions, type ReaderFindOptions } from './useReader.js';
|
|
59
|
+
export { useMutators, type MutatorInvokers, type InvokerFor, type UseMutatorsOptions, } from './useMutators.js';
|
|
60
|
+
export { useUndoScope, type UseUndoScopeResult } from './useUndoScope.js';
|
|
61
|
+
export { useAblo, type UseAbloHydratedModelResult, type UseAbloModelOptions, type UseAbloModelResult, } from './useAblo.js';
|
|
62
|
+
export { usePresence } from './usePresence.js';
|
|
63
|
+
export { useIntent } from './useIntent.js';
|
|
64
|
+
export { ModelScope } from '../types/index.js';
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ablo/sync-engine/react — React bindings (v0.3.0)
|
|
3
|
+
*
|
|
4
|
+
* Umbrella provider:
|
|
5
|
+
* <AbloProvider schema={...} userId={...} orgId={...} fallback={<Skeleton/>}>
|
|
6
|
+
* — owns sync engine + multiplayer lifecycle; the `fallback` prop
|
|
7
|
+
* gates children on first bootstrap. Pass `fallback="passthrough"`
|
|
8
|
+
* to disable the gate.
|
|
9
|
+
* <SyncGroupProvider id="matter:..."> — per-entity scope
|
|
10
|
+
* <ClientSideSuspense fallback={<Skeleton/>}> — NESTED gate inside an
|
|
11
|
+
* already-ready provider. Use only when you need a separate gate
|
|
12
|
+
* for a heavy subtree (e.g. a canvas) while app chrome renders
|
|
13
|
+
* immediately. The provider-level `fallback` is the default path.
|
|
14
|
+
*
|
|
15
|
+
* Data hooks:
|
|
16
|
+
* useAblo((ablo) => ablo.tasks.retrieve(id)) — primary React read API
|
|
17
|
+
* useAblo() — typed client for callbacks/effects
|
|
18
|
+
* useQuery/useOne/useReader/useMutate — compatibility helpers for older
|
|
19
|
+
* string-keyed integrations
|
|
20
|
+
* useMutators(defs, opts?) — Zero-style custom mutators
|
|
21
|
+
* useUndoScope(name) — per-surface undo/redo
|
|
22
|
+
*
|
|
23
|
+
* Status + errors:
|
|
24
|
+
* useSyncStatus() — tagged-union lifecycle snapshot
|
|
25
|
+
* useErrorListener(cb) — imperative error callback (Sentry/Datadog)
|
|
26
|
+
* useCurrentUserId() — the provider's userId prop
|
|
27
|
+
*
|
|
28
|
+
* Multiplayer (always available — `<AbloProvider>` always constructs a client):
|
|
29
|
+
* useAblo((ablo) => ablo.intents.list(...)) — reactive coordination reads
|
|
30
|
+
* useParticipant({ scope }) — join multiplayer for a scope, get peers/claims
|
|
31
|
+
* usePresence() — typed presence view
|
|
32
|
+
* useIntent(name) — typed intent dispatcher
|
|
33
|
+
*
|
|
34
|
+
* ── Breaking changes from v0.2.x ───────────────────────────────────
|
|
35
|
+
* Removed: <SyncProvider>, SyncContext, useSyncContext — folded into
|
|
36
|
+
* <AbloProvider>. Access the raw engine with `useSync()`.
|
|
37
|
+
* Removed: createAbloContext() factory + its returned AbloProvider —
|
|
38
|
+
* multiplayer is now always-on inside <AbloProvider>. Schema-typed
|
|
39
|
+
* participant hooks ship in a follow-up release.
|
|
40
|
+
* Removed: withSync (no-op alias of observer). Import observer
|
|
41
|
+
* from mobx-react-lite directly if you still need it.
|
|
42
|
+
* Changed: useSyncStatus() now returns a discriminated union. See the
|
|
43
|
+
* migration notes in CHANGELOG.md.
|
|
44
|
+
*/
|
|
45
|
+
// ── Umbrella provider + lifecycle hooks ────────────────────────────
|
|
46
|
+
export { AbloProvider, useParticipant, useSync, useSyncStore, } from './AbloProvider.js';
|
|
47
|
+
export { SyncGroupProvider, useSyncGroup, } from './SyncGroupProvider.js';
|
|
48
|
+
export { ClientSideSuspense, } from './ClientSideSuspense.js';
|
|
49
|
+
export { DefaultFallback } from './DefaultFallback.js';
|
|
50
|
+
// ── Status + errors + identity ─────────────────────────────────────
|
|
51
|
+
export { useSyncStatus, } from './useSyncStatus.js';
|
|
52
|
+
export { useErrorListener } from './useErrorListener.js';
|
|
53
|
+
export { useMutationFailureListener, } from './useMutationFailureListener.js';
|
|
54
|
+
export { useCurrentUserId } from './useCurrentUserId.js';
|
|
55
|
+
// ── Primitive for building custom reactive hooks ──────────────────
|
|
56
|
+
//
|
|
57
|
+
// Consumers building bespoke hooks on top of the SDK should call
|
|
58
|
+
// `useReactive(() => compute())` instead of reaching for React's
|
|
59
|
+
// lower-level `useSyncExternalStore`. Hides the cached-snapshot
|
|
60
|
+
// contract and handles default structural equality for arrays.
|
|
61
|
+
export { useReactive } from './useReactive.js';
|
|
62
|
+
// ── Data hooks ─────────────────────────────────────────────────────
|
|
63
|
+
export { useQuery, useOne } from './useQuery.js';
|
|
64
|
+
export { useMutate } from './useMutate.js';
|
|
65
|
+
export { useReader } from './useReader.js';
|
|
66
|
+
export { useMutators, } from './useMutators.js';
|
|
67
|
+
export { useUndoScope } from './useUndoScope.js';
|
|
68
|
+
export { useAblo, } from './useAblo.js';
|
|
69
|
+
// ── Presence + intent (typed via AbloSync global augmentation) ─────
|
|
70
|
+
export { usePresence } from './usePresence.js';
|
|
71
|
+
export { useIntent } from './useIntent.js';
|
|
72
|
+
// ── ModelScope re-export ───────────────────────────────────────────
|
|
73
|
+
export { ModelScope } from '../types/index.js';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Ablo } from '../client/Ablo.js';
|
|
2
|
+
import type { SchemaRecord } from '../schema/schema.js';
|
|
3
|
+
/**
|
|
4
|
+
* Internal context populated by `<AbloProvider>`. Separate from
|
|
5
|
+
* `SyncContext` (which carries the store + schema for the data
|
|
6
|
+
* hooks) because these fields are owned by the umbrella provider
|
|
7
|
+
* and don't belong on the raw `SyncStoreContract`.
|
|
8
|
+
*
|
|
9
|
+
* Consumers should NOT use this directly — access the fields via
|
|
10
|
+
* the typed hooks (`useCurrentUserId`, `useErrorListener`, etc.).
|
|
11
|
+
*/
|
|
12
|
+
export interface AbloInternalContextValue {
|
|
13
|
+
/**
|
|
14
|
+
* Optional app user id when the application passed one. Hosted Ablo
|
|
15
|
+
* identity is server-derived, so this may be null.
|
|
16
|
+
*/
|
|
17
|
+
currentUserId: string | null;
|
|
18
|
+
/** Subscribe to provider-level errors (engine errors, bootstrap failures, session issues). */
|
|
19
|
+
subscribeError: (listener: (error: Error) => void) => () => void;
|
|
20
|
+
/** Fire an error to all subscribed listeners. Called internally by the provider. */
|
|
21
|
+
emitError: (error: Error) => void;
|
|
22
|
+
/**
|
|
23
|
+
* The SyncEngine proxy for this provider. `null` before bootstrap
|
|
24
|
+
* resolves. Exposed through the internal context so `useSync()`
|
|
25
|
+
* can return it without having to reach into the store — the two
|
|
26
|
+
* are sibling objects constructed together by `createSyncEngine`
|
|
27
|
+
* and shouldn't be coerced through each other.
|
|
28
|
+
*
|
|
29
|
+
* Typed as `Ablo<SchemaRecord>` on the context because
|
|
30
|
+
* generics don't flow through React context. `useSync<R>()` widens
|
|
31
|
+
* via its own generic — runtime value is the concrete engine.
|
|
32
|
+
*/
|
|
33
|
+
engine: Ablo<SchemaRecord> | null;
|
|
34
|
+
}
|
|
35
|
+
export declare const AbloInternalContext: import("react").Context<AbloInternalContextValue | null>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Ablo, ResourceIntent } from '../client/Ablo.js';
|
|
2
|
+
import type { ModelOperations } from '../client/createModelProxy.js';
|
|
3
|
+
import type { SchemaRecord } from '../schema/schema.js';
|
|
4
|
+
import type { ResolveSchema } from '../types/global.js';
|
|
5
|
+
/**
|
|
6
|
+
* Resolved schema-record type for the consumer's app. Reads the
|
|
7
|
+
* `AbloSync` global augmentation if declared, falls back to the
|
|
8
|
+
* loose `SchemaRecord` if not. This lets `useAblo()` produce a
|
|
9
|
+
* fully typed engine handle without the consumer having to pass
|
|
10
|
+
* `<(typeof schema)['models']>` at every call site.
|
|
11
|
+
*/
|
|
12
|
+
type DefaultModels = ResolveSchema extends {
|
|
13
|
+
models: infer M;
|
|
14
|
+
} ? M extends SchemaRecord ? M : SchemaRecord : SchemaRecord;
|
|
15
|
+
type ModelResourceSelector<R extends SchemaRecord, T, C> = (ablo: Ablo<R>) => ModelOperations<T, C>;
|
|
16
|
+
type AbloSelector<R extends SchemaRecord, T> = (ablo: Ablo<R>) => T;
|
|
17
|
+
export interface UseAbloModelOptions<T> {
|
|
18
|
+
/**
|
|
19
|
+
* Initial row, usually from a Server Component or loader. The hook returns it
|
|
20
|
+
* until the model resource has a newer row in the local pool.
|
|
21
|
+
*/
|
|
22
|
+
readonly initial?: T;
|
|
23
|
+
}
|
|
24
|
+
export interface UseAbloModelResult<T> {
|
|
25
|
+
/** Current row for the id, or `initial` until the row has hydrated. */
|
|
26
|
+
readonly data: T | undefined;
|
|
27
|
+
/** Active work claims on this model row. */
|
|
28
|
+
readonly intents: readonly ResourceIntent[];
|
|
29
|
+
/** Convenience flag for disabling UI while another participant is active. */
|
|
30
|
+
readonly busy: boolean;
|
|
31
|
+
}
|
|
32
|
+
export type UseAbloHydratedModelResult<T> = Omit<UseAbloModelResult<T>, 'data'> & {
|
|
33
|
+
readonly data: T;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* useAblo — access the typed engine instance, or subscribe to a specific
|
|
37
|
+
* `ablo.<model>` row from inside an `<AbloProvider>` subtree.
|
|
38
|
+
*
|
|
39
|
+
* Zero-arg when the consumer declares the `AbloSync` global
|
|
40
|
+
* augmentation (`declare global { interface AbloSync { Schema:
|
|
41
|
+
* typeof schema } }`). The default generic resolves through
|
|
42
|
+
* `ResolveSchema['models']` so call sites stay clean:
|
|
43
|
+
*
|
|
44
|
+
* ```ts
|
|
45
|
+
* // With AbloSync augmentation (recommended):
|
|
46
|
+
* const ablo = useAblo();
|
|
47
|
+
* if (!ablo) return <Loading />;
|
|
48
|
+
* const docs = await ablo.documents.load({ where: { id } });
|
|
49
|
+
*
|
|
50
|
+
* // Reactive selector:
|
|
51
|
+
* const doc = useAblo((ablo) => ablo.documents.retrieve(id)) ?? serverDoc;
|
|
52
|
+
* const intents = useAblo((ablo) => ablo.intents.list({ resource: 'documents', id }));
|
|
53
|
+
*
|
|
54
|
+
* // Without augmentation, pass the schema generic:
|
|
55
|
+
* const ablo = useAblo<(typeof schema)['models']>();
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* Returns `null` while the engine is bootstrapping. Branch on null
|
|
59
|
+
* and render a loading state (or use `useSyncStatus()` to gate on
|
|
60
|
+
* `'connected'`) before reaching for model methods.
|
|
61
|
+
*/
|
|
62
|
+
export declare function useAblo<R extends SchemaRecord = DefaultModels>(): Ablo<R> | null;
|
|
63
|
+
export declare function useAblo<R extends SchemaRecord = DefaultModels, T = unknown>(select: AbloSelector<R, T>): T | undefined;
|
|
64
|
+
export declare function useAblo<T, C>(resource: ModelOperations<T, C>, id: string, options: UseAbloModelOptions<T> & {
|
|
65
|
+
readonly initial: T;
|
|
66
|
+
}): UseAbloHydratedModelResult<T>;
|
|
67
|
+
export declare function useAblo<R extends SchemaRecord = DefaultModels, T = Record<string, unknown>, C = unknown>(select: ModelResourceSelector<R, T, C>, id: string, options: UseAbloModelOptions<T> & {
|
|
68
|
+
readonly initial: T;
|
|
69
|
+
}): UseAbloHydratedModelResult<T>;
|
|
70
|
+
export declare function useAblo<T, C>(resource: ModelOperations<T, C>, id: string, options?: UseAbloModelOptions<T>): UseAbloModelResult<T>;
|
|
71
|
+
export declare function useAblo<R extends SchemaRecord = DefaultModels, T = Record<string, unknown>, C = unknown>(select: ModelResourceSelector<R, T, C>, id: string, options?: UseAbloModelOptions<T>): UseAbloModelResult<T>;
|
|
72
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useContext, useEffect, useState } from 'react';
|
|
3
|
+
import { AbloInternalContext } from './internalContext.js';
|
|
4
|
+
import { getModelResourceMeta } from '../client/createModelProxy.js';
|
|
5
|
+
import { Model, modelAsRow } from '../Model.js';
|
|
6
|
+
import { useReactive } from './useReactive.js';
|
|
7
|
+
const EMPTY_INTENTS = Object.freeze([]);
|
|
8
|
+
function readModelResult(engine, resource, id, initial) {
|
|
9
|
+
if (!resource || id === undefined) {
|
|
10
|
+
return { data: initial, intents: EMPTY_INTENTS, busy: false };
|
|
11
|
+
}
|
|
12
|
+
const data = snapshotValue(resource.retrieve(id) ?? initial);
|
|
13
|
+
const meta = getModelResourceMeta(resource);
|
|
14
|
+
const intents = meta && engine
|
|
15
|
+
? engine.intents.list({ resource: meta.key, id })
|
|
16
|
+
: EMPTY_INTENTS;
|
|
17
|
+
return { data, intents, busy: intents.length > 0 };
|
|
18
|
+
}
|
|
19
|
+
function snapshotValue(value) {
|
|
20
|
+
if (value instanceof Model) {
|
|
21
|
+
return modelAsRow(value);
|
|
22
|
+
}
|
|
23
|
+
if (Array.isArray(value)) {
|
|
24
|
+
return value.map((item) => snapshotValue(item));
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
export function useAblo(resourceOrSelect, id, options) {
|
|
29
|
+
const ctx = useContext(AbloInternalContext);
|
|
30
|
+
const engine = ctx?.engine ?? null;
|
|
31
|
+
const initial = options?.initial;
|
|
32
|
+
const hasSelection = resourceOrSelect !== undefined;
|
|
33
|
+
const isSelectorOnly = typeof resourceOrSelect === 'function' && id === undefined;
|
|
34
|
+
const resource = typeof resourceOrSelect === 'function' && id !== undefined
|
|
35
|
+
? engine
|
|
36
|
+
? resourceOrSelect(engine)
|
|
37
|
+
: undefined
|
|
38
|
+
: typeof resourceOrSelect === 'function'
|
|
39
|
+
? undefined
|
|
40
|
+
: resourceOrSelect;
|
|
41
|
+
const [intentVersion, setIntentVersion] = useState(0);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!engine || !hasSelection)
|
|
44
|
+
return;
|
|
45
|
+
return engine.intents.subscribe(() => setIntentVersion((version) => version + 1));
|
|
46
|
+
}, [engine, hasSelection]);
|
|
47
|
+
const selected = useReactive(() => {
|
|
48
|
+
void intentVersion;
|
|
49
|
+
if (!engine || !isSelectorOnly || typeof resourceOrSelect !== 'function') {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
return snapshotValue(resourceOrSelect(engine));
|
|
53
|
+
});
|
|
54
|
+
const modelResult = useReactive(() => {
|
|
55
|
+
void intentVersion;
|
|
56
|
+
return readModelResult(engine, resource, id, initial);
|
|
57
|
+
});
|
|
58
|
+
if (isSelectorOnly)
|
|
59
|
+
return selected;
|
|
60
|
+
if (resourceOrSelect)
|
|
61
|
+
return modelResult;
|
|
62
|
+
return engine;
|
|
63
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the app user ID passed to the nearest `<AbloProvider>`, when
|
|
3
|
+
* the app chose to provide one.
|
|
4
|
+
*
|
|
5
|
+
* Hosted Ablo identity is resolved server-side from the API key, session,
|
|
6
|
+
* or capability token. This hook is only for app-owned fields like
|
|
7
|
+
* `assigneeId`; it is not required for Ablo sync to connect.
|
|
8
|
+
*
|
|
9
|
+
* Use this in leaf components that need the current user ID for
|
|
10
|
+
* mutation payloads, presence labels, permission checks, etc.
|
|
11
|
+
* @example
|
|
12
|
+
* function TaskRow({ id }) {
|
|
13
|
+
* const userId = useCurrentUserId();
|
|
14
|
+
* const ablo = useAblo();
|
|
15
|
+
* if (!userId) return null;
|
|
16
|
+
* return <button onClick={() => ablo?.tasks.update(id, { assigneeId: userId })}>
|
|
17
|
+
* Assign to me
|
|
18
|
+
* </button>;
|
|
19
|
+
* }
|
|
20
|
+
*/
|
|
21
|
+
export declare function useCurrentUserId(): string | null;
|