@abloatai/humans 0.63.1 → 0.64.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Ablo.d.ts +6 -0
- package/dist/client.d.ts +12 -32
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/local/client/createModelOperations.d.ts +3 -3
- package/dist/local/client/createModelOperations.js +1 -1
- package/dist/local/client/reactiveEngine.js +7 -8
- package/dist/{react/useSyncStatus.d.ts → local/client/status.d.ts} +4 -3
- package/dist/local/client/status.js +14 -0
- package/dist/local/client/storeLifecycle.js +2 -2
- package/dist/local/mutators/defineMutators.d.ts +3 -48
- package/dist/local/mutators/defineMutators.js +0 -15
- package/dist/local/storeAccess.d.ts +7 -0
- package/dist/local/storeAccess.js +6 -0
- package/dist/local/storeContract.d.ts +3 -4
- package/dist/presence/index.d.ts +2 -2
- package/dist/presence/index.js +8 -4
- package/dist/react/AbloProvider.d.ts +57 -121
- package/dist/react/AbloProvider.js +55 -160
- package/dist/react/context.d.ts +6 -45
- package/dist/react/context.js +8 -20
- package/dist/react/createAbloReact.d.ts +13 -48
- package/dist/react/createAbloReact.js +8 -54
- package/dist/react/internalContext.d.ts +1 -27
- package/dist/react/snapshot.d.ts +4 -0
- package/dist/react/snapshot.js +75 -0
- package/dist/react/useAblo.d.ts +28 -92
- package/dist/react/useAblo.js +36 -93
- package/dist/react/useAbloClient.d.ts +5 -0
- package/dist/react/useAbloClient.js +9 -0
- package/dist/react/useMutationFailure.d.ts +6 -0
- package/dist/react/useMutationFailure.js +11 -0
- package/dist/react/useMutators.d.ts +22 -19
- package/dist/react/useMutators.js +3 -3
- package/dist/react/usePresence.d.ts +9 -9
- package/dist/react/usePresence.js +8 -11
- package/dist/react/useReactive.d.ts +12 -0
- package/dist/react/useReactive.js +57 -0
- package/dist/react/useUndoScope.d.ts +16 -30
- package/dist/react/useUndoScope.js +21 -4
- package/dist/react.d.ts +9 -19
- package/dist/react.js +9 -15
- package/dist/reactRuntime.d.ts +1 -1
- package/dist/reactRuntime.js +1 -1
- package/package.json +4 -3
- package/src/Ablo.ts +7 -0
- package/src/client.ts +13 -32
- package/src/index.ts +2 -0
- package/src/local/client/createModelOperations.ts +4 -4
- package/src/local/client/reactiveEngine.ts +7 -8
- package/src/local/client/status.ts +20 -0
- package/src/local/client/storeLifecycle.ts +2 -2
- package/src/local/mutators/defineMutators.ts +3 -51
- package/src/local/storeAccess.ts +10 -0
- package/src/local/storeContract.ts +3 -4
- package/src/presence/index.ts +10 -5
- package/src/react/AbloProvider.tsx +88 -263
- package/src/react/context.ts +10 -61
- package/src/react/createAbloReact.ts +16 -125
- package/src/react/internalContext.ts +1 -27
- package/src/react/snapshot.ts +67 -0
- package/src/react/useAblo.ts +73 -209
- package/src/react/useAbloClient.ts +14 -0
- package/src/react/useMutationFailure.ts +17 -0
- package/src/react/useMutators.ts +36 -32
- package/src/react/usePresence.ts +22 -27
- package/src/react/useReactive.ts +56 -0
- package/src/react/useUndoScope.ts +24 -20
- package/src/react.ts +9 -69
- package/src/reactRuntime.ts +2 -2
- package/dist/react/ClientSideSuspense.d.ts +0 -36
- package/dist/react/ClientSideSuspense.js +0 -17
- package/dist/react/useCurrentUserId.d.ts +0 -2
- package/dist/react/useCurrentUserId.js +0 -12
- package/dist/react/useErrorListener.d.ts +0 -2
- package/dist/react/useErrorListener.js +0 -14
- package/dist/react/useMutationFailureListener.d.ts +0 -8
- package/dist/react/useMutationFailureListener.js +0 -19
- package/dist/react/useSyncStatus.js +0 -48
- package/dist/useReactive.d.ts +0 -6
- package/dist/useReactive.js +0 -41
- package/src/react/ClientSideSuspense.tsx +0 -57
- package/src/react/useCurrentUserId.ts +0 -17
- package/src/react/useErrorListener.ts +0 -22
- package/src/react/useMutationFailureListener.ts +0 -34
- package/src/react/useSyncStatus.ts +0 -53
- package/src/useReactive.ts +0 -49
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SyncStoreContract } from '../storeContract.js';
|
|
2
|
+
|
|
3
|
+
/** The client lifecycle, shared by every framework binding. */
|
|
4
|
+
export type ClientStatus =
|
|
5
|
+
| { readonly name: 'initial' }
|
|
6
|
+
| { readonly name: 'connecting'; readonly progress: number }
|
|
7
|
+
| { readonly name: 'connected'; readonly hasUnsyncedChanges: boolean }
|
|
8
|
+
| { readonly name: 'reconnecting'; readonly reason?: string }
|
|
9
|
+
| { readonly name: 'disconnected'; readonly reason?: string }
|
|
10
|
+
| { readonly name: 'needs-auth' };
|
|
11
|
+
|
|
12
|
+
export function readStatus(store: SyncStoreContract): ClientStatus {
|
|
13
|
+
const { state, progress, pendingChanges, isSessionError, error } = store.syncStatus;
|
|
14
|
+
if (isSessionError) return { name: 'needs-auth' };
|
|
15
|
+
if (state === 'reconnecting') return { name: 'reconnecting', reason: error?.message };
|
|
16
|
+
if (state === 'offline') return { name: 'disconnected', reason: 'offline' };
|
|
17
|
+
if (state === 'error') return { name: 'disconnected', reason: error?.message };
|
|
18
|
+
if (store.isReady) return { name: 'connected', hasUnsyncedChanges: pendingChanges > 0 };
|
|
19
|
+
return { name: 'connecting', progress };
|
|
20
|
+
}
|
|
@@ -301,7 +301,7 @@ export function startStoreLifecycle<S extends SchemaRecord>(
|
|
|
301
301
|
//
|
|
302
302
|
// The store.initialize() generator updates store.syncStatus as it
|
|
303
303
|
// progresses (syncing → idle on success, error on failure), so the
|
|
304
|
-
// consumer's `
|
|
304
|
+
// consumer's `ablo.status` projection reflects real-time state.
|
|
305
305
|
// Resolve bootstrap mode: explicit option wins; otherwise
|
|
306
306
|
// agents default to 'none' (transactional participant — see
|
|
307
307
|
// option doc) and everyone else defaults to 'full'.
|
|
@@ -386,7 +386,7 @@ export function startStoreLifecycle<S extends SchemaRecord>(
|
|
|
386
386
|
if (!validationError && internalOptions.autoStart) {
|
|
387
387
|
void ready().catch(() => {
|
|
388
388
|
// Error is captured in store.syncStatus; consumers should check
|
|
389
|
-
// `
|
|
389
|
+
// `ablo.status.name === 'disconnected'` to detect failures.
|
|
390
390
|
});
|
|
391
391
|
}
|
|
392
392
|
|
|
@@ -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
|
+
}
|
|
@@ -19,8 +19,7 @@ import type { GroupScope } from './sync/scopeGroups.js';
|
|
|
19
19
|
/**
|
|
20
20
|
* A snapshot of the client's synchronization state, shaped for binding to UI.
|
|
21
21
|
* {@link SyncStoreContract.syncStatus} exposes a reactive instance of this, and
|
|
22
|
-
* the
|
|
23
|
-
* indicators.
|
|
22
|
+
* the client projects it into `ablo.status` for connection indicators.
|
|
24
23
|
*/
|
|
25
24
|
export interface SyncStatus {
|
|
26
25
|
state: 'idle' | 'syncing' | 'error' | 'offline' | 'reconnecting';
|
|
@@ -115,7 +114,7 @@ export interface SyncStoreContract {
|
|
|
115
114
|
* are backed by observable computeds, so reading them inside a reactive
|
|
116
115
|
* context — an observer component or a reaction — re-runs that context when
|
|
117
116
|
* the state changes. Code that prefers not to work with the reactivity system
|
|
118
|
-
* directly can read the same values through
|
|
117
|
+
* directly can read the same values through `useAblo(ablo => ablo.status)`.
|
|
119
118
|
*/
|
|
120
119
|
readonly isReady: boolean;
|
|
121
120
|
readonly isSyncing: boolean;
|
|
@@ -137,7 +136,7 @@ export interface SyncStoreContract {
|
|
|
137
136
|
pinScope?(scope: GroupScope): Promise<void>;
|
|
138
137
|
unpinScope?(scope: GroupScope): Promise<void>;
|
|
139
138
|
/**
|
|
140
|
-
* The full reactive {@link SyncStatus} record. The
|
|
139
|
+
* The full reactive {@link SyncStatus} record. The client status projection
|
|
141
140
|
* reads its fields — `state`, `progress`, `pendingChanges`, `isSessionError`,
|
|
142
141
|
* and `error` — to present the current sync state. It is part of the contract
|
|
143
142
|
* so hooks and test doubles can read or set it directly.
|
package/src/presence/index.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { observable, runInAction } from 'mobx';
|
|
1
2
|
import {
|
|
2
3
|
createPresenceProjection,
|
|
3
4
|
type PresenceProjection,
|
|
4
5
|
type PresenceProjectionEvents,
|
|
5
6
|
type PresenceView,
|
|
7
|
+
type PresenceQueryOptions,
|
|
6
8
|
} from '@abloatai/transaction/presence';
|
|
7
9
|
import type { PresenceTarget } from '@abloatai/transaction/presence';
|
|
8
10
|
import {
|
|
@@ -15,7 +17,7 @@ type PresenceTransport = PresenceProjectionEvents & ReadActivityTransport;
|
|
|
15
17
|
|
|
16
18
|
/** Reactive-client presence backed by the client's existing live connection. */
|
|
17
19
|
export interface ReactivePresence extends PresenceView {
|
|
18
|
-
forModel(model: string, recordId?: string): ReturnType<PresenceProjection['forModel']>;
|
|
20
|
+
forModel(model: string, recordId?: string, options?: PresenceQueryOptions): ReturnType<PresenceProjection['forModel']>;
|
|
19
21
|
onChange(listener: () => void): () => void;
|
|
20
22
|
}
|
|
21
23
|
|
|
@@ -44,11 +46,13 @@ export function createPresence(
|
|
|
44
46
|
): AttachablePresence {
|
|
45
47
|
let projection: PresenceProjection | null = null;
|
|
46
48
|
let attachedTransport: PresenceTransport | null = null;
|
|
49
|
+
const version = observable.box(0);
|
|
47
50
|
const listeners = new Set<() => void>();
|
|
48
51
|
const reads = new Set<ReadActivityLifetime>();
|
|
49
52
|
let unsubscribe: (() => void) | null = null;
|
|
50
53
|
|
|
51
54
|
const notify = (): void => {
|
|
55
|
+
runInAction(() => { version.set(version.get() + 1); });
|
|
52
56
|
for (const listener of listeners) listener();
|
|
53
57
|
};
|
|
54
58
|
|
|
@@ -63,8 +67,8 @@ export function createPresence(
|
|
|
63
67
|
if (transport !== null) attach(transport);
|
|
64
68
|
|
|
65
69
|
return {
|
|
66
|
-
get active() { return projection?.active ?? []; },
|
|
67
|
-
get others() { return projection?.others ?? []; },
|
|
70
|
+
get active() { version.get(); return projection?.active ?? []; },
|
|
71
|
+
get others() { version.get(); return projection?.others ?? []; },
|
|
68
72
|
onChange(listener) {
|
|
69
73
|
listeners.add(listener);
|
|
70
74
|
return () => { listeners.delete(listener); };
|
|
@@ -84,8 +88,9 @@ export function createPresence(
|
|
|
84
88
|
lifetime.stop();
|
|
85
89
|
};
|
|
86
90
|
},
|
|
87
|
-
forModel(model, recordId) {
|
|
88
|
-
|
|
91
|
+
forModel(model, recordId, options) {
|
|
92
|
+
version.get();
|
|
93
|
+
return projection?.forModel(model, recordId, options) ?? [];
|
|
89
94
|
},
|
|
90
95
|
dispose() {
|
|
91
96
|
for (const read of reads) read.dispose();
|