@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.
- package/dist/Ablo.d.ts +6 -0
- package/dist/client.d.ts +12 -32
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/local/client/createModelOperations.d.ts +3 -3
- package/dist/local/client/createModelOperations.js +1 -1
- package/dist/local/client/reactiveEngine.js +7 -8
- package/dist/{react/useSyncStatus.d.ts → local/client/status.d.ts} +4 -3
- package/dist/local/client/status.js +14 -0
- package/dist/local/client/storeLifecycle.js +2 -2
- package/dist/local/mutators/defineMutators.d.ts +3 -48
- package/dist/local/mutators/defineMutators.js +0 -15
- package/dist/local/storeAccess.d.ts +7 -0
- package/dist/local/storeAccess.js +6 -0
- package/dist/local/storeContract.d.ts +3 -4
- package/dist/presence/index.d.ts +2 -2
- package/dist/presence/index.js +8 -4
- package/dist/react/AbloProvider.d.ts +57 -121
- package/dist/react/AbloProvider.js +55 -160
- package/dist/react/context.d.ts +6 -45
- package/dist/react/context.js +8 -20
- package/dist/react/createAbloReact.d.ts +13 -48
- package/dist/react/createAbloReact.js +8 -54
- package/dist/react/internalContext.d.ts +1 -27
- package/dist/react/snapshot.d.ts +4 -0
- package/dist/react/snapshot.js +75 -0
- package/dist/react/useAblo.d.ts +28 -92
- package/dist/react/useAblo.js +36 -93
- package/dist/react/useAbloClient.d.ts +5 -0
- package/dist/react/useAbloClient.js +9 -0
- package/dist/react/useMutationFailure.d.ts +6 -0
- package/dist/react/useMutationFailure.js +11 -0
- package/dist/react/useMutators.d.ts +22 -19
- package/dist/react/useMutators.js +3 -3
- package/dist/react/usePresence.d.ts +9 -9
- package/dist/react/usePresence.js +8 -11
- package/dist/react/useReactive.d.ts +12 -0
- package/dist/react/useReactive.js +57 -0
- package/dist/react/useUndoScope.d.ts +16 -30
- package/dist/react/useUndoScope.js +21 -4
- package/dist/react.d.ts +9 -19
- package/dist/react.js +9 -15
- package/dist/reactRuntime.d.ts +1 -1
- package/dist/reactRuntime.js +1 -1
- package/package.json +4 -3
- package/src/Ablo.ts +7 -0
- package/src/client.ts +13 -32
- package/src/index.ts +2 -0
- package/src/local/client/createModelOperations.ts +4 -4
- package/src/local/client/reactiveEngine.ts +7 -8
- package/src/local/client/status.ts +20 -0
- package/src/local/client/storeLifecycle.ts +2 -2
- package/src/local/mutators/defineMutators.ts +3 -51
- package/src/local/storeAccess.ts +10 -0
- package/src/local/storeContract.ts +3 -4
- package/src/presence/index.ts +10 -5
- package/src/react/AbloProvider.tsx +88 -263
- package/src/react/context.ts +10 -61
- package/src/react/createAbloReact.ts +16 -125
- package/src/react/internalContext.ts +1 -27
- package/src/react/snapshot.ts +67 -0
- package/src/react/useAblo.ts +73 -209
- package/src/react/useAbloClient.ts +14 -0
- package/src/react/useMutationFailure.ts +17 -0
- package/src/react/useMutators.ts +36 -32
- package/src/react/usePresence.ts +22 -27
- package/src/react/useReactive.ts +56 -0
- package/src/react/useUndoScope.ts +24 -20
- package/src/react.ts +9 -69
- package/src/reactRuntime.ts +2 -2
- package/dist/react/ClientSideSuspense.d.ts +0 -36
- package/dist/react/ClientSideSuspense.js +0 -17
- package/dist/react/useCurrentUserId.d.ts +0 -2
- package/dist/react/useCurrentUserId.js +0 -12
- package/dist/react/useErrorListener.d.ts +0 -2
- package/dist/react/useErrorListener.js +0 -14
- package/dist/react/useMutationFailureListener.d.ts +0 -8
- package/dist/react/useMutationFailureListener.js +0 -19
- package/dist/react/useSyncStatus.js +0 -48
- package/dist/useReactive.d.ts +0 -6
- package/dist/useReactive.js +0 -41
- package/src/react/ClientSideSuspense.tsx +0 -57
- package/src/react/useCurrentUserId.ts +0 -17
- package/src/react/useErrorListener.ts +0 -22
- package/src/react/useMutationFailureListener.ts +0 -34
- package/src/react/useSyncStatus.ts +0 -53
- package/src/useReactive.ts +0 -49
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Schema } from '@abloatai/transaction/schema/schema';
|
|
2
2
|
import type { MutatorDefs } from '../local/mutators/defineMutators.js';
|
|
3
3
|
import type { UndoScope } from '../local/mutators/UndoManager.js';
|
|
4
|
-
import type { ResolveSchema } from '@abloatai/transaction/types/global';
|
|
4
|
+
import type { ResolveSchema, RequireRegisteredSchema } from '@abloatai/transaction/types/global';
|
|
5
5
|
/**
|
|
6
6
|
* Turns a mutator tree built with `defineMutators` into callable invokers. The
|
|
7
7
|
* returned object mirrors that tree one-to-one, but each leaf becomes an
|
|
@@ -14,7 +14,7 @@ import type { ResolveSchema } from '@abloatai/transaction/types/global';
|
|
|
14
14
|
* If a mutator throws, the error propagates to the caller and any writes it
|
|
15
15
|
* already dispatched stay in place — there is no automatic rollback. Wrap the
|
|
16
16
|
* call in your own try/catch and issue compensating writes when you need to
|
|
17
|
-
* undo a partial change, or pass an `undoScope` (see {@link
|
|
17
|
+
* undo a partial change, or pass an `undoScope` (see {@link useMutators.Options})
|
|
18
18
|
* to record inverses for undo and redo.
|
|
19
19
|
*/
|
|
20
20
|
/**
|
|
@@ -34,23 +34,26 @@ export type InvokerFor<F> = F extends (options: infer O) => Promise<infer R> ? O
|
|
|
34
34
|
* The hook's return shape: same tree as the input `MutatorDefs`, every leaf
|
|
35
35
|
* rewritten to its invoker form.
|
|
36
36
|
*/
|
|
37
|
-
export type MutatorInvokers<M> = {
|
|
38
|
-
[K in keyof M]: {
|
|
39
|
-
[N in keyof M[K]]: InvokerFor<M[K][N]>;
|
|
40
|
-
};
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* Options passed to `useMutators`. When `undoScope` is set, every mutator
|
|
44
|
-
* invocation is wrapped in a `RecordingMutation` and its inverses are
|
|
45
|
-
* pushed to the scope as one undo entry.
|
|
46
|
-
*/
|
|
47
|
-
export interface UseMutatorsOptions<S extends Schema> {
|
|
48
|
-
/** Target undo scope for recording inverses. Omit to disable recording. */
|
|
49
|
-
undoScope?: UndoScope<S>;
|
|
50
|
-
}
|
|
51
37
|
/** Mutator invokers (explicit schema arg). */
|
|
52
|
-
export declare function useMutators<S extends Schema, M extends MutatorDefs<S>>(schema: S, mutators: M, options?:
|
|
38
|
+
export declare function useMutators<S extends Schema, M extends MutatorDefs<S>>(schema: S, mutators: M, options?: useMutators.Options<S>): useMutators.Result<M>;
|
|
53
39
|
/** Mutator invokers via the `Register` module augmentation. Schema comes
|
|
54
|
-
* from the `
|
|
40
|
+
* from the `AbloProvider` store context; the mutator tree is typed against
|
|
55
41
|
* `ResolveSchema` at the call site. */
|
|
56
|
-
export declare function useMutators<M extends ResolveSchema extends Schema ? MutatorDefs<ResolveSchema> : MutatorDefs<Schema>>(mutators: M
|
|
42
|
+
export declare function useMutators<M extends ResolveSchema extends Schema ? MutatorDefs<ResolveSchema> : MutatorDefs<Schema>>(mutators: RequireRegisteredSchema<M>, options?: useMutators.Options<ResolveSchema extends Schema ? ResolveSchema : Schema>): useMutators.Result<M>;
|
|
43
|
+
/** Optional annotations for custom mutation bindings. */
|
|
44
|
+
export declare namespace useMutators {
|
|
45
|
+
type Result<M> = {
|
|
46
|
+
[K in keyof M]: {
|
|
47
|
+
[N in keyof M[K]]: InvokerFor<M[K][N]>;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Options passed to `useMutators`. When `undoScope` is set, every mutator
|
|
52
|
+
* invocation is wrapped in a `RecordingMutation` and its inverses are
|
|
53
|
+
* pushed to the scope as one undo entry.
|
|
54
|
+
*/
|
|
55
|
+
interface Options<S extends Schema> {
|
|
56
|
+
/** Target undo scope for recording inverses. Omit to disable recording. */
|
|
57
|
+
undoScope?: UndoScope<S>;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
3
|
import { createTransaction } from '../local/mutators/Transaction.js';
|
|
4
4
|
import { createRecordingMutation } from '../local/mutators/RecordingMutation.js';
|
|
5
|
-
import {
|
|
5
|
+
import { useAbloStoreContext } from './context.js';
|
|
6
6
|
import { AbloValidationError } from '@abloatai/transaction/errors';
|
|
7
7
|
import { getContext } from '../local/context.js';
|
|
8
8
|
export function useMutators(schemaOrMutators, mutatorsOrOptions, maybeOptions) {
|
|
9
|
-
const { store, organizationId, schema: ctxSchema } =
|
|
9
|
+
const { store, organizationId, schema: ctxSchema } = useAbloStoreContext();
|
|
10
10
|
// Disambiguate: explicit-schema path has the schema object in first slot;
|
|
11
11
|
// the global-resolved path has the mutator tree there. A schema object
|
|
12
12
|
// has a `.models` property; a mutator tree doesn't.
|
|
@@ -19,7 +19,7 @@ export function useMutators(schemaOrMutators, mutatorsOrOptions, maybeOptions) {
|
|
|
19
19
|
if (!schema) {
|
|
20
20
|
throw new AbloValidationError('useMutators: no schema available. Pass the schema as the first arg, ' +
|
|
21
21
|
'or build the <AbloProvider> above with `Ablo({ schema })` so the ' +
|
|
22
|
-
'
|
|
22
|
+
'schema-free overload can read it from context.', { code: 'mutators_schema_missing' });
|
|
23
23
|
}
|
|
24
24
|
const { undoScope } = options ?? {};
|
|
25
25
|
return useMemo(() => {
|
|
@@ -2,18 +2,18 @@ import type { PresenceSession } from '@abloatai/transaction/presence';
|
|
|
2
2
|
import { type ModelOperations } from '../local/client/createModelOperations.js';
|
|
3
3
|
import type { AbloClient as Ablo } from '../client.js';
|
|
4
4
|
import type { SchemaRecord } from '@abloatai/transaction/schema/schema';
|
|
5
|
-
import type {
|
|
6
|
-
type DefaultModels = ResolveSchema extends {
|
|
7
|
-
models: infer M;
|
|
8
|
-
} ? M extends SchemaRecord ? M : SchemaRecord : SchemaRecord;
|
|
5
|
+
import type { ResolveModels as DefaultModels } from '@abloatai/transaction/types/global';
|
|
9
6
|
export type PresenceModelSelector<R extends SchemaRecord, T, C> = (ablo: Ablo<R>) => ModelOperations<T, C>;
|
|
10
7
|
/**
|
|
11
8
|
* Declare that this component is reading one row and return every live session
|
|
12
9
|
* reading or otherwise acting on that row. Ablo owns the lease, refresh,
|
|
13
10
|
* reconnect, and cleanup mechanics for the component's lifetime.
|
|
14
11
|
*/
|
|
15
|
-
export declare function usePresence<T, C>(modelClient: ModelOperations<T, C>, recordId: string): readonly PresenceSession[];
|
|
16
|
-
export declare function usePresence<R extends SchemaRecord = DefaultModels, T = Record<string, unknown>, C = unknown>(select: PresenceModelSelector<R, T, C>, recordId: string): readonly PresenceSession[];
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
export declare function usePresence<T, C>(modelClient: ModelOperations<T, C>, recordId: string, options?: usePresence.Options): readonly PresenceSession[];
|
|
13
|
+
export declare function usePresence<R extends SchemaRecord = DefaultModels, T = Record<string, unknown>, C = unknown>(select: PresenceModelSelector<R, T, C>, recordId: string, options?: usePresence.Options): readonly PresenceSession[];
|
|
14
|
+
export declare namespace usePresence {
|
|
15
|
+
interface Bound<S extends SchemaRecord> {
|
|
16
|
+
<T, C>(model: ModelOperations<T, C> | PresenceModelSelector<S, T, C>, recordId: string, options?: Options): readonly PresenceSession[];
|
|
17
|
+
}
|
|
18
|
+
type Options = import('@abloatai/transaction/presence').PresenceQueryOptions;
|
|
19
|
+
}
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
2
|
+
import { useCallback, useEffect } from 'react';
|
|
3
3
|
import { AbloValidationError } from '@abloatai/transaction/errors';
|
|
4
4
|
import { getModelClientMeta, } from '../local/client/createModelOperations.js';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
/** @internal Shared by the global hook and schema-bound React factory. */
|
|
11
|
-
export function usePresenceImpl(engine, modelOrSelect, recordId) {
|
|
5
|
+
import { useAbloClient } from './useAbloClient.js';
|
|
6
|
+
import { useReactive } from './useReactive.js';
|
|
7
|
+
export function usePresence(modelOrSelect, recordId, options) {
|
|
8
|
+
const engine = useAbloClient();
|
|
12
9
|
if (recordId.length === 0) {
|
|
13
10
|
throw new AbloValidationError('usePresence requires a non-empty record id.', { code: 'invalid_request', param: 'recordId' });
|
|
14
11
|
}
|
|
@@ -21,8 +18,8 @@ export function usePresenceImpl(engine, modelOrSelect, recordId) {
|
|
|
21
18
|
if (modelClient !== null && presence === undefined) {
|
|
22
19
|
throw new AbloValidationError('usePresence requires a model from the reactive Ablo client.', { code: 'invalid_request', param: 'modelClient' });
|
|
23
20
|
}
|
|
24
|
-
const
|
|
25
|
-
|
|
21
|
+
const subscribe = useCallback((notify) => presence?.subscribe(notify) ?? (() => undefined), [presence]);
|
|
22
|
+
const sessions = useReactive(() => presence?.get(recordId, options) ?? [], { subscribe });
|
|
26
23
|
useEffect(() => presence?.read(recordId), [presence, recordId]);
|
|
27
|
-
return
|
|
24
|
+
return sessions;
|
|
28
25
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface ObservationOptions<T> {
|
|
2
|
+
equals?: (a: T, b: T) => boolean;
|
|
3
|
+
subscribe?: (listener: () => void) => () => void;
|
|
4
|
+
serverSnapshot?: () => T;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Each render's computation has its own observation. A suspended render cannot
|
|
8
|
+
* replace the computation used by the committed tree's subscription. Reactions
|
|
9
|
+
* start only on subscription, so abandoned renders retain no store observers.
|
|
10
|
+
*/
|
|
11
|
+
export declare function useReactive<T>(compute: () => T, options?: ObservationOptions<T>): T;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';
|
|
3
|
+
import { reaction } from 'mobx';
|
|
4
|
+
import { equalSnapshots, snapshotValue } from './snapshot.js';
|
|
5
|
+
/**
|
|
6
|
+
* Each render's computation has its own observation. A suspended render cannot
|
|
7
|
+
* replace the computation used by the committed tree's subscription. Reactions
|
|
8
|
+
* start only on subscription, so abandoned renders retain no store observers.
|
|
9
|
+
*/
|
|
10
|
+
export function useReactive(compute, options = {}) {
|
|
11
|
+
const { equals = equalSnapshots, subscribe, serverSnapshot = compute } = options;
|
|
12
|
+
const committed = useRef(null);
|
|
13
|
+
const observation = useMemo(() => {
|
|
14
|
+
let cached = committed.current;
|
|
15
|
+
let server = null;
|
|
16
|
+
const failed = Symbol('selector error');
|
|
17
|
+
const read = () => {
|
|
18
|
+
const next = snapshotValue(compute());
|
|
19
|
+
if (cached === null || !equals(cached.value, next))
|
|
20
|
+
cached = { value: next };
|
|
21
|
+
return cached.value;
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
read,
|
|
25
|
+
readServer: () => {
|
|
26
|
+
server ??= { value: snapshotValue(serverSnapshot()) };
|
|
27
|
+
return server.value;
|
|
28
|
+
},
|
|
29
|
+
subscribe: (notify) => {
|
|
30
|
+
const stop = reaction(() => {
|
|
31
|
+
// Notify React of selector failures; React re-reads and delivers the
|
|
32
|
+
// exception to its error boundary instead of MobX swallowing it.
|
|
33
|
+
try {
|
|
34
|
+
return read();
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return failed;
|
|
38
|
+
}
|
|
39
|
+
}, () => { notify(); });
|
|
40
|
+
let stopExternal;
|
|
41
|
+
try {
|
|
42
|
+
stopExternal = subscribe?.(notify);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
stop();
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
return () => { stop(); stopExternal?.(); };
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}, [compute, equals, subscribe, serverSnapshot]);
|
|
52
|
+
// read() recomputes from the actual store, including React's consistency
|
|
53
|
+
// checks between render and commit, while equal snapshots retain identity.
|
|
54
|
+
const value = useSyncExternalStore(observation.subscribe, observation.read, observation.readServer);
|
|
55
|
+
useEffect(() => { committed.current = { value }; }, [value]);
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
@@ -1,34 +1,20 @@
|
|
|
1
1
|
import type { Schema } from '@abloatai/transaction/schema/schema';
|
|
2
2
|
import { type UndoScope, type UndoScopeOptions } from '../local/mutators/UndoManager.js';
|
|
3
|
-
import type { ResolveSchema } from '@abloatai/transaction/types/global';
|
|
4
|
-
/**
|
|
5
|
-
* Provides per-surface undo and redo for mutator invocations. Each named scope
|
|
6
|
-
* owns an independent undo/redo stack, so different parts of your app — a main
|
|
7
|
-
* editor, a sidebar form — can undo separately without stepping on each other.
|
|
8
|
-
*
|
|
9
|
-
* Wire the returned `scope` into `useMutators(schema, mutators, { undoScope:
|
|
10
|
-
* scope })` and those invocations become recorded. `undo()` and `redo()` replay
|
|
11
|
-
* the captured inverses and forwards as new transactions that do not record
|
|
12
|
-
* themselves; the manager moves the entry between the two stacks explicitly.
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* const { undo, redo, canUndo, canRedo, scope } = useUndoScope('report-editor');
|
|
16
|
-
* const mutate = useMutators(schema, reportMutators, { undoScope: scope });
|
|
17
|
-
*
|
|
18
|
-
* // Cmd+Z handler
|
|
19
|
-
* useHotkey('mod+z', () => { if (canUndo) void undo(); });
|
|
20
|
-
*/
|
|
21
|
-
export interface UseUndoScopeResult<S extends Schema> {
|
|
22
|
-
/** Pass to `useMutators(..., { undoScope })` to enable recording. */
|
|
23
|
-
scope: UndoScope<S>;
|
|
24
|
-
undo: () => Promise<void>;
|
|
25
|
-
redo: () => Promise<void>;
|
|
26
|
-
canUndo: boolean;
|
|
27
|
-
canRedo: boolean;
|
|
28
|
-
/** Drop history. Use after sync errors / auth context changes. */
|
|
29
|
-
clear: () => void;
|
|
30
|
-
}
|
|
3
|
+
import type { ResolveSchema, RequireRegisteredSchema } from '@abloatai/transaction/types/global';
|
|
31
4
|
/** Per-surface undo/redo (explicit schema arg). */
|
|
32
|
-
export declare function useUndoScope<S extends Schema>(schema: S, name: string, options?: UndoScopeOptions):
|
|
5
|
+
export declare function useUndoScope<S extends Schema>(schema: S, name: string, options?: UndoScopeOptions): useUndoScope.Result<S>;
|
|
33
6
|
/** Per-surface undo/redo via the `Register` module augmentation. */
|
|
34
|
-
export declare function useUndoScope(name: string
|
|
7
|
+
export declare function useUndoScope(name: RequireRegisteredSchema<string>, options?: UndoScopeOptions): useUndoScope.Result<ResolveSchema extends Schema ? ResolveSchema : Schema>;
|
|
8
|
+
/** The state returned by an undo scope; inferred for ordinary hook calls. */
|
|
9
|
+
export declare namespace useUndoScope {
|
|
10
|
+
interface Result<S extends Schema> {
|
|
11
|
+
/** Pass to `useMutators(..., { undoScope })` to enable recording. */
|
|
12
|
+
scope: UndoScope<S>;
|
|
13
|
+
undo: () => Promise<void>;
|
|
14
|
+
redo: () => Promise<void>;
|
|
15
|
+
canUndo: boolean;
|
|
16
|
+
canRedo: boolean;
|
|
17
|
+
/** Drop history. Use after sync errors / auth context changes. */
|
|
18
|
+
clear: () => void;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useEffect, useMemo, useState } from 'react';
|
|
3
3
|
import { UndoManager, } from '../local/mutators/UndoManager.js';
|
|
4
|
-
import {
|
|
4
|
+
import { useAbloStoreContext } from './context.js';
|
|
5
5
|
import { AbloValidationError } from '@abloatai/transaction/errors';
|
|
6
|
+
/**
|
|
7
|
+
* Provides per-surface undo and redo for mutator invocations. Each named scope
|
|
8
|
+
* owns an independent undo/redo stack, so different parts of your app — a main
|
|
9
|
+
* editor, a sidebar form — can undo separately without stepping on each other.
|
|
10
|
+
*
|
|
11
|
+
* Wire the returned `scope` into `useMutators(schema, mutators, { undoScope:
|
|
12
|
+
* scope })` and those invocations become recorded. `undo()` and `redo()` replay
|
|
13
|
+
* the captured inverses and forwards as new transactions that do not record
|
|
14
|
+
* themselves; the manager moves the entry between the two stacks explicitly.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const { undo, redo, canUndo, canRedo, scope } = useUndoScope('report-editor');
|
|
18
|
+
* const mutate = useMutators(schema, reportMutators, { undoScope: scope });
|
|
19
|
+
*
|
|
20
|
+
* // Cmd+Z handler
|
|
21
|
+
* useHotkey('mod+z', () => { if (canUndo) void undo(); });
|
|
22
|
+
*/
|
|
6
23
|
// Module-level weak registry: `SyncStoreContract` → `UndoManager`.
|
|
7
|
-
// A single
|
|
24
|
+
// A single AbloProvider shares one manager across
|
|
8
25
|
// every useUndoScope call, so scopes with the same name are identity-equal.
|
|
9
26
|
// The hook implementation already operates on the runtime-wide `Schema` type;
|
|
10
27
|
// its overloads restore the caller's precise schema type at the public boundary,
|
|
@@ -20,7 +37,7 @@ function getManager(key, factory) {
|
|
|
20
37
|
return m;
|
|
21
38
|
}
|
|
22
39
|
export function useUndoScope(schemaOrName, nameOrOptions, maybeOptions) {
|
|
23
|
-
const { store, organizationId, schema: ctxSchema } =
|
|
40
|
+
const { store, organizationId, schema: ctxSchema } = useAbloStoreContext();
|
|
24
41
|
const isExplicit = typeof schemaOrName !== 'string';
|
|
25
42
|
const schema = isExplicit ? (schemaOrName) : ctxSchema;
|
|
26
43
|
const name = isExplicit ? nameOrOptions : schemaOrName;
|
|
@@ -31,7 +48,7 @@ export function useUndoScope(schemaOrName, nameOrOptions, maybeOptions) {
|
|
|
31
48
|
'zero-arg overload can read it from context.', { code: 'undo_scope_schema_missing' });
|
|
32
49
|
}
|
|
33
50
|
const scope = useMemo(() => {
|
|
34
|
-
// Store is the identity for the manager — one per
|
|
51
|
+
// Store is the identity for the manager — one per AbloProvider.
|
|
35
52
|
const manager = getManager(store, () => new UndoManager(schema, store, organizationId));
|
|
36
53
|
return manager.getScope(name, options);
|
|
37
54
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
package/dist/react.d.ts
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
|
-
/** React
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
10
|
-
export { DefaultFallback } from './react/DefaultFallback.js';
|
|
11
|
-
export { createAbloReact, type AbloReactBinding, } from './react/createAbloReact.js';
|
|
12
|
-
export { useAblo, type UseAbloHydratedModelResult, type UseAbloModelOptions, type UseAbloModelResult, } from './react/useAblo.js';
|
|
13
|
-
export { useMutators, type InvokerFor, type MutatorInvokers, type UseMutatorsOptions, } from './react/useMutators.js';
|
|
14
|
-
export { useUndoScope, type UseUndoScopeResult, } from './react/useUndoScope.js';
|
|
15
|
-
export type { DefaultSyncShape, ResolveSchema, ResolveUserMeta, ResolveClaimMeta, ResolveModelKey, } from '@abloatai/transaction/types/global';
|
|
16
|
-
export { ModelScope } from '@abloatai/transaction/types';
|
|
17
|
-
export type { SyncStoreContract } from './react/context.js';
|
|
18
|
-
export type { MutateActions } from './local/mutators/mutateActions.js';
|
|
19
|
-
export type { ReaderActions, ReaderFindOptions, } 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';
|
package/dist/react.js
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
/** React
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
10
|
-
export { DefaultFallback } from './react/DefaultFallback.js';
|
|
11
|
-
export { createAbloReact, } from './react/createAbloReact.js';
|
|
12
|
-
export { useAblo, } from './react/useAblo.js';
|
|
13
|
-
export { useMutators, } from './react/useMutators.js';
|
|
14
|
-
export { useUndoScope, } from './react/useUndoScope.js';
|
|
15
|
-
export { ModelScope } from '@abloatai/transaction/types';
|
|
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';
|
package/dist/reactRuntime.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { AbloInternalContext, type AbloInternalContextValue, } from './react/internalContext.js';
|
|
2
|
-
export {
|
|
2
|
+
export { useAbloStoreContext, type AbloStoreContextValue, } from './react/context.js';
|
|
3
3
|
export type { SyncStoreContract } from './local/storeContract.js';
|
|
4
4
|
export type { QueuedMutation } from './local/transactions/mutations/MutationQueue.js';
|
package/dist/reactRuntime.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { AbloInternalContext, } from './react/internalContext.js';
|
|
2
|
-
export {
|
|
2
|
+
export { useAbloStoreContext, } from './react/context.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abloatai/humans",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.64.1",
|
|
4
4
|
"description": "The optional human-facing local-state package for Ablo: presence, live queries, and React bindings.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -71,7 +71,8 @@
|
|
|
71
71
|
"lint:eslint": "eslint . --cache --suppressions-location eslint-suppressions.json",
|
|
72
72
|
"check:boundary": "node scripts/check-boundary.mjs",
|
|
73
73
|
"test:integration": "npm run build && node scripts/check-package-integration.mjs",
|
|
74
|
-
"lint:pkg": "publint"
|
|
74
|
+
"lint:pkg": "publint",
|
|
75
|
+
"typecheck:react": "tsc -p typetests/binding/tsconfig.json"
|
|
75
76
|
},
|
|
76
77
|
"publishConfig": {
|
|
77
78
|
"access": "public",
|
|
@@ -84,7 +85,7 @@
|
|
|
84
85
|
"directory": "packages/humans"
|
|
85
86
|
},
|
|
86
87
|
"dependencies": {
|
|
87
|
-
"@abloatai/transaction": "
|
|
88
|
+
"@abloatai/transaction": "0.64.1",
|
|
88
89
|
"mobx": "^6.13.7",
|
|
89
90
|
"uuid": "^11.1.0",
|
|
90
91
|
"zod": "^4.4.3"
|
package/src/Ablo.ts
CHANGED
|
@@ -264,6 +264,12 @@ import type * as _Global from '@abloatai/transaction/types/global';
|
|
|
264
264
|
*/
|
|
265
265
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
266
266
|
export namespace Ablo {
|
|
267
|
+
/** Payload delivered by the core client's onMutationFailure subscription. */
|
|
268
|
+
export type MutationFailure = Parameters<Parameters<AbloClient<SchemaRecord>['onMutationFailure']>[0]>[0];
|
|
269
|
+
/** Current client lifecycle, also selected through React's useAblo. */
|
|
270
|
+
export type Store = import('./local/storeContract.js').SyncStoreContract;
|
|
271
|
+
export type Status = import('./local/client/status.js').ClientStatus;
|
|
272
|
+
|
|
267
273
|
// ── Factory options ────────────────────────────────────────────────
|
|
268
274
|
export type Options<S extends SchemaRecord = SchemaRecord> = AbloOptions<S>;
|
|
269
275
|
/**
|
|
@@ -315,6 +321,7 @@ export namespace Ablo {
|
|
|
315
321
|
* different schemas.
|
|
316
322
|
*/
|
|
317
323
|
export type ResolveSchema = _Global.ResolveSchema;
|
|
324
|
+
export type ResolveClaimMeta = _Global.ResolveClaimMeta;
|
|
318
325
|
/**
|
|
319
326
|
* `ResolveSchema` guaranteed to satisfy the `Schema` bound. `ResolveSchema`
|
|
320
327
|
* falls back to a loose `{ models }` shape when nothing is registered, which
|
package/src/client.ts
CHANGED
|
@@ -19,9 +19,9 @@ import type {
|
|
|
19
19
|
InferRow,
|
|
20
20
|
} from '@abloatai/transaction/schema/schema';
|
|
21
21
|
import type { InstanceCache } from './local/InstanceCache.js';
|
|
22
|
-
import type { SyncStoreContract } from './
|
|
22
|
+
import type { SyncStoreContract } from './local/storeContract.js';
|
|
23
23
|
import type { SyncWebSocket, CoreSyncEventMap } from './local/sync/SyncWebSocket.js';
|
|
24
|
-
import type {
|
|
24
|
+
import type { ClientStatus } from './local/client/status.js';
|
|
25
25
|
import type { ModelOperations } from './local/client/createModelOperations.js';
|
|
26
26
|
import type {
|
|
27
27
|
ClaimResource,
|
|
@@ -39,7 +39,10 @@ export type AbloClient<S extends SchemaRecord> = {
|
|
|
39
39
|
Model<Schema<S>, K>,
|
|
40
40
|
InferCreate<Schema<S>, K>
|
|
41
41
|
>;
|
|
42
|
-
} &
|
|
42
|
+
} & AbloCore<S>;
|
|
43
|
+
|
|
44
|
+
/** Core members stay intact even when the model schema is not registered. */
|
|
45
|
+
interface AbloCore<S extends SchemaRecord> {
|
|
43
46
|
/**
|
|
44
47
|
* Wait for the sync engine to finish its initial bootstrap.
|
|
45
48
|
* Resolves once entity data is loaded and the WebSocket is connected.
|
|
@@ -65,7 +68,7 @@ export type AbloClient<S extends SchemaRecord> = {
|
|
|
65
68
|
* acknowledged everything before continuing — for example, before
|
|
66
69
|
* navigating away, before triggering a server-side workflow, or in tests.
|
|
67
70
|
*
|
|
68
|
-
* Resolves when
|
|
71
|
+
* Resolves when all pending local changes are confirmed. If the engine is
|
|
69
72
|
* offline, this waits until reconnect + flush completes.
|
|
70
73
|
*
|
|
71
74
|
* ```ts
|
|
@@ -205,32 +208,10 @@ export type AbloClient<S extends SchemaRecord> = {
|
|
|
205
208
|
waitForConfirmation(modelName: string, modelId: string): Promise<void>;
|
|
206
209
|
|
|
207
210
|
/**
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
* Single source of truth for "what's the sync engine doing?" Contains:
|
|
211
|
-
* - `state`: `'idle' | 'syncing' | 'error' | 'offline' | 'reconnecting'`
|
|
212
|
-
* - `progress`: 0-100 for bootstrap progress
|
|
213
|
-
* - `error?`: Error object when `state === 'error'`
|
|
214
|
-
* - `pendingChanges`: Number of unconfirmed mutations in the queue
|
|
215
|
-
* - `lastSyncAt?`: Timestamp of the last successful delta processing
|
|
216
|
-
* - `offlineSince?`: When the connection dropped
|
|
217
|
-
* - `isSessionError`: True when the error requires re-authentication
|
|
218
|
-
*
|
|
219
|
-
* React components using `observer()` re-render automatically when
|
|
220
|
-
* any field changes — no manual subscription or polling needed.
|
|
221
|
-
*
|
|
222
|
-
* ```tsx
|
|
223
|
-
* import { observer } from 'mobx-react-lite';
|
|
224
|
-
*
|
|
225
|
-
* const SyncIndicator = observer(() => {
|
|
226
|
-
* if (sync.syncStatus.state === 'syncing') return <Spinner />;
|
|
227
|
-
* if (sync.syncStatus.state === 'error') return <Error msg={sync.syncStatus.error} />;
|
|
228
|
-
* if (sync.syncStatus.state === 'offline') return <OfflineBadge />;
|
|
229
|
-
* return null;
|
|
230
|
-
* });
|
|
231
|
-
* ```
|
|
211
|
+
* Current connection and confirmation state. Available before ready().
|
|
212
|
+
* React reads the same value with `useAblo(ablo => ablo.status)`.
|
|
232
213
|
*/
|
|
233
|
-
readonly
|
|
214
|
+
readonly status: ClientStatus;
|
|
234
215
|
|
|
235
216
|
/**
|
|
236
217
|
* Session-owned live activity projected from this client's existing
|
|
@@ -275,7 +256,7 @@ export type AbloClient<S extends SchemaRecord> = {
|
|
|
275
256
|
|
|
276
257
|
/**
|
|
277
258
|
* The internal store. It implements {@link SyncStoreContract} — pass it to
|
|
278
|
-
* `
|
|
259
|
+
* `AbloStoreContext.Provider` so the SDK's React data hooks
|
|
279
260
|
* hooks can reach it.
|
|
280
261
|
*/
|
|
281
262
|
readonly _store: SyncStoreContract;
|
|
@@ -290,7 +271,7 @@ export type AbloClient<S extends SchemaRecord> = {
|
|
|
290
271
|
* no window in which this is absent and nothing needs to guard for one.
|
|
291
272
|
*/
|
|
292
273
|
readonly _ws: SyncWebSocket;
|
|
293
|
-
}
|
|
274
|
+
}
|
|
294
275
|
|
|
295
276
|
/**
|
|
296
277
|
* The reactive-read client a `useAblo` selector receives. The same surface as
|
|
@@ -301,7 +282,7 @@ export type AbloClient<S extends SchemaRecord> = {
|
|
|
301
282
|
* compile error here instead of a silent runtime `undefined`; compose
|
|
302
283
|
* relations through selectors or hooks that resolve the pool's instance.
|
|
303
284
|
*/
|
|
304
|
-
export type AbloReads<S extends SchemaRecord> =
|
|
285
|
+
export type AbloReads<S extends SchemaRecord> = AbloCore<S> & {
|
|
305
286
|
readonly [K in keyof S & string]: ModelOperations<
|
|
306
287
|
InferRow<Schema<S>, K>,
|
|
307
288
|
InferCreate<Schema<S>, K>
|
package/src/index.ts
CHANGED
|
@@ -143,7 +143,7 @@ import type {
|
|
|
143
143
|
HttpModelClient,
|
|
144
144
|
} from '@abloatai/transaction/transport/http';
|
|
145
145
|
import type { ParticipantKind } from '@abloatai/transaction/types/participant';
|
|
146
|
-
import type { PresenceSession } from '@abloatai/transaction/presence';
|
|
146
|
+
import type { PresenceSession, PresenceQueryOptions } from '@abloatai/transaction/presence';
|
|
147
147
|
import type {
|
|
148
148
|
CollaborationEventContext,
|
|
149
149
|
ModelEventEnvelope,
|
|
@@ -165,7 +165,7 @@ export interface ModelClientMeta {
|
|
|
165
165
|
readonly key: string;
|
|
166
166
|
readonly typename: string;
|
|
167
167
|
readonly presence?: {
|
|
168
|
-
get(recordId: string): readonly PresenceSession[];
|
|
168
|
+
get(recordId: string, options?: PresenceQueryOptions): readonly PresenceSession[];
|
|
169
169
|
subscribe(listener: () => void): () => void;
|
|
170
170
|
read(recordId: string): () => void;
|
|
171
171
|
};
|
|
@@ -192,7 +192,7 @@ type EntityHalf = Pick<ModelTarget, 'model' | 'id'>;
|
|
|
192
192
|
// while reading as though they differed.
|
|
193
193
|
export interface ModelCollaboration {
|
|
194
194
|
/** Session projections already held by this client's one presence store. */
|
|
195
|
-
presence(model: string, recordId?: string): readonly PresenceSession[];
|
|
195
|
+
presence(model: string, recordId?: string, options?: PresenceQueryOptions): readonly PresenceSession[];
|
|
196
196
|
/** Subscribe once to the connection-owned presence projection. */
|
|
197
197
|
onPresenceChange(listener: () => void): () => void;
|
|
198
198
|
/** Start one session-owned read activity and return its cleanup. */
|
|
@@ -1784,7 +1784,7 @@ export function createModelOperations<T, C>(
|
|
|
1784
1784
|
...(collaboration
|
|
1785
1785
|
? {
|
|
1786
1786
|
presence: {
|
|
1787
|
-
get: (recordId
|
|
1787
|
+
get: (recordId, options) => collaboration.presence(registeredModelName, recordId, options),
|
|
1788
1788
|
subscribe: (listener: () => void) => collaboration.onPresenceChange(listener),
|
|
1789
1789
|
read: (recordId: string) => {
|
|
1790
1790
|
const scope = { [schemaKey]: recordId };
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
streamTarget,
|
|
30
30
|
subTarget,
|
|
31
31
|
} from '@abloatai/transaction/coordination';
|
|
32
|
+
import { readStatus } from './status.js';
|
|
32
33
|
import { validateAbloOptions } from './validateAbloOptions.js';
|
|
33
34
|
import type { StoreCluster } from './storeCluster.js';
|
|
34
35
|
import { startStoreLifecycle } from './storeLifecycle.js';
|
|
@@ -575,7 +576,7 @@ export function buildReactiveEngine<const S extends SchemaRecord>(
|
|
|
575
576
|
modelRegistry,
|
|
576
577
|
hydration,
|
|
577
578
|
{
|
|
578
|
-
presence: (model, recordId) => presenceStream.forModel(model, recordId),
|
|
579
|
+
presence: (model, recordId, options) => presenceStream.forModel(model, recordId, options),
|
|
579
580
|
onPresenceChange: (listener) => presenceStream.onChange(listener),
|
|
580
581
|
startReadPresence: (target) => presenceStream.startRead(target),
|
|
581
582
|
modelEventTarget: (recordId) => {
|
|
@@ -842,11 +843,9 @@ export function buildReactiveEngine<const S extends SchemaRecord>(
|
|
|
842
843
|
return store.waitForConfirmation(modelName, modelId);
|
|
843
844
|
},
|
|
844
845
|
|
|
845
|
-
//
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
get syncStatus() {
|
|
849
|
-
return store.syncStatus;
|
|
846
|
+
// One core lifecycle projection; React selects the same observable reads.
|
|
847
|
+
get status() {
|
|
848
|
+
return readStatus(store);
|
|
850
849
|
},
|
|
851
850
|
|
|
852
851
|
// The humans capability owns the connection-backed presence projection.
|
|
@@ -858,11 +857,11 @@ export function buildReactiveEngine<const S extends SchemaRecord>(
|
|
|
858
857
|
|
|
859
858
|
// ── Internal accessors for framework integration ─────────────────
|
|
860
859
|
// These expose internal components for consumers that need direct
|
|
861
|
-
// access (e.g.,
|
|
860
|
+
// access (e.g., AbloProvider wiring its store context, collaboration
|
|
862
861
|
// events accessing the WebSocket handle, demand loaders accessing
|
|
863
862
|
// the pool). Prefixed with _ to signal "internal but stable."
|
|
864
863
|
|
|
865
|
-
/** The BaseSyncedStore — implements SyncStoreContract for
|
|
864
|
+
/** The BaseSyncedStore — implements SyncStoreContract for AbloStoreContext.Provider. */
|
|
866
865
|
get _store() { return store; },
|
|
867
866
|
|
|
868
867
|
/** The InstanceCache — for demand loaders that need pool.createFromData(). */
|