@abloatai/humans 0.64.0 → 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 (56) 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/client/createModelOperations.d.ts +3 -3
  6. package/dist/local/client/createModelOperations.js +1 -1
  7. package/dist/local/client/reactiveEngine.js +3 -3
  8. package/dist/local/mutators/defineMutators.d.ts +3 -48
  9. package/dist/local/mutators/defineMutators.js +0 -15
  10. package/dist/local/storeAccess.d.ts +7 -0
  11. package/dist/local/storeAccess.js +6 -0
  12. package/dist/presence/index.d.ts +2 -2
  13. package/dist/presence/index.js +2 -2
  14. package/dist/react/AbloProvider.js +6 -6
  15. package/dist/react/context.d.ts +6 -45
  16. package/dist/react/context.js +8 -20
  17. package/dist/react/createAbloReact.d.ts +9 -22
  18. package/dist/react/createAbloReact.js +5 -7
  19. package/dist/react/internalContext.d.ts +0 -9
  20. package/dist/react/useAblo.d.ts +7 -54
  21. package/dist/react/useAblo.js +6 -28
  22. package/dist/react/useAbloClient.d.ts +5 -0
  23. package/dist/react/useAbloClient.js +9 -0
  24. package/dist/react/useMutationFailure.d.ts +6 -0
  25. package/dist/react/useMutationFailure.js +11 -0
  26. package/dist/react/useMutators.d.ts +3 -3
  27. package/dist/react/useMutators.js +3 -3
  28. package/dist/react/usePresence.d.ts +9 -9
  29. package/dist/react/usePresence.js +3 -7
  30. package/dist/react/useUndoScope.d.ts +2 -2
  31. package/dist/react/useUndoScope.js +4 -4
  32. package/dist/react.d.ts +2 -0
  33. package/dist/react.js +2 -0
  34. package/dist/reactRuntime.d.ts +1 -1
  35. package/dist/reactRuntime.js +1 -1
  36. package/package.json +2 -2
  37. package/src/Ablo.ts +2 -0
  38. package/src/client.ts +1 -1
  39. package/src/index.ts +2 -0
  40. package/src/local/client/createModelOperations.ts +4 -4
  41. package/src/local/client/reactiveEngine.ts +3 -3
  42. package/src/local/mutators/defineMutators.ts +3 -51
  43. package/src/local/storeAccess.ts +10 -0
  44. package/src/presence/index.ts +4 -3
  45. package/src/react/AbloProvider.tsx +8 -8
  46. package/src/react/context.ts +10 -61
  47. package/src/react/createAbloReact.ts +12 -40
  48. package/src/react/internalContext.ts +0 -9
  49. package/src/react/useAblo.ts +19 -89
  50. package/src/react/useAbloClient.ts +14 -0
  51. package/src/react/useMutationFailure.ts +17 -0
  52. package/src/react/useMutators.ts +6 -6
  53. package/src/react/usePresence.ts +18 -18
  54. package/src/react/useUndoScope.ts +6 -6
  55. package/src/react.ts +2 -0
  56. package/src/reactRuntime.ts +2 -2
@@ -6,15 +6,6 @@ import type { SchemaRecord } from '@abloatai/transaction/schema/schema';
6
6
 
7
7
  /** The provider owns only the reference to the application-owned client. */
8
8
  export interface AbloInternalContextValue {
9
- /**
10
- * The typed `Ablo` client for this provider, available before bootstrap resolves. It is held here so `useAblo()` can return it without
11
- * reaching into the store; the client and the store are sibling objects, and
12
- * neither is derived from the other.
13
- *
14
- * It is typed loosely as `Ablo<SchemaRecord>` because generics do not flow
15
- * through React context. `useAblo<R>()` restores the precise type through its
16
- * own generic; the runtime value is the fully typed client.
17
- */
18
9
  engine: Ablo<SchemaRecord> | null;
19
10
  }
20
11
 
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
- import { useCallback, useContext, useEffect, useMemo } from 'react';
4
- import { AbloInternalContext } from './internalContext.js';
3
+ import { useCallback, useEffect, useMemo } from 'react';
4
+ import { useAbloClient } from './useAbloClient.js';
5
5
  import type { AbloClient as Ablo, AbloReads } from '../client.js';
