@abloatai/humans 0.64.0 → 0.64.2
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 +2 -0
- package/dist/client.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/local/BaseSyncedStore.d.ts +5 -4
- package/dist/local/BaseSyncedStore.js +13 -15
- package/dist/local/client/createModelOperations.d.ts +3 -3
- package/dist/local/client/createModelOperations.js +1 -1
- package/dist/local/client/reactiveEngine.js +3 -3
- 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/sync/groupChange.d.ts +7 -4
- package/dist/local/sync/groupChange.js +7 -4
- package/dist/local/sync/scopeGroups.d.ts +4 -1
- package/dist/presence/index.d.ts +2 -2
- package/dist/presence/index.js +2 -2
- package/dist/react/AbloProvider.js +6 -6
- package/dist/react/context.d.ts +6 -45
- package/dist/react/context.js +8 -20
- package/dist/react/createAbloReact.d.ts +9 -22
- package/dist/react/createAbloReact.js +5 -7
- package/dist/react/internalContext.d.ts +0 -9
- package/dist/react/useAblo.d.ts +7 -54
- package/dist/react/useAblo.js +6 -28
- 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 +3 -3
- package/dist/react/useMutators.js +3 -3
- package/dist/react/usePresence.d.ts +9 -9
- package/dist/react/usePresence.js +3 -7
- package/dist/react/useUndoScope.d.ts +2 -2
- package/dist/react/useUndoScope.js +4 -4
- package/dist/react.d.ts +2 -0
- package/dist/react.js +2 -0
- package/dist/reactRuntime.d.ts +1 -1
- package/dist/reactRuntime.js +1 -1
- package/package.json +2 -2
- package/src/Ablo.ts +2 -0
- package/src/client.ts +1 -1
- package/src/index.ts +2 -0
- package/src/local/BaseSyncedStore.ts +13 -15
- package/src/local/client/createModelOperations.ts +4 -4
- package/src/local/client/reactiveEngine.ts +3 -3
- package/src/local/mutators/defineMutators.ts +3 -51
- package/src/local/storeAccess.ts +10 -0
- package/src/local/sync/groupChange.ts +7 -4
- package/src/local/sync/scopeGroups.ts +4 -1
- package/src/presence/index.ts +4 -3
- package/src/react/AbloProvider.tsx +8 -8
- package/src/react/context.ts +10 -61
- package/src/react/createAbloReact.ts +12 -40
- package/src/react/internalContext.ts +0 -9
- package/src/react/useAblo.ts +19 -89
- package/src/react/useAbloClient.ts +14 -0
- package/src/react/useMutationFailure.ts +17 -0
- package/src/react/useMutators.ts +6 -6
- package/src/react/usePresence.ts +18 -18
- package/src/react/useUndoScope.ts +6 -6
- package/src/react.ts +2 -0
- package/src/reactRuntime.ts +2 -2
package/dist/react/useAblo.d.ts
CHANGED
|
@@ -1,66 +1,20 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AbloReads } from '../client.js';
|
|
2
2
|
import type { ModelClaim } from '@abloatai/transaction/coordination';
|
|
3
3
|
import { type ModelOperations } from '../local/client/createModelOperations.js';
|
|
4
4
|
import type { SchemaRecord } from '@abloatai/transaction/schema/schema';
|
|
5
|
-
import type {
|
|
6
|
-
/**
|
|
7
|
-
* The app's resolved schema-record type. It reads your `Register` module
|
|
8
|
-
* augmentation when you declare one and falls back to the loose
|
|
9
|
-
* {@link SchemaRecord} otherwise, so `useAblo()` returns a fully typed client
|
|
10
|
-
* without you passing `<(typeof schema)['models']>` at every call site.
|
|
11
|
-
*/
|
|
12
|
-
type DefaultModels = ResolveSchema extends {
|
|
13
|
-
models: infer M;
|
|
14
|
-
} ? M extends SchemaRecord ? M : SchemaRecord : SchemaRecord;
|
|
5
|
+
import type { ResolveModels as DefaultModels } from '@abloatai/transaction/types/global';
|
|
15
6
|
export type ModelClientSelector<R extends SchemaRecord, T, C> = (ablo: AbloReads<R>) => ModelOperations<T, C>;
|
|
16
7
|
export type AbloSelector<R extends SchemaRecord, T> = (ablo: AbloReads<R>) => T;
|
|
17
|
-
/**
|
|
18
|
-
* Reads Ablo from inside an `<AbloProvider>` subtree. Called with no arguments
|
|
19
|
-
* it returns the typed client for use in callbacks and effects; called with a
|
|
20
|
-
* selector it subscribes the component to a reactive read — such as one
|
|
21
|
-
* `ablo.<model>` row — and re-renders when that read changes.
|
|
22
|
-
*
|
|
23
|
-
* You can call it with no type arguments once you declare the `Register` module
|
|
24
|
-
* augmentation (`declare module '@abloatai/ablo' { interface Register {
|
|
25
|
-
* Schema: typeof schema } }`); the default type then resolves through your
|
|
26
|
-
* schema's models, so call sites stay clean:
|
|
27
|
-
*
|
|
28
|
-
* **Prefer the binding.** `createAbloReact(schema)` captures the schema once
|
|
29
|
-
* in your app's binding file and returns a `useAblo` that needs none of the
|
|
30
|
-
* typing arrangements below — no type argument, no `Register` declaration
|
|
31
|
-
* (see `react.md`). Passing an explicit schema type argument to THIS hook is
|
|
32
|
-
* deprecated in favor of that binding; it keeps working for shared packages
|
|
33
|
-
* that cannot bind a concrete schema.
|
|
34
|
-
*
|
|
35
|
-
* ```ts
|
|
36
|
-
* // With the Register augmentation (recommended):
|
|
37
|
-
* const ablo = useAblo();
|
|
38
|
-
* if (!ablo) return <Loading />;
|
|
39
|
-
* const doc = await ablo.records.get({ id }); // observational async server read
|
|
40
|
-
*
|
|
41
|
-
* // Reactive selector (a synchronous local snapshot). The selector's reads
|
|
42
|
-
* // are typed as snapshot rows — data fields + computeds, no relation
|
|
43
|
-
* // accessors — matching what the hook actually returns:
|
|
44
|
-
* const doc = useAblo((ablo) => ablo.records.local.get(id)) ?? serverDoc;
|
|
45
|
-
* const { claimed } = useAblo((ablo) => ablo.records, id);
|
|
46
|
-
*
|
|
47
|
-
* // Without the augmentation, pass the schema as a type argument:
|
|
48
|
-
* const ablo = useAblo<(typeof schema)['models']>();
|
|
49
|
-
* ```
|
|
50
|
-
*
|
|
51
|
-
* The client and its status are available during provider startup. Select
|
|
52
|
-
* `ablo.status` to display connection state; await `ablo.ready()` before
|
|
53
|
-
* operations that require an initialized client. Without a provider, the
|
|
54
|
-
* no-argument form returns `null` and selectors return `undefined`.
|
|
55
|
-
*/
|
|
56
|
-
export declare function useAblo<R extends SchemaRecord = DefaultModels>(): Ablo<R> | null;
|
|
8
|
+
/** Select a reactive snapshot or read a row with its current claims. */
|
|
57
9
|
export declare function useAblo<R extends SchemaRecord = DefaultModels, T = unknown>(select: AbloSelector<R, T>): T | undefined;
|
|
58
10
|
export declare function useAblo<T, C>(modelClient: ModelOperations<T, C>, id: string, options?: useAblo.Options<T>): useAblo.Result<T>;
|
|
59
11
|
export declare function useAblo<R extends SchemaRecord = DefaultModels, T = Record<string, unknown>, C = unknown>(select: ModelClientSelector<R, T, C>, id: string, options?: useAblo.Options<T>): useAblo.Result<T>;
|
|
60
|
-
/** @internal Resolve the nearest provider's client through one schema rebind. */
|
|
61
|
-
export declare function useAbloClient<R extends SchemaRecord>(): Ablo<R> | null;
|
|
62
12
|
/** Type annotations belong to the operation; most callers rely on inference. */
|
|
63
13
|
export declare namespace useAblo {
|
|
14
|
+
interface Bound<S extends SchemaRecord> {
|
|
15
|
+
<T>(select: AbloSelector<S, T>): T | undefined;
|
|
16
|
+
<T, C>(model: ModelOperations<T, C> | ModelClientSelector<S, T, C>, id: string, options?: Options<T>): Result<T>;
|
|
17
|
+
}
|
|
64
18
|
interface Options<T> {
|
|
65
19
|
/**
|
|
66
20
|
* An initial row, usually from a server component or a route loader. The hook
|
|
@@ -78,4 +32,3 @@ export declare namespace useAblo {
|
|
|
78
32
|
readonly claimed: boolean;
|
|
79
33
|
}
|
|
80
34
|
}
|
|
81
|
-
export {};
|
package/dist/react/useAblo.js
CHANGED
|
@@ -1,24 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { useCallback,
|
|
3
|
-
import {
|
|
2
|
+
import { useCallback, useEffect, useMemo } from 'react';
|
|
3
|
+
import { useAbloClient } from './useAbloClient.js';
|
|
4
4
|
import { getModelClientMeta, } from '../local/client/createModelOperations.js';
|
|
5
5
|
import { useReactive } from './useReactive.js';
|
|
6
6
|
const EMPTY_CLAIMS = Object.freeze([]);
|
|
7
|
-
|
|
8
|
-
* Restore the caller's schema generics on the context-held engine. React
|
|
9
|
-
* context erases generics (see `AbloInternalContextValue.engine`), so this is
|
|
10
|
-
* the one deliberate rebind point: the runtime value is the fully typed
|
|
11
|
-
* client, and `R` is the compile-time view the calling hook declared.
|
|
12
|
-
*/
|
|
13
|
-
function rebindEngine(engine) {
|
|
14
|
-
return engine;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* The reactive-read view of a client — the identical runtime object, with
|
|
18
|
-
* model reads typed as snapshot rows, because everything a selector returns
|
|
19
|
-
* is converted through `snapshotValue` before the hook hands it back. Same
|
|
20
|
-
* generic in and out, so this compiles with no schema rebinding.
|
|
21
|
-
*/
|
|
7
|
+
// Selector results are detached snapshots with no model methods or relations.
|
|
22
8
|
function reactiveReads(engine) {
|
|
23
9
|
return engine;
|
|
24
10
|
}
|
|
@@ -50,12 +36,11 @@ export function useAblo(modelOrSelect, id, options) {
|
|
|
50
36
|
// These dependencies define when the seed belongs to a different row.
|
|
51
37
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
52
38
|
const seed = useMemo(() => ({ received: false }), [engine, modelClient, id]);
|
|
53
|
-
const reading = modelOrSelect !== undefined;
|
|
54
39
|
const subscribe = useCallback((notify) => {
|
|
55
|
-
if (!engine
|
|
40
|
+
if (!engine)
|
|
56
41
|
return () => undefined;
|
|
57
42
|
return engine.claims.onChange(notify);
|
|
58
|
-
}, [engine
|
|
43
|
+
}, [engine]);
|
|
59
44
|
const value = useReactive(() => {
|
|
60
45
|
if (isSelectorOnly && typeof modelOrSelect === 'function') {
|
|
61
46
|
return engine ? modelOrSelect(reactiveReads(engine)) : undefined;
|
|
@@ -76,12 +61,5 @@ export function useAblo(modelOrSelect, id, options) {
|
|
|
76
61
|
if (id !== undefined && modelClient?.local.get(id) !== undefined)
|
|
77
62
|
seed.received = true;
|
|
78
63
|
}, [seed, modelClient, id, value]);
|
|
79
|
-
|
|
80
|
-
return value;
|
|
81
|
-
return engine;
|
|
82
|
-
}
|
|
83
|
-
/** @internal Resolve the nearest provider's client through one schema rebind. */
|
|
84
|
-
export function useAbloClient() {
|
|
85
|
-
const ctx = useContext(AbloInternalContext);
|
|
86
|
-
return ctx?.engine ? rebindEngine(ctx.engine) : null;
|
|
64
|
+
return value;
|
|
87
65
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AbloClient } from '../client.js';
|
|
2
|
+
import type { SchemaRecord } from '@abloatai/transaction/schema/schema';
|
|
3
|
+
import type { ResolveModels } from '@abloatai/transaction/types/global';
|
|
4
|
+
/** Writable client for event handlers. Available before ready(); null without a provider. */
|
|
5
|
+
export declare function useAbloClient<S extends SchemaRecord = ResolveModels>(): AbloClient<S> | null;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useContext } from 'react';
|
|
3
|
+
import { AbloInternalContext } from './internalContext.js';
|
|
4
|
+
/** Writable client for event handlers. Available before ready(); null without a provider. */
|
|
5
|
+
export function useAbloClient() {
|
|
6
|
+
const client = useContext(AbloInternalContext)?.engine;
|
|
7
|
+
// React context erases the schema; the application binding restores it.
|
|
8
|
+
return client ? client : null;
|
|
9
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AbloClient } from '../client.js';
|
|
2
|
+
import type { SchemaRecord } from '@abloatai/transaction/schema/schema';
|
|
3
|
+
/** Subscribe for this component's lifetime using the latest committed listener.
|
|
4
|
+
* Replacing the provider client moves the subscription; unmount removes it.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useMutationFailure(listener: Parameters<AbloClient<SchemaRecord>['onMutationFailure']>[0]): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useEffect, useEffectEvent } from 'react';
|
|
3
|
+
import { useAbloClient } from './useAbloClient.js';
|
|
4
|
+
/** Subscribe for this component's lifetime using the latest committed listener.
|
|
5
|
+
* Replacing the provider client moves the subscription; unmount removes it.
|
|
6
|
+
*/
|
|
7
|
+
export function useMutationFailure(listener) {
|
|
8
|
+
const client = useAbloClient();
|
|
9
|
+
const onFailure = useEffectEvent(listener);
|
|
10
|
+
useEffect(() => client?.onMutationFailure(onFailure), [client]);
|
|
11
|
+
}
|
|
@@ -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
|
|
@@ -37,9 +37,9 @@ export type InvokerFor<F> = F extends (options: infer O) => Promise<infer R> ? O
|
|
|
37
37
|
/** Mutator invokers (explicit schema arg). */
|
|
38
38
|
export declare function useMutators<S extends Schema, M extends MutatorDefs<S>>(schema: S, mutators: M, options?: useMutators.Options<S>): useMutators.Result<M>;
|
|
39
39
|
/** Mutator invokers via the `Register` module augmentation. Schema comes
|
|
40
|
-
* from the `
|
|
40
|
+
* from the `AbloProvider` store context; the mutator tree is typed against
|
|
41
41
|
* `ResolveSchema` at the call site. */
|
|
42
|
-
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
43
|
/** Optional annotations for custom mutation bindings. */
|
|
44
44
|
export declare namespace useMutators {
|
|
45
45
|
type Result<M> = {
|
|
@@ -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
|
+
}
|
|
@@ -2,14 +2,10 @@
|
|
|
2
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 { useAbloClient } from './
|
|
5
|
+
import { useAbloClient } from './useAbloClient.js';
|
|
6
6
|
import { useReactive } from './useReactive.js';
|
|
7
|
-
export function usePresence(modelOrSelect, recordId) {
|
|
7
|
+
export function usePresence(modelOrSelect, recordId, options) {
|
|
8
8
|
const engine = useAbloClient();
|
|
9
|
-
return usePresenceImpl(engine, modelOrSelect, recordId);
|
|
10
|
-
}
|
|
11
|
-
/** @internal Shared by the global hook and schema-bound React factory. */
|
|
12
|
-
export function usePresenceImpl(engine, modelOrSelect, recordId) {
|
|
13
9
|
if (recordId.length === 0) {
|
|
14
10
|
throw new AbloValidationError('usePresence requires a non-empty record id.', { code: 'invalid_request', param: 'recordId' });
|
|
15
11
|
}
|
|
@@ -23,7 +19,7 @@ export function usePresenceImpl(engine, modelOrSelect, recordId) {
|
|
|
23
19
|
throw new AbloValidationError('usePresence requires a model from the reactive Ablo client.', { code: 'invalid_request', param: 'modelClient' });
|
|
24
20
|
}
|
|
25
21
|
const subscribe = useCallback((notify) => presence?.subscribe(notify) ?? (() => undefined), [presence]);
|
|
26
|
-
const sessions = useReactive(() => presence?.get(recordId) ?? [], { subscribe });
|
|
22
|
+
const sessions = useReactive(() => presence?.get(recordId, options) ?? [], { subscribe });
|
|
27
23
|
useEffect(() => presence?.read(recordId), [presence, recordId]);
|
|
28
24
|
return sessions;
|
|
29
25
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
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';
|
|
3
|
+
import type { ResolveSchema, RequireRegisteredSchema } from '@abloatai/transaction/types/global';
|
|
4
4
|
/** Per-surface undo/redo (explicit schema arg). */
|
|
5
5
|
export declare function useUndoScope<S extends Schema>(schema: S, name: string, options?: UndoScopeOptions): useUndoScope.Result<S>;
|
|
6
6
|
/** Per-surface undo/redo via the `Register` module augmentation. */
|
|
7
|
-
export declare function useUndoScope(name: string
|
|
7
|
+
export declare function useUndoScope(name: RequireRegisteredSchema<string>, options?: UndoScopeOptions): useUndoScope.Result<ResolveSchema extends Schema ? ResolveSchema : Schema>;
|
|
8
8
|
/** The state returned by an undo scope; inferred for ordinary hook calls. */
|
|
9
9
|
export declare namespace useUndoScope {
|
|
10
10
|
interface Result<S extends Schema> {
|
|
@@ -1,7 +1,7 @@
|
|
|
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
6
|
/**
|
|
7
7
|
* Provides per-surface undo and redo for mutator invocations. Each named scope
|
|
@@ -21,7 +21,7 @@ import { AbloValidationError } from '@abloatai/transaction/errors';
|
|
|
21
21
|
* useHotkey('mod+z', () => { if (canUndo) void undo(); });
|
|
22
22
|
*/
|
|
23
23
|
// Module-level weak registry: `SyncStoreContract` → `UndoManager`.
|
|
24
|
-
// A single
|
|
24
|
+
// A single AbloProvider shares one manager across
|
|
25
25
|
// every useUndoScope call, so scopes with the same name are identity-equal.
|
|
26
26
|
// The hook implementation already operates on the runtime-wide `Schema` type;
|
|
27
27
|
// its overloads restore the caller's precise schema type at the public boundary,
|
|
@@ -37,7 +37,7 @@ function getManager(key, factory) {
|
|
|
37
37
|
return m;
|
|
38
38
|
}
|
|
39
39
|
export function useUndoScope(schemaOrName, nameOrOptions, maybeOptions) {
|
|
40
|
-
const { store, organizationId, schema: ctxSchema } =
|
|
40
|
+
const { store, organizationId, schema: ctxSchema } = useAbloStoreContext();
|
|
41
41
|
const isExplicit = typeof schemaOrName !== 'string';
|
|
42
42
|
const schema = isExplicit ? (schemaOrName) : ctxSchema;
|
|
43
43
|
const name = isExplicit ? nameOrOptions : schemaOrName;
|
|
@@ -48,7 +48,7 @@ export function useUndoScope(schemaOrName, nameOrOptions, maybeOptions) {
|
|
|
48
48
|
'zero-arg overload can read it from context.', { code: 'undo_scope_schema_missing' });
|
|
49
49
|
}
|
|
50
50
|
const scope = useMemo(() => {
|
|
51
|
-
// Store is the identity for the manager — one per
|
|
51
|
+
// Store is the identity for the manager — one per AbloProvider.
|
|
52
52
|
const manager = getManager(store, () => new UndoManager(schema, store, organizationId));
|
|
53
53
|
return manager.getScope(name, options);
|
|
54
54
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
package/dist/react.d.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';
|
package/dist/react.js
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';
|
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.64.
|
|
3
|
+
"version": "0.64.2",
|
|
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",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"directory": "packages/humans"
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
|
-
"@abloatai/transaction": "
|
|
88
|
+
"@abloatai/transaction": "0.64.2",
|
|
89
89
|
"mobx": "^6.13.7",
|
|
90
90
|
"uuid": "^11.1.0",
|
|
91
91
|
"zod": "^4.4.3"
|
package/src/Ablo.ts
CHANGED
|
@@ -267,6 +267,7 @@ export namespace Ablo {
|
|
|
267
267
|
/** Payload delivered by the core client's onMutationFailure subscription. */
|
|
268
268
|
export type MutationFailure = Parameters<Parameters<AbloClient<SchemaRecord>['onMutationFailure']>[0]>[0];
|
|
269
269
|
/** Current client lifecycle, also selected through React's useAblo. */
|
|
270
|
+
export type Store = import('./local/storeContract.js').SyncStoreContract;
|
|
270
271
|
export type Status = import('./local/client/status.js').ClientStatus;
|
|
271
272
|
|
|
272
273
|
// ── Factory options ────────────────────────────────────────────────
|
|
@@ -320,6 +321,7 @@ export namespace Ablo {
|
|
|
320
321
|
* different schemas.
|
|
321
322
|
*/
|
|
322
323
|
export type ResolveSchema = _Global.ResolveSchema;
|
|
324
|
+
export type ResolveClaimMeta = _Global.ResolveClaimMeta;
|
|
323
325
|
/**
|
|
324
326
|
* `ResolveSchema` guaranteed to satisfy the `Schema` bound. `ResolveSchema`
|
|
325
327
|
* falls back to a loose `{ models }` shape when nothing is registered, which
|
package/src/client.ts
CHANGED
|
@@ -256,7 +256,7 @@ interface AbloCore<S extends SchemaRecord> {
|
|
|
256
256
|
|
|
257
257
|
/**
|
|
258
258
|
* The internal store. It implements {@link SyncStoreContract} — pass it to
|
|
259
|
-
* `
|
|
259
|
+
* `AbloStoreContext.Provider` so the SDK's React data hooks
|
|
260
260
|
* hooks can reach it.
|
|
261
261
|
*/
|
|
262
262
|
readonly _store: SyncStoreContract;
|
package/src/index.ts
CHANGED
|
@@ -379,18 +379,15 @@ export class BaseSyncedStore<
|
|
|
379
379
|
this.syncWebSocket.sendCollaborationEvent(messageType, payload);
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
-
// ──
|
|
382
|
+
// ── Group interest and loading ──────────────────────────────────
|
|
383
383
|
//
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
//
|
|
391
|
-
// never reject when the transport is offline (see
|
|
392
|
-
// {@link SubscriptionManager.reconcile}); the on-connect `resync` pushes
|
|
393
|
-
// whatever interest accumulated.
|
|
384
|
+
// Authority comes from the server-issued session. Enter/leave track what
|
|
385
|
+
// this connection wants to receive; pin/unpin keep an active scope subscribed.
|
|
386
|
+
// All resolve selectors through scopeToGroups. Hydration separately loads
|
|
387
|
+
// a scoped baseline into the local pool. Leaving interest does not revoke
|
|
388
|
+
// authority or selectively evict cached records.
|
|
389
|
+
// Offline interest is recorded locally and reconciled when the connection
|
|
390
|
+
// opens; recording interest is not confirmation of a server subscription.
|
|
394
391
|
|
|
395
392
|
private scopeToGroups(scope: GroupScope): string[] {
|
|
396
393
|
return resolveScopeGroups(scope, this.schema);
|
|
@@ -399,10 +396,11 @@ export class BaseSyncedStore<
|
|
|
399
396
|
/**
|
|
400
397
|
* Bring a scope into view and subscribe to its sync groups. With
|
|
401
398
|
* `{ hydrate: true }`, also backfill the groups' current state into the pool
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
* Hydration is best-effort
|
|
405
|
-
*
|
|
399
|
+
* after subscription reconciliation. Offline reconciliation may only record
|
|
400
|
+
* interest locally; it is not proof that the server is delivering changes.
|
|
401
|
+
* Hydration is best-effort: failure leaves the groups unmarked for retry and
|
|
402
|
+
* does not reject `enterScope`. Snapshot application uses version guards so
|
|
403
|
+
* older baseline rows cannot overwrite newer deltas already in the pool.
|
|
406
404
|
*/
|
|
407
405
|
enterScope(scope: GroupScope, opts?: { hydrate?: boolean }): Promise<void> {
|
|
408
406
|
const groups = this.scopeToGroups(scope);
|
|
@@ -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 };
|
|
@@ -576,7 +576,7 @@ export function buildReactiveEngine<const S extends SchemaRecord>(
|
|
|
576
576
|
modelRegistry,
|
|
577
577
|
hydration,
|
|
578
578
|
{
|
|
579
|
-
presence: (model, recordId) => presenceStream.forModel(model, recordId),
|
|
579
|
+
presence: (model, recordId, options) => presenceStream.forModel(model, recordId, options),
|
|
580
580
|
onPresenceChange: (listener) => presenceStream.onChange(listener),
|
|
581
581
|
startReadPresence: (target) => presenceStream.startRead(target),
|
|
582
582
|
modelEventTarget: (recordId) => {
|
|
@@ -857,11 +857,11 @@ export function buildReactiveEngine<const S extends SchemaRecord>(
|
|
|
857
857
|
|
|
858
858
|
// ── Internal accessors for framework integration ─────────────────
|
|
859
859
|
// These expose internal components for consumers that need direct
|
|
860
|
-
// access (e.g.,
|
|
860
|
+
// access (e.g., AbloProvider wiring its store context, collaboration
|
|
861
861
|
// events accessing the WebSocket handle, demand loaders accessing
|
|
862
862
|
// the pool). Prefixed with _ to signal "internal but stable."
|
|
863
863
|
|
|
864
|
-
/** The BaseSyncedStore — implements SyncStoreContract for
|
|
864
|
+
/** The BaseSyncedStore — implements SyncStoreContract for AbloStoreContext.Provider. */
|
|
865
865
|
get _store() { return store; },
|
|
866
866
|
|
|
867
867
|
/** The InstanceCache — for demand loaders that need pool.createFromData(). */
|
|
@@ -1,76 +1,28 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Declares a tree of named custom mutators grouped by model key. Each mutator is
|
|
3
|
-
* a plain async function that receives `{ tx, args }` and composes any number of
|
|
4
|
-
* `tx.mutations.*` and `tx.read.*` calls to carry out a named operation, such as
|
|
5
|
-
* `sections.createWithBlocks`.
|
|
6
|
-
*
|
|
7
|
-
* The function is purely a place for types to anchor and returns its input
|
|
8
|
-
* unchanged; the runtime that dispatches a mutator lives elsewhere — the
|
|
9
|
-
* transaction object it receives and the React hook that invokes it. Because
|
|
10
|
-
* `defineMutators(schema, { ... })` returns the exact object you wrote,
|
|
11
|
-
* `typeof mutators` carries every mutator's precise `args` and result types
|
|
12
|
-
* through to wherever they are invoked.
|
|
13
|
-
*/
|
|
14
|
-
|
|
1
|
+
/** Define typed named operations. Pass the schema explicitly across package boundaries. */
|
|
15
2
|
import type { Schema } from '@abloatai/transaction/schema/schema';
|
|
16
3
|
import type { Transaction } from './Transaction.js';
|
|
17
|
-
import type { ResolveSchema } from '@abloatai/transaction/types/global';
|
|
4
|
+
import type { ResolveSchema, RequireRegisteredSchema } from '@abloatai/transaction/types/global';
|
|
18
5
|
|
|
19
|
-
/**
|
|
20
|
-
* `ResolveSchema` narrowed to satisfy the `Schema` bound — mirrors
|
|
21
|
-
* {@link Ablo.RegisteredSchema}. When nothing is registered, `ResolveSchema`
|
|
22
|
-
* is a loose `{ models }` shape that doesn't extend `Schema`, so we fall back
|
|
23
|
-
* to `Schema` to keep the mutator tree typed rather than collapsing.
|
|
24
|
-
*/
|
|
25
6
|
type RegisteredSchema = ResolveSchema extends Schema ? ResolveSchema : Schema;
|
|
26
7
|
|
|
27
|
-
/**
|
|
28
|
-
* The signature of a single custom mutator. The engine supplies `tx`; you control
|
|
29
|
-
* `args`, in whatever shape you like, and the resolved return value. `TArgs` and
|
|
30
|
-
* `TResult` are bounded by `unknown` rather than `any`, so a mixed tree of
|
|
31
|
-
* mutators can be typed together without falling back to `any`.
|
|
32
|
-
*/
|
|
33
8
|
export type MutatorFn<S extends Schema, TArgs, TResult = void> = (
|
|
34
9
|
options: { tx: Transaction<S>; args: TArgs },
|
|
35
10
|
) => Promise<TResult>;
|
|
36
11
|
|
|
37
|
-
/**
|
|
38
|
-
* The shape {@link defineMutators} accepts: an optional record per model key
|
|
39
|
-
* whose values are named mutator functions. The `unknown` bounds keep the public
|
|
40
|
-
* boundary type-safe without `any`; when you write your mutators inline,
|
|
41
|
-
* TypeScript still infers the concrete `args` and result of each function, so the
|
|
42
|
-
* `unknown` here is only a ceiling, not what you end up working with.
|
|
43
|
-
*/
|
|
44
12
|
export type MutatorDefs<S extends Schema> = {
|
|
45
13
|
[K in keyof S['models']]?: Record<string, MutatorFn<S, never, unknown>>;
|
|
46
14
|
};
|
|
47
15
|
|
|
48
|
-
/**
|
|
49
|
-
* Returns the mutators object unchanged while constraining its shape against the
|
|
50
|
-
* schema. The `S` generic pins the model keys, and the `M` generic is inferred as
|
|
51
|
-
* a `const`, so each mutator's literal signature survives. There is no runtime
|
|
52
|
-
* work here; the function exists purely as a place for type inference to anchor.
|
|
53
|
-
*/
|
|
54
16
|
export function defineMutators<
|
|
55
17
|
S extends Schema,
|
|
56
18
|
const M extends MutatorDefs<S>,
|
|
57
19
|
>(_schema: S, mutators: M): M;
|
|
58
|
-
/**
|
|
59
|
-
* Register-anchored overload: omit the schema value and the tree is typed
|
|
60
|
-
* against `ResolveSchema` (this app's registered schema). Shared product code
|
|
61
|
-
* used across apps that bind different schemas should use this form — it moves
|
|
62
|
-
* with each app's `Register` instead of pinning one concrete schema, so the
|
|
63
|
-
* mutator tree stays assignable at every consumer (which reads the same
|
|
64
|
-
* `Register`). See docs/plans/per-product-schema-projections.md.
|
|
65
|
-
*/
|
|
66
20
|
export function defineMutators<const M extends MutatorDefs<RegisteredSchema>>(
|
|
67
|
-
mutators: M
|
|
21
|
+
mutators: RequireRegisteredSchema<M>,
|
|
68
22
|
): M;
|
|
69
23
|
export function defineMutators(
|
|
70
24
|
schemaOrMutators: Schema | MutatorDefs<Schema>,
|
|
71
25
|
maybeMutators?: MutatorDefs<Schema>,
|
|
72
26
|
): MutatorDefs<Schema> {
|
|
73
|
-
// The schema argument is a type anchor only — never read at runtime. With one
|
|
74
|
-
// argument the mutator tree is in the first slot; with two it's in the second.
|
|
75
27
|
return (maybeMutators ?? schemaOrMutators) as MutatorDefs<Schema>;
|
|
76
28
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AbloClient } from '../client.js';
|
|
2
|
+
import type { SchemaRecord } from '@abloatai/transaction/schema/schema';
|
|
3
|
+
import type { SyncStoreContract } from './storeContract.js';
|
|
4
|
+
|
|
5
|
+
/** Access the supported local store contract for custom framework adapters,
|
|
6
|
+
* demand loading, scope management and custom undo infrastructure.
|
|
7
|
+
*/
|
|
8
|
+
export function getAbloStore<S extends SchemaRecord>(client: AbloClient<S>): SyncStoreContract {
|
|
9
|
+
return client._store;
|
|
10
|
+
}
|