@abloatai/ablo 0.11.1 → 0.11.2
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 +34 -0
- package/README.md +10 -2
- package/dist/Model.d.ts +39 -0
- package/dist/Model.js +68 -0
- package/dist/auth/credentialPolicy.d.ts +145 -0
- package/dist/auth/credentialPolicy.js +130 -0
- package/dist/cli.cjs +39 -6
- package/dist/client/Ablo.d.ts +39 -88
- package/dist/client/Ablo.js +38 -98
- package/dist/client/ApiClient.d.ts +10 -1
- package/dist/client/ApiClient.js +19 -11
- package/dist/client/auth.d.ts +12 -5
- package/dist/client/auth.js +2 -1
- package/dist/client/createModelProxy.d.ts +49 -10
- package/dist/client/createModelProxy.js +6 -0
- package/dist/client/httpClient.d.ts +17 -3
- package/dist/client/httpClient.js +1 -0
- package/dist/client/identity.js +134 -122
- package/dist/client/index.d.ts +1 -1
- package/dist/client/sessionMint.d.ts +15 -0
- package/dist/client/sessionMint.js +86 -0
- package/dist/errorCodes.d.ts +2 -0
- package/dist/errorCodes.js +2 -0
- package/dist/errors.d.ts +3 -2
- package/dist/errors.js +3 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -7
- package/dist/mutators/RecordingTransaction.js +14 -42
- package/dist/react/AbloProvider.d.ts +1 -6
- package/dist/react/AbloProvider.js +1 -5
- package/dist/react/context.d.ts +1 -31
- package/dist/react/context.js +2 -2
- package/dist/react/index.d.ts +0 -6
- package/dist/react/index.js +0 -7
- package/dist/react/useSyncStatus.d.ts +1 -1
- package/dist/realtime/index.d.ts +1 -1
- package/dist/schema/generate.js +1 -2
- package/dist/schema/schema.d.ts +13 -2
- package/dist/schema/schema.js +26 -0
- package/dist/surface.d.ts +29 -0
- package/dist/surface.js +60 -0
- package/dist/sync/ConnectionManager.d.ts +16 -5
- package/dist/sync/ConnectionManager.js +42 -7
- package/dist/transactions/TransactionQueue.d.ts +0 -11
- package/dist/transactions/TransactionQueue.js +12 -56
- package/dist/types/global.d.ts +3 -0
- package/dist/types/streams.d.ts +0 -22
- package/dist/utils/mobx-setup.js +1 -0
- package/docs/api-keys.md +49 -0
- package/docs/api.md +3 -2
- package/docs/client-behavior.md +1 -0
- package/docs/coordination.md +75 -21
- package/docs/examples/existing-python-backend.md +9 -5
- package/docs/examples/scoped-agent.md +1 -1
- package/docs/guarantees.md +4 -3
- package/docs/identity.md +89 -82
- package/docs/integration-guide.md +19 -10
- package/docs/migration.md +9 -2
- package/docs/quickstart.md +6 -2
- package/docs/react.md +3 -3
- package/docs/schema-contract.md +23 -5
- package/llms-full.txt +18 -16
- package/llms.txt +6 -6
- package/package.json +1 -1
- package/dist/api/index.d.ts +0 -10
- package/dist/api/index.js +0 -9
- package/dist/principal.d.ts +0 -44
- package/dist/principal.js +0 -49
- package/dist/react/SyncGroupProvider.d.ts +0 -19
- package/dist/react/SyncGroupProvider.js +0 -44
- package/dist/react/useClaim.d.ts +0 -29
- package/dist/react/useClaim.js +0 -42
- package/dist/react/usePresence.d.ts +0 -32
- package/dist/react/usePresence.js +0 -41
package/dist/errors.d.ts
CHANGED
|
@@ -186,8 +186,9 @@ export declare function formatClaimedErrorMessage(args: {
|
|
|
186
186
|
* The target entity is currently claimed by another participant and the caller
|
|
187
187
|
* asked the SDK not to read/write through that claim.
|
|
188
188
|
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
189
|
+
* Pass `ifClaimed: 'return'` to inspect active claims yourself instead of
|
|
190
|
+
* throwing; to wait for the claim to clear, take `ablo.<model>.claim({ id })`
|
|
191
|
+
* (it queues fairly) rather than blocking the read.
|
|
191
192
|
*/
|
|
192
193
|
export declare class AbloClaimedError extends AbloError {
|
|
193
194
|
readonly type: "AbloClaimedError";
|
package/dist/errors.js
CHANGED
|
@@ -208,8 +208,9 @@ export function formatClaimedErrorMessage(args) {
|
|
|
208
208
|
* The target entity is currently claimed by another participant and the caller
|
|
209
209
|
* asked the SDK not to read/write through that claim.
|
|
210
210
|
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
211
|
+
* Pass `ifClaimed: 'return'` to inspect active claims yourself instead of
|
|
212
|
+
* throwing; to wait for the claim to clear, take `ablo.<model>.claim({ id })`
|
|
213
|
+
* (it queues fairly) rather than blocking the read.
|
|
213
214
|
*/
|
|
214
215
|
export class AbloClaimedError extends AbloError {
|
|
215
216
|
type = 'AbloClaimedError';
|
package/dist/index.d.ts
CHANGED
|
@@ -43,7 +43,6 @@
|
|
|
43
43
|
* Advanced — opt-in, most apps never import these (each is tagged
|
|
44
44
|
* "Advanced —" at its export below, with the one situation it's for):
|
|
45
45
|
* • `dataSource` / `abloSource` — only if your own DB stays canonical
|
|
46
|
-
* • `session` / `agent` — only for delegated agent principals
|
|
47
46
|
* • `defaultPolicy` — only to customize conflict resolution
|
|
48
47
|
* • `defineMutators` / `createTransaction` — only for custom mutators
|
|
49
48
|
* If you don't recognize one, you don't need it — the default path covers you.
|
|
@@ -51,11 +50,10 @@
|
|
|
51
50
|
export { Ablo } from './client/Ablo.js';
|
|
52
51
|
export type { MutationExecutor } from './interfaces/index.js';
|
|
53
52
|
export type { HttpClaimApi, InternalAbloOptions } from './client/Ablo.js';
|
|
54
|
-
export {
|
|
53
|
+
export { type AbloHttpClientOptions, type AbloHttpClient, type HttpModelClient, } from './client/httpClient.js';
|
|
55
54
|
export { ABLO_DEFAULT_BASE_URL, ABLO_HOSTED_API_DOMAIN, ABLO_HOSTED_HTTP_BASE_URL, normalizeAbloHostedBaseUrl, } from './client/auth.js';
|
|
56
|
-
export type { AbloOptions,
|
|
55
|
+
export type { AbloOptions, LocalCountOptions, LocalReadOptions, ModelListScope, ServerReadOptions, ModelRetrieveParams, ModelCreateParams, ModelUpdateParams, ModelDeleteParams, ClaimOptions, ClaimParams, ClaimLookupParams, ClaimReorderParams, ClaimHandle, ModelOperations, } from './client/Ablo.js';
|
|
57
56
|
export type { AbloPersistence } from './client/persistence.js';
|
|
58
|
-
export { session, agent } from './principal.js';
|
|
59
57
|
import { Ablo } from './client/Ablo.js';
|
|
60
58
|
export default Ablo;
|
|
61
59
|
export { dataSource, abloSource, sourceEventForOperation, signAbloSourceRequest, verifyAbloSourceRequest, } from './source/index.js';
|
|
@@ -70,6 +68,8 @@ export { writeOptionsSchema, onStaleModeSchema, assertWriteOptions, } from './cl
|
|
|
70
68
|
export type { WriteOptionsInput } from './client/writeOptionsSchema.js';
|
|
71
69
|
export type { WriteOptions, MutationOptions } from './interfaces/index.js';
|
|
72
70
|
export { IDBOpenTimeoutError, isStorageOpenTimeout } from './core/openIDBWithTimeout.js';
|
|
71
|
+
export { PUBLIC_MODEL_VERBS, PUBLIC_LIST_OPTION_KEYS, PUBLIC_ABLO_OPTION_KEYS, } from './surface.js';
|
|
72
|
+
export type { ModelVerb, ListOptionKey, AbloOptionKey } from './surface.js';
|
|
73
73
|
export type { Register, DefaultSyncShape } from './types/global.js';
|
|
74
74
|
export { defineMutators } from './mutators/defineMutators.js';
|
|
75
75
|
export { createTransaction, type Transaction } from './mutators/Transaction.js';
|
package/dist/index.js
CHANGED
|
@@ -43,7 +43,6 @@
|
|
|
43
43
|
* Advanced — opt-in, most apps never import these (each is tagged
|
|
44
44
|
* "Advanced —" at its export below, with the one situation it's for):
|
|
45
45
|
* • `dataSource` / `abloSource` — only if your own DB stays canonical
|
|
46
|
-
* • `session` / `agent` — only for delegated agent principals
|
|
47
46
|
* • `defaultPolicy` — only to customize conflict resolution
|
|
48
47
|
* • `defineMutators` / `createTransaction` — only for custom mutators
|
|
49
48
|
* If you don't recognize one, you don't need it — the default path covers you.
|
|
@@ -56,17 +55,11 @@
|
|
|
56
55
|
// `import Ablo from '@abloatai/ablo'` works; named export so
|
|
57
56
|
// `import { Ablo }` also compiles.
|
|
58
57
|
export { Ablo } from './client/Ablo.js';
|
|
59
|
-
export { createAbloHttpClient, } from './client/httpClient.js';
|
|
60
58
|
export { ABLO_DEFAULT_BASE_URL, ABLO_HOSTED_API_DOMAIN, ABLO_HOSTED_HTTP_BASE_URL, normalizeAbloHostedBaseUrl, } from './client/auth.js';
|
|
61
59
|
// Participant types live under `Ablo.Participant.*` —
|
|
62
60
|
// `Ablo.Participant.Joined`, `Ablo.Participant.Manager`,
|
|
63
61
|
// `Ablo.Participant.JoinOptions`, etc. Same dot-access shape as
|
|
64
62
|
// `Ablo.Peer`, `Ablo.Claim`. No flat re-exports.
|
|
65
|
-
// Advanced — most apps never import this. Principal constructors for
|
|
66
|
-
// delegated agent paths (`Ablo({ kind: 'agent', as: session({...}) })`).
|
|
67
|
-
// The default `Ablo({ schema, apiKey })` resolves identity from the key;
|
|
68
|
-
// reach for these only when minting a delegated agent principal.
|
|
69
|
-
export { session, agent } from './principal.js';
|
|
70
63
|
import { Ablo } from './client/Ablo.js';
|
|
71
64
|
export default Ablo;
|
|
72
65
|
// Advanced — most apps never import this. Customer-owned storage adapter
|
|
@@ -100,6 +93,10 @@ export { writeOptionsSchema, onStaleModeSchema, assertWriteOptions, } from './cl
|
|
|
100
93
|
// Storage-wedge detection — lets app shells render a recovery screen when the
|
|
101
94
|
// IndexedDB backing store is stuck (see core/openIDBWithTimeout.ts).
|
|
102
95
|
export { IDBOpenTimeoutError, isStorageOpenTimeout } from './core/openIDBWithTimeout.js';
|
|
96
|
+
// Machine-checked surface manifest — the SDK's own description of its public
|
|
97
|
+
// verb/option names, compile-time-bound to the real types (see surface.ts).
|
|
98
|
+
// The MCP `get_api_surface` imports these so docs can't name a phantom verb.
|
|
99
|
+
export { PUBLIC_MODEL_VERBS, PUBLIC_LIST_OPTION_KEYS, PUBLIC_ABLO_OPTION_KEYS, } from './surface.js';
|
|
103
100
|
// Advanced — most apps never import this. Custom (Zero-style) mutators:
|
|
104
101
|
// `ablo.<model>.create/update/delete` already covers normal writes. Reach
|
|
105
102
|
// for `defineMutators` only when you need a named, multi-step mutation with
|
|
@@ -59,55 +59,27 @@ function wrapMutateForKey(modelKey, mutate, store, inverses, forwards) {
|
|
|
59
59
|
// wider shape is exactly right.
|
|
60
60
|
return model.toJSON();
|
|
61
61
|
};
|
|
62
|
+
// Before-image for the undo inverse. Delegates to `Model.capturePreviousValues`
|
|
63
|
+
// — the SINGLE shared implementation (the stream path's
|
|
64
|
+
// `TransactionQueue.extractPreviousData` calls the same method). `fallbackToLive`
|
|
65
|
+
// is ON here: the manual-record path wants the live value as a last resort for
|
|
66
|
+
// a field that was neither pre-mutated nor in the original snapshot. (The
|
|
67
|
+
// stream path passes `false` so it can omit-and-drop instead — that flag is
|
|
68
|
+
// the one intentional difference between the two callers.)
|
|
62
69
|
const snapshotFields = (id, fieldNames) => {
|
|
63
70
|
const model = store.pool.get(id);
|
|
64
71
|
if (!model)
|
|
65
72
|
return null;
|
|
66
|
-
|
|
67
|
-
// `modifiedProperties` is populated by M1's `observe()` listener the
|
|
68
|
-
// moment the caller mutates an observable field directly. Thanks to
|
|
69
|
-
// `Model.propertyChanged`'s first-old-wins policy, `.old` holds the TRUE
|
|
70
|
-
// pre-session baseline even after many in-place mutations (e.g. a drag
|
|
71
|
-
// frame loop). That makes it the authoritative source for the undo
|
|
72
|
-
// inverse when the caller pre-mutates before invoking the mutator.
|
|
73
|
-
//
|
|
74
|
-
// Fallback chain for models/fields that weren't pre-mutated (so no
|
|
75
|
-
// `modifiedProperties` entry exists yet): `getOriginalSnapshot()`
|
|
76
|
-
// (populated on load/`markAsPersisted`/sync-ack), then the live
|
|
77
|
-
// observable. The live read is correct only when the caller didn't
|
|
78
|
-
// touch the field first.
|
|
79
|
-
const original = model.getOriginalSnapshot();
|
|
80
|
-
for (const f of fieldNames) {
|
|
81
|
-
if (f === 'id')
|
|
82
|
-
continue;
|
|
83
|
-
const mod = model.modifiedProperties.get(f);
|
|
84
|
-
if (mod) {
|
|
85
|
-
out[f] = mod.old;
|
|
86
|
-
}
|
|
87
|
-
else if (original && f in original) {
|
|
88
|
-
out[f] = original[f];
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
out[f] = Reflect.get(model, f);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return out;
|
|
73
|
+
return model.capturePreviousValues(fieldNames, { fallbackToLive: true });
|
|
95
74
|
};
|
|
96
75
|
// After a mutator's `base.update` succeeds, drop the `modifiedProperties`
|
|
97
|
-
// entries we snapshotted from
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
76
|
+
// entries we snapshotted from so the next mutator call sees THIS update's
|
|
77
|
+
// result as its baseline, not the pre-session old value. The transaction
|
|
78
|
+
// queue already captured its frozen copy synchronously inside `store.save`,
|
|
79
|
+
// so this clear is safe for server rollback. Shared with the stream path via
|
|
80
|
+
// `Model.consumeModifiedFields`.
|
|
102
81
|
const consumeModifiedFields = (id, fieldNames) => {
|
|
103
|
-
|
|
104
|
-
if (!model)
|
|
105
|
-
return;
|
|
106
|
-
for (const f of fieldNames) {
|
|
107
|
-
if (f === 'id')
|
|
108
|
-
continue;
|
|
109
|
-
model.modifiedProperties.delete(f);
|
|
110
|
-
}
|
|
82
|
+
store.pool.get(id)?.consumeModifiedFields(fieldNames);
|
|
111
83
|
};
|
|
112
84
|
return {
|
|
113
85
|
// Overloaded — single row or array. The recorder dispatches the
|
|
@@ -36,7 +36,7 @@ import { type SyncStoreContract } from './context.js';
|
|
|
36
36
|
* // Build once at module scope — a new instance per render tears down the socket.
|
|
37
37
|
* const ablo = Ablo({
|
|
38
38
|
* schema,
|
|
39
|
-
*
|
|
39
|
+
* apiKey: () =>
|
|
40
40
|
* fetch('/api/ablo-session', { method: 'POST' })
|
|
41
41
|
* .then((r) => r.json())
|
|
42
42
|
* .then((d) => d.token),
|
|
@@ -120,12 +120,7 @@ export type { EngineParticipant, ParticipantScope, ParticipantStatus };
|
|
|
120
120
|
*/
|
|
121
121
|
export interface UseParticipantOptions {
|
|
122
122
|
readonly scope?: ParticipantScope;
|
|
123
|
-
readonly label?: string;
|
|
124
|
-
readonly as?: unknown;
|
|
125
123
|
readonly ttlSeconds?: number | string | null;
|
|
126
|
-
readonly agent?: unknown;
|
|
127
|
-
readonly idempotencyKey?: string | null;
|
|
128
|
-
readonly autoRefreshThresholdSeconds?: number | null;
|
|
129
124
|
/** Tear down + don't re-join while true. */
|
|
130
125
|
readonly paused?: boolean;
|
|
131
126
|
/**
|
|
@@ -36,7 +36,7 @@ export function AbloProvider(props) {
|
|
|
36
36
|
// REACTIVE binding over it (context + bootstrap gate + error/session
|
|
37
37
|
// forwarding); it does NOT construct, configure, or own the connection. The
|
|
38
38
|
// client owns auth, the credential lifecycle (first mint, refresh, and
|
|
39
|
-
// wake/online/focus re-mint — see `Ablo({
|
|
39
|
+
// wake/online/focus re-mint — see `Ablo({ apiKey })`), transport, and
|
|
40
40
|
// `dispose()`. The CONSUMER built the client, so the consumer owns teardown;
|
|
41
41
|
// the provider never disposes it.
|
|
42
42
|
const engine = client;
|
|
@@ -338,10 +338,6 @@ export function useParticipant(opts) {
|
|
|
338
338
|
unsubClaims();
|
|
339
339
|
};
|
|
340
340
|
}, [participant, paused]);
|
|
341
|
-
// `opts.as`, `opts.agent`, `opts.idempotencyKey`, and
|
|
342
|
-
// `opts.autoRefreshThresholdSeconds` remain migration placeholders
|
|
343
|
-
// for future capability-mint/attenuation wiring. `scope` is already
|
|
344
|
-
// active: it opens a multiplexed claim on the engine WebSocket.
|
|
345
341
|
return { participant, peers, claims, status, error };
|
|
346
342
|
}
|
|
347
343
|
/**
|
package/dist/react/context.d.ts
CHANGED
|
@@ -137,24 +137,6 @@ export interface SyncReactContext {
|
|
|
137
137
|
* augmentation — see `src/types/global.ts`.
|
|
138
138
|
*/
|
|
139
139
|
schema?: Schema;
|
|
140
|
-
/**
|
|
141
|
-
* Optional presence source. When set, `usePresence()` returns this
|
|
142
|
-
* value cast to the consumer's `ResolvePresence` type (declared via
|
|
143
|
-
* `interface Register { Presence: ... }`). The SDK doesn't own a
|
|
144
|
-
* presence wire format — consumers plug whatever backs their cursors,
|
|
145
|
-
* status, or activity state (a MobX store, a Zustand slice, a custom
|
|
146
|
-
* subscription). The typed-global gives it a call-site-ergonomic
|
|
147
|
-
* type without the SDK dictating the transport.
|
|
148
|
-
*/
|
|
149
|
-
presence?: unknown;
|
|
150
|
-
/**
|
|
151
|
-
* Optional claim initiator. Same pattern as presence — consumers
|
|
152
|
-
* plug a function that turns an claim claim into a handle they
|
|
153
|
-
* control (WebSocket send, optimistic local update, whatever).
|
|
154
|
-
* `useClaim(name)` returns a typed invoker for the named claim
|
|
155
|
-
* from `interface Register { Claims: ... }`.
|
|
156
|
-
*/
|
|
157
|
-
beginClaim?: (claimName: string, claim: unknown) => unknown;
|
|
158
140
|
}
|
|
159
141
|
export declare const SyncContext: import("react").Context<SyncReactContext | null>;
|
|
160
142
|
/**
|
|
@@ -177,18 +159,6 @@ export interface SyncProviderProps {
|
|
|
177
159
|
* their legacy `(schema, modelKey, …)` signatures.
|
|
178
160
|
*/
|
|
179
161
|
schema?: Schema;
|
|
180
|
-
/**
|
|
181
|
-
* Optional presence source for `usePresence()`. See
|
|
182
|
-
* {@link SyncReactContext.presence} — the consumer plugs whatever
|
|
183
|
-
* backs their presence state; the hook returns it with
|
|
184
|
-
* `ResolvePresence` typing.
|
|
185
|
-
*/
|
|
186
|
-
presence?: unknown;
|
|
187
|
-
/**
|
|
188
|
-
* Optional claim initiator for `useClaim()`. See
|
|
189
|
-
* {@link SyncReactContext.beginClaim}.
|
|
190
|
-
*/
|
|
191
|
-
beginClaim?: (claimName: string, claim: unknown) => unknown;
|
|
192
162
|
children?: ReactNode;
|
|
193
163
|
}
|
|
194
164
|
/**
|
|
@@ -206,4 +176,4 @@ export interface SyncProviderProps {
|
|
|
206
176
|
* );
|
|
207
177
|
* }
|
|
208
178
|
*/
|
|
209
|
-
export declare function SyncProvider({ store, organizationId, schema,
|
|
179
|
+
export declare function SyncProvider({ store, organizationId, schema, children, }: SyncProviderProps): import("react").FunctionComponentElement<import("react").ProviderProps<SyncReactContext | null>>;
|
package/dist/react/context.js
CHANGED
|
@@ -30,6 +30,6 @@ export function useSyncContext() {
|
|
|
30
30
|
* );
|
|
31
31
|
* }
|
|
32
32
|
*/
|
|
33
|
-
export function SyncProvider({ store, organizationId, schema,
|
|
34
|
-
return createElement(SyncContext.Provider, { value: { store, organizationId, schema
|
|
33
|
+
export function SyncProvider({ store, organizationId, schema, children, }) {
|
|
34
|
+
return createElement(SyncContext.Provider, { value: { store, organizationId, schema } }, children);
|
|
35
35
|
}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* — owns sync engine + multiplayer lifecycle; the `fallback` prop
|
|
7
7
|
* gates children on first bootstrap. Pass `fallback="passthrough"`
|
|
8
8
|
* to disable the gate.
|
|
9
|
-
* <SyncGroupProvider id="matter:..."> — per-entity scope
|
|
10
9
|
* <ClientSideSuspense fallback={<Skeleton/>}> — NESTED gate inside an
|
|
11
10
|
* already-ready provider. Use only when you need a separate gate
|
|
12
11
|
* for a heavy subtree (e.g. a canvas) while app chrome renders
|
|
@@ -29,8 +28,6 @@
|
|
|
29
28
|
* Multiplayer (always available — `<AbloProvider>` always constructs a client):
|
|
30
29
|
* useAblo((ablo) => ablo.<model>.claim.state(...)) — reactive coordination reads
|
|
31
30
|
* useParticipant({ scope }) — join multiplayer for a scope, get peers/claims
|
|
32
|
-
* usePresence() — typed presence view
|
|
33
|
-
* useClaim(name) — typed claim dispatcher
|
|
34
31
|
*
|
|
35
32
|
* ── Breaking changes from v0.2.x ───────────────────────────────────
|
|
36
33
|
* Removed: <SyncProvider>, SyncContext, useSyncContext — folded into
|
|
@@ -45,7 +42,6 @@
|
|
|
45
42
|
*/
|
|
46
43
|
export type { DefaultSyncShape, ResolveSchema, ResolvePresence, ResolveClaims, ResolveUserMeta, ResolveModelKey, } from '../types/global.js';
|
|
47
44
|
export { AbloProvider, useParticipant, usePeers, useSync, useSyncStore, type AbloProviderProps, type ParticipantScope, type ParticipantStatus, type UseParticipantOptions, type UseParticipantReturn, type MeshParticipantStatus, } from './AbloProvider.js';
|
|
48
|
-
export { SyncGroupProvider, useSyncGroup, type SyncGroupProviderProps, } from './SyncGroupProvider.js';
|
|
49
45
|
export { ClientSideSuspense, type ClientSideSuspenseProps, } from './ClientSideSuspense.js';
|
|
50
46
|
export { DefaultFallback } from './DefaultFallback.js';
|
|
51
47
|
export type { SyncStoreContract } from './context.js';
|
|
@@ -59,6 +55,4 @@ export type { ReaderActions, ReaderFindOptions } from '../mutators/readerActions
|
|
|
59
55
|
export { useMutators, type MutatorInvokers, type InvokerFor, type UseMutatorsOptions, } from './useMutators.js';
|
|
60
56
|
export { useUndoScope, type UseUndoScopeResult } from './useUndoScope.js';
|
|
61
57
|
export { useAblo, type UseAbloHydratedModelResult, type UseAbloModelOptions, type UseAbloModelResult, } from './useAblo.js';
|
|
62
|
-
export { usePresence } from './usePresence.js';
|
|
63
|
-
export { useClaim } from './useClaim.js';
|
|
64
58
|
export { ModelScope } from '../types/index.js';
|
package/dist/react/index.js
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* — owns sync engine + multiplayer lifecycle; the `fallback` prop
|
|
7
7
|
* gates children on first bootstrap. Pass `fallback="passthrough"`
|
|
8
8
|
* to disable the gate.
|
|
9
|
-
* <SyncGroupProvider id="matter:..."> — per-entity scope
|
|
10
9
|
* <ClientSideSuspense fallback={<Skeleton/>}> — NESTED gate inside an
|
|
11
10
|
* already-ready provider. Use only when you need a separate gate
|
|
12
11
|
* for a heavy subtree (e.g. a canvas) while app chrome renders
|
|
@@ -29,8 +28,6 @@
|
|
|
29
28
|
* Multiplayer (always available — `<AbloProvider>` always constructs a client):
|
|
30
29
|
* useAblo((ablo) => ablo.<model>.claim.state(...)) — reactive coordination reads
|
|
31
30
|
* useParticipant({ scope }) — join multiplayer for a scope, get peers/claims
|
|
32
|
-
* usePresence() — typed presence view
|
|
33
|
-
* useClaim(name) — typed claim dispatcher
|
|
34
31
|
*
|
|
35
32
|
* ── Breaking changes from v0.2.x ───────────────────────────────────
|
|
36
33
|
* Removed: <SyncProvider>, SyncContext, useSyncContext — folded into
|
|
@@ -45,7 +42,6 @@
|
|
|
45
42
|
*/
|
|
46
43
|
// ── Umbrella provider + lifecycle hooks ────────────────────────────
|
|
47
44
|
export { AbloProvider, useParticipant, usePeers, useSync, useSyncStore, } from './AbloProvider.js';
|
|
48
|
-
export { SyncGroupProvider, useSyncGroup, } from './SyncGroupProvider.js';
|
|
49
45
|
export { ClientSideSuspense, } from './ClientSideSuspense.js';
|
|
50
46
|
export { DefaultFallback } from './DefaultFallback.js';
|
|
51
47
|
// ── Status + errors + identity ─────────────────────────────────────
|
|
@@ -63,8 +59,5 @@ export { useReactive } from './useReactive.js';
|
|
|
63
59
|
export { useMutators, } from './useMutators.js';
|
|
64
60
|
export { useUndoScope } from './useUndoScope.js';
|
|
65
61
|
export { useAblo, } from './useAblo.js';
|
|
66
|
-
// ── Presence + claim (typed via Register module augmentation) ─────
|
|
67
|
-
export { usePresence } from './usePresence.js';
|
|
68
|
-
export { useClaim } from './useClaim.js';
|
|
69
62
|
// ── ModelScope re-export ───────────────────────────────────────────
|
|
70
63
|
export { ModelScope } from '../types/index.js';
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* `reason` carries the human-readable close reason when available.
|
|
18
18
|
* - `disconnected` — network failure, server error, or the retry loop
|
|
19
19
|
* gave up. Show the offline / error UI.
|
|
20
|
-
* - `needs-auth` — server rejected the
|
|
20
|
+
* - `needs-auth` — server rejected the auth token (1008/4001/4003). The
|
|
21
21
|
* consumer's `onSessionExpired` callback has already been invoked
|
|
22
22
|
* by `<AbloProvider>`; this variant exists for UI that wants to
|
|
23
23
|
* reflect the auth state itself.
|
package/dist/realtime/index.d.ts
CHANGED
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
* subscriptions, presence, offline queueing, and a long-lived WebSocket.
|
|
6
6
|
*/
|
|
7
7
|
export { Ablo, computeFKDepthPriority } from '../client/Ablo.js';
|
|
8
|
-
export type { AbloOptions, InternalAbloOptions,
|
|
8
|
+
export type { AbloOptions, InternalAbloOptions, LocalCountOptions, LocalReadOptions, ModelListScope, ServerReadOptions, ModelOperations, } from '../client/Ablo.js';
|
|
9
9
|
import { Ablo } from '../client/Ablo.js';
|
|
10
10
|
export default Ablo;
|
package/dist/schema/generate.js
CHANGED
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
* unions). Relations are resolved by the runtime SDK's typed accessors and are
|
|
15
15
|
* not expanded here.
|
|
16
16
|
*/
|
|
17
|
-
|
|
18
|
-
const BASE_FIELDS = ['id', 'createdAt', 'updatedAt', 'organizationId', 'createdBy'];
|
|
17
|
+
import { BASE_FIELDS } from './schema.js';
|
|
19
18
|
function tsType(meta) {
|
|
20
19
|
switch (meta.type) {
|
|
21
20
|
case 'string':
|
package/dist/schema/schema.d.ts
CHANGED
|
@@ -78,6 +78,15 @@ export declare const baseFieldsSchema: z.ZodObject<{
|
|
|
78
78
|
organizationId: z.ZodOptional<z.ZodString>;
|
|
79
79
|
createdBy: z.ZodOptional<z.ZodString>;
|
|
80
80
|
}, z.core.$strip>;
|
|
81
|
+
/**
|
|
82
|
+
* The base-column names every model carries automatically (the keys of
|
|
83
|
+
* {@link baseFieldsSchema}). The single source of truth — `generate.ts`
|
|
84
|
+
* imports this to avoid double-emitting a redeclared base column, and the
|
|
85
|
+
* `defineSchema` field loop uses it to reject a model that tries to redeclare
|
|
86
|
+
* one (Zod `.merge` would otherwise silently overwrite the base field with the
|
|
87
|
+
* user's, producing a `string & Date` type that breaks the build).
|
|
88
|
+
*/
|
|
89
|
+
export declare const BASE_FIELDS: readonly ["id", "createdAt", "updatedAt", "organizationId", "createdBy"];
|
|
81
90
|
/** The base fields type — pure data columns. */
|
|
82
91
|
export type BaseModelFields = z.infer<typeof baseFieldsSchema>;
|
|
83
92
|
/**
|
|
@@ -138,7 +147,8 @@ export type Model<A, B = never> = [B] extends [never] ? A extends keyof Register
|
|
|
138
147
|
* Drizzle deprecated its own `InferModel` for the same reason. Kept as an
|
|
139
148
|
* alias; no behavior difference.
|
|
140
149
|
*/
|
|
141
|
-
export type InferModel<S extends Schema, ModelName extends keyof S['models']> = S['models'][ModelName] extends ModelDef<infer Shape, infer R, infer C> ?
|
|
150
|
+
export type InferModel<S extends Schema, ModelName extends keyof S['models']> = S['models'][ModelName] extends ModelDef<infer Shape, infer R, infer C> ? // `Omit<…, keyof BaseModelFields>` so a model that (wrongly) redeclares a
|
|
151
|
+
Omit<z.infer<z.ZodObject<Shape>>, keyof BaseModelFields> & BaseModelFields & BaseModelMethods & InferComputed<C> & InferRelations<S, R> : never;
|
|
142
152
|
/**
|
|
143
153
|
* Infer relation accessor types from a model's relations record.
|
|
144
154
|
*
|
|
@@ -184,7 +194,8 @@ export type InferComputed<C> = string extends keyof C ? unknown : {
|
|
|
184
194
|
* // createdAt, updatedAt are NOT accepted — they're auto-generated
|
|
185
195
|
* ```
|
|
186
196
|
*/
|
|
187
|
-
export type InferCreate<S extends Schema, ModelName extends keyof S['models']> = S['models'][ModelName] extends ModelDef<infer Shape> ?
|
|
197
|
+
export type InferCreate<S extends Schema, ModelName extends keyof S['models']> = S['models'][ModelName] extends ModelDef<infer Shape> ? // Same reserved-field guard as InferModel: drop any (wrongly) redeclared
|
|
198
|
+
Omit<z.input<z.ZodObject<Shape>>, keyof BaseModelFields> & Partial<BaseModelFields> : never;
|
|
188
199
|
/**
|
|
189
200
|
* Extract all model names from a schema.
|
|
190
201
|
*/
|
package/dist/schema/schema.js
CHANGED
|
@@ -58,6 +58,21 @@ export const baseFieldsSchema = z.object({
|
|
|
58
58
|
organizationId: z.string().optional(),
|
|
59
59
|
createdBy: z.string().optional(),
|
|
60
60
|
});
|
|
61
|
+
/**
|
|
62
|
+
* The base-column names every model carries automatically (the keys of
|
|
63
|
+
* {@link baseFieldsSchema}). The single source of truth — `generate.ts`
|
|
64
|
+
* imports this to avoid double-emitting a redeclared base column, and the
|
|
65
|
+
* `defineSchema` field loop uses it to reject a model that tries to redeclare
|
|
66
|
+
* one (Zod `.merge` would otherwise silently overwrite the base field with the
|
|
67
|
+
* user's, producing a `string & Date` type that breaks the build).
|
|
68
|
+
*/
|
|
69
|
+
export const BASE_FIELDS = [
|
|
70
|
+
'id',
|
|
71
|
+
'createdAt',
|
|
72
|
+
'updatedAt',
|
|
73
|
+
'organizationId',
|
|
74
|
+
'createdBy',
|
|
75
|
+
];
|
|
61
76
|
// ── Factory ───────────────────────────────────────────────────────────────
|
|
62
77
|
/**
|
|
63
78
|
* Define a sync engine schema.
|
|
@@ -146,6 +161,17 @@ export function defineSchema(models, options) {
|
|
|
146
161
|
// failure immediate and unambiguous.
|
|
147
162
|
for (const fieldName of Object.keys(def.shape)) {
|
|
148
163
|
assertRoundTrippableCamelCase(name, fieldName);
|
|
164
|
+
// Reserved base columns are merged in below via `baseFieldsSchema.merge`,
|
|
165
|
+
// and Zod `.merge` silently OVERWRITES the base field with the user's —
|
|
166
|
+
// e.g. a model declaring `createdAt: z.string()` ends up with a field
|
|
167
|
+
// typed `string & Date`, which breaks the build. Reject the collision at
|
|
168
|
+
// definition time so the author sees an unambiguous error instead.
|
|
169
|
+
if (BASE_FIELDS.includes(fieldName)) {
|
|
170
|
+
throw new AbloValidationError(`[defineSchema] ${name}.${fieldName}: field \`${fieldName}\` collides with a ` +
|
|
171
|
+
`reserved field that the SDK provides automatically ` +
|
|
172
|
+
`(${BASE_FIELDS.join(', ')}). Remove it from your model — redeclaring it ` +
|
|
173
|
+
`produces a \`string & Date\` type and breaks the build.`, { code: 'schema_reserved_field', param: `${name}.${fieldName}` });
|
|
174
|
+
}
|
|
149
175
|
}
|
|
150
176
|
validators[name] = baseFieldsSchema.merge(def.schema);
|
|
151
177
|
// Resolve every relation's `foreignKeyColumn` once, now. The builder
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Machine-checked public API-surface manifest — the SDK owns the description of
|
|
3
|
+
* its OWN surface, bound to the real exported types at COMPILE TIME so the MCP
|
|
4
|
+
* `get_api_surface` / docs can never drift from reality.
|
|
5
|
+
*
|
|
6
|
+
* This exists because the hand-authored surface (apps/sync-web/.../api-surface.ts)
|
|
7
|
+
* once named `load` / `count` / `scope` — verbs/options that don't exist — with no
|
|
8
|
+
* coupling to the code. The fix: the name lists live HERE, next to the types, and
|
|
9
|
+
* each is proven EXACTLY equal to the keys of its source interface via
|
|
10
|
+
* `Expect<Equal<…>>`. Add or remove a verb/option without updating the matching
|
|
11
|
+
* tuple and THIS FILE FAILS TO COMPILE (the `Equal` constraint is checked eagerly
|
|
12
|
+
* at the alias declaration — both directions: no phantom name, no missing name).
|
|
13
|
+
*
|
|
14
|
+
* Consumers (the MCP `get_api_surface`) import these NAME tuples and build their
|
|
15
|
+
* prose from them, so a summary can never reference a verb that doesn't exist.
|
|
16
|
+
* NAMES are guaranteed; descriptions stay hand-written (prose can't be type-checked).
|
|
17
|
+
*/
|
|
18
|
+
/** Every method on `ablo.<model>` (the stateful `ModelOperations`). The single
|
|
19
|
+
* source of truth for the model-verb names the docs/MCP may describe. */
|
|
20
|
+
export declare const PUBLIC_MODEL_VERBS: readonly ["retrieve", "list", "get", "getAll", "getCount", "create", "update", "delete", "claim", "watch", "onChange"];
|
|
21
|
+
/** Keys accepted by `list`/`getAll`/`onChange` options (`LocalReadOptions`).
|
|
22
|
+
* Note `state` (lifecycle filter) — NOT `scope` (a historic doc drift). */
|
|
23
|
+
export declare const PUBLIC_LIST_OPTION_KEYS: readonly ["where", "filter", "orderBy", "limit", "offset", "state"];
|
|
24
|
+
/** Public keys of `AbloOptions`. `schema` is required; the rest are optional
|
|
25
|
+
* (the locked happy path is `Ablo({ schema, apiKey, databaseUrl, transport })`). */
|
|
26
|
+
export declare const PUBLIC_ABLO_OPTION_KEYS: readonly ["schema", "apiKey", "databaseUrl", "persistence", "transport", "authToken", "baseURL", "fetch", "defaultHeaders", "defaultQuery", "dangerouslyAllowBrowser"];
|
|
27
|
+
export type ModelVerb = (typeof PUBLIC_MODEL_VERBS)[number];
|
|
28
|
+
export type ListOptionKey = (typeof PUBLIC_LIST_OPTION_KEYS)[number];
|
|
29
|
+
export type AbloOptionKey = (typeof PUBLIC_ABLO_OPTION_KEYS)[number];
|
package/dist/surface.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Machine-checked public API-surface manifest — the SDK owns the description of
|
|
3
|
+
* its OWN surface, bound to the real exported types at COMPILE TIME so the MCP
|
|
4
|
+
* `get_api_surface` / docs can never drift from reality.
|
|
5
|
+
*
|
|
6
|
+
* This exists because the hand-authored surface (apps/sync-web/.../api-surface.ts)
|
|
7
|
+
* once named `load` / `count` / `scope` — verbs/options that don't exist — with no
|
|
8
|
+
* coupling to the code. The fix: the name lists live HERE, next to the types, and
|
|
9
|
+
* each is proven EXACTLY equal to the keys of its source interface via
|
|
10
|
+
* `Expect<Equal<…>>`. Add or remove a verb/option without updating the matching
|
|
11
|
+
* tuple and THIS FILE FAILS TO COMPILE (the `Equal` constraint is checked eagerly
|
|
12
|
+
* at the alias declaration — both directions: no phantom name, no missing name).
|
|
13
|
+
*
|
|
14
|
+
* Consumers (the MCP `get_api_surface`) import these NAME tuples and build their
|
|
15
|
+
* prose from them, so a summary can never reference a verb that doesn't exist.
|
|
16
|
+
* NAMES are guaranteed; descriptions stay hand-written (prose can't be type-checked).
|
|
17
|
+
*/
|
|
18
|
+
// ── the per-`ablo.<model>` verb surface ────────────────────────────────────
|
|
19
|
+
/** Every method on `ablo.<model>` (the stateful `ModelOperations`). The single
|
|
20
|
+
* source of truth for the model-verb names the docs/MCP may describe. */
|
|
21
|
+
export const PUBLIC_MODEL_VERBS = [
|
|
22
|
+
'retrieve',
|
|
23
|
+
'list',
|
|
24
|
+
'get',
|
|
25
|
+
'getAll',
|
|
26
|
+
'getCount',
|
|
27
|
+
'create',
|
|
28
|
+
'update',
|
|
29
|
+
'delete',
|
|
30
|
+
'claim',
|
|
31
|
+
'watch',
|
|
32
|
+
'onChange',
|
|
33
|
+
];
|
|
34
|
+
// ── the read/list query option surface ─────────────────────────────────────
|
|
35
|
+
/** Keys accepted by `list`/`getAll`/`onChange` options (`LocalReadOptions`).
|
|
36
|
+
* Note `state` (lifecycle filter) — NOT `scope` (a historic doc drift). */
|
|
37
|
+
export const PUBLIC_LIST_OPTION_KEYS = [
|
|
38
|
+
'where',
|
|
39
|
+
'filter',
|
|
40
|
+
'orderBy',
|
|
41
|
+
'limit',
|
|
42
|
+
'offset',
|
|
43
|
+
'state',
|
|
44
|
+
];
|
|
45
|
+
// ── the `Ablo({ … })` constructor option surface ───────────────────────────
|
|
46
|
+
/** Public keys of `AbloOptions`. `schema` is required; the rest are optional
|
|
47
|
+
* (the locked happy path is `Ablo({ schema, apiKey, databaseUrl, transport })`). */
|
|
48
|
+
export const PUBLIC_ABLO_OPTION_KEYS = [
|
|
49
|
+
'schema',
|
|
50
|
+
'apiKey',
|
|
51
|
+
'databaseUrl',
|
|
52
|
+
'persistence',
|
|
53
|
+
'transport',
|
|
54
|
+
'authToken',
|
|
55
|
+
'baseURL',
|
|
56
|
+
'fetch',
|
|
57
|
+
'defaultHeaders',
|
|
58
|
+
'defaultQuery',
|
|
59
|
+
'dangerouslyAllowBrowser',
|
|
60
|
+
];
|
|
@@ -20,18 +20,29 @@
|
|
|
20
20
|
* Designed to be embedded by `BaseSyncedStore`: one instance per store,
|
|
21
21
|
* started on first successful connect, disposed on teardown.
|
|
22
22
|
*
|
|
23
|
-
* CONNECTED
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
23
|
+
* CONNECTED ──(socket drop)──► PROBING_NETWORK ──► RECONNECTING ──► CONNECTED
|
|
24
|
+
* │ │ │
|
|
25
|
+
* (network lost) ▼ ▼
|
|
26
|
+
* ▼ SESSION_EXPIRED BACKOFF ──► PROBING_NETWORK
|
|
27
|
+
* OFFLINE ──(online)──► PROBING_NETWORK
|
|
28
|
+
* │
|
|
29
|
+
* ▼
|
|
30
|
+
* WAITING_FOR_NETWORK
|
|
27
31
|
*
|
|
28
|
-
* Includes
|
|
32
|
+
* Includes three fixes over the original app-side FSM:
|
|
29
33
|
* 1. `backoff` accepts `NETWORK_ONLINE` / `TAB_VISIBLE` — jumps to
|
|
30
34
|
* probing immediately when the network comes back, without
|
|
31
35
|
* waiting for the backoff timer to elapse.
|
|
32
36
|
* 2. `scheduleBackoff` parks in `waiting_for_network` (resetting
|
|
33
37
|
* `attempt`) when `navigator.onLine === false` at max retries,
|
|
34
38
|
* instead of hard-reloading an already-offline browser.
|
|
39
|
+
* 3. A socket drop (`WS_DISCONNECTED`, typically code 1006) goes
|
|
40
|
+
* STRAIGHT to `probing_network`, not the passive `offline` state.
|
|
41
|
+
* 1006 is browser-local and carries no connectivity signal, so on a
|
|
42
|
+
* healthy machine no `online`/`offline` event ever fires — parking in
|
|
43
|
+
* `offline` stranded recovery until the 30s watchdog, long enough for
|
|
44
|
+
* queued commits to roll back. Only a genuine OS-level `NETWORK_LOST`
|
|
45
|
+
* parks in `offline` and waits for the `online` event.
|
|
35
46
|
*/
|
|
36
47
|
import { type ProbeResult } from './NetworkProbe.js';
|
|
37
48
|
import type { AuthTokenGetter } from '../auth/credentialSource.js';
|