6
6
  import type { ModelClaim } from '@abloatai/transaction/coordination';
7
7
  import {
@@ -9,48 +9,16 @@ import {
9
9
  type ModelOperations,
10
10
  } from '../local/client/createModelOperations.js';
11
11
  import type { SchemaRecord } from '@abloatai/transaction/schema/schema';
12
- import type { ResolveSchema } from '@abloatai/transaction/types/global';
12
+ import type { ResolveModels as DefaultModels } from '@abloatai/transaction/types/global';
13
13
  import { useReactive } from './useReactive.js';
14
14
 
15
- /**
16
- * The app's resolved schema-record type. It reads your `Register` module
17
- * augmentation when you declare one and falls back to the loose
18
- * {@link SchemaRecord} otherwise, so `useAblo()` returns a fully typed client
19
- * without you passing `<(typeof schema)['models']>` at every call site.
20
- */
21
- type DefaultModels = ResolveSchema extends { models: infer M }
22
- ? M extends SchemaRecord
23
- ? M
24
- : SchemaRecord
25
- : SchemaRecord;
26
-
27
15
  const EMPTY_CLAIMS: readonly ModelClaim[] = Object.freeze([]);
28
16
 
29
- /**
30
- * Restore the caller's schema generics on the context-held engine. React
31
- * context erases generics (see `AbloInternalContextValue.engine`), so this is
32
- * the one deliberate rebind point: the runtime value is the fully typed
33
- * client, and `R` is the compile-time view the calling hook declared.
34
- */
35
- function rebindEngine<R extends SchemaRecord>(engine: Ablo<SchemaRecord>): Ablo<R> {
36
- return engine as Ablo<R>;
37
- }
38
-
39
- /**
40
- * The reactive-read view of a client — the identical runtime object, with
41
- * model reads typed as snapshot rows, because everything a selector returns
42
- * is converted through `snapshotValue` before the hook hands it back. Same
43
- * generic in and out, so this compiles with no schema rebinding.
44
- */
17
+ // Selector results are detached snapshots with no model methods or relations.
45
18
  function reactiveReads<R extends SchemaRecord>(engine: Ablo<R>): AbloReads<R> {
46
19
  return engine as AbloReads<R>;
47
20
  }
48
21
 
49
- // Selectors receive the reactive-read client: model reads are typed as
50
- // snapshot rows (data fields + computeds, no relation accessors), which is the
51
- // shape the hook actually returns after `toReactiveSnapshot()`. This makes the
52
- // selector's inferred result type honest — `row.layers` fails to compile here
53
- // instead of reading `undefined` at runtime.
54
22
  export type ModelClientSelector<R extends SchemaRecord, T, C> =
55
23
  (ablo: AbloReads<R>) => ModelOperations<T, C>;
56
24
  export type AbloSelector<R extends SchemaRecord, T> = (ablo: AbloReads<R>) => T;
@@ -74,46 +42,7 @@ function readModelResult<R extends SchemaRecord, T, C>(
74
42
  return { data, claims, claimed: claims.length > 0 };
75
43
  }
76
44
 
77
- /**
78
- * Reads Ablo from inside an `<AbloProvider>` subtree. Called with no arguments
79
- * it returns the typed client for use in callbacks and effects; called with a
80
- * selector it subscribes the component to a reactive read — such as one
81
- * `ablo.<model>` row — and re-renders when that read changes.
82
- *
83
- * You can call it with no type arguments once you declare the `Register` module
84
- * augmentation (`declare module '@abloatai/ablo' { interface Register {
85
- * Schema: typeof schema } }`); the default type then resolves through your
86
- * schema's models, so call sites stay clean:
87
- *
88
- * **Prefer the binding.** `createAbloReact(schema)` captures the schema once
89
- * in your app's binding file and returns a `useAblo` that needs none of the
90
- * typing arrangements below — no type argument, no `Register` declaration
91
- * (see `react.md`). Passing an explicit schema type argument to THIS hook is
92
- * deprecated in favor of that binding; it keeps working for shared packages
93
- * that cannot bind a concrete schema.
94
- *
95
- * ```ts
96
- * // With the Register augmentation (recommended):
97
- * const ablo = useAblo();
98
- * if (!ablo) return <Loading />;
99
- * const doc = await ablo.records.get({ id }); // observational async server read
100
- *
101
- * // Reactive selector (a synchronous local snapshot). The selector's reads
102
- * // are typed as snapshot rows — data fields + computeds, no relation
103
- * // accessors — matching what the hook actually returns:
104
- * const doc = useAblo((ablo) => ablo.records.local.get(id)) ?? serverDoc;
105
- * const { claimed } = useAblo((ablo) => ablo.records, id);
106
- *
107
- * // Without the augmentation, pass the schema as a type argument:
108
- * const ablo = useAblo<(typeof schema)['models']>();
109
- * ```
110
- *
111
- * The client and its status are available during provider startup. Select
112
- * `ablo.status` to display connection state; await `ablo.ready()` before
113
- * operations that require an initialized client. Without a provider, the
114
- * no-argument form returns `null` and selectors return `undefined`.
115
- */
116
- export function useAblo<R extends SchemaRecord = DefaultModels>(): Ablo<R> | null;
45
+ /** Select a reactive snapshot or read a row with its current claims. */
117
46
  export function useAblo<
118
47
  R extends SchemaRecord = DefaultModels,
119
48
  T = unknown,
@@ -139,10 +68,10 @@ export function useAblo<
139
68
  T = Record<string, unknown>,
140
69
  C = unknown,
141
70
  >(
142
- modelOrSelect?: ModelOperations<T, C> | ModelClientSelector<R, T, C> | AbloSelector<R, T>,
71
+ modelOrSelect: ModelOperations<T, C> | ModelClientSelector<R, T, C> | AbloSelector<R, T>,
143
72
  id?: string,
144
73
  options?: useAblo.Options<T>,
145
- ): Ablo<R> | null | useAblo.Result<T> | T | undefined {
74
+ ): useAblo.Result<T> | T | undefined {
146
75
  const engine = useAbloClient<R>();
147
76
  const initial = options?.initial;
148
77
  const isSelectorOnly = typeof modelOrSelect === 'function' && id === undefined;
@@ -161,11 +90,10 @@ export function useAblo<
161
90
  // These dependencies define when the seed belongs to a different row.
162
91
  // eslint-disable-next-line react-hooks/exhaustive-deps
163
92
  const seed = useMemo(() => ({ received: false }), [engine, modelClient, id]);
164
- const reading = modelOrSelect !== undefined;
165
93
  const subscribe = useCallback((notify: () => void) => {
166
- if (!engine || !reading) return () => undefined;
94
+ if (!engine) return () => undefined;
167
95
  return engine.claims.onChange(notify);
168
- }, [engine, reading]);
96
+ }, [engine]);
169
97
  const value = useReactive<T | useAblo.Result<T> | undefined>(() => {
170
98
  if (isSelectorOnly && typeof modelOrSelect === 'function') {
171
99
  return engine ? modelOrSelect(reactiveReads<R>(engine)) as T : undefined;
@@ -186,19 +114,21 @@ export function useAblo<
186
114
  if (id !== undefined && modelClient?.local.get(id) !== undefined) seed.received = true;
187
115
  }, [seed, modelClient, id, value]);
188
116
 
189
- if (isSelectorOnly || modelOrSelect) return value;
190
- return engine;
191
- }
192
-
193
- /** @internal Resolve the nearest provider's client through one schema rebind. */
194
- export function useAbloClient<R extends SchemaRecord>(): Ablo<R> | null {
195
- const ctx = useContext(AbloInternalContext);
196
- return ctx?.engine ? rebindEngine<R>(ctx.engine) : null;
117
+ return value;
197
118
  }
