@abloatai/humans 0.64.0 → 0.64.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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/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/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/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
|
@@ -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.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",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"directory": "packages/humans"
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
|
-
"@abloatai/transaction": "
|
|
88
|
+
"@abloatai/transaction": "0.64.1",
|
|
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
|
@@ -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
|
+
}
|
package/src/presence/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type PresenceProjection,
|
|
5
5
|
type PresenceProjectionEvents,
|
|
6
6
|
type PresenceView,
|
|
7
|
+
type PresenceQueryOptions,
|
|
7
8
|
} from '@abloatai/transaction/presence';
|
|
8
9
|
import type { PresenceTarget } from '@abloatai/transaction/presence';
|
|
9
10
|
import {
|
|
@@ -16,7 +17,7 @@ type PresenceTransport = PresenceProjectionEvents & ReadActivityTransport;
|
|
|
16
17
|
|
|
17
18
|
/** Reactive-client presence backed by the client's existing live connection. */
|
|
18
19
|
export interface ReactivePresence extends PresenceView {
|
|
19
|
-
forModel(model: string, recordId?: string): ReturnType<PresenceProjection['forModel']>;
|
|
20
|
+
forModel(model: string, recordId?: string, options?: PresenceQueryOptions): ReturnType<PresenceProjection['forModel']>;
|
|
20
21
|
onChange(listener: () => void): () => void;
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -87,9 +88,9 @@ export function createPresence(
|
|
|
87
88
|
lifetime.stop();
|
|
88
89
|
};
|
|
89
90
|
},
|
|
90
|
-
forModel(model, recordId) {
|
|
91
|
+
forModel(model, recordId, options) {
|
|
91
92
|
version.get();
|
|
92
|
-
return projection?.forModel(model, recordId) ?? [];
|
|
93
|
+
return projection?.forModel(model, recordId, options) ?? [];
|
|
93
94
|
},
|
|
94
95
|
dispose() {
|
|
95
96
|
for (const read of reads) read.dispose();
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from 'react';
|
|
12
12
|
import type { Schema, SchemaRecord } from '@abloatai/transaction/schema/schema';
|
|
13
13
|
import type { AbloClient as Ablo } from '../client.js';
|
|
14
|
-
import {
|
|
14
|
+
import { AbloStoreContext, type SyncStoreContract } from './context.js';
|
|
15
15
|
import { AbloInternalContext, type AbloInternalContextValue } from './internalContext.js';
|
|
16
16
|
import { AbloValidationError } from '@abloatai/transaction/errors';
|
|
17
17
|
import { useAblo } from './useAblo.js';
|
|
@@ -100,7 +100,7 @@ export function AbloProvider<R extends SchemaRecord = SchemaRecord>(
|
|
|
100
100
|
// onSessionExpired. Credential cleanup lives in the CLIENT, so direct
|
|
101
101
|
// consumers and React consumers have the same security boundary.
|
|
102
102
|
// 2. Drive `ready()` (idempotent) so bootstrap starts on mount, then read the
|
|
103
|
-
// resolved org scope for
|
|
103
|
+
// resolved org scope for the Ablo store context.
|
|
104
104
|
// It does NOT dispose the client (consumer-owned) and does NOT touch auth.
|
|
105
105
|
useEffect(() => {
|
|
106
106
|
let stale = false;
|
|
@@ -158,12 +158,12 @@ export function AbloProvider<R extends SchemaRecord = SchemaRecord>(
|
|
|
158
158
|
return () => { window.removeEventListener('beforeunload', handler); };
|
|
159
159
|
}, [engine, preventUnsavedChanges]);
|
|
160
160
|
|
|
161
|
-
// ──
|
|
161
|
+
// ── Store context value (for Ablo data hooks) ────────────────────
|
|
162
162
|
//
|
|
163
163
|
// The engine is always present (it's the `client` prop), but its org scope is
|
|
164
|
-
// unknown until `ready()` resolves identity — so
|
|
164
|
+
// unknown until `ready()` resolves identity — so the store context is null until
|
|
165
165
|
// then, which drives the initial fallback below.
|
|
166
|
-
const
|
|
166
|
+
const storeContextValue = useMemo(() => {
|
|
167
167
|
const currentAccountScope =
|
|
168
168
|
(resolvedScope?.engine === engine ? resolvedScope.account : null) ??
|
|
169
169
|
(engine._store as SyncStoreContract & { orgId?: string }).orgId;
|
|
@@ -189,15 +189,15 @@ export function AbloProvider<R extends SchemaRecord = SchemaRecord>(
|
|
|
189
189
|
|
|
190
190
|
return (
|
|
191
191
|
<AbloInternalContext.Provider value={internalValue}>
|
|
192
|
-
<
|
|
192
|
+
<AbloStoreContext.Provider value={storeContextValue}>
|
|
193
193
|
{passthrough ? (
|
|
194
194
|
children
|
|
195
|
-
) :
|
|
195
|
+
) : storeContextValue ? (
|
|
196
196
|
<BootstrapGate key={engineKey} fallback={fallback}>
|
|
197
197
|
{children}
|
|
198
198
|
</BootstrapGate>
|
|
199
199
|
) : fallback}
|
|
200
|
-
</
|
|
200
|
+
</AbloStoreContext.Provider>
|
|
201
201
|
</AbloInternalContext.Provider>
|
|
202
202
|
);
|
|
203
203
|
}
|
package/src/react/context.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { createContext,
|
|
3
|
+
import { createContext, useContext } from 'react';
|
|
4
4
|
import type { Schema } from '@abloatai/transaction/schema/schema';
|
|
5
5
|
import type { SyncStoreContract } from '../local/storeContract.js';
|
|
6
6
|
import { AbloValidationError } from '@abloatai/transaction/errors';
|
|
@@ -13,77 +13,26 @@ export type {
|
|
|
13
13
|
LocalMutation,
|
|
14
14
|
} from '../local/storeContract.js';
|
|
15
15
|
|
|
16
|
-
export interface
|
|
16
|
+
export interface AbloStoreContextValue {
|
|
17
17
|
store: SyncStoreContract;
|
|
18
18
|
/** The organization id used as the default scope for reads and writes. */
|
|
19
19
|
organizationId: string;
|
|
20
|
-
/**
|
|
21
|
-
* An optional schema. When provided, hooks that take a model by name (such as
|
|
22
|
-
* `useQuery('items')`) read that model's metadata from this schema, so
|
|
23
|
-
* callers don't pass a schema at every call site. When omitted, those hooks
|
|
24
|
-
* require the schema as an argument instead.
|
|
25
|
-
*
|
|
26
|
-
* The field is loosely typed here because a single runtime context value is
|
|
27
|
-
* shared by every hook. Precise per-model types come from your `Register`
|
|
28
|
-
* module augmentation
|
|
29
|
-
* (`declare module '@abloatai/ablo' { interface Register { Schema: typeof schema } }`),
|
|
30
|
-
* not from this reference.
|
|
31
|
-
*/
|
|
20
|
+
/** Runtime schema used by ambient mutator overloads. */
|
|
32
21
|
schema?: Schema;
|
|
33
22
|
}
|
|
34
23
|
|
|
35
|
-
export const
|
|
24
|
+
export const AbloStoreContext = createContext<AbloStoreContextValue | null>(null);
|
|
36
25
|
|
|
37
26
|
/**
|
|
38
|
-
* Reads the
|
|
39
|
-
*
|
|
40
|
-
* context by rendering the internal {@link SyncProvider}; you wire
|
|
41
|
-
* `<AbloProvider client={ablo}>` rather than touching this directly.
|
|
27
|
+
* Reads the store scope owned by `<AbloProvider>`, throwing a clear error when
|
|
28
|
+
* no provider is mounted above it.
|
|
42
29
|
*/
|
|
43
|
-
export function
|
|
44
|
-
const ctx = useContext(
|
|
30
|
+
export function useAbloStoreContext(): AbloStoreContextValue {
|
|
31
|
+
const ctx = useContext(AbloStoreContext);
|
|
45
32
|
if (!ctx) {
|
|
46
|
-
throw new AbloValidationError('
|
|
47
|
-
code: '
|
|
33
|
+
throw new AbloValidationError('Ablo hooks must be used within an <AbloProvider>.', {
|
|
34
|
+
code: 'ablo_context_missing_provider',
|
|
48
35
|
});
|
|
49
36
|
}
|
|
50
37
|
return ctx;
|
|
51
38
|
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Props for SyncProvider.
|
|
55
|
-
*/
|
|
56
|
-
export interface SyncProviderProps {
|
|
57
|
-
/** The sync store, which must implement {@link SyncStoreContract}. */
|
|
58
|
-
store: SyncStoreContract;
|
|
59
|
-
/** The organization id used as the default scope for reads and writes. */
|
|
60
|
-
organizationId: string;
|
|
61
|
-
/**
|
|
62
|
-
* An optional schema. Provide it to enable hooks that take a model by name
|
|
63
|
-
* (such as `useQuery('items')`); the model types also narrow through your
|
|
64
|
-
* `Register` augmentation. Omit it to pass the schema to those hooks directly
|
|
65
|
-
* instead.
|
|
66
|
-
*/
|
|
67
|
-
schema?: Schema;
|
|
68
|
-
children?: ReactNode;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* A low-level provider that places a built sync store on React context so the
|
|
73
|
-
* data hooks can reach it. This is an internal building block: it is not part
|
|
74
|
-
* of the package's public entry point. Reach for `<AbloProvider>` instead,
|
|
75
|
-
* which builds the store from your `Ablo({ schema, apiKey })` client and
|
|
76
|
-
* renders this provider underneath.
|
|
77
|
-
*/
|
|
78
|
-
export function SyncProvider({
|
|
79
|
-
store,
|
|
80
|
-
organizationId,
|
|
81
|
-
schema,
|
|
82
|
-
children,
|
|
83
|
-
}: SyncProviderProps) {
|
|
84
|
-
return createElement(
|
|
85
|
-
SyncContext.Provider,
|
|
86
|
-
{ value: { store, organizationId, schema } },
|
|
87
|
-
children
|
|
88
|
-
);
|
|
89
|
-
}
|
|
@@ -1,47 +1,23 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Capture schema inference once while reusing module-level React functions.
|
|
5
|
-
* This helper creates no components, hooks, contexts or client instances.
|
|
6
|
-
*
|
|
7
|
-
* Define the app binding at module scope:
|
|
8
|
-
* `export const { AbloProvider, useAblo, usePresence } = createAbloReact(schema)`.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
3
|
import type { ReactElement } from 'react';
|
|
4
|
+
import { useAbloClient } from './useAbloClient.js';
|
|
5
|
+
import { useMutationFailure } from './useMutationFailure.js';
|
|
12
6
|
import { AbloProvider } from './AbloProvider.js';
|
|
13
|
-
import {
|
|
14
|
-
useAblo,
|
|
15
|
-
type AbloSelector,
|
|
16
|
-
type ModelClientSelector,
|
|
17
|
-
} from './useAblo.js';
|
|
7
|
+
import { useAblo } from './useAblo.js';
|
|
18
8
|
import type { AbloClient as Ablo } from '../client.js';
|
|
19
|
-
import type { ModelOperations } from '../local/client/createModelOperations.js';
|
|
20
9
|
import type { Schema, SchemaRecord } from '@abloatai/transaction/schema/schema';
|
|
21
|
-
import { usePresence
|
|
22
|
-
import type { PresenceSession } from '@abloatai/transaction/presence';
|
|
10
|
+
import { usePresence } from './usePresence.js';
|
|
23
11
|
|
|
24
|
-
/**
|
|
12
|
+
/** Shared provider and hooks specialized to one schema. */
|
|
25
13
|
export interface AbloReactBinding<S extends SchemaRecord> {
|
|
26
|
-
/** `AbloProvider` with its `client` prop typed `Ablo<S>` — same component,
|
|
27
|
-
* no per-app generics. */
|
|
28
14
|
AbloProvider: (props: AbloProvider.Props<S>) => ReactElement;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
<T>(select: AbloSelector<S, T>): T | undefined;
|
|
34
|
-
<T, C>(
|
|
35
|
-
modelClientOrSelect: ModelOperations<T, C> | ModelClientSelector<S, T, C>,
|
|
36
|
-
id: string,
|
|
37
|
-
options?: useAblo.Options<T>,
|
|
38
|
-
): useAblo.Result<T>;
|
|
39
|
-
};
|
|
15
|
+
useAblo: useAblo.Bound<S>;
|
|
16
|
+
/** Writable client for actions; useAblo(selector) supplies render snapshots. */
|
|
17
|
+
useAbloClient: () => Ablo<S> | null;
|
|
18
|
+
useMutationFailure: typeof useMutationFailure;
|
|
40
19
|
/** Declare and reactively read record presence with the same model clients. */
|
|
41
|
-
usePresence: <
|
|
42
|
-
modelOrSelect: ModelOperations<T, C> | PresenceModelSelector<S, T, C>,
|
|
43
|
-
recordId: string,
|
|
44
|
-
) => readonly PresenceSession[];
|
|
20
|
+
usePresence: usePresence.Bound<S>;
|
|
45
21
|
}
|
|
46
22
|
|
|
47
23
|
/** Bind the existing React functions to one schema's types. */
|
|
@@ -50,10 +26,6 @@ export function createAbloReact<S extends SchemaRecord>(
|
|
|
50
26
|
): AbloReactBinding<S> {
|
|
51
27
|
void schema;
|
|
52
28
|
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
// type tests verify the specialization; no runtime value changes.
|
|
56
|
-
// Specialize types only. Every binding uses the same module-level functions,
|
|
57
|
-
// so calling this helper again cannot change component identity or reset state.
|
|
58
|
-
return { AbloProvider, useAblo, usePresence } as AbloReactBinding<S>;
|
|
29
|
+
// Specialize the shared functions without creating new contexts or identities.
|
|
30
|
+
return { AbloProvider, useAblo, useAbloClient, useMutationFailure, usePresence } as AbloReactBinding<S>;
|
|
59
31
|
}
|