@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
@@ -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
 
@@ -26,7 +26,7 @@ import { getContext } from '../local/context.js';
26
26
  * If a mutator throws, the error propagates to the caller and any writes it
27
27
  * already dispatched stay in place — there is no automatic rollback. Wrap the
28
28
  * call in your own try/catch and issue compensating writes when you need to
29
- * undo a partial change, or pass an `undoScope` (see {@link UseMutatorsOptions})
29
+ * undo a partial change, or pass an `undoScope` (see {@link useMutators.Options})
30
30
  * to record inverses for undo and redo.
31
31
  */
32
32
 
@@ -50,45 +50,29 @@ export type InvokerFor<F> = F extends (options: infer O) => Promise<infer R>
50
50
  * The hook's return shape: same tree as the input `MutatorDefs`, every leaf
51
51
  * rewritten to its invoker form.
52
52
  */
53
- export type MutatorInvokers<M> = {
54
- [K in keyof M]: {
55
- [N in keyof M[K]]: InvokerFor<M[K][N]>;
56
- };
57
- };
58
-
59
- /**
60
- * Options passed to `useMutators`. When `undoScope` is set, every mutator
61
- * invocation is wrapped in a `RecordingMutation` and its inverses are
62
- * pushed to the scope as one undo entry.
63
- */
64
- export interface UseMutatorsOptions<S extends Schema> {
65
- /** Target undo scope for recording inverses. Omit to disable recording. */
66
- undoScope?: UndoScope<S>;
67
- }
68
-
69
53
  /** Mutator invokers (explicit schema arg). */
70
54
  export function useMutators<S extends Schema, M extends MutatorDefs<S>>(
71
55
  schema: S,
72
56
  mutators: M,
73
- options?: UseMutatorsOptions<S>,
74
- ): MutatorInvokers<M>;
57
+ options?: useMutators.Options<S>,
58
+ ): useMutators.Result<M>;
75
59
 
76
60
  /** Mutator invokers via the `Register` module augmentation. Schema comes
77
- * from the `SyncProvider`'s context; the mutator tree is typed against
61
+ * from the `AbloProvider` store context; the mutator tree is typed against
78
62
  * `ResolveSchema` at the call site. */
79
63
  export function useMutators<
80
64
  M extends ResolveSchema extends Schema ? MutatorDefs<ResolveSchema> : MutatorDefs<Schema>,
81
65
  >(
82
- mutators: M,
83
- options?: UseMutatorsOptions<ResolveSchema extends Schema ? ResolveSchema : Schema>,
84
- ): MutatorInvokers<M>;
66
+ mutators: RequireRegisteredSchema<M>,
67
+ options?: useMutators.Options<ResolveSchema extends Schema ? ResolveSchema : Schema>,
68
+ ): useMutators.Result<M>;
85
69
 
