@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
@@ -0,0 +1,20 @@
1
+ import type { SyncStoreContract } from '../storeContract.js';
2
+
3
+ /** The client lifecycle, shared by every framework binding. */
4
+ export type ClientStatus =
5
+ | { readonly name: 'initial' }
6
+ | { readonly name: 'connecting'; readonly progress: number }
7
+ | { readonly name: 'connected'; readonly hasUnsyncedChanges: boolean }
8
+ | { readonly name: 'reconnecting'; readonly reason?: string }
9
+ | { readonly name: 'disconnected'; readonly reason?: string }
10
+ | { readonly name: 'needs-auth' };
11
+
12
+ export function readStatus(store: SyncStoreContract): ClientStatus {
13
+ const { state, progress, pendingChanges, isSessionError, error } = store.syncStatus;
14
+ if (isSessionError) return { name: 'needs-auth' };
15
+ if (state === 'reconnecting') return { name: 'reconnecting', reason: error?.message };
16
+ if (state === 'offline') return { name: 'disconnected', reason: 'offline' };
17
+ if (state === 'error') return { name: 'disconnected', reason: error?.message };
18
+ if (store.isReady) return { name: 'connected', hasUnsyncedChanges: pendingChanges > 0 };
19
+ return { name: 'connecting', progress };
20
+ }
@@ -301,7 +301,7 @@ export function startStoreLifecycle<S extends SchemaRecord>(
301
301
  //
302
302
  // The store.initialize() generator updates store.syncStatus as it
303
303
  // progresses (syncing → idle on success, error on failure), so the
304
- // consumer's `sync.syncStatus` observable reflects real-time state.
304
+ // consumer's `ablo.status` projection reflects real-time state.
305
305
  // Resolve bootstrap mode: explicit option wins; otherwise
306
306
  // agents default to 'none' (transactional participant — see
307
307
  // option doc) and everyone else defaults to 'full'.
@@ -386,7 +386,7 @@ export function startStoreLifecycle<S extends SchemaRecord>(
386
386
  if (!validationError && internalOptions.autoStart) {
387
387
  void ready().catch(() => {
388
388
  // Error is captured in store.syncStatus; consumers should check
389
- // `sync.syncStatus.state === 'error'` to detect failures.
389
+ // `ablo.status.name === 'disconnected'` to detect failures.
390
390
  });
391
391
  }
392
392
 
@@ -1,76 +1,28 @@
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
+ /** Define typed named operations. Pass the schema explicitly across package boundaries. */
15
2
  import type { Schema } from '@abloatai/transaction/schema/schema';
16
3
  import type { Transaction } from './Transaction.js';
17
- import type { ResolveSchema } from '@abloatai/transaction/types/global';
4
+ import type { ResolveSchema, RequireRegisteredSchema } from '@abloatai/transaction/types/global';
18
5
 
19
- /**
20
- * `ResolveSchema` narrowed to satisfy the `Schema` bound — mirrors
21
- * {@link Ablo.RegisteredSchema}. When nothing is registered, `ResolveSchema`
22
- * is a loose `{ models }` shape that doesn't extend `Schema`, so we fall back
23
- * to `Schema` to keep the mutator tree typed rather than collapsing.
24
- */
25
6
  type RegisteredSchema = ResolveSchema extends Schema ? ResolveSchema : Schema;
26
7
 
27
- /**
28
- * The signature of a single custom mutator. The engine supplies `tx`; you control
29
- * `args`, in whatever shape you like, and the resolved return value. `TArgs` and
30
- * `TResult` are bounded by `unknown` rather than `any`, so a mixed tree of
31
- * mutators can be typed together without falling back to `any`.
32
- */
33
8
  export type MutatorFn<S extends Schema, TArgs, TResult = void> = (
34
9
  options: { tx: Transaction<S>; args: TArgs },
35
10
  ) => Promise<TResult>;
36
11
 
37
- /**
38
- * The shape {@link defineMutators} accepts: an optional record per model key
39
- * whose values are named mutator functions. The `unknown` bounds keep the public
40
- * boundary type-safe without `any`; when you write your mutators inline,
41
- * TypeScript still infers the concrete `args` and result of each function, so the
42
- * `unknown` here is only a ceiling, not what you end up working with.
43
- */
44
12
  export type MutatorDefs<S extends Schema> = {
45
13
  [K in keyof S['models']]?: Record<string, MutatorFn<S, never, unknown>>;
46
14
  };
47
15
 
48
- /**
49
- * Returns the mutators object unchanged while constraining its shape against the
50
- * schema. The `S` generic pins the model keys, and the `M` generic is inferred as
51
- * a `const`, so each mutator's literal signature survives. There is no runtime
52
- * work here; the function exists purely as a place for type inference to anchor.
53
- */
54
16
  export function defineMutators<
55
17
  S extends Schema,
56
18
  const M extends MutatorDefs<S>,
57
19
  >(_schema: S, mutators: M): M;
58
- /**
59
- * Register-anchored overload: omit the schema value and the tree is typed
60
- * against `ResolveSchema` (this app's registered schema). Shared product code
61
- * used across apps that bind different schemas should use this form — it moves
62
- * with each app's `Register` instead of pinning one concrete schema, so the
63
- * mutator tree stays assignable at every consumer (which reads the same
64
- * `Register`). See docs/plans/per-product-schema-projections.md.
65
- */
66
20
  export function defineMutators<const M extends MutatorDefs<RegisteredSchema>>(
67
- mutators: M,
21
+ mutators: RequireRegisteredSchema<M>,
68
22
  ): M;
69
23
  export function defineMutators(
70
24
  schemaOrMutators: Schema | MutatorDefs<Schema>,
71
25
  maybeMutators?: MutatorDefs<Schema>,
72
26
  ): MutatorDefs<Schema> {
73
- // The schema argument is a type anchor only — never read at runtime. With one
74
- // argument the mutator tree is in the first slot; with two it's in the second.
75
27
  return (maybeMutators ?? schemaOrMutators) as MutatorDefs<Schema>;
76
28
  }
@@ -0,0 +1,10 @@
1
+ import type { AbloClient } from '../client.js';
2
+ import type { SchemaRecord } from '@abloatai/transaction/schema/schema';
3
+ import type { SyncStoreContract } from './storeContract.js';
4
+
5
+ /** Access the supported local store contract for custom framework adapters,
6
+ * demand loading, scope management and custom undo infrastructure.
7
+ */
8
+ export function getAbloStore<S extends SchemaRecord>(client: AbloClient<S>): SyncStoreContract {
9
+ return client._store;
10
+ }
@@ -19,8 +19,7 @@ import type { GroupScope } from './sync/scopeGroups.js';
19
19
  /**
20
20
  * A snapshot of the client's synchronization state, shaped for binding to UI.
21
21
  * {@link SyncStoreContract.syncStatus} exposes a reactive instance of this, and
22
- * the `useSyncStatus()` hook reads its fields to render connection and progress
23
- * indicators.
22
+ * the client projects it into `ablo.status` for connection indicators.
24
23
  */
25
24
  export interface SyncStatus {
26
25
  state: 'idle' | 'syncing' | 'error' | 'offline' | 'reconnecting';
@@ -115,7 +114,7 @@ export interface SyncStoreContract {
115
114
  * are backed by observable computeds, so reading them inside a reactive
116
115
  * context — an observer component or a reaction — re-runs that context when
117
116
  * the state changes. Code that prefers not to work with the reactivity system
118
- * directly can read the same values through the `useSyncStatus()` hook.
117
+ * directly can read the same values through `useAblo(ablo => ablo.status)`.
119
118
  */
120
119
  readonly isReady: boolean;
121
120
  readonly isSyncing: boolean;
@@ -137,7 +136,7 @@ export interface SyncStoreContract {
137
136
  pinScope?(scope: GroupScope): Promise<void>;
138
137
  unpinScope?(scope: GroupScope): Promise<void>;
139
138
  /**
140
- * The full reactive {@link SyncStatus} record. The `useSyncStatus()` hook
139
+ * The full reactive {@link SyncStatus} record. The client status projection
141
140
  * reads its fields — `state`, `progress`, `pendingChanges`, `isSessionError`,
142
141
  * and `error` — to present the current sync state. It is part of the contract
143
142
  * so hooks and test doubles can read or set it directly.
@@ -1,8 +1,10 @@
1
+ import { observable, runInAction } from 'mobx';
1
2
  import {
2
3
  createPresenceProjection,
3
4
  type PresenceProjection,
4
5
  type PresenceProjectionEvents,
5
6
  type PresenceView,
7
+ type PresenceQueryOptions,
6
8
  } from '@abloatai/transaction/presence';
7
9
  import type { PresenceTarget } from '@abloatai/transaction/presence';
8
10
  import {
@@ -15,7 +17,7 @@ type PresenceTransport = PresenceProjectionEvents & ReadActivityTransport;
15
17
 
16
18
  /** Reactive-client presence backed by the client's existing live connection. */
17
19
  export interface ReactivePresence extends PresenceView {
18
- forModel(model: string, recordId?: string): ReturnType<PresenceProjection['forModel']>;
20
+ forModel(model: string, recordId?: string, options?: PresenceQueryOptions): ReturnType<PresenceProjection['forModel']>;
19
21
  onChange(listener: () => void): () => void;
20
22
  }
21
23
 
@@ -44,11 +46,13 @@ export function createPresence(
44
46
  ): AttachablePresence {
45
47
  let projection: PresenceProjection | null = null;
46
48
  let attachedTransport: PresenceTransport | null = null;
49
+ const version = observable.box(0);
47
50
  const listeners = new Set<() => void>();
48
51
  const reads = new Set<ReadActivityLifetime>();
49
52
  let unsubscribe: (() => void) | null = null;
50
53
 
51
54
  const notify = (): void => {
55
+ runInAction(() => { version.set(version.get() + 1); });
52
56
  for (const listener of listeners) listener();
53
57
  };
54
58
 
@@ -63,8 +67,8 @@ export function createPresence(
63
67
  if (transport !== null) attach(transport);
64
68
 
65
69
  return {
66
- get active() { return projection?.active ?? []; },
67
- get others() { return projection?.others ?? []; },
70
+ get active() { version.get(); return projection?.active ?? []; },
71
+ get others() { version.get(); return projection?.others ?? []; },
68
72
  onChange(listener) {
69
73
  listeners.add(listener);
70
74
  return () => { listeners.delete(listener); };
@@ -84,8 +88,9 @@ export function createPresence(
84
88
  lifetime.stop();
85
89
  };
86
90
  },
87
- forModel(model, recordId) {
88
- return projection?.forModel(model, recordId) ?? [];
91
+ forModel(model, recordId, options) {
92
+ version.get();
93
+ return projection?.forModel(model, recordId, options) ?? [];
89
94
  },
90
95
  dispose() {
91
96
  for (const read of reads) read.dispose();