@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
package/dist/Ablo.d.ts
CHANGED
|
@@ -89,6 +89,7 @@ export declare namespace Ablo {
|
|
|
89
89
|
/** Payload delivered by the core client's onMutationFailure subscription. */
|
|
90
90
|
type MutationFailure = Parameters<Parameters<AbloClient<SchemaRecord>['onMutationFailure']>[0]>[0];
|
|
91
91
|
/** Current client lifecycle, also selected through React's useAblo. */
|
|
92
|
+
type Store = import('./local/storeContract.js').SyncStoreContract;
|
|
92
93
|
type Status = import('./local/client/status.js').ClientStatus;
|
|
93
94
|
type Options<S extends SchemaRecord = SchemaRecord> = AbloOptions<S>;
|
|
94
95
|
/**
|
|
@@ -123,6 +124,7 @@ export declare namespace Ablo {
|
|
|
123
124
|
* different schemas.
|
|
124
125
|
*/
|
|
125
126
|
type ResolveSchema = _Global.ResolveSchema;
|
|
127
|
+
type ResolveClaimMeta = _Global.ResolveClaimMeta;
|
|
126
128
|
/**
|
|
127
129
|
* `ResolveSchema` guaranteed to satisfy the `Schema` bound. `ResolveSchema`
|
|
128
130
|
* falls back to a loose `{ models }` shape when nothing is registered, which
|
package/dist/client.d.ts
CHANGED
|
@@ -210,7 +210,7 @@ interface AbloCore<S extends SchemaRecord> {
|
|
|
210
210
|
subscribe<K extends keyof CoreSyncEventMap>(event: K, handler: (...args: CoreSyncEventMap[K]) => void): () => void;
|
|
211
211
|
/**
|
|
212
212
|
* The internal store. It implements {@link SyncStoreContract} — pass it to
|
|
213
|
-
* `
|
|
213
|
+
* `AbloStoreContext.Provider` so the SDK's React data hooks
|
|
214
214
|
* hooks can reach it.
|
|
215
215
|
*/
|
|
216
216
|
readonly _store: SyncStoreContract;
|
package/dist/index.d.ts
CHANGED
|
@@ -9,3 +9,4 @@ export { createTransaction, type Transaction, type ReaderFindOptions, } from './
|
|
|
9
9
|
export { ClaimLog, formatClaim, formatConflict, type ClaimLogEntry, } from './local/coordination/ClaimLog.js';
|
|
10
10
|
export { isStorageOpenTimeout, } from './local/stores/openIDBWithTimeout.js';
|
|
11
11
|
export type { CommitLatencySample, } from './local/transactions/mutations/commitLatency.js';
|
|
12
|
+
export { getAbloStore } from './local/storeAccess.js';
|
package/dist/index.js
CHANGED
|
@@ -4,3 +4,4 @@ export { defineMutators, } from './local/mutators/defineMutators.js';
|
|
|
4
4
|
export { createTransaction, } from './local/mutators/Transaction.js';
|
|
5
5
|
export { ClaimLog, formatClaim, formatConflict, } from './local/coordination/ClaimLog.js';
|
|
6
6
|
export { isStorageOpenTimeout, } from './local/stores/openIDBWithTimeout.js';
|
|
7
|
+
export { getAbloStore } from './local/storeAccess.js';
|
|
@@ -22,14 +22,14 @@ export type { Claim, ClaimHeartbeat, ClaimHeartbeatOptions, HeldClaim, HeldLease
|
|
|
22
22
|
import type { ClaimApi, ClaimAttemptEvent, LocalCountOptions, LocalReadOptions } from '@abloatai/transaction/client/resources/modelOperations';
|
|
23
23
|
import type { HttpModelClient } from '@abloatai/transaction/transport/http';
|
|
24
24
|
import type { ParticipantKind } from '@abloatai/transaction/types/participant';
|
|
25
|
-
import type { PresenceSession } from '@abloatai/transaction/presence';
|
|
25
|
+
import type { PresenceSession, PresenceQueryOptions } from '@abloatai/transaction/presence';
|
|
26
26
|
import type { CollaborationEventContext, ModelEventEnvelope, ModelEventInput, ModelEventTarget } from '@abloatai/transaction/collaboration';
|
|
27
27
|
import { type ReadSetContext } from '@abloatai/transaction/internal/read-set';
|
|
28
28
|
export interface ModelClientMeta {
|
|
29
29
|
readonly key: string;
|
|
30
30
|
readonly typename: string;
|
|
31
31
|
readonly presence?: {
|
|
32
|
-
get(recordId: string): readonly PresenceSession[];
|
|
32
|
+
get(recordId: string, options?: PresenceQueryOptions): readonly PresenceSession[];
|
|
33
33
|
subscribe(listener: () => void): () => void;
|
|
34
34
|
read(recordId: string): () => void;
|
|
35
35
|
};
|
|
@@ -42,7 +42,7 @@ export declare function getModelClientMeta(modelClient: unknown): ModelClientMet
|
|
|
42
42
|
type EntityHalf = Pick<ModelTarget, 'model' | 'id'>;
|
|
43
43
|
export interface ModelCollaboration {
|
|
44
44
|
/** Session projections already held by this client's one presence store. */
|
|
45
|
-
presence(model: string, recordId?: string): readonly PresenceSession[];
|
|
45
|
+
presence(model: string, recordId?: string, options?: PresenceQueryOptions): readonly PresenceSession[];
|
|
46
46
|
/** Subscribe once to the connection-owned presence projection. */
|
|
47
47
|
onPresenceChange(listener: () => void): () => void;
|
|
48
48
|
/** Start one session-owned read activity and return its cleanup. */
|
|
@@ -1091,7 +1091,7 @@ hydration, collaboration, readSetContext) {
|
|
|
1091
1091
|
...(collaboration
|
|
1092
1092
|
? {
|
|
1093
1093
|
presence: {
|
|
1094
|
-
get: (recordId) => collaboration.presence(registeredModelName, recordId),
|
|
1094
|
+
get: (recordId, options) => collaboration.presence(registeredModelName, recordId, options),
|
|
1095
1095
|
subscribe: (listener) => collaboration.onPresenceChange(listener),
|
|
1096
1096
|
read: (recordId) => {
|
|
1097
1097
|
const scope = { [schemaKey]: recordId };
|
|
@@ -395,7 +395,7 @@ export function buildReactiveEngine(inputs) {
|
|
|
395
395
|
for (const [schemaKey, modelDef] of Object.entries(schema.models)) {
|
|
396
396
|
const registeredModelName = modelDef.typename ?? schemaKey;
|
|
397
397
|
modelProxies[schemaKey] = createModelOperations(schemaKey, registeredModelName, objectPool, syncClient, modelRegistry, hydration, {
|
|
398
|
-
presence: (model, recordId) => presenceStream.forModel(model, recordId),
|
|
398
|
+
presence: (model, recordId, options) => presenceStream.forModel(model, recordId, options),
|
|
399
399
|
onPresenceChange: (listener) => presenceStream.onChange(listener),
|
|
400
400
|
startReadPresence: (target) => presenceStream.startRead(target),
|
|
401
401
|
modelEventTarget: (recordId) => {
|
|
@@ -617,10 +617,10 @@ export function buildReactiveEngine(inputs) {
|
|
|
617
617
|
schema,
|
|
618
618
|
// ── Internal accessors for framework integration ─────────────────
|
|
619
619
|
// These expose internal components for consumers that need direct
|
|
620
|
-
// access (e.g.,
|
|
620
|
+
// access (e.g., AbloProvider wiring its store context, collaboration
|
|
621
621
|
// events accessing the WebSocket handle, demand loaders accessing
|
|
622
622
|
// the pool). Prefixed with _ to signal "internal but stable."
|
|
623
|
-
/** The BaseSyncedStore — implements SyncStoreContract for
|
|
623
|
+
/** The BaseSyncedStore — implements SyncStoreContract for AbloStoreContext.Provider. */
|
|
624
624
|
get _store() { return store; },
|
|
625
625
|
/** The InstanceCache — for demand loaders that need pool.createFromData(). */
|
|
626
626
|
get _pool() { return objectPool; },
|
|
@@ -1,60 +1,15 @@
|
|
|
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
|
-
*/
|
|
1
|
+
/** Define typed named operations. Pass the schema explicitly across package boundaries. */
|
|
14
2
|
import type { Schema } from '@abloatai/transaction/schema/schema';
|
|
15
3
|
import type { Transaction } from './Transaction.js';
|
|
16
|
-
import type { ResolveSchema } from '@abloatai/transaction/types/global';
|
|
17
|
-
/**
|
|
18
|
-
* `ResolveSchema` narrowed to satisfy the `Schema` bound — mirrors
|
|
19
|
-
* {@link Ablo.RegisteredSchema}. When nothing is registered, `ResolveSchema`
|
|
20
|
-
* is a loose `{ models }` shape that doesn't extend `Schema`, so we fall back
|
|
21
|
-
* to `Schema` to keep the mutator tree typed rather than collapsing.
|
|
22
|
-
*/
|
|
4
|
+
import type { ResolveSchema, RequireRegisteredSchema } from '@abloatai/transaction/types/global';
|
|
23
5
|
type RegisteredSchema = ResolveSchema extends Schema ? ResolveSchema : Schema;
|
|
24
|
-
/**
|
|
25
|
-
* The signature of a single custom mutator. The engine supplies `tx`; you control
|
|
26
|
-
* `args`, in whatever shape you like, and the resolved return value. `TArgs` and
|
|
27
|
-
* `TResult` are bounded by `unknown` rather than `any`, so a mixed tree of
|
|
28
|
-
* mutators can be typed together without falling back to `any`.
|
|
29
|
-
*/
|
|
30
6
|
export type MutatorFn<S extends Schema, TArgs, TResult = void> = (options: {
|
|
31
7
|
tx: Transaction<S>;
|
|
32
8
|
args: TArgs;
|
|
33
9
|
}) => Promise<TResult>;
|
|
34
|
-
/**
|
|
35
|
-
* The shape {@link defineMutators} accepts: an optional record per model key
|
|
36
|
-
* whose values are named mutator functions. The `unknown` bounds keep the public
|
|
37
|
-
* boundary type-safe without `any`; when you write your mutators inline,
|
|
38
|
-
* TypeScript still infers the concrete `args` and result of each function, so the
|
|
39
|
-
* `unknown` here is only a ceiling, not what you end up working with.
|
|
40
|
-
*/
|
|
41
10
|
export type MutatorDefs<S extends Schema> = {
|
|
42
11
|
[K in keyof S['models']]?: Record<string, MutatorFn<S, never, unknown>>;
|
|
43
12
|
};
|
|
44
|
-
/**
|
|
45
|
-
* Returns the mutators object unchanged while constraining its shape against the
|
|
46
|
-
* schema. The `S` generic pins the model keys, and the `M` generic is inferred as
|
|
47
|
-
* a `const`, so each mutator's literal signature survives. There is no runtime
|
|
48
|
-
* work here; the function exists purely as a place for type inference to anchor.
|
|
49
|
-
*/
|
|
50
13
|
export declare function defineMutators<S extends Schema, const M extends MutatorDefs<S>>(_schema: S, mutators: M): M;
|
|
51
|
-
|
|
52
|
-
* Register-anchored overload: omit the schema value and the tree is typed
|
|
53
|
-
* against `ResolveSchema` (this app's registered schema). Shared product code
|
|
54
|
-
* used across apps that bind different schemas should use this form — it moves
|
|
55
|
-
* with each app's `Register` instead of pinning one concrete schema, so the
|
|
56
|
-
* mutator tree stays assignable at every consumer (which reads the same
|
|
57
|
-
* `Register`). See docs/plans/per-product-schema-projections.md.
|
|
58
|
-
*/
|
|
59
|
-
export declare function defineMutators<const M extends MutatorDefs<RegisteredSchema>>(mutators: M): M;
|
|
14
|
+
export declare function defineMutators<const M extends MutatorDefs<RegisteredSchema>>(mutators: RequireRegisteredSchema<M>): M;
|
|
60
15
|
export {};
|
|
@@ -1,18 +1,3 @@
|
|
|
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
|
export function defineMutators(schemaOrMutators, maybeMutators) {
|
|
15
|
-
// The schema argument is a type anchor only — never read at runtime. With one
|
|
16
|
-
// argument the mutator tree is in the first slot; with two it's in the second.
|
|
17
2
|
return (maybeMutators ?? schemaOrMutators);
|
|
18
3
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AbloClient } from '../client.js';
|
|
2
|
+
import type { SchemaRecord } from '@abloatai/transaction/schema/schema';
|
|
3
|
+
import type { SyncStoreContract } from './storeContract.js';
|
|
4
|
+
/** Access the supported local store contract for custom framework adapters,
|
|
5
|
+
* demand loading, scope management and custom undo infrastructure.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getAbloStore<S extends SchemaRecord>(client: AbloClient<S>): SyncStoreContract;
|
package/dist/presence/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { type PresenceProjection, type PresenceProjectionEvents, type PresenceView } from '@abloatai/transaction/presence';
|
|
1
|
+
import { type PresenceProjection, type PresenceProjectionEvents, type PresenceView, type PresenceQueryOptions } from '@abloatai/transaction/presence';
|
|
2
2
|
import type { PresenceTarget } from '@abloatai/transaction/presence';
|
|
3
3
|
import { type ReadActivityTransport } from './readActivity.js';
|
|
4
4
|
type PresenceTransport = PresenceProjectionEvents & ReadActivityTransport;
|
|
5
5
|
/** Reactive-client presence backed by the client's existing live connection. */
|
|
6
6
|
export interface ReactivePresence extends PresenceView {
|
|
7
|
-
forModel(model: string, recordId?: string): ReturnType<PresenceProjection['forModel']>;
|
|
7
|
+
forModel(model: string, recordId?: string, options?: PresenceQueryOptions): ReturnType<PresenceProjection['forModel']>;
|
|
8
8
|
onChange(listener: () => void): () => void;
|
|
9
9
|
}
|
|
10
10
|
/** Lifecycle hooks kept inside the humans composition boundary. */
|
package/dist/presence/index.js
CHANGED
|
@@ -52,9 +52,9 @@ export function createPresence(transport = null) {
|
|
|
52
52
|
lifetime.stop();
|
|
53
53
|
};
|
|
54
54
|
},
|
|
55
|
-
forModel(model, recordId) {
|
|
55
|
+
forModel(model, recordId, options) {
|
|
56
56
|
version.get();
|
|
57
|
-
return projection?.forModel(model, recordId) ?? [];
|
|
57
|
+
return projection?.forModel(model, recordId, options) ?? [];
|
|
58
58
|
},
|
|
59
59
|
dispose() {
|
|
60
60
|
for (const read of reads)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState, createContext, } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { AbloStoreContext } from './context.js';
|
|
5
5
|
import { AbloInternalContext } from './internalContext.js';
|
|
6
6
|
import { AbloValidationError } from '@abloatai/transaction/errors';
|
|
7
7
|
import { useAblo } from './useAblo.js';
|
|
@@ -75,7 +75,7 @@ export function AbloProvider(props) {
|
|
|
75
75
|
// onSessionExpired. Credential cleanup lives in the CLIENT, so direct
|
|
76
76
|
// consumers and React consumers have the same security boundary.
|
|
77
77
|
// 2. Drive `ready()` (idempotent) so bootstrap starts on mount, then read the
|
|
78
|
-
// resolved org scope for
|
|
78
|
+
// resolved org scope for the Ablo store context.
|
|
79
79
|
// It does NOT dispose the client (consumer-owned) and does NOT touch auth.
|
|
80
80
|
useEffect(() => {
|
|
81
81
|
let stale = false;
|
|
@@ -131,12 +131,12 @@ export function AbloProvider(props) {
|
|
|
131
131
|
window.addEventListener('beforeunload', handler);
|
|
132
132
|
return () => { window.removeEventListener('beforeunload', handler); };
|
|
133
133
|
}, [engine, preventUnsavedChanges]);
|
|
134
|
-
// ──
|
|
134
|
+
// ── Store context value (for Ablo data hooks) ────────────────────
|
|
135
135
|
//
|
|
136
136
|
// The engine is always present (it's the `client` prop), but its org scope is
|
|
137
|
-
// unknown until `ready()` resolves identity — so
|
|
137
|
+
// unknown until `ready()` resolves identity — so the store context is null until
|
|
138
138
|
// then, which drives the initial fallback below.
|
|
139
|
-
const
|
|
139
|
+
const storeContextValue = useMemo(() => {
|
|
140
140
|
const currentAccountScope = (resolvedScope?.engine === engine ? resolvedScope.account : null) ??
|
|
141
141
|
engine._store.orgId;
|
|
142
142
|
if (!currentAccountScope)
|
|
@@ -156,7 +156,7 @@ export function AbloProvider(props) {
|
|
|
156
156
|
// Keep the context tree stable during startup so passthrough children retain
|
|
157
157
|
// their component state when authenticated row scope becomes available.
|
|
158
158
|
const passthrough = fallback === 'passthrough';
|
|
159
|
-
return (_jsx(AbloInternalContext.Provider, { value: internalValue, children: _jsx(
|
|
159
|
+
return (_jsx(AbloInternalContext.Provider, { value: internalValue, children: _jsx(AbloStoreContext.Provider, { value: storeContextValue, children: passthrough ? (children) : storeContextValue ? (_jsx(BootstrapGate, { fallback: fallback, children: children }, engineKey)) : fallback }) }));
|
|
160
160
|
}
|
|
161
161
|
/**
|
|
162
162
|
* Internal gate that renders `fallback` only during the very first
|
package/dist/react/context.d.ts
CHANGED
|
@@ -1,55 +1,16 @@
|
|
|
1
|
-
import { type ReactNode } from 'react';
|
|
2
1
|
import type { Schema } from '@abloatai/transaction/schema/schema';
|
|
3
2
|
import type { SyncStoreContract } from '../local/storeContract.js';
|
|
4
3
|
export type { SyncStoreContract, LocalMutation, } from '../local/storeContract.js';
|
|
5
|
-
export interface
|
|
4
|
+
export interface AbloStoreContextValue {
|
|
6
5
|
store: SyncStoreContract;
|
|
7
6
|
/** The organization id used as the default scope for reads and writes. */
|
|
8
7
|
organizationId: string;
|
|
9
|
-
/**
|
|
10
|
-
* An optional schema. When provided, hooks that take a model by name (such as
|
|
11
|
-
* `useQuery('items')`) read that model's metadata from this schema, so
|
|
12
|
-
* callers don't pass a schema at every call site. When omitted, those hooks
|
|
13
|
-
* require the schema as an argument instead.
|
|
14
|
-
*
|
|
15
|
-
* The field is loosely typed here because a single runtime context value is
|
|
16
|
-
* shared by every hook. Precise per-model types come from your `Register`
|
|
17
|
-
* module augmentation
|
|
18
|
-
* (`declare module '@abloatai/ablo' { interface Register { Schema: typeof schema } }`),
|
|
19
|
-
* not from this reference.
|
|
20
|
-
*/
|
|
8
|
+
/** Runtime schema used by ambient mutator overloads. */
|
|
21
9
|
schema?: Schema;
|
|
22
10
|
}
|
|
23
|
-
export declare const
|
|
11
|
+
export declare const AbloStoreContext: import("react").Context<AbloStoreContextValue | null>;
|
|
24
12
|
/**
|
|
25
|
-
* Reads the
|
|
26
|
-
*
|
|
27
|
-
* context by rendering the internal {@link SyncProvider}; you wire
|
|
28
|
-
* `<AbloProvider client={ablo}>` rather than touching this directly.
|
|
13
|
+
* Reads the store scope owned by `<AbloProvider>`, throwing a clear error when
|
|
14
|
+
* no provider is mounted above it.
|
|
29
15
|
*/
|
|
30
|
-
export declare function
|
|
31
|
-
/**
|
|
32
|
-
* Props for SyncProvider.
|
|
33
|
-
*/
|
|
34
|
-
export interface SyncProviderProps {
|
|
35
|
-
/** The sync store, which must implement {@link SyncStoreContract}. */
|
|
36
|
-
store: SyncStoreContract;
|
|
37
|
-
/** The organization id used as the default scope for reads and writes. */
|
|
38
|
-
organizationId: string;
|
|
39
|
-
/**
|
|
40
|
-
* An optional schema. Provide it to enable hooks that take a model by name
|
|
41
|
-
* (such as `useQuery('items')`); the model types also narrow through your
|
|
42
|
-
* `Register` augmentation. Omit it to pass the schema to those hooks directly
|
|
43
|
-
* instead.
|
|
44
|
-
*/
|
|
45
|
-
schema?: Schema;
|
|
46
|
-
children?: ReactNode;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* A low-level provider that places a built sync store on React context so the
|
|
50
|
-
* data hooks can reach it. This is an internal building block: it is not part
|
|
51
|
-
* of the package's public entry point. Reach for `<AbloProvider>` instead,
|
|
52
|
-
* which builds the store from your `Ablo({ schema, apiKey })` client and
|
|
53
|
-
* renders this provider underneath.
|
|
54
|
-
*/
|
|
55
|
-
export declare function SyncProvider({ store, organizationId, schema, children, }: SyncProviderProps): import("react").FunctionComponentElement<import("react").ProviderProps<SyncReactContext | null>>;
|
|
16
|
+
export declare function useAbloStoreContext(): AbloStoreContextValue;
|
package/dist/react/context.js
CHANGED
|
@@ -1,29 +1,17 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { createContext,
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
3
|
import { AbloValidationError } from '@abloatai/transaction/errors';
|
|
4
|
-
export const
|
|
4
|
+
export const AbloStoreContext = createContext(null);
|
|
5
5
|
/**
|
|
6
|
-
* Reads the
|
|
7
|
-
*
|
|
8
|
-
* context by rendering the internal {@link SyncProvider}; you wire
|
|
9
|
-
* `<AbloProvider client={ablo}>` rather than touching this directly.
|
|
6
|
+
* Reads the store scope owned by `<AbloProvider>`, throwing a clear error when
|
|
7
|
+
* no provider is mounted above it.
|
|
10
8
|
*/
|
|
11
|
-
export function
|
|
12
|
-
const ctx = useContext(
|
|
9
|
+
export function useAbloStoreContext() {
|
|
10
|
+
const ctx = useContext(AbloStoreContext);
|
|
13
11
|
if (!ctx) {
|
|
14
|
-
throw new AbloValidationError('
|
|
15
|
-
code: '
|
|
12
|
+
throw new AbloValidationError('Ablo hooks must be used within an <AbloProvider>.', {
|
|
13
|
+
code: 'ablo_context_missing_provider',
|
|
16
14
|
});
|
|
17
15
|
}
|
|
18
16
|
return ctx;
|
|
19
17
|
}
|
|
20
|
-
/**
|
|
21
|
-
* A low-level provider that places a built sync store on React context so the
|
|
22
|
-
* data hooks can reach it. This is an internal building block: it is not part
|
|
23
|
-
* of the package's public entry point. Reach for `<AbloProvider>` instead,
|
|
24
|
-
* which builds the store from your `Ablo({ schema, apiKey })` client and
|
|
25
|
-
* renders this provider underneath.
|
|
26
|
-
*/
|
|
27
|
-
export function SyncProvider({ store, organizationId, schema, children, }) {
|
|
28
|
-
return createElement(SyncContext.Provider, { value: { store, organizationId, schema } }, children);
|
|
29
|
-
}
|
|
@@ -1,32 +1,19 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Capture schema inference once while reusing module-level React functions.
|
|
3
|
-
* This helper creates no components, hooks, contexts or client instances.
|
|
4
|
-
*
|
|
5
|
-
* Define the app binding at module scope:
|
|
6
|
-
* `export const { AbloProvider, useAblo, usePresence } = createAbloReact(schema)`.
|
|
7
|
-
*/
|
|
8
1
|
import type { ReactElement } from 'react';
|
|
2
|
+
import { useMutationFailure } from './useMutationFailure.js';
|
|
9
3
|
import { AbloProvider } from './AbloProvider.js';
|
|
10
|
-
import { useAblo
|
|
4
|
+
import { useAblo } from './useAblo.js';
|
|
11
5
|
import type { AbloClient as Ablo } from '../client.js';
|
|
12
|
-
import type { ModelOperations } from '../local/client/createModelOperations.js';
|
|
13
6
|
import type { Schema, SchemaRecord } from '@abloatai/transaction/schema/schema';
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
/** What a binding returns: the provider and the hook, with `S` fixed. */
|
|
7
|
+
import { usePresence } from './usePresence.js';
|
|
8
|
+
/** Shared provider and hooks specialized to one schema. */
|
|
17
9
|
export interface AbloReactBinding<S extends SchemaRecord> {
|
|
18
|
-
/** `AbloProvider` with its `client` prop typed `Ablo<S>` — same component,
|
|
19
|
-
* no per-app generics. */
|
|
20
10
|
AbloProvider: (props: AbloProvider.Props<S>) => ReactElement;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<T>(select: AbloSelector<S, T>): T | undefined;
|
|
26
|
-
<T, C>(modelClientOrSelect: ModelOperations<T, C> | ModelClientSelector<S, T, C>, id: string, options?: useAblo.Options<T>): useAblo.Result<T>;
|
|
27
|
-
};
|
|
11
|
+
useAblo: useAblo.Bound<S>;
|
|
12
|
+
/** Writable client for actions; useAblo(selector) supplies render snapshots. */
|
|
13
|
+
useAbloClient: () => Ablo<S> | null;
|
|
14
|
+
useMutationFailure: typeof useMutationFailure;
|
|
28
15
|
/** Declare and reactively read record presence with the same model clients. */
|
|
29
|
-
usePresence: <
|
|
16
|
+
usePresence: usePresence.Bound<S>;
|
|
30
17
|
}
|
|
31
18
|
/** Bind the existing React functions to one schema's types. */
|
|
32
19
|
export declare function createAbloReact<S extends SchemaRecord>(schema: Schema<S>): AbloReactBinding<S>;
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
|
+
import { useAbloClient } from './useAbloClient.js';
|
|
3
|
+
import { useMutationFailure } from './useMutationFailure.js';
|
|
2
4
|
import { AbloProvider } from './AbloProvider.js';
|
|
3
|
-
import { useAblo
|
|
5
|
+
import { useAblo } from './useAblo.js';
|
|
4
6
|
import { usePresence } from './usePresence.js';
|
|
5
7
|
/** Bind the existing React functions to one schema's types. */
|
|
6
8
|
export function createAbloReact(schema) {
|
|
7
9
|
void schema;
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
// type tests verify the specialization; no runtime value changes.
|
|
11
|
-
// Specialize types only. Every binding uses the same module-level functions,
|
|
12
|
-
// so calling this helper again cannot change component identity or reset state.
|
|
13
|
-
return { AbloProvider, useAblo, usePresence };
|
|
10
|
+
// Specialize the shared functions without creating new contexts or identities.
|
|
11
|
+
return { AbloProvider, useAblo, useAbloClient, useMutationFailure, usePresence };
|
|
14
12
|
}
|
|
@@ -2,15 +2,6 @@ import type { AbloClient as Ablo } from '../client.js';
|
|
|
2
2
|
import type { SchemaRecord } from '@abloatai/transaction/schema/schema';
|
|
3
3
|
/** The provider owns only the reference to the application-owned client. */
|
|
4
4
|
export interface AbloInternalContextValue {
|
|
5
|
-
/**
|
|
6
|
-
* The typed `Ablo` client for this provider, available before bootstrap resolves. It is held here so `useAblo()` can return it without
|
|
7
|
-
* reaching into the store; the client and the store are sibling objects, and
|
|
8
|
-
* neither is derived from the other.
|
|
9
|
-
*
|
|
10
|
-
* It is typed loosely as `Ablo<SchemaRecord>` because generics do not flow
|
|
11
|
-
* through React context. `useAblo<R>()` restores the precise type through its
|
|
12
|
-
* own generic; the runtime value is the fully typed client.
|
|
13
|
-
*/
|
|
14
5
|
engine: Ablo<SchemaRecord> | null;
|
|
15
6
|
}
|
|
16
7
|
export declare const AbloInternalContext: import("react").Context<AbloInternalContextValue | null>;
|
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
|
+
}
|