86
70
  export function useMutators(
87
71
  schemaOrMutators: Schema | MutatorDefs<Schema>,
88
- mutatorsOrOptions?: MutatorDefs<Schema> | UseMutatorsOptions<Schema>,
89
- maybeOptions?: UseMutatorsOptions<Schema>,
90
- ): MutatorInvokers<MutatorDefs<Schema>> {
91
- const { store, organizationId, schema: ctxSchema } = useSyncContext();
72
+ mutatorsOrOptions?: MutatorDefs<Schema> | useMutators.Options<Schema>,
73
+ maybeOptions?: useMutators.Options<Schema>,
74
+ ): useMutators.Result<MutatorDefs<Schema>> {
75
+ const { store, organizationId, schema: ctxSchema } = useAbloStoreContext();
92
76
 
93
77
  // Disambiguate: explicit-schema path has the schema object in first slot;
94
78
  // the global-resolved path has the mutator tree there. A schema object
@@ -101,21 +85,21 @@ export function useMutators(
101
85
  const schema = isExplicit ? (schemaOrMutators as Schema) : ctxSchema;
102
86
  const mutators = (isExplicit ? mutatorsOrOptions : schemaOrMutators) as MutatorDefs<Schema>;
103
87
  const options = (isExplicit ? maybeOptions : mutatorsOrOptions) as
104
- | UseMutatorsOptions<Schema>
88
+ | useMutators.Options<Schema>
105
89
  | undefined;
106
90
 
107
91
  if (!schema) {
108
92
  throw new AbloValidationError(
109
93
  'useMutators: no schema available. Pass the schema as the first arg, ' +
110
94
  'or build the <AbloProvider> above with `Ablo({ schema })` so the ' +
111
- 'zero-arg overload can read it from context.',
95
+ 'schema-free overload can read it from context.',
112
96
  { code: 'mutators_schema_missing' },
113
97
  );
114
98
  }
115
99
 
116
100
  const { undoScope } = options ?? {};
117
101
 
118
- return useMemo<MutatorInvokers<MutatorDefs<Schema>>>(() => {
102
+ return useMemo<useMutators.Result<MutatorDefs<Schema>>>(() => {
119
103
  const out: Record<string, Record<string, (args: unknown) => Promise<unknown>>> = {};
120
104
 
121
105
  for (const modelKey of Object.keys(mutators)) {
@@ -182,3 +166,23 @@ export function useMutators(
182
166
  return out;
183
167
  }, [schema, mutators, store, organizationId, undoScope]);
184
168
  }
169
+
170
+ /** Optional annotations for custom mutation bindings. */
171
+ // eslint-disable-next-line @typescript-eslint/no-namespace
172
+ export namespace useMutators {
173
+ export type Result<M> = {
174
+ [K in keyof M]: {
175
+ [N in keyof M[K]]: InvokerFor<M[K][N]>;
176
+ };
177
+ };
178
+
179
+ /**
180
+ * Options passed to `useMutators`. When `undoScope` is set, every mutator
181
+ * invocation is wrapped in a `RecordingMutation` and its inverses are
182
+ * pushed to the scope as one undo entry.
183
+ */
184
+ export interface Options<S extends Schema> {
185
+ /** Target undo scope for recording inverses. Omit to disable recording. */
186
+ undoScope?: UndoScope<S>;
187
+ }
188
+ }
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useEffect, useState } from 'react';
3
+ import { useCallback, useEffect } from 'react';
4
4
  import type { PresenceSession } from '@abloatai/transaction/presence';
5
5
  import { AbloValidationError } from '@abloatai/transaction/errors';
6
6
  import {
@@ -9,14 +9,9 @@ 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 { useAbloClientImpl } from './useAblo.js';
14
-
15
- type DefaultModels = ResolveSchema extends { models: infer M }
16
- ? M extends SchemaRecord
17
- ? M
18
- : SchemaRecord
19
- : SchemaRecord;
12
+ import type { ResolveModels as DefaultModels } from '@abloatai/transaction/types/global';
13
+ import { useAbloClient } from './useAbloClient.js';
14
+ import { useReactive } from './useReactive.js';
20
15
 
21
16
  export type PresenceModelSelector<R extends SchemaRecord, T, C> =
22
17
  (ablo: Ablo<R>) => ModelOperations<T, C>;
@@ -29,6 +24,7 @@ export type PresenceModelSelector<R extends SchemaRecord, T, C> =
29
24
  export function usePresence<T, C>(
30
25
  modelClient: ModelOperations<T, C>,
31
26
  recordId: string,
27
+ options?: usePresence.Options,
32
28
  ): readonly PresenceSession[];
33
29
  export function usePresence<
34
30
  R extends SchemaRecord = DefaultModels,
@@ -37,6 +33,7 @@ export function usePresence<
37
33
  >(
38
34
  select: PresenceModelSelector<R, T, C>,
39
35
  recordId: string,
36
+ options?: usePresence.Options,
40
37
  ): readonly PresenceSession[];
41
38
  export function usePresence<
42
39
  R extends SchemaRecord = DefaultModels,
@@ -45,17 +42,9 @@ export function usePresence<
45
42
  >(
46
43
  modelOrSelect: ModelOperations<T, C> | PresenceModelSelector<R, T, C>,
47
44
  recordId: string,
45
+ options?: usePresence.Options,
48
46
  ): readonly PresenceSession[] {
49
- const engine = useAbloClientImpl<R>(null);
50
- return usePresenceImpl(engine, modelOrSelect, recordId);
51
- }
52
-
53
- /** @internal Shared by the global hook and schema-bound React factory. */
54
- export function usePresenceImpl<R extends SchemaRecord, T, C>(
55
- engine: Ablo<R> | null,
56
- modelOrSelect: ModelOperations<T, C> | PresenceModelSelector<R, T, C>,
57
- recordId: string,
58
- ): readonly PresenceSession[] {
47
+ const engine = useAbloClient<R>();
59
48
  if (recordId.length === 0) {
60
49
  throw new AbloValidationError(
61
50
  'usePresence requires a non-empty record id.',
@@ -75,14 +64,20 @@ export function usePresenceImpl<R extends SchemaRecord, T, C>(
75
64
  );
76
65
  }
77
66
 
78
- const [, render] = useState(0);
79
-
80
- useEffect(
81
- () => presence?.subscribe(() => { render((version) => version + 1); }),
82
- [presence],
83
- );
84
-
67
+ const subscribe = useCallback((notify: () => void) => presence?.subscribe(notify) ?? (() => undefined), [presence]);
68
+ const sessions = useReactive(() => presence?.get(recordId, options) ?? [], { subscribe });
85
69
  useEffect(() => presence?.read(recordId), [presence, recordId]);
70
+ return sessions;
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
+ }
86
81
 
87
- return presence?.get(recordId) ?? [];
82
+ export type Options = import('@abloatai/transaction/presence').PresenceQueryOptions;
88
83
  }
@@ -0,0 +1,56 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';
4
+ import { reaction } from 'mobx';
5
+ import { equalSnapshots, snapshotValue } from './snapshot.js';
6
+
7
+ interface ObservationOptions<T> {
8
+ equals?: (a: T, b: T) => boolean;
9
+ subscribe?: (listener: () => void) => () => void;
10
+ serverSnapshot?: () => T;
11
+ }
12
+
13
+ /**
14
+ * Each render's computation has its own observation. A suspended render cannot
15
+ * replace the computation used by the committed tree's subscription. Reactions
16
+ * start only on subscription, so abandoned renders retain no store observers.
17
+ */
18
+ export function useReactive<T>(compute: () => T, options: ObservationOptions<T> = {}): T {
19
+ const { equals = equalSnapshots, subscribe, serverSnapshot = compute } = options;
20
+ const committed = useRef<{ value: T } | null>(null);
21
+ const observation = useMemo(() => {
22
+ let cached = committed.current;
23
+ let server: { value: T } | null = null;
24
+ const failed = Symbol('selector error');
25
+ const read = (): T => {
26
+ const next = snapshotValue(compute());
27
+ if (cached === null || !equals(cached.value, next)) cached = { value: next };
28
+ return cached.value;
29
+ };
30
+ return {
31
+ read,
32
+ readServer: (): T => {
33
+ server ??= { value: snapshotValue(serverSnapshot()) };
34
+ return server.value;
35
+ },
36
+ subscribe: (notify: () => void): (() => void) => {
37
+ const stop = reaction(
38
+ () => {
39
+ // Notify React of selector failures; React re-reads and delivers the
40
+ // exception to its error boundary instead of MobX swallowing it.
41
+ try { return read(); } catch { return failed; }
42
+ },
43
+ () => { notify(); },
44
+ );
45
+ let stopExternal: (() => void) | undefined;
46
+ try { stopExternal = subscribe?.(notify); } catch (error) { stop(); throw error; }
47
+ return () => { stop(); stopExternal?.(); };
48
+ },
49
+ };
50
+ }, [compute, equals, subscribe, serverSnapshot]);
51
+ // read() recomputes from the actual store, including React's consistency
52
+ // checks between render and commit, while equal snapshots retain identity.
53
+ const value = useSyncExternalStore(observation.subscribe, observation.read, observation.readServer);
54
+ useEffect(() => { committed.current = { value }; }, [value]);
55
+ return value;
56
+ }
@@ -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
  /**
@@ -29,19 +29,8 @@ import { AbloValidationError } from '@abloatai/transaction/errors';
29
29
  * useHotkey('mod+z', () => { if (canUndo) void undo(); });
30
30
  */
31
31
 
32
- export interface UseUndoScopeResult<S extends Schema> {
33
- /** Pass to `useMutators(..., { undoScope })` to enable recording. */
34
- scope: UndoScope<S>;
35
- undo: () => Promise<void>;
36
- redo: () => Promise<void>;
37
- canUndo: boolean;
38
- canRedo: boolean;
39
- /** Drop history. Use after sync errors / auth context changes. */
40
- clear: () => void;
41
- }
42
-
43
32
  // Module-level weak registry: `SyncStoreContract` → `UndoManager`.
44
- // A single app wiring through one SyncProvider shares one manager across
33
+ // A single AbloProvider shares one manager across
45
34
  // every useUndoScope call, so scopes with the same name are identity-equal.
46
35
  // The hook implementation already operates on the runtime-wide `Schema` type;
47
36
  // its overloads restore the caller's precise schema type at the public boundary,
@@ -66,20 +55,20 @@ export function useUndoScope<S extends Schema>(
66
55
  schema: S,
67
56
  name: string,
68
57
  options?: UndoScopeOptions,
69
- ): UseUndoScopeResult<S>;
58
+ ): useUndoScope.Result<S>;
70
59
 
71
60
  /** Per-surface undo/redo via the `Register` module augmentation. */
72
61
  export function useUndoScope(
73
- name: string,
62
+ name: RequireRegisteredSchema<string>,
74
63
  options?: UndoScopeOptions,
75
- ): UseUndoScopeResult<ResolveSchema extends Schema ? ResolveSchema : Schema>;
64
+ ): useUndoScope.Result<ResolveSchema extends Schema ? ResolveSchema : Schema>;
76
65
 
77
66
  export function useUndoScope(
78
67
  schemaOrName: Schema | string,
79
68
  nameOrOptions?: string | UndoScopeOptions,
80
69
  maybeOptions?: UndoScopeOptions,
81
- ): UseUndoScopeResult<Schema> {
82
- const { store, organizationId, schema: ctxSchema } = useSyncContext();
70
+ ): useUndoScope.Result<Schema> {
71
+ const { store, organizationId, schema: ctxSchema } = useAbloStoreContext();
83
72
 
84
73
  const isExplicit = typeof schemaOrName !== 'string';
85
74
  const schema = isExplicit ? (schemaOrName) : ctxSchema;
@@ -96,7 +85,7 @@ export function useUndoScope(
96
85
  }
97
86
 
98
87
  const scope = useMemo(() => {
99
- // Store is the identity for the manager — one per SyncProvider.
88
+ // Store is the identity for the manager — one per AbloProvider.
100
89
  const manager = getManager(store, () => new UndoManager(schema, store, organizationId));
101
90
  return manager.getScope(name, options);
102
91
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -141,3 +130,18 @@ export function useUndoScope(
141
130
  },
142
131
  };
143
132
  }
133
+
134
+ /** The state returned by an undo scope; inferred for ordinary hook calls. */
135
+ // eslint-disable-next-line @typescript-eslint/no-namespace
136
+ export namespace useUndoScope {
137
+ export interface Result<S extends Schema> {
138
+ /** Pass to `useMutators(..., { undoScope })` to enable recording. */
139
+ scope: UndoScope<S>;
140
+ undo: () => Promise<void>;
141
+ redo: () => Promise<void>;
142
+ canUndo: boolean;
143
+ canRedo: boolean;
144
+ /** Drop history. Use after sync errors / auth context changes. */
145
+ clear: () => void;
146
+ }
147
+ }
package/src/react.ts CHANGED
@@ -1,69 +1,9 @@
1
- /** React bindings for the optional human-facing local-state package. */
2
- export { useReactive } from './useReactive.js';
3
- export { useCurrentUserId } from './react/useCurrentUserId.js';
4
- export { useErrorListener } from './react/useErrorListener.js';
5
- export { useSyncStatus, type SyncStatusSnapshot } from './react/useSyncStatus.js';
6
- export {
7
- usePresence,
8
- type PresenceModelSelector,
9
- } from './react/usePresence.js';
10
- export {
11
- useMutationFailureListener,
12
- type MutationFailurePayload,
13
- } from './react/useMutationFailureListener.js';
14
-
15
- export {
16
- AbloProvider,
17
- usePeers,
18
- useSync,
19
- useSyncStore,
20
- type AbloProviderProps,
21
- type GroupScope,
22
- } from './react/AbloProvider.js';
23
-
24
- export {
25
- ClientSideSuspense,
26
- type ClientSideSuspenseProps,
27
- } from './react/ClientSideSuspense.js';
28
-
29
- export { DefaultFallback } from './react/DefaultFallback.js';
30
-
31
- export {
32
- createAbloReact,
33
- type AbloReactBinding,
34
- } from './react/createAbloReact.js';
35
-
36
- export {
37
- useAblo,
38
- type UseAbloHydratedModelResult,
39
- type UseAbloModelOptions,
40
- type UseAbloModelResult,
41
- } from './react/useAblo.js';
42
-
43
- export {
44
- useMutators,
45
- type InvokerFor,
46
- type MutatorInvokers,
47
- type UseMutatorsOptions,
48
- } from './react/useMutators.js';
49
-
50
- export {
51
- useUndoScope,
52
- type UseUndoScopeResult,
53
- } from './react/useUndoScope.js';
54
-
55
- export type {
56
- DefaultSyncShape,
57
- ResolveSchema,
58
- ResolveUserMeta,
59
- ResolveClaimMeta,
60
- ResolveModelKey,
61
- } from '@abloatai/transaction/types/global';
62
-
63
- export { ModelScope } from '@abloatai/transaction/types';
64
- export type { SyncStoreContract } from './react/context.js';
65
- export type { MutateActions } from './local/mutators/mutateActions.js';
66
- export type {
67
- ReaderActions,
68
- ReaderFindOptions,
69
- } from './local/mutators/readerActions.js';
1
+ /** React owns context, subscriptions and component lifetimes over core Ablo. */
2
+ export { AbloProvider } from './react/AbloProvider.js';
3
+ export { createAbloReact } from './react/createAbloReact.js';
4
+ export { useAblo } from './react/useAblo.js';
5
+ export { useAbloClient } from './react/useAbloClient.js';
6
+ export { useMutationFailure } from './react/useMutationFailure.js';
7
+ export { usePresence } from './react/usePresence.js';
8
+ export { useMutators } from './react/useMutators.js';
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';
@@ -1,36 +0,0 @@
1
- import { type ReactNode } from 'react';
2
- /**
3
- * Nested bootstrap gate for a subtree. `<AbloProvider>` already ships
4
- * its own built-in gate (via its `fallback` prop) that handles the
5
- * common "wait for first bootstrap" case. Use `ClientSideSuspense`
6
- * only when you need a SEPARATE gate inside an already-ready provider —
7
- * for example, rendering app chrome immediately while gating a single
8
- * heavy product surface on its own query resolving.
9
- *
10
- * Like the provider-level gate, this component latches open on the
11
- * first `connected` / `reconnecting` / `disconnected` transition and
12
- * stays open. Subsequent transient `connecting` states (hard reconnect
13
- * after offline) do NOT re-show the fallback — the app has already
14
- * rendered once and its own reconnect UI should take over.
15
- *
16
- * v0.3.x implementation is non-Suspense: reads `useSyncStatus()` and
17
- * conditionally renders. v0.3.x+ will ship a
18
- * the React suspense entry point where `useQuery` / `useOne`
19
- * actually throw Promises; this component becomes a thin wrapper around
20
- * React's real `<Suspense>` at that point.
21
- *
22
- * @example
23
- * <AbloProvider fallback={<AppSkeleton />}>
24
- * <AppChrome />
25
- * <ClientSideSuspense fallback={<CanvasSkeleton />}>
26
- * <HeavyCanvas />
27
- * </ClientSideSuspense>
28
- * </AbloProvider>
29
- */
30
- export interface ClientSideSuspenseProps {
31
- /** What to render while the nested subtree is waiting for first bootstrap. */
32
- fallback: ReactNode;
33
- /** What to render once the subtree is cleared to render. */
34
- children: ReactNode;
35
- }
36
- export declare function ClientSideSuspense({ fallback, children }: ClientSideSuspenseProps): import("react").JSX.Element;
@@ -1,17 +0,0 @@
1
- 'use client';
2
- import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
3
- import { useEffect, useState } from 'react';
4
- import { useSyncStatus } from './useSyncStatus.js';
5
- export function ClientSideSuspense({ fallback, children }) {
6
- const status = useSyncStatus();
7
- const [everConnected, setEverConnected] = useState(false);
8
- useEffect(() => {
9
- if (status.name === 'connected' ||
10
- status.name === 'reconnecting' ||
11
- status.name === 'disconnected') {
12
- setEverConnected(true);
13
- }
14
- }, [status.name]);
15
- const showFallback = !everConnected && status.name === 'connecting';
16
- return _jsx(_Fragment, { children: showFallback ? fallback : children });
17
- }
@@ -1,2 +0,0 @@
1
- /** Returns the application user id supplied to the nearest AbloProvider. */
2
- export declare function useCurrentUserId(): string | null;
@@ -1,12 +0,0 @@
1
- 'use client';
2
- import { useContext } from 'react';
3
- import { AbloInternalContext } from './internalContext.js';
4
- import { AbloValidationError } from '@abloatai/transaction/errors';
5
- /** Returns the application user id supplied to the nearest AbloProvider. */
6
- export function useCurrentUserId() {
7
- const context = useContext(AbloInternalContext);
8
- if (!context) {
9
- throw new AbloValidationError('useCurrentUserId: no <AbloProvider> mounted above this component.', { code: 'no_ablo_provider' });
10
- }
11
- return context.currentUserId;
12
- }
@@ -1,2 +0,0 @@
1
- /** Subscribe to provider-level errors without causing component re-renders. */
2
- export declare function useErrorListener(listener: (error: Error) => void): void;
@@ -1,14 +0,0 @@
1
- 'use client';
2
- import { useContext, useEffect, useRef } from 'react';
3
- import { AbloInternalContext } from './internalContext.js';
4
- import { AbloValidationError } from '@abloatai/transaction/errors';
5
- /** Subscribe to provider-level errors without causing component re-renders. */
6
- export function useErrorListener(listener) {
7
- const context = useContext(AbloInternalContext);
8
- if (!context) {
9
- throw new AbloValidationError('useErrorListener: no <AbloProvider> mounted above this component.', { code: 'no_ablo_provider' });
10
- }
11
- const listenerRef = useRef(listener);
12
- listenerRef.current = listener;
13
- useEffect(() => context.subscribeError((error) => { listenerRef.current(error); }), [context]);
14
- }
@@ -1,8 +0,0 @@
1
- import { type QueuedMutation } from '../reactRuntime.js';
2
- export interface MutationFailurePayload {
3
- transaction: QueuedMutation;
4
- error: Error;
5
- permanent?: boolean;
6
- }
7
- /** Subscribe to optimistic mutation rollbacks and permanent write failures. */
8
- export declare function useMutationFailureListener(listener: (payload: MutationFailurePayload) => void): void;
@@ -1,19 +0,0 @@
1
- 'use client';
2
- import { useContext, useEffect, useRef } from 'react';
3
- import { AbloInternalContext, } from '../reactRuntime.js';
4
- import { AbloValidationError } from '@abloatai/transaction/errors';
5
- /** Subscribe to optimistic mutation rollbacks and permanent write failures. */
6
- export function useMutationFailureListener(listener) {
7
- const context = useContext(AbloInternalContext);
8
- if (!context) {
9
- throw new AbloValidationError('useMutationFailureListener: no <AbloProvider> mounted above this component.', { code: 'no_ablo_provider' });
10
- }
11
- const listenerRef = useRef(listener);
12
- listenerRef.current = listener;
13
- useEffect(() => {
14
- const engine = context.engine;
15
- if (!engine)
16
- return;
17
- return engine.onMutationFailure((payload) => { listenerRef.current(payload); });
18
- }, [context, context.engine]);
19
- }
@@ -1,48 +0,0 @@
1
- 'use client';
2
- import { useCallback, useContext } from 'react';
3
- import { SyncContext, } from './context.js';
4
- import { AbloInternalContext } from './internalContext.js';
5
- import { AbloValidationError } from '@abloatai/transaction/errors';
6
- import { useReactive } from '../useReactive.js';
7
- /** Reactively exposes the local store's connection and confirmation status. */
8
- export function useSyncStatus() {
9
- const provider = useContext(AbloInternalContext);
10
- const sync = useContext(SyncContext);
11
- // Status does not require authenticated row scope. The client exists before
12
- // ready() resolves, including inside passthrough children and custom fallbacks.
13
- const store = provider?.engine?._store ?? sync?.store;
14
- if (!store) {
15
- throw new AbloValidationError('Sync hooks must be used within an <AbloProvider>.', {
16
- code: 'sync_context_missing_provider',
17
- });
18
- }
19
- const compute = useCallback(() => deriveStatus(store), [store]);
20
- return useReactive(compute, sameSnapshot);
21
- }
22
- function deriveStatus(store) {
23
- const { state, progress, pendingChanges, isSessionError, error } = store.syncStatus;
24
- if (isSessionError)
25
- return { name: 'needs-auth' };
26
- if (state === 'reconnecting')
27
- return { name: 'reconnecting', reason: error?.message };
28
- if (state === 'offline')
29
- return { name: 'disconnected', reason: 'offline' };
30
- if (state === 'error')
31
- return { name: 'disconnected', reason: error?.message };
32
- if (store.isReady)
33
- return { name: 'connected', hasUnsyncedChanges: pendingChanges > 0 };
34
- if (state === 'idle' || state === 'syncing')
35
- return { name: 'connecting', progress };
36
- return { name: 'initial' };
37
- }
38
- function sameSnapshot(a, b) {
39
- if (a.name !== b.name)
40
- return false;
41
- if (a.name === 'initial' || a.name === 'needs-auth')
42
- return true;
43
- if (a.name === 'connecting')
44
- return b.name === 'connecting' && a.progress === b.progress;
45
- if (a.name === 'connected')
46
- return b.name === 'connected' && a.hasUnsyncedChanges === b.hasUnsyncedChanges;
47
- return (b.name === 'reconnecting' || b.name === 'disconnected') && a.reason === b.reason;
48
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * Subscribe a React component to a MobX computation with a cached snapshot.
3
- * This is the framework-level reactive primitive owned by @abloatai/humans;
4
- * store/model hooks are layered above it.
5
- */
6
- export declare function useReactive<T>(compute: () => T, equals?: (a: T, b: T) => boolean): T;