@abloatai/humans 0.63.1 → 0.64.1

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 (87) hide show
  1. package/dist/Ablo.d.ts +6 -0
  2. package/dist/client.d.ts +12 -32
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +1 -0
  5. package/dist/local/client/createModelOperations.d.ts +3 -3
  6. package/dist/local/client/createModelOperations.js +1 -1
  7. package/dist/local/client/reactiveEngine.js +7 -8
  8. package/dist/{react/useSyncStatus.d.ts → local/client/status.d.ts} +4 -3
  9. package/dist/local/client/status.js +14 -0
  10. package/dist/local/client/storeLifecycle.js +2 -2
  11. package/dist/local/mutators/defineMutators.d.ts +3 -48
  12. package/dist/local/mutators/defineMutators.js +0 -15
  13. package/dist/local/storeAccess.d.ts +7 -0
  14. package/dist/local/storeAccess.js +6 -0
  15. package/dist/local/storeContract.d.ts +3 -4
  16. package/dist/presence/index.d.ts +2 -2
  17. package/dist/presence/index.js +8 -4
  18. package/dist/react/AbloProvider.d.ts +57 -121
  19. package/dist/react/AbloProvider.js +55 -160
  20. package/dist/react/context.d.ts +6 -45
  21. package/dist/react/context.js +8 -20
  22. package/dist/react/createAbloReact.d.ts +13 -48
  23. package/dist/react/createAbloReact.js +8 -54
  24. package/dist/react/internalContext.d.ts +1 -27
  25. package/dist/react/snapshot.d.ts +4 -0
  26. package/dist/react/snapshot.js +75 -0
  27. package/dist/react/useAblo.d.ts +28 -92
  28. package/dist/react/useAblo.js +36 -93
  29. package/dist/react/useAbloClient.d.ts +5 -0
  30. package/dist/react/useAbloClient.js +9 -0
  31. package/dist/react/useMutationFailure.d.ts +6 -0
  32. package/dist/react/useMutationFailure.js +11 -0
  33. package/dist/react/useMutators.d.ts +22 -19
  34. package/dist/react/useMutators.js +3 -3
  35. package/dist/react/usePresence.d.ts +9 -9
  36. package/dist/react/usePresence.js +8 -11
  37. package/dist/react/useReactive.d.ts +12 -0
  38. package/dist/react/useReactive.js +57 -0
  39. package/dist/react/useUndoScope.d.ts +16 -30
  40. package/dist/react/useUndoScope.js +21 -4
  41. package/dist/react.d.ts +9 -19
  42. package/dist/react.js +9 -15
  43. package/dist/reactRuntime.d.ts +1 -1
  44. package/dist/reactRuntime.js +1 -1
  45. package/package.json +4 -3
  46. package/src/Ablo.ts +7 -0
  47. package/src/client.ts +13 -32
  48. package/src/index.ts +2 -0
  49. package/src/local/client/createModelOperations.ts +4 -4
  50. package/src/local/client/reactiveEngine.ts +7 -8
  51. package/src/local/client/status.ts +20 -0
  52. package/src/local/client/storeLifecycle.ts +2 -2
  53. package/src/local/mutators/defineMutators.ts +3 -51
  54. package/src/local/storeAccess.ts +10 -0
  55. package/src/local/storeContract.ts +3 -4
  56. package/src/presence/index.ts +10 -5
  57. package/src/react/AbloProvider.tsx +88 -263
  58. package/src/react/context.ts +10 -61
  59. package/src/react/createAbloReact.ts +16 -125
  60. package/src/react/internalContext.ts +1 -27
  61. package/src/react/snapshot.ts +67 -0
  62. package/src/react/useAblo.ts +73 -209
  63. package/src/react/useAbloClient.ts +14 -0
  64. package/src/react/useMutationFailure.ts +17 -0
  65. package/src/react/useMutators.ts +36 -32
  66. package/src/react/usePresence.ts +22 -27
  67. package/src/react/useReactive.ts +56 -0
  68. package/src/react/useUndoScope.ts +24 -20
  69. package/src/react.ts +9 -69
  70. package/src/reactRuntime.ts +2 -2
  71. package/dist/react/ClientSideSuspense.d.ts +0 -36
  72. package/dist/react/ClientSideSuspense.js +0 -17
  73. package/dist/react/useCurrentUserId.d.ts +0 -2
  74. package/dist/react/useCurrentUserId.js +0 -12
  75. package/dist/react/useErrorListener.d.ts +0 -2
  76. package/dist/react/useErrorListener.js +0 -14
  77. package/dist/react/useMutationFailureListener.d.ts +0 -8
  78. package/dist/react/useMutationFailureListener.js +0 -19
  79. package/dist/react/useSyncStatus.js +0 -48
  80. package/dist/useReactive.d.ts +0 -6
  81. package/dist/useReactive.js +0 -41
  82. package/src/react/ClientSideSuspense.tsx +0 -57
  83. package/src/react/useCurrentUserId.ts +0 -17
  84. package/src/react/useErrorListener.ts +0 -22
  85. package/src/react/useMutationFailureListener.ts +0 -34
  86. package/src/react/useSyncStatus.ts +0 -53
  87. package/src/useReactive.ts +0 -49
