@abloatai/humans 0.64.0 → 0.64.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.
Files changed (64) hide show
  1. package/dist/Ablo.d.ts +2 -0
  2. package/dist/client.d.ts +1 -1
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +1 -0
  5. package/dist/local/BaseSyncedStore.d.ts +5 -4
  6. package/dist/local/BaseSyncedStore.js +13 -15
  7. package/dist/local/client/createModelOperations.d.ts +3 -3
  8. package/dist/local/client/createModelOperations.js +1 -1
  9. package/dist/local/client/reactiveEngine.js +3 -3
  10. package/dist/local/mutators/defineMutators.d.ts +3 -48
  11. package/dist/local/mutators/defineMutators.js +0 -15
  12. package/dist/local/storeAccess.d.ts +7 -0
  13. package/dist/local/storeAccess.js +6 -0
  14. package/dist/local/sync/groupChange.d.ts +7 -4
  15. package/dist/local/sync/groupChange.js +7 -4
  16. package/dist/local/sync/scopeGroups.d.ts +4 -1
  17. package/dist/presence/index.d.ts +2 -2
  18. package/dist/presence/index.js +2 -2
  19. package/dist/react/AbloProvider.js +6 -6
  20. package/dist/react/context.d.ts +6 -45
  21. package/dist/react/context.js +8 -20
  22. package/dist/react/createAbloReact.d.ts +9 -22
  23. package/dist/react/createAbloReact.js +5 -7
  24. package/dist/react/internalContext.d.ts +0 -9
  25. package/dist/react/useAblo.d.ts +7 -54
  26. package/dist/react/useAblo.js +6 -28
  27. package/dist/react/useAbloClient.d.ts +5 -0
  28. package/dist/react/useAbloClient.js +9 -0
  29. package/dist/react/useMutationFailure.d.ts +6 -0
  30. package/dist/react/useMutationFailure.js +11 -0
  31. package/dist/react/useMutators.d.ts +3 -3
  32. package/dist/react/useMutators.js +3 -3
  33. package/dist/react/usePresence.d.ts +9 -9
  34. package/dist/react/usePresence.js +3 -7
  35. package/dist/react/useUndoScope.d.ts +2 -2
  36. package/dist/react/useUndoScope.js +4 -4
  37. package/dist/react.d.ts +2 -0
  38. package/dist/react.js +2 -0
  39. package/dist/reactRuntime.d.ts +1 -1
  40. package/dist/reactRuntime.js +1 -1
  41. package/package.json +2 -2
  42. package/src/Ablo.ts +2 -0
  43. package/src/client.ts +1 -1
  44. package/src/index.ts +2 -0
  45. package/src/local/BaseSyncedStore.ts +13 -15
  46. package/src/local/client/createModelOperations.ts +4 -4
  47. package/src/local/client/reactiveEngine.ts +3 -3
  48. package/src/local/mutators/defineMutators.ts +3 -51
  49. package/src/local/storeAccess.ts +10 -0
  50. package/src/local/sync/groupChange.ts +7 -4
  51. package/src/local/sync/scopeGroups.ts +4 -1
  52. package/src/presence/index.ts +4 -3
  53. package/src/react/AbloProvider.tsx +8 -8
  54. package/src/react/context.ts +10 -61
  55. package/src/react/createAbloReact.ts +12 -40
  56. package/src/react/internalContext.ts +0 -9
  57. package/src/react/useAblo.ts +19 -89
  58. package/src/react/useAbloClient.ts +14 -0
  59. package/src/react/useMutationFailure.ts +17 -0
  60. package/src/react/useMutators.ts +6 -6
  61. package/src/react/usePresence.ts +18 -18
  62. package/src/react/useUndoScope.ts +6 -6
  63. package/src/react.ts +2 -0
  64. package/src/reactRuntime.ts +2 -2