198
119
 
199
120
  /** Type annotations belong to the operation; most callers rely on inference. */
200
121
  // eslint-disable-next-line @typescript-eslint/no-namespace
201
122
  export namespace useAblo {
123
+ export interface Bound<S extends SchemaRecord> {
124
+ <T>(select: AbloSelector<S, T>): T | undefined;
125
+ <T, C>(
126
+ model: ModelOperations<T, C> | ModelClientSelector<S, T, C>,
127
+ id: string,
128
+ options?: Options<T>,
129
+ ): Result<T>;
130
+ }
131
+
202
132
  export interface Options<T> {
203
133
  /**
204
134
  * An initial row, usually from a server component or a route loader. The hook
@@ -0,0 +1,14 @@
1
+ 'use client';
2
+
3
+ import { useContext } from 'react';
4
+ import type { AbloClient } from '../client.js';
5
+ import type { SchemaRecord } from '@abloatai/transaction/schema/schema';
6
+ import type { ResolveModels } from '@abloatai/transaction/types/global';
7
+ import { AbloInternalContext } from './internalContext.js';
8
+
9
+ /** Writable client for event handlers. Available before ready(); null without a provider. */
10
+ export function useAbloClient<S extends SchemaRecord = ResolveModels>(): AbloClient<S> | null {
11
+ const client = useContext(AbloInternalContext)?.engine;
12
+ // React context erases the schema; the application binding restores it.
13
+ return client ? client as AbloClient<S> : null;
14
+ }
@@ -0,0 +1,17 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useEffectEvent } from 'react';
4
+ import type { AbloClient } from '../client.js';
5
+ import type { SchemaRecord } from '@abloatai/transaction/schema/schema';
6
+ import { useAbloClient } from './useAbloClient.js';
7
+
8
+ /** Subscribe for this component's lifetime using the latest committed listener.
9
+ * Replacing the provider client moves the subscription; unmount removes it.
10
+ */
11
+ export function useMutationFailure(
12
+ listener: Parameters<AbloClient<SchemaRecord>['onMutationFailure']>[0],
13
+ ): void {
14
+ const client = useAbloClient();
15
+ const onFailure = useEffectEvent(listener);
16
+ useEffect(() => client?.onMutationFailure(onFailure), [client]);
17
+ }
@@ -9,8 +9,8 @@ import type {
9
9
  import { createTransaction } from '../local/mutators/Transaction.js';
10
10
  import { createRecordingMutation } from '../local/mutators/RecordingMutation.js';
11
11
  import type { UndoScope } from '../local/mutators/UndoManager.js';
12
- import type { ResolveSchema } from '@abloatai/transaction/types/global';
13
- import { useSyncContext } from './context.js';
12
+ import type { ResolveSchema, RequireRegisteredSchema } from '@abloatai/transaction/types/global';
13
+ import { useAbloStoreContext } from './context.js';
14
14
  import { AbloValidationError } from '@abloatai/transaction/errors';
15
15
  import { getContext } from '../local/context.js';
16
16
 
@@ -58,12 +58,12 @@ export function useMutators<S extends Schema, M extends MutatorDefs<S>>(
58
58
  ): useMutators.Result<M>;
59
59
 
60
60
  /** Mutator invokers via the `Register` module augmentation. Schema comes
61
- * from the `SyncProvider`'s context; the mutator tree is typed against
61
+ * from the `AbloProvider` store context; the mutator tree is typed against
62
62
  * `ResolveSchema` at the call site. */
63
63
  export function useMutators<
64
64
  M extends ResolveSchema extends Schema ? MutatorDefs<ResolveSchema> : MutatorDefs<Schema>,
65
65
  >(
66
- mutators: M,
66
+ mutators: RequireRegisteredSchema<M>,
67
67
  options?: useMutators.Options<ResolveSchema extends Schema ? ResolveSchema : Schema>,
68
68
  ): useMutators.Result<M>;
69
69
 
@@ -72,7 +72,7 @@ export function useMutators(
72
72
  mutatorsOrOptions?: MutatorDefs<Schema> | useMutators.Options<Schema>,
73
73
  maybeOptions?: useMutators.Options<Schema>,
74
74
  ): useMutators.Result<MutatorDefs<Schema>> {
75
- const { store, organizationId, schema: ctxSchema } = useSyncContext();
75
+ const { store, organizationId, schema: ctxSchema } = useAbloStoreContext();
76
76
 
77
77
  // Disambiguate: explicit-schema path has the schema object in first slot;
78
78
  // the global-resolved path has the mutator tree there. A schema object
@@ -92,7 +92,7 @@ export function useMutators(
92
92
  throw new AbloValidationError(
93
93
  'useMutators: no schema available. Pass the schema as the first arg, ' +
94
94
  'or build the <AbloProvider> above with `Ablo({ schema })` so the ' +
95
- 'zero-arg overload can read it from context.',
95
+ 'schema-free overload can read it from context.',
96
96
  { code: 'mutators_schema_missing' },
97
97
  );
98
98
  }
@@ -9,16 +9,10 @@ import {
9
9
  } from '../local/client/createModelOperations.js';
10
10
  import type { AbloClient as Ablo } from '../client.js';
11
11
  import type { SchemaRecord } from '@abloatai/transaction/schema/schema';
12
- import type { ResolveSchema } from '@abloatai/transaction/types/global';
13
- import { useAbloClient } from './useAblo.js';
12
+ import type { ResolveModels as DefaultModels } from '@abloatai/transaction/types/global';
13
+ import { useAbloClient } from './useAbloClient.js';
14
14
  import { useReactive } from './useReactive.js';
15
15
 
16
- type DefaultModels = ResolveSchema extends { models: infer M }
17
- ? M extends SchemaRecord
18
- ? M
19
- : SchemaRecord
20
- : SchemaRecord;
21
-
22
16
  export type PresenceModelSelector<R extends SchemaRecord, T, C> =
23
17
  (ablo: Ablo<R>) => ModelOperations<T, C>;
24
18
 
@@ -30,6 +24,7 @@ export type PresenceModelSelector<R extends SchemaRecord, T, C> =
30
24
  export function usePresence<T, C>(
31
25
  modelClient: ModelOperations<T, C>,
32
26
  recordId: string,
27
+ options?: usePresence.Options,
33
28
  ): readonly PresenceSession[];
34
29
  export function usePresence<
35
30
  R extends SchemaRecord = DefaultModels,
@@ -38,6 +33,7 @@ export function usePresence<
38
33
  >(
39
34
  select: PresenceModelSelector<R, T, C>,
40
35
  recordId: string,
36
+ options?: usePresence.Options,
41
37
  ): readonly PresenceSession[];
42
38
  export function usePresence<
43
39
  R extends SchemaRecord = DefaultModels,
@@ -46,17 +42,9 @@ export function usePresence<
46
42
  >(
47
43
  modelOrSelect: ModelOperations<T, C> | PresenceModelSelector<R, T, C>,
48
44
  recordId: string,
45
+ options?: usePresence.Options,
49
46
  ): readonly PresenceSession[] {
50
47
  const engine = useAbloClient<R>();
51
- return usePresenceImpl(engine, modelOrSelect, recordId);
52
- }
53
-
54
- /** @internal Shared by the global hook and schema-bound React factory. */
55
- export function usePresenceImpl<R extends SchemaRecord, T, C>(
56
- engine: Ablo<R> | null,
57
- modelOrSelect: ModelOperations<T, C> | PresenceModelSelector<R, T, C>,
58
- recordId: string,
59
- ): readonly PresenceSession[] {
60
48
  if (recordId.length === 0) {
61
49
  throw new AbloValidationError(
62
50
  'usePresence requires a non-empty record id.',
@@ -77,7 +65,19 @@ export function usePresenceImpl<R extends SchemaRecord, T, C>(
77
65
  }
78
66
 
79
67
  const subscribe = useCallback((notify: () => void) => presence?.subscribe(notify) ?? (() => undefined), [presence]);
80
- const sessions = useReactive(() => presence?.get(recordId) ?? [], { subscribe });
68
+ const sessions = useReactive(() => presence?.get(recordId, options) ?? [], { subscribe });
81
69
  useEffect(() => presence?.read(recordId), [presence, recordId]);
82
70
  return sessions;
83
71
  }
72
+
73
+ export namespace usePresence {
74
+ export interface Bound<S extends SchemaRecord> {
75
+ <T, C>(
76
+ model: ModelOperations<T, C> | PresenceModelSelector<S, T, C>,
77
+ recordId: string,
78
+ options?: Options,
79
+ ): readonly PresenceSession[];
80
+ }
81
+
82
+ export type Options = import('@abloatai/transaction/presence').PresenceQueryOptions;
83
+ }
@@ -7,8 +7,8 @@ import {
7
7
  type UndoScope,
8
8
  type UndoScopeOptions,
9
9
  } from '../local/mutators/UndoManager.js';
10
- import type { ResolveSchema } from '@abloatai/transaction/types/global';
11
- import { useSyncContext } from './context.js';
10
+ import type { ResolveSchema, RequireRegisteredSchema } from '@abloatai/transaction/types/global';
11
+ import { useAbloStoreContext } from './context.js';
12
12
  import { AbloValidationError } from '@abloatai/transaction/errors';
13
13
 
14
14
  /**
@@ -30,7 +30,7 @@ import { AbloValidationError } from '@abloatai/transaction/errors';
30
30
  */
31
31
 
32
32
  // Module-level weak registry: `SyncStoreContract` → `UndoManager`.
33
- // A single app wiring through one SyncProvider shares one manager across
33
+ // A single AbloProvider shares one manager across
34
34
  // every useUndoScope call, so scopes with the same name are identity-equal.
35
35
  // The hook implementation already operates on the runtime-wide `Schema` type;
36
36
  // its overloads restore the caller's precise schema type at the public boundary,
@@ -59,7 +59,7 @@ export function useUndoScope<S extends Schema>(
59
59
 
60
60
  /** Per-surface undo/redo via the `Register` module augmentation. */
61
61
  export function useUndoScope(
62
- name: string,
62
+ name: RequireRegisteredSchema<string>,
63
63
  options?: UndoScopeOptions,
64
64
  ): useUndoScope.Result<ResolveSchema extends Schema ? ResolveSchema : Schema>;
65
65
 
@@ -68,7 +68,7 @@ export function useUndoScope(
68
68
  nameOrOptions?: string | UndoScopeOptions,
69
69
  maybeOptions?: UndoScopeOptions,
70
70
  ): useUndoScope.Result<Schema> {
71
- const { store, organizationId, schema: ctxSchema } = useSyncContext();
71
+ const { store, organizationId, schema: ctxSchema } = useAbloStoreContext();
72
72
 
73
73
  const isExplicit = typeof schemaOrName !== 'string';
74
74
  const schema = isExplicit ? (schemaOrName) : ctxSchema;
@@ -85,7 +85,7 @@ export function useUndoScope(
85
85
  }
86
86
 
87
87
  const scope = useMemo(() => {
88
- // Store is the identity for the manager — one per SyncProvider.
88
+ // Store is the identity for the manager — one per AbloProvider.
89
89
  const manager = getManager(store, () => new UndoManager(schema, store, organizationId));
90
90
  return manager.getScope(name, options);
91
91
  // eslint-disable-next-line react-hooks/exhaustive-deps
package/src/react.ts CHANGED
@@ -2,6 +2,8 @@
2
2
  export { AbloProvider } from './react/AbloProvider.js';
3
3
  export { createAbloReact } from './react/createAbloReact.js';
4
4
  export { useAblo } from './react/useAblo.js';
5
+ export { useAbloClient } from './react/useAbloClient.js';
6
+ export { useMutationFailure } from './react/useMutationFailure.js';
5
7
  export { usePresence } from './react/usePresence.js';
6
8
  export { useMutators } from './react/useMutators.js';
7
9
  export { useUndoScope } from './react/useUndoScope.js';
@@ -3,8 +3,8 @@ export {
3
3
  type AbloInternalContextValue,
4
4
  } from './react/internalContext.js';
5
5
  export {
6
- useSyncContext,
7
- type SyncReactContext,
6
+ useAbloStoreContext,
7
+ type AbloStoreContextValue,
8
8
  } from './react/context.js';
9
9
  export type { SyncStoreContract } from './local/storeContract.js';
10
10
  export type { QueuedMutation } from './local/transactions/mutations/MutationQueue.js';