package/dist/Ablo.d.ts CHANGED
@@ -86,6 +86,11 @@ import type * as _Global from '@abloatai/transaction/types/global';
86
86
  * into one of the public subpaths.
87
87
  */
88
88
  export declare namespace Ablo {
89
+ /** Payload delivered by the core client's onMutationFailure subscription. */
90
+ type MutationFailure = Parameters<Parameters<AbloClient<SchemaRecord>['onMutationFailure']>[0]>[0];
91
+ /** Current client lifecycle, also selected through React's useAblo. */
92
+ type Store = import('./local/storeContract.js').SyncStoreContract;
93
+ type Status = import('./local/client/status.js').ClientStatus;
89
94
  type Options<S extends SchemaRecord = SchemaRecord> = AbloOptions<S>;
90
95
  /**
91
96
  * The read view of the client that `useAblo` selectors receive: model reads
@@ -119,6 +124,7 @@ export declare namespace Ablo {
119
124
  * different schemas.
120
125
  */
121
126
  type ResolveSchema = _Global.ResolveSchema;
127
+ type ResolveClaimMeta = _Global.ResolveClaimMeta;
122
128
  /**
123
129
  * `ResolveSchema` guaranteed to satisfy the `Schema` bound. `ResolveSchema`
124
130
  * falls back to a loose `{ models }` shape when nothing is registered, which
package/dist/client.d.ts CHANGED
@@ -12,9 +12,9 @@
12
12
  */
13
13
  import type { Schema, SchemaRecord, Model, InferCreate, InferRow } from '@abloatai/transaction/schema/schema';
14
14
  import type { InstanceCache } from './local/InstanceCache.js';
15
- import type { SyncStoreContract } from './react/context.js';
15
+ import type { SyncStoreContract } from './local/storeContract.js';
16
16
  import type { SyncWebSocket, CoreSyncEventMap } from './local/sync/SyncWebSocket.js';
17
- import type { SyncStatus } from './local/BaseSyncedStore.js';
17
+ import type { ClientStatus } from './local/client/status.js';
18
18
  import type { ModelOperations } from './local/client/createModelOperations.js';
19
19
  import type { ClaimResource, CommitResource } from '@abloatai/transaction/client/resources/httpResources';
20
20
  import type { EffectiveAuthority } from '@abloatai/transaction/auth';
@@ -25,7 +25,9 @@ export type { LocalReadOptions } from './local/client/resourceTypes.js';
25
25
  /** The typed sync engine client — one property per model in the schema */
26
26
  export type AbloClient<S extends SchemaRecord> = {
27
27
  readonly [K in keyof S & string]: ModelOperations<Model<Schema<S>, K>, InferCreate<Schema<S>, K>>;
28
- } & {
28
+ } & AbloCore<S>;
29
+ /** Core members stay intact even when the model schema is not registered. */
30
+ interface AbloCore<S extends SchemaRecord> {
29
31
  /**
30
32
  * Wait for the sync engine to finish its initial bootstrap.
31
33
  * Resolves once entity data is loaded and the WebSocket is connected.
@@ -50,7 +52,7 @@ export type AbloClient<S extends SchemaRecord> = {
50
52
  * acknowledged everything before continuing — for example, before
51
53
  * navigating away, before triggering a server-side workflow, or in tests.
52
54
  *
53
- * Resolves when `syncStatus.pendingChanges` reaches 0. If the engine is
55
+ * Resolves when all pending local changes are confirmed. If the engine is
54
56
  * offline, this waits until reconnect + flush completes.
55
57
  *
56
58
  * ```ts
@@ -171,32 +173,10 @@ export type AbloClient<S extends SchemaRecord> = {
171
173
  */
172
174
  waitForConfirmation(modelName: string, modelId: string): Promise<void>;
173
175
  /**
174
- * Reactive sync status — a MobX observable.
175
- *
176
- * Single source of truth for "what's the sync engine doing?" Contains:
177
- * - `state`: `'idle' | 'syncing' | 'error' | 'offline' | 'reconnecting'`
178
- * - `progress`: 0-100 for bootstrap progress
179
- * - `error?`: Error object when `state === 'error'`
180
- * - `pendingChanges`: Number of unconfirmed mutations in the queue
181
- * - `lastSyncAt?`: Timestamp of the last successful delta processing
182
- * - `offlineSince?`: When the connection dropped
183
- * - `isSessionError`: True when the error requires re-authentication
184
- *
185
- * React components using `observer()` re-render automatically when
186
- * any field changes — no manual subscription or polling needed.
187
- *
188
- * ```tsx
189
- * import { observer } from 'mobx-react-lite';
190
- *
191
- * const SyncIndicator = observer(() => {
192
- * if (sync.syncStatus.state === 'syncing') return <Spinner />;
193
- * if (sync.syncStatus.state === 'error') return <Error msg={sync.syncStatus.error} />;
194
- * if (sync.syncStatus.state === 'offline') return <OfflineBadge />;
195
- * return null;
196
- * });
197
- * ```
176
+ * Current connection and confirmation state. Available before ready().
177
+ * React reads the same value with `useAblo(ablo => ablo.status)`.
198
178
  */
199
- readonly syncStatus: SyncStatus;
179
+ readonly status: ClientStatus;
200
180
  /**
201
181
  * Session-owned live activity projected from this client's existing
202
182
  * connection. Use `active` for every visible activity, `others` to exclude
@@ -230,7 +210,7 @@ export type AbloClient<S extends SchemaRecord> = {
230
210
  subscribe<K extends keyof CoreSyncEventMap>(event: K, handler: (...args: CoreSyncEventMap[K]) => void): () => void;
231
211
  /**
232
212
  * The internal store. It implements {@link SyncStoreContract} — pass it to
233
- * `SyncContext.Provider` so the SDK's `useModel` / `useModels` / `useMutations`
213
+ * `AbloStoreContext.Provider` so the SDK's React data hooks
234
214
  * hooks can reach it.
235
215
  */
236
216
  readonly _store: SyncStoreContract;
@@ -243,7 +223,7 @@ export type AbloClient<S extends SchemaRecord> = {
243
223
  * no window in which this is absent and nothing needs to guard for one.
244
224
  */
245
225
  readonly _ws: SyncWebSocket;
246
- };
226
+ }
247
227
  /**
248
228
  * The reactive-read client a `useAblo` selector receives. The same surface as
249
229
  * {@link AbloClient}, except model reads are typed as reactive rows
@@ -253,6 +233,6 @@ export type AbloClient<S extends SchemaRecord> = {
253
233
  * compile error here instead of a silent runtime `undefined`; compose
254
234
  * relations through selectors or hooks that resolve the pool's instance.
255
235
  */
256
- export type AbloReads<S extends SchemaRecord> = Omit<AbloClient<S>, keyof S & string> & {
236
+ export type AbloReads<S extends SchemaRecord> = AbloCore<S> & {
257
237
  readonly [K in keyof S & string]: ModelOperations<InferRow<Schema<S>, K>, InferCreate<Schema<S>, K>>;
258
238
  };
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';
@@ -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 };
@@ -15,6 +15,7 @@ import { omittedModelError } from '@abloatai/transaction/schema/select';
15
15
  import { durableCommitOperationSchema, } from '@abloatai/transaction/commit';
16
16
  import { AbloConnectionError, AbloValidationError, claimedError } from '@abloatai/transaction/errors';
17
17
  import { batchFence, claimIdFor, fenceTokenFor, modelTarget, streamTarget, subTarget, } from '@abloatai/transaction/coordination';
18
+ import { readStatus } from './status.js';
18
19
  import { validateAbloOptions } from './validateAbloOptions.js';
19
20
  import { startStoreLifecycle } from './storeLifecycle.js';
20
21
  import { createClaimStream } from '../sync/createClaimStream.js';
@@ -394,7 +395,7 @@ export function buildReactiveEngine(inputs) {
394
395
  for (const [schemaKey, modelDef] of Object.entries(schema.models)) {
395
396
  const registeredModelName = modelDef.typename ?? schemaKey;
396
397
  modelProxies[schemaKey] = createModelOperations(schemaKey, registeredModelName, objectPool, syncClient, modelRegistry, hydration, {
397
- presence: (model, recordId) => presenceStream.forModel(model, recordId),
398
+ presence: (model, recordId, options) => presenceStream.forModel(model, recordId, options),
398
399
  onPresenceChange: (listener) => presenceStream.onChange(listener),
399
400
  startReadPresence: (target) => presenceStream.startRead(target),
400
401
  modelEventTarget: (recordId) => {
@@ -605,11 +606,9 @@ export function buildReactiveEngine(inputs) {
605
606
  waitForConfirmation(modelName, modelId) {
606
607
  return store.waitForConfirmation(modelName, modelId);
607
608
  },
608
- // Expose the store's MobX observable directly — single source of truth.
609
- // React components using observer() will re-render automatically on
610
- // any state change (syncing, error, offline, pendingChanges, progress).
611
- get syncStatus() {
612
- return store.syncStatus;
609
+ // One core lifecycle projection; React selects the same observable reads.
610
+ get status() {
611
+ return readStatus(store);
613
612
  },
614
613
  // The humans capability owns the connection-backed presence projection.
615
614
  // Keep it on the base client as well as in the plugin surface so the
@@ -618,10 +617,10 @@ export function buildReactiveEngine(inputs) {
618
617
  schema,
619
618
  // ── Internal accessors for framework integration ─────────────────
620
619
  // These expose internal components for consumers that need direct
621
- // access (e.g., SyncEngineProvider wiring SyncContext, collaboration
620
+ // access (e.g., AbloProvider wiring its store context, collaboration
622
621
  // events accessing the WebSocket handle, demand loaders accessing
623
622
  // the pool). Prefixed with _ to signal "internal but stable."
624
- /** The BaseSyncedStore — implements SyncStoreContract for SyncContext.Provider. */
623
+ /** The BaseSyncedStore — implements SyncStoreContract for AbloStoreContext.Provider. */
625
624
  get _store() { return store; },
626
625
  /** The InstanceCache — for demand loaders that need pool.createFromData(). */
627
626
  get _pool() { return objectPool; },
@@ -1,4 +1,6 @@
1
- export type SyncStatusSnapshot = {
1
+ import type { SyncStoreContract } from '../storeContract.js';
2
+ /** The client lifecycle, shared by every framework binding. */
3
+ export type ClientStatus = {
2
4
  readonly name: 'initial';
3
5
  } | {
4
6
  readonly name: 'connecting';
@@ -15,5 +17,4 @@ export type SyncStatusSnapshot = {
15
17
  } | {
16
18
  readonly name: 'needs-auth';
17
19
  };
18
- /** Reactively exposes the local store's connection and confirmation status. */
19
- export declare function useSyncStatus(): SyncStatusSnapshot;
20
+ export declare function readStatus(store: SyncStoreContract): ClientStatus;
@@ -0,0 +1,14 @@
1
+ export function readStatus(store) {
2
+ const { state, progress, pendingChanges, isSessionError, error } = store.syncStatus;
3
+ if (isSessionError)
4
+ return { name: 'needs-auth' };
5
+ if (state === 'reconnecting')
6
+ return { name: 'reconnecting', reason: error?.message };
7
+ if (state === 'offline')
8
+ return { name: 'disconnected', reason: 'offline' };
9
+ if (state === 'error')
10
+ return { name: 'disconnected', reason: error?.message };
11
+ if (store.isReady)
12
+ return { name: 'connected', hasUnsyncedChanges: pendingChanges > 0 };
13
+ return { name: 'connecting', progress };
14
+ }
@@ -183,7 +183,7 @@ export function startStoreLifecycle(deps) {
183
183
  //
184
184
  // The store.initialize() generator updates store.syncStatus as it
185
185
  // progresses (syncing → idle on success, error on failure), so the
186
- // consumer's `sync.syncStatus` observable reflects real-time state.
186
+ // consumer's `ablo.status` projection reflects real-time state.
187
187
  // Resolve bootstrap mode: explicit option wins; otherwise
188
188
  // agents default to 'none' (transactional participant — see
189
189
  // option doc) and everyone else defaults to 'full'.
@@ -260,7 +260,7 @@ export function startStoreLifecycle(deps) {
260
260
  if (!validationError && internalOptions.autoStart) {
261
261
  void ready().catch(() => {
262
262
  // Error is captured in store.syncStatus; consumers should check
263
- // `sync.syncStatus.state === 'error'` to detect failures.
263
+ // `ablo.status.name === 'disconnected'` to detect failures.
264
264
  });
265
265
  }
266
266
  return {
@@ -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
+ }
@@ -17,8 +17,7 @@ import type { GroupScope } from './sync/scopeGroups.js';
17
17
  /**
18
18
  * A snapshot of the client's synchronization state, shaped for binding to UI.
19
19
  * {@link SyncStoreContract.syncStatus} exposes a reactive instance of this, and
20
- * the `useSyncStatus()` hook reads its fields to render connection and progress
21
- * indicators.
20
+ * the client projects it into `ablo.status` for connection indicators.
22
21
  */
23
22
  export interface SyncStatus {
24
23
  state: 'idle' | 'syncing' | 'error' | 'offline' | 'reconnecting';
@@ -112,7 +111,7 @@ export interface SyncStoreContract {
112
111
  * are backed by observable computeds, so reading them inside a reactive
113
112
  * context — an observer component or a reaction — re-runs that context when
114
113
  * the state changes. Code that prefers not to work with the reactivity system
115
- * directly can read the same values through the `useSyncStatus()` hook.
114
+ * directly can read the same values through `useAblo(ablo => ablo.status)`.
116
115
  */
117
116
  readonly isReady: boolean;
118
117
  readonly isSyncing: boolean;
@@ -136,7 +135,7 @@ export interface SyncStoreContract {
136
135
  pinScope?(scope: GroupScope): Promise<void>;
137
136
  unpinScope?(scope: GroupScope): Promise<void>;
138
137
  /**
139
- * The full reactive {@link SyncStatus} record. The `useSyncStatus()` hook
138
+ * The full reactive {@link SyncStatus} record. The client status projection
140
139
  * reads its fields — `state`, `progress`, `pendingChanges`, `isSessionError`,
141
140
  * and `error` — to present the current sync state. It is part of the contract
142
141
  * so hooks and test doubles can read or set it directly.
@@ -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. */
@@ -1,3 +1,4 @@
1
+ import { observable, runInAction } from 'mobx';
1
2
  import { createPresenceProjection, } from '@abloatai/transaction/presence';
2
3
  import { startReadActivity, } from './readActivity.js';
3
4
  const clientPresence = new WeakMap();
@@ -14,10 +15,12 @@ export function presenceOfClient(client) {
14
15
  export function createPresence(transport = null) {
15
16
  let projection = null;
16
17
  let attachedTransport = null;
18
+ const version = observable.box(0);
17
19
  const listeners = new Set();
18
20
  const reads = new Set();
19
21
  let unsubscribe = null;
20
22
  const notify = () => {
23
+ runInAction(() => { version.set(version.get() + 1); });
21
24
  for (const listener of listeners)
22
25
  listener();
23
26
  };
@@ -32,8 +35,8 @@ export function createPresence(transport = null) {
32
35
  if (transport !== null)
33
36
  attach(transport);
34
37
  return {
35
- get active() { return projection?.active ?? []; },
36
- get others() { return projection?.others ?? []; },
38
+ get active() { version.get(); return projection?.active ?? []; },
39
+ get others() { version.get(); return projection?.others ?? []; },
37
40
  onChange(listener) {
38
41
  listeners.add(listener);
39
42
  return () => { listeners.delete(listener); };
@@ -49,8 +52,9 @@ export function createPresence(transport = null) {
49
52
  lifetime.stop();
50
53
  };
51
54
  },
52
- forModel(model, recordId) {
53
- return projection?.forModel(model, recordId) ?? [];
55
+ forModel(model, recordId, options) {
56
+ version.get();
57
+ return projection?.forModel(model, recordId, options) ?? [];
54
58
  },
55
59
  dispose() {
56
60
  for (const read of reads)