package/dist/Ablo.d.ts CHANGED
@@ -89,6 +89,7 @@ export declare namespace Ablo {
89
89
  /** Payload delivered by the core client's onMutationFailure subscription. */
90
90
  type MutationFailure = Parameters<Parameters<AbloClient<SchemaRecord>['onMutationFailure']>[0]>[0];
91
91
  /** Current client lifecycle, also selected through React's useAblo. */
92
+ type Store = import('./local/storeContract.js').SyncStoreContract;
92
93
  type Status = import('./local/client/status.js').ClientStatus;
93
94
  type Options<S extends SchemaRecord = SchemaRecord> = AbloOptions<S>;
94
95
  /**
@@ -123,6 +124,7 @@ export declare namespace Ablo {
123
124
  * different schemas.
124
125
  */
125
126
  type ResolveSchema = _Global.ResolveSchema;
127
+ type ResolveClaimMeta = _Global.ResolveClaimMeta;
126
128
  /**
127
129
  * `ResolveSchema` guaranteed to satisfy the `Schema` bound. `ResolveSchema`
128
130
  * falls back to a loose `{ models }` shape when nothing is registered, which
package/dist/client.d.ts CHANGED
@@ -210,7 +210,7 @@ interface AbloCore<S extends SchemaRecord> {
210
210
  subscribe<K extends keyof CoreSyncEventMap>(event: K, handler: (...args: CoreSyncEventMap[K]) => void): () => void;
211
211
  /**
212
212
  * The internal store. It implements {@link SyncStoreContract} — pass it to
213
- * `SyncContext.Provider` so the SDK's `useModel` / `useModels` / `useMutations`
213
+ * `AbloStoreContext.Provider` so the SDK's React data hooks
214
214
  * hooks can reach it.
215
215
  */
216
216
  readonly _store: SyncStoreContract;
package/dist/index.d.ts CHANGED
@@ -9,3 +9,4 @@ export { createTransaction, type Transaction, type ReaderFindOptions, } from './
9
9
  export { ClaimLog, formatClaim, formatConflict, type ClaimLogEntry, } from './local/coordination/ClaimLog.js';
10
10
  export { isStorageOpenTimeout, } from './local/stores/openIDBWithTimeout.js';
11
11
  export type { CommitLatencySample, } from './local/transactions/mutations/commitLatency.js';
12
+ export { getAbloStore } from './local/storeAccess.js';
package/dist/index.js CHANGED
@@ -4,3 +4,4 @@ export { defineMutators, } from './local/mutators/defineMutators.js';
4
4
  export { createTransaction, } from './local/mutators/Transaction.js';
5
5
  export { ClaimLog, formatClaim, formatConflict, } from './local/coordination/ClaimLog.js';
6
6
  export { isStorageOpenTimeout, } from './local/stores/openIDBWithTimeout.js';
7
+ export { getAbloStore } from './local/storeAccess.js';
@@ -249,10 +249,11 @@ export declare class BaseSyncedStore<TCollaboration extends EventMap<TCollaborat
249
249
  /**
250
250
  * Bring a scope into view and subscribe to its sync groups. With
251
251
  * `{ hydrate: true }`, also backfill the groups' current state into the pool
252
- * once the subscription is active. The order matters: subscribing first
253
- * guarantees no live delta is missed in the gap before the snapshot lands.
254
- * Hydration is best-effort — a failed backfill never rejects `enterScope`,
255
- * and the live delta stream keeps flowing regardless.
252
+ * after subscription reconciliation. Offline reconciliation may only record
253
+ * interest locally; it is not proof that the server is delivering changes.
254
+ * Hydration is best-effort: failure leaves the groups unmarked for retry and
255
+ * does not reject `enterScope`. Snapshot application uses version guards so
256
+ * older baseline rows cannot overwrite newer deltas already in the pool.
256
257
  */
257
258
  enterScope(scope: GroupScope, opts?: {
258
259
  hydrate?: boolean;
@@ -167,28 +167,26 @@ export class BaseSyncedStore {
167
167
  sendCollaborationEvent(messageType, payload) {
168
168
  this.syncWebSocket.sendCollaborationEvent(messageType, payload);
169
169
  }
170
- // ── Area-of-interest (dynamic read subscription) ─────────────────
170
+ // ── Group interest and loading ──────────────────────────────────
171
171
  //
172
- // `enterScope`/`leaveScope` move the connection's read interest as the
173
- // user navigates (open or close a record); `pinScope`/`unpinScope`
174
- // express prominence (an active claim keeps a group subscribed). All four
175
- // resolve the scope to sync-group strings through the same resolver the
176
- // claim path uses (`resolveParticipantSyncGroups`), so read interest and
177
- // write claims always agree on the string for a given entity. Before the
178
- // connection opens they record interest without a wire send, and they
179
- // never reject when the transport is offline (see
180
- // {@link SubscriptionManager.reconcile}); the on-connect `resync` pushes
181
- // whatever interest accumulated.
172
+ // Authority comes from the server-issued session. Enter/leave track what
173
+ // this connection wants to receive; pin/unpin keep an active scope subscribed.
174
+ // All resolve selectors through scopeToGroups. Hydration separately loads
175
+ // a scoped baseline into the local pool. Leaving interest does not revoke
176
+ // authority or selectively evict cached records.
177
+ // Offline interest is recorded locally and reconciled when the connection
178
+ // opens; recording interest is not confirmation of a server subscription.
182
179
  scopeToGroups(scope) {
183
180
  return resolveScopeGroups(scope, this.schema);
184
181
  }
185
182
  /**
186
183
  * Bring a scope into view and subscribe to its sync groups. With
187
184
  * `{ hydrate: true }`, also backfill the groups' current state into the pool
188
- * once the subscription is active. The order matters: subscribing first
189
- * guarantees no live delta is missed in the gap before the snapshot lands.
190
- * Hydration is best-effort — a failed backfill never rejects `enterScope`,
191
- * and the live delta stream keeps flowing regardless.
185
+ * after subscription reconciliation. Offline reconciliation may only record
186
+ * interest locally; it is not proof that the server is delivering changes.
187
+ * Hydration is best-effort: failure leaves the groups unmarked for retry and
188
+ * does not reject `enterScope`. Snapshot application uses version guards so
189
+ * older baseline rows cannot overwrite newer deltas already in the pool.
192
190
  */
193
191
  enterScope(scope, opts) {
194
192
  const groups = this.scopeToGroups(scope);
@@ -22,14 +22,14 @@ export type { Claim, ClaimHeartbeat, ClaimHeartbeatOptions, HeldClaim, HeldLease
22
22
  import type { ClaimApi, ClaimAttemptEvent, LocalCountOptions, LocalReadOptions } from '@abloatai/transaction/client/resources/modelOperations';
23
23
  import type { HttpModelClient } from '@abloatai/transaction/transport/http';
24
24
  import type { ParticipantKind } from '@abloatai/transaction/types/participant';
25
- import type { PresenceSession } from '@abloatai/transaction/presence';
25
+ import type { PresenceSession, PresenceQueryOptions } from '@abloatai/transaction/presence';
26
26
  import type { CollaborationEventContext, ModelEventEnvelope, ModelEventInput, ModelEventTarget } from '@abloatai/transaction/collaboration';
27
27
  import { type ReadSetContext } from '@abloatai/transaction/internal/read-set';
28
28
  export interface ModelClientMeta {
29
29
  readonly key: string;
30
30
  readonly typename: string;
31
31
  readonly presence?: {
32
- get(recordId: string): readonly PresenceSession[];
32
+ get(recordId: string, options?: PresenceQueryOptions): readonly PresenceSession[];
33
33
  subscribe(listener: () => void): () => void;
34
34
  read(recordId: string): () => void;
35
35
  };
@@ -42,7 +42,7 @@ export declare function getModelClientMeta(modelClient: unknown): ModelClientMet
42
42
  type EntityHalf = Pick<ModelTarget, 'model' | 'id'>;
43
43
  export interface ModelCollaboration {
44
44
  /** Session projections already held by this client's one presence store. */
45
- presence(model: string, recordId?: string): readonly PresenceSession[];
45
+ presence(model: string, recordId?: string, options?: PresenceQueryOptions): readonly PresenceSession[];
46
46
  /** Subscribe once to the connection-owned presence projection. */
47
47
  onPresenceChange(listener: () => void): () => void;
48
48
  /** Start one session-owned read activity and return its cleanup. */
@@ -1091,7 +1091,7 @@ hydration, collaboration, readSetContext) {
1091
1091
  ...(collaboration
1092
1092
  ? {
1093
1093
  presence: {
1094
- get: (recordId) => collaboration.presence(registeredModelName, recordId),
1094
+ get: (recordId, options) => collaboration.presence(registeredModelName, recordId, options),
1095
1095
  subscribe: (listener) => collaboration.onPresenceChange(listener),
1096
1096
  read: (recordId) => {
1097
1097
  const scope = { [schemaKey]: recordId };
@@ -395,7 +395,7 @@ export function buildReactiveEngine(inputs) {
395
395
  for (const [schemaKey, modelDef] of Object.entries(schema.models)) {
396
396
  const registeredModelName = modelDef.typename ?? schemaKey;
397
397
  modelProxies[schemaKey] = createModelOperations(schemaKey, registeredModelName, objectPool, syncClient, modelRegistry, hydration, {
398
- presence: (model, recordId) => presenceStream.forModel(model, recordId),
398
+ presence: (model, recordId, options) => presenceStream.forModel(model, recordId, options),
399
399
  onPresenceChange: (listener) => presenceStream.onChange(listener),
400
400
  startReadPresence: (target) => presenceStream.startRead(target),
401
401
  modelEventTarget: (recordId) => {
@@ -617,10 +617,10 @@ export function buildReactiveEngine(inputs) {
617
617
  schema,
618
618
  // ── Internal accessors for framework integration ─────────────────
619
619
  // These expose internal components for consumers that need direct
620
- // access (e.g., SyncEngineProvider wiring SyncContext, collaboration
620
+ // access (e.g., AbloProvider wiring its store context, collaboration
621
621
  // events accessing the WebSocket handle, demand loaders accessing
622
622
  // the pool). Prefixed with _ to signal "internal but stable."
623
- /** The BaseSyncedStore — implements SyncStoreContract for SyncContext.Provider. */
623
+ /** The BaseSyncedStore — implements SyncStoreContract for AbloStoreContext.Provider. */
624
624
  get _store() { return store; },
625
625
  /** The InstanceCache — for demand loaders that need pool.createFromData(). */
626
626
  get _pool() { return objectPool; },
@@ -1,60 +1,15 @@
1
- /**
2
- * Declares a tree of named custom mutators grouped by model key. Each mutator is
3
- * a plain async function that receives `{ tx, args }` and composes any number of
4
- * `tx.mutations.*` and `tx.read.*` calls to carry out a named operation, such as
5
- * `sections.createWithBlocks`.
6
- *
7
- * The function is purely a place for types to anchor and returns its input
8
- * unchanged; the runtime that dispatches a mutator lives elsewhere — the
9
- * transaction object it receives and the React hook that invokes it. Because
10
- * `defineMutators(schema, { ... })` returns the exact object you wrote,
11
- * `typeof mutators` carries every mutator's precise `args` and result types
12
- * through to wherever they are invoked.
13
- */
1
+ /** Define typed named operations. Pass the schema explicitly across package boundaries. */
14
2
  import type { Schema } from '@abloatai/transaction/schema/schema';
15
3
  import type { Transaction } from './Transaction.js';
16
- import type { ResolveSchema } from '@abloatai/transaction/types/global';
17
- /**
18
- * `ResolveSchema` narrowed to satisfy the `Schema` bound — mirrors
19
- * {@link Ablo.RegisteredSchema}. When nothing is registered, `ResolveSchema`
20
- * is a loose `{ models }` shape that doesn't extend `Schema`, so we fall back
21
- * to `Schema` to keep the mutator tree typed rather than collapsing.
22
- */
4
+ import type { ResolveSchema, RequireRegisteredSchema } from '@abloatai/transaction/types/global';
23
5
  type RegisteredSchema = ResolveSchema extends Schema ? ResolveSchema : Schema;
24
- /**
25
- * The signature of a single custom mutator. The engine supplies `tx`; you control
26
- * `args`, in whatever shape you like, and the resolved return value. `TArgs` and
27
- * `TResult` are bounded by `unknown` rather than `any`, so a mixed tree of
28
- * mutators can be typed together without falling back to `any`.
29
- */
30
6
  export type MutatorFn<S extends Schema, TArgs, TResult = void> = (options: {
31
7
  tx: Transaction<S>;
32
8
  args: TArgs;
33
9
  }) => Promise<TResult>;
34
- /**
35
- * The shape {@link defineMutators} accepts: an optional record per model key
36
- * whose values are named mutator functions. The `unknown` bounds keep the public
37
- * boundary type-safe without `any`; when you write your mutators inline,
38
- * TypeScript still infers the concrete `args` and result of each function, so the
39
- * `unknown` here is only a ceiling, not what you end up working with.
40
- */
41
10
  export type MutatorDefs<S extends Schema> = {
42
11
  [K in keyof S['models']]?: Record<string, MutatorFn<S, never, unknown>>;
43
12
  };
44
- /**
45
- * Returns the mutators object unchanged while constraining its shape against the
46
- * schema. The `S` generic pins the model keys, and the `M` generic is inferred as
47
- * a `const`, so each mutator's literal signature survives. There is no runtime
48
- * work here; the function exists purely as a place for type inference to anchor.
49
- */
50
13
  export declare function defineMutators<S extends Schema, const M extends MutatorDefs<S>>(_schema: S, mutators: M): M;
51
- /**
52
- * Register-anchored overload: omit the schema value and the tree is typed
53
- * against `ResolveSchema` (this app's registered schema). Shared product code
54
- * used across apps that bind different schemas should use this form — it moves
55
- * with each app's `Register` instead of pinning one concrete schema, so the
56
- * mutator tree stays assignable at every consumer (which reads the same
57
- * `Register`). See docs/plans/per-product-schema-projections.md.
58
- */
59
- export declare function defineMutators<const M extends MutatorDefs<RegisteredSchema>>(mutators: M): M;
14
+ export declare function defineMutators<const M extends MutatorDefs<RegisteredSchema>>(mutators: RequireRegisteredSchema<M>): M;
60
15
  export {};
@@ -1,18 +1,3 @@
1
- /**
2
- * Declares a tree of named custom mutators grouped by model key. Each mutator is
3
- * a plain async function that receives `{ tx, args }` and composes any number of
4
- * `tx.mutations.*` and `tx.read.*` calls to carry out a named operation, such as
5
- * `sections.createWithBlocks`.
6
- *
7
- * The function is purely a place for types to anchor and returns its input
8
- * unchanged; the runtime that dispatches a mutator lives elsewhere — the
9
- * transaction object it receives and the React hook that invokes it. Because
10
- * `defineMutators(schema, { ... })` returns the exact object you wrote,
11
- * `typeof mutators` carries every mutator's precise `args` and result types
12
- * through to wherever they are invoked.
13
- */
14
1
  export function defineMutators(schemaOrMutators, maybeMutators) {
15
- // The schema argument is a type anchor only — never read at runtime. With one
16
- // argument the mutator tree is in the first slot; with two it's in the second.
17
2
  return (maybeMutators ?? schemaOrMutators);
18
3
  }
@@ -0,0 +1,7 @@
1
+ import type { AbloClient } from '../client.js';
2
+ import type { SchemaRecord } from '@abloatai/transaction/schema/schema';
3
+ import type { SyncStoreContract } from './storeContract.js';
4
+ /** Access the supported local store contract for custom framework adapters,
5
+ * demand loading, scope management and custom undo infrastructure.
6
+ */
7
+ export declare function getAbloStore<S extends SchemaRecord>(client: AbloClient<S>): SyncStoreContract;
@@ -0,0 +1,6 @@
1
+ /** Access the supported local store contract for custom framework adapters,
2
+ * demand loading, scope management and custom undo infrastructure.
3
+ */
4
+ export function getAbloStore(client) {
5
+ return client._store;
6
+ }
@@ -1,10 +1,13 @@
1
1
  /**
2
2
  * Handles the delta types that change which sync groups a session can see. A
3
- * sync group is a fan-out scope the server uses to decide which entities a
4
- * client receives. When a session's membership changes, these handlers update
3
+ * sync group connects shared state to authorized participant subscriptions.
4
+ * This module owns the client side of membership changes and local-state
5
+ * rebuilding; it neither grants server authority nor configures per-group loading.
6
+ * When a session's membership changes, these handlers update
5
7
  * the client's subscription list; when access is revoked, they clear cached
6
- * data and trigger a full re-bootstrap so revoked rows cannot linger on the
7
- * device.
8
+ * managed data and request re-bootstrap. Clients without automatic bootstrap
9
+ * rely on covering deltas or explicit reads instead. This cannot retract copies
10
+ * retained outside the managed cache.
8
11
  *
9
12
  * Every handler takes a {@link GroupChangeContext}, the narrow facade through
10
13
  * which it reaches the client's local storage and connection lifecycle hooks.
@@ -1,10 +1,13 @@
1
1
  /**
2
2
  * Handles the delta types that change which sync groups a session can see. A
3
- * sync group is a fan-out scope the server uses to decide which entities a
4
- * client receives. When a session's membership changes, these handlers update
3
+ * sync group connects shared state to authorized participant subscriptions.
4
+ * This module owns the client side of membership changes and local-state
5
+ * rebuilding; it neither grants server authority nor configures per-group loading.
6
+ * When a session's membership changes, these handlers update
5
7
  * the client's subscription list; when access is revoked, they clear cached
6
- * data and trigger a full re-bootstrap so revoked rows cannot linger on the
7
- * device.
8
+ * managed data and request re-bootstrap. Clients without automatic bootstrap
9
+ * rely on covering deltas or explicit reads instead. This cannot retract copies
10
+ * retained outside the managed cache.
8
11
  *
9
12
  * Every handler takes a {@link GroupChangeContext}, the narrow facade through
10
13
  * which it reaches the client's local storage and connection lifecycle hooks.
@@ -1,6 +1,9 @@
1
1
  import type { ClaimTarget } from '@abloatai/transaction/types/streams';
2
2
  import type { Schema } from '@abloatai/transaction/schema/schema';
3
- /** A schema-shaped selector used to narrow connection groups and presence reads. */
3
+ /**
4
+ * Selects group interest using model records or explicit group names. Resolving
5
+ * a selector names a requested scope; the server still checks authority.
6
+ */
4
7
  export type GroupScope = ClaimTarget | readonly ClaimTarget[] | string | readonly string[] | {
5
8
  readonly syncGroup: string;
6
9
  } | {
@@ -1,10 +1,10 @@
1
- import { type PresenceProjection, type PresenceProjectionEvents, type PresenceView } from '@abloatai/transaction/presence';
1
+ import { type PresenceProjection, type PresenceProjectionEvents, type PresenceView, type PresenceQueryOptions } from '@abloatai/transaction/presence';
2
2
  import type { PresenceTarget } from '@abloatai/transaction/presence';
3
3
  import { type ReadActivityTransport } from './readActivity.js';
4
4
  type PresenceTransport = PresenceProjectionEvents & ReadActivityTransport;
5
5
  /** Reactive-client presence backed by the client's existing live connection. */
6
6
  export interface ReactivePresence extends PresenceView {
7
- forModel(model: string, recordId?: string): ReturnType<PresenceProjection['forModel']>;
7
+ forModel(model: string, recordId?: string, options?: PresenceQueryOptions): ReturnType<PresenceProjection['forModel']>;
8
8
  onChange(listener: () => void): () => void;
9
9
  }
10
10
  /** Lifecycle hooks kept inside the humans composition boundary. */
@@ -52,9 +52,9 @@ export function createPresence(transport = null) {
52
52
  lifetime.stop();
53
53
  };
54
54
  },
55
- forModel(model, recordId) {
55
+ forModel(model, recordId, options) {
56
56
  version.get();
57
- return projection?.forModel(model, recordId) ?? [];
57
+ return projection?.forModel(model, recordId, options) ?? [];
58
58
  },
59
59
  dispose() {
60
60
  for (const read of reads)
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useMemo, useRef, useState, createContext, } from 'react';
4
- import { SyncContext } from './context.js';
4
+ import { AbloStoreContext } from './context.js';
5
5
  import { AbloInternalContext } from './internalContext.js';
6
6
  import { AbloValidationError } from '@abloatai/transaction/errors';
7
7
  import { useAblo } from './useAblo.js';
@@ -75,7 +75,7 @@ export function AbloProvider(props) {
75
75
  // onSessionExpired. Credential cleanup lives in the CLIENT, so direct
76
76
  // consumers and React consumers have the same security boundary.
77
77
  // 2. Drive `ready()` (idempotent) so bootstrap starts on mount, then read the
78
- // resolved org scope for SyncContext.
78
+ // resolved org scope for the Ablo store context.
79
79
  // It does NOT dispose the client (consumer-owned) and does NOT touch auth.
80
80
  useEffect(() => {
81
81
  let stale = false;
@@ -131,12 +131,12 @@ export function AbloProvider(props) {
131
131
  window.addEventListener('beforeunload', handler);
132
132
  return () => { window.removeEventListener('beforeunload', handler); };
133
133
  }, [engine, preventUnsavedChanges]);
134
- // ── SyncContext value (for useQuery/useOne/useMutate hooks) ──────
134
+ // ── Store context value (for Ablo data hooks) ────────────────────
135
135
  //
136
136
  // The engine is always present (it's the `client` prop), but its org scope is
137
- // unknown until `ready()` resolves identity — so `syncValue` is null until
137
+ // unknown until `ready()` resolves identity — so the store context is null until
138
138
  // then, which drives the initial fallback below.
139
- const syncValue = useMemo(() => {
139
+ const storeContextValue = useMemo(() => {
140
140
  const currentAccountScope = (resolvedScope?.engine === engine ? resolvedScope.account : null) ??
141
141
  engine._store.orgId;
142
142
  if (!currentAccountScope)
@@ -156,7 +156,7 @@ export function AbloProvider(props) {
156
156
  // Keep the context tree stable during startup so passthrough children retain
157
157
  // their component state when authenticated row scope becomes available.
158
158
  const passthrough = fallback === 'passthrough';
159
- return (_jsx(AbloInternalContext.Provider, { value: internalValue, children: _jsx(SyncContext.Provider, { value: syncValue, children: passthrough ? (children) : syncValue ? (_jsx(BootstrapGate, { fallback: fallback, children: children }, engineKey)) : fallback }) }));
159
+ return (_jsx(AbloInternalContext.Provider, { value: internalValue, children: _jsx(AbloStoreContext.Provider, { value: storeContextValue, children: passthrough ? (children) : storeContextValue ? (_jsx(BootstrapGate, { fallback: fallback, children: children }, engineKey)) : fallback }) }));
160
160
  }
161
161
  /**
162
162
  * Internal gate that renders `fallback` only during the very first
@@ -1,55 +1,16 @@
1
- import { type ReactNode } from 'react';
2
1
  import type { Schema } from '@abloatai/transaction/schema/schema';
3
2
  import type { SyncStoreContract } from '../local/storeContract.js';
4
3
  export type { SyncStoreContract, LocalMutation, } from '../local/storeContract.js';
5
- export interface SyncReactContext {
4
+ export interface AbloStoreContextValue {
6
5
  store: SyncStoreContract;
7
6
  /** The organization id used as the default scope for reads and writes. */
8
7
  organizationId: string;
9
- /**
10
- * An optional schema. When provided, hooks that take a model by name (such as
11
- * `useQuery('items')`) read that model's metadata from this schema, so
12
- * callers don't pass a schema at every call site. When omitted, those hooks
13
- * require the schema as an argument instead.
14
- *
15
- * The field is loosely typed here because a single runtime context value is
16
- * shared by every hook. Precise per-model types come from your `Register`
17
- * module augmentation
18
- * (`declare module '@abloatai/ablo' { interface Register { Schema: typeof schema } }`),
19
- * not from this reference.
20
- */
8
+ /** Runtime schema used by ambient mutator overloads. */
21
9
  schema?: Schema;
22
10
  }
23
- export declare const SyncContext: import("react").Context<SyncReactContext | null>;
11
+ export declare const AbloStoreContext: import("react").Context<AbloStoreContextValue | null>;
24
12
  /**
25
- * Reads the sync store context from inside a provider subtree, throwing a clear
26
- * error when no provider is mounted above. `<AbloProvider>` supplies this
27
- * context by rendering the internal {@link SyncProvider}; you wire
28
- * `<AbloProvider client={ablo}>` rather than touching this directly.
13
+ * Reads the store scope owned by `<AbloProvider>`, throwing a clear error when
14
+ * no provider is mounted above it.
29
15
  */
30
- export declare function useSyncContext(): SyncReactContext;
31
- /**
32
- * Props for SyncProvider.
33
- */
34
- export interface SyncProviderProps {
35
- /** The sync store, which must implement {@link SyncStoreContract}. */
36
- store: SyncStoreContract;
37
- /** The organization id used as the default scope for reads and writes. */
38
- organizationId: string;
39
- /**
40
- * An optional schema. Provide it to enable hooks that take a model by name
41
- * (such as `useQuery('items')`); the model types also narrow through your
42
- * `Register` augmentation. Omit it to pass the schema to those hooks directly
43
- * instead.
44
- */
45
- schema?: Schema;
46
- children?: ReactNode;
47
- }
48
- /**
49
- * A low-level provider that places a built sync store on React context so the
50
- * data hooks can reach it. This is an internal building block: it is not part
51
- * of the package's public entry point. Reach for `<AbloProvider>` instead,
52
- * which builds the store from your `Ablo({ schema, apiKey })` client and
53
- * renders this provider underneath.
54
- */
55
- export declare function SyncProvider({ store, organizationId, schema, children, }: SyncProviderProps): import("react").FunctionComponentElement<import("react").ProviderProps<SyncReactContext | null>>;
16
+ export declare function useAbloStoreContext(): AbloStoreContextValue;
@@ -1,29 +1,17 @@
1
1
  'use client';
2
- import { createContext, createElement, useContext } from 'react';
2
+ import { createContext, useContext } from 'react';
3
3
  import { AbloValidationError } from '@abloatai/transaction/errors';
4
- export const SyncContext = createContext(null);
4
+ export const AbloStoreContext = createContext(null);
5
5
  /**
6
- * Reads the sync store context from inside a provider subtree, throwing a clear
7
- * error when no provider is mounted above. `<AbloProvider>` supplies this
8
- * context by rendering the internal {@link SyncProvider}; you wire
9
- * `<AbloProvider client={ablo}>` rather than touching this directly.
6
+ * Reads the store scope owned by `<AbloProvider>`, throwing a clear error when
7
+ * no provider is mounted above it.
10
8
  */
11
- export function useSyncContext() {
12
- const ctx = useContext(SyncContext);
9
+ export function useAbloStoreContext() {
10
+ const ctx = useContext(AbloStoreContext);
13
11
  if (!ctx) {
14
- throw new AbloValidationError('Sync hooks must be used within an <AbloProvider>.', {
15
- code: 'sync_context_missing_provider',
12
+ throw new AbloValidationError('Ablo hooks must be used within an <AbloProvider>.', {
13
+ code: 'ablo_context_missing_provider',
16
14
  });
17
15
  }
18
16
  return ctx;
19
17
  }
20
- /**
21
- * A low-level provider that places a built sync store on React context so the
22
- * data hooks can reach it. This is an internal building block: it is not part
23
- * of the package's public entry point. Reach for `<AbloProvider>` instead,
24
- * which builds the store from your `Ablo({ schema, apiKey })` client and
25
- * renders this provider underneath.
26
- */
27
- export function SyncProvider({ store, organizationId, schema, children, }) {
28
- return createElement(SyncContext.Provider, { value: { store, organizationId, schema } }, children);
29
- }
@@ -1,32 +1,19 @@
1
- /**
2
- * Capture schema inference once while reusing module-level React functions.
3
- * This helper creates no components, hooks, contexts or client instances.
4
- *
5
- * Define the app binding at module scope:
6
- * `export const { AbloProvider, useAblo, usePresence } = createAbloReact(schema)`.
7
- */
8
1
  import type { ReactElement } from 'react';
2
+ import { useMutationFailure } from './useMutationFailure.js';
9
3
  import { AbloProvider } from './AbloProvider.js';
10
- import { useAblo, type AbloSelector, type ModelClientSelector } from './useAblo.js';
4
+ import { useAblo } from './useAblo.js';
11
5
  import type { AbloClient as Ablo } from '../client.js';
12
- import type { ModelOperations } from '../local/client/createModelOperations.js';
13
6
  import type { Schema, SchemaRecord } from '@abloatai/transaction/schema/schema';
14
- import { type PresenceModelSelector } from './usePresence.js';
15
- import type { PresenceSession } from '@abloatai/transaction/presence';
16
- /** What a binding returns: the provider and the hook, with `S` fixed. */
7
+ import { usePresence } from './usePresence.js';
8
+ /** Shared provider and hooks specialized to one schema. */
17
9
  export interface AbloReactBinding<S extends SchemaRecord> {
18
- /** `AbloProvider` with its `client` prop typed `Ablo<S>` — same component,
19
- * no per-app generics. */
20
10
  AbloProvider: (props: AbloProvider.Props<S>) => ReactElement;
21
- /** `useAblo` with the schema bound — the same overloads as the global
22
- * hook, minus the type arguments. */
23
- useAblo: {
24
- (): Ablo<S> | null;
25
- <T>(select: AbloSelector<S, T>): T | undefined;
26
- <T, C>(modelClientOrSelect: ModelOperations<T, C> | ModelClientSelector<S, T, C>, id: string, options?: useAblo.Options<T>): useAblo.Result<T>;
27
- };
11
+ useAblo: useAblo.Bound<S>;
12
+ /** Writable client for actions; useAblo(selector) supplies render snapshots. */
13
+ useAbloClient: () => Ablo<S> | null;
14
+ useMutationFailure: typeof useMutationFailure;
28
15
  /** Declare and reactively read record presence with the same model clients. */
29
- usePresence: <T, C>(modelOrSelect: ModelOperations<T, C> | PresenceModelSelector<S, T, C>, recordId: string) => readonly PresenceSession[];
16
+ usePresence: usePresence.Bound<S>;
30
17
  }
31
18
  /** Bind the existing React functions to one schema's types. */
32
19
  export declare function createAbloReact<S extends SchemaRecord>(schema: Schema<S>): AbloReactBinding<S>;
@@ -1,14 +1,12 @@
1
1
  'use client';
2
+ import { useAbloClient } from './useAbloClient.js';
3
+ import { useMutationFailure } from './useMutationFailure.js';
2
4
  import { AbloProvider } from './AbloProvider.js';
3
- import { useAblo, } from './useAblo.js';
5
+ import { useAblo } from './useAblo.js';
4
6
  import { usePresence } from './usePresence.js';
5
7
  /** Bind the existing React functions to one schema's types. */
6
8
  export function createAbloReact(schema) {
7
9
  void schema;
8
- // TypeScript cannot partially specialize the generic overloads, so this
9
- // assertion binds their schema parameter. Positive and negative consumer
10
- // type tests verify the specialization; no runtime value changes.
11
- // Specialize types only. Every binding uses the same module-level functions,
12
- // so calling this helper again cannot change component identity or reset state.
13
- return { AbloProvider, useAblo, usePresence };
10
+ // Specialize the shared functions without creating new contexts or identities.
11
+ return { AbloProvider, useAblo, useAbloClient, useMutationFailure, usePresence };
14
12
  }
@@ -2,15 +2,6 @@ import type { AbloClient as Ablo } from '../client.js';
2
2
  import type { SchemaRecord } from '@abloatai/transaction/schema/schema';
3
3
  /** The provider owns only the reference to the application-owned client. */
4
4
  export interface AbloInternalContextValue {
5
- /**
6
- * The typed `Ablo` client for this provider, available before bootstrap resolves. It is held here so `useAblo()` can return it without
7
- * reaching into the store; the client and the store are sibling objects, and
8
- * neither is derived from the other.
9
- *
10
- * It is typed loosely as `Ablo<SchemaRecord>` because generics do not flow
11
- * through React context. `useAblo<R>()` restores the precise type through its
12
- * own generic; the runtime value is the fully typed client.
13
- */
14
5
  engine: Ablo<SchemaRecord> | null;
15
6
  }
16
7
  export declare const AbloInternalContext: import("react").Context<AbloInternalContextValue | null>;