@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
package/dist/useReactive.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { useCallback, useRef } from 'react';
|
|
3
|
-
import { useSyncExternalStore } from 'react';
|
|
4
|
-
import { reaction } from 'mobx';
|
|
5
|
-
/**
|
|
6
|
-
* Subscribe a React component to a MobX computation with a cached snapshot.
|
|
7
|
-
* This is the framework-level reactive primitive owned by @abloatai/humans;
|
|
8
|
-
* store/model hooks are layered above it.
|
|
9
|
-
*/
|
|
10
|
-
export function useReactive(compute, equals = defaultEquals) {
|
|
11
|
-
const computeRef = useRef(compute);
|
|
12
|
-
const equalsRef = useRef(equals);
|
|
13
|
-
const snapshotRef = useRef(null);
|
|
14
|
-
equalsRef.current = equals;
|
|
15
|
-
if (snapshotRef.current === null) {
|
|
16
|
-
snapshotRef.current = { value: compute() };
|
|
17
|
-
}
|
|
18
|
-
else if (computeRef.current !== compute) {
|
|
19
|
-
const next = compute();
|
|
20
|
-
if (!equals(snapshotRef.current.value, next)) {
|
|
21
|
-
snapshotRef.current = { value: next };
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
computeRef.current = compute;
|
|
25
|
-
const subscribe = useCallback((onChange) => reaction(() => computeRef.current(), (next) => {
|
|
26
|
-
const current = snapshotRef.current.value;
|
|
27
|
-
if (!equalsRef.current(current, next)) {
|
|
28
|
-
snapshotRef.current = { value: next };
|
|
29
|
-
onChange();
|
|
30
|
-
}
|
|
31
|
-
}), [compute]);
|
|
32
|
-
const getSnapshot = useCallback(() => snapshotRef.current.value, []);
|
|
33
|
-
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
34
|
-
}
|
|
35
|
-
function defaultEquals(a, b) {
|
|
36
|
-
if (Object.is(a, b))
|
|
37
|
-
return true;
|
|
38
|
-
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
|
|
39
|
-
return false;
|
|
40
|
-
return a.every((value, index) => Object.is(value, b[index]));
|
|
41
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState, type ReactNode } from 'react';
|
|
4
|
-
import { useSyncStatus } from './useSyncStatus.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Nested bootstrap gate for a subtree. `<AbloProvider>` already ships
|
|
8
|
-
* its own built-in gate (via its `fallback` prop) that handles the
|
|
9
|
-
* common "wait for first bootstrap" case. Use `ClientSideSuspense`
|
|
10
|
-
* only when you need a SEPARATE gate inside an already-ready provider —
|
|
11
|
-
* for example, rendering app chrome immediately while gating a single
|
|
12
|
-
* heavy product surface on its own query resolving.
|
|
13
|
-
*
|
|
14
|
-
* Like the provider-level gate, this component latches open on the
|
|
15
|
-
* first `connected` / `reconnecting` / `disconnected` transition and
|
|
16
|
-
* stays open. Subsequent transient `connecting` states (hard reconnect
|
|
17
|
-
* after offline) do NOT re-show the fallback — the app has already
|
|
18
|
-
* rendered once and its own reconnect UI should take over.
|
|
19
|
-
*
|
|
20
|
-
* v0.3.x implementation is non-Suspense: reads `useSyncStatus()` and
|
|
21
|
-
* conditionally renders. v0.3.x+ will ship a
|
|
22
|
-
* the React suspense entry point where `useQuery` / `useOne`
|
|
23
|
-
* actually throw Promises; this component becomes a thin wrapper around
|
|
24
|
-
* React's real `<Suspense>` at that point.
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* <AbloProvider fallback={<AppSkeleton />}>
|
|
28
|
-
* <AppChrome />
|
|
29
|
-
* <ClientSideSuspense fallback={<CanvasSkeleton />}>
|
|
30
|
-
* <HeavyCanvas />
|
|
31
|
-
* </ClientSideSuspense>
|
|
32
|
-
* </AbloProvider>
|
|
33
|
-
*/
|
|
34
|
-
export interface ClientSideSuspenseProps {
|
|
35
|
-
/** What to render while the nested subtree is waiting for first bootstrap. */
|
|
36
|
-
fallback: ReactNode;
|
|
37
|
-
/** What to render once the subtree is cleared to render. */
|
|
38
|
-
children: ReactNode;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function ClientSideSuspense({ fallback, children }: ClientSideSuspenseProps) {
|
|
42
|
-
const status = useSyncStatus();
|
|
43
|
-
const [everConnected, setEverConnected] = useState(false);
|
|
44
|
-
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
if (
|
|
47
|
-
status.name === 'connected' ||
|
|
48
|
-
status.name === 'reconnecting' ||
|
|
49
|
-
status.name === 'disconnected'
|
|
50
|
-
) {
|
|
51
|
-
setEverConnected(true);
|
|
52
|
-
}
|
|
53
|
-
}, [status.name]);
|
|
54
|
-
|
|
55
|
-
const showFallback = !everConnected && status.name === 'connecting';
|
|
56
|
-
return <>{showFallback ? fallback : children}</>;
|
|
57
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useContext } from 'react';
|
|
4
|
-
import { AbloInternalContext } from './internalContext.js';
|
|
5
|
-
import { AbloValidationError } from '@abloatai/transaction/errors';
|
|
6
|
-
|
|
7
|
-
/** Returns the application user id supplied to the nearest AbloProvider. */
|
|
8
|
-
export function useCurrentUserId(): string | null {
|
|
9
|
-
const context = useContext(AbloInternalContext);
|
|
10
|
-
if (!context) {
|
|
11
|
-
throw new AbloValidationError(
|
|
12
|
-
'useCurrentUserId: no <AbloProvider> mounted above this component.',
|
|
13
|
-
{ code: 'no_ablo_provider' },
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
return context.currentUserId;
|
|
17
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useContext, useEffect, useRef } from 'react';
|
|
4
|
-
import { AbloInternalContext } from './internalContext.js';
|
|
5
|
-
import { AbloValidationError } from '@abloatai/transaction/errors';
|
|
6
|
-
|
|
7
|
-
/** Subscribe to provider-level errors without causing component re-renders. */
|
|
8
|
-
export function useErrorListener(listener: (error: Error) => void): void {
|
|
9
|
-
const context = useContext(AbloInternalContext);
|
|
10
|
-
if (!context) {
|
|
11
|
-
throw new AbloValidationError(
|
|
12
|
-
'useErrorListener: no <AbloProvider> mounted above this component.',
|
|
13
|
-
{ code: 'no_ablo_provider' },
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
const listenerRef = useRef(listener);
|
|
17
|
-
listenerRef.current = listener;
|
|
18
|
-
useEffect(
|
|
19
|
-
() => context.subscribeError((error) => { listenerRef.current(error); }),
|
|
20
|
-
[context],
|
|
21
|
-
);
|
|
22
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useContext, useEffect, useRef } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
AbloInternalContext,
|
|
6
|
-
type QueuedMutation,
|
|
7
|
-
} from '../reactRuntime.js';
|
|
8
|
-
import { AbloValidationError } from '@abloatai/transaction/errors';
|
|
9
|
-
|
|
10
|
-
export interface MutationFailurePayload {
|
|
11
|
-
transaction: QueuedMutation;
|
|
12
|
-
error: Error;
|
|
13
|
-
permanent?: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** Subscribe to optimistic mutation rollbacks and permanent write failures. */
|
|
17
|
-
export function useMutationFailureListener(
|
|
18
|
-
listener: (payload: MutationFailurePayload) => void,
|
|
19
|
-
): void {
|
|
20
|
-
const context = useContext(AbloInternalContext);
|
|
21
|
-
if (!context) {
|
|
22
|
-
throw new AbloValidationError(
|
|
23
|
-
'useMutationFailureListener: no <AbloProvider> mounted above this component.',
|
|
24
|
-
{ code: 'no_ablo_provider' },
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
const listenerRef = useRef(listener);
|
|
28
|
-
listenerRef.current = listener;
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
const engine = context.engine;
|
|
31
|
-
if (!engine) return;
|
|
32
|
-
return engine.onMutationFailure((payload) => { listenerRef.current(payload); });
|
|
33
|
-
}, [context, context.engine]);
|
|
34
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useCallback, useContext } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
SyncContext,
|
|
6
|
-
type SyncStoreContract,
|
|
7
|
-
} from './context.js';
|
|
8
|
-
import { AbloInternalContext } from './internalContext.js';
|
|
9
|
-
import { AbloValidationError } from '@abloatai/transaction/errors';
|
|
10
|
-
import { useReactive } from '../useReactive.js';
|
|
11
|
-
|
|
12
|
-
export type SyncStatusSnapshot =
|
|
13
|
-
| { readonly name: 'initial' }
|
|
14
|
-
| { readonly name: 'connecting'; readonly progress: number }
|
|
15
|
-
| { readonly name: 'connected'; readonly hasUnsyncedChanges: boolean }
|
|
16
|
-
| { readonly name: 'reconnecting'; readonly reason?: string }
|
|
17
|
-
| { readonly name: 'disconnected'; readonly reason?: string }
|
|
18
|
-
| { readonly name: 'needs-auth' };
|
|
19
|
-
|
|
20
|
-
/** Reactively exposes the local store's connection and confirmation status. */
|
|
21
|
-
export function useSyncStatus(): SyncStatusSnapshot {
|
|
22
|
-
const provider = useContext(AbloInternalContext);
|
|
23
|
-
const sync = useContext(SyncContext);
|
|
24
|
-
// Status does not require authenticated row scope. The client exists before
|
|
25
|
-
// ready() resolves, including inside passthrough children and custom fallbacks.
|
|
26
|
-
const store = provider?.engine?._store ?? sync?.store;
|
|
27
|
-
if (!store) {
|
|
28
|
-
throw new AbloValidationError('Sync hooks must be used within an <AbloProvider>.', {
|
|
29
|
-
code: 'sync_context_missing_provider',
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
const compute = useCallback(() => deriveStatus(store), [store]);
|
|
33
|
-
return useReactive(compute, sameSnapshot);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function deriveStatus(store: SyncStoreContract): SyncStatusSnapshot {
|
|
37
|
-
const { state, progress, pendingChanges, isSessionError, error } = store.syncStatus;
|
|
38
|
-
if (isSessionError) return { name: 'needs-auth' };
|
|
39
|
-
if (state === 'reconnecting') return { name: 'reconnecting', reason: error?.message };
|
|
40
|
-
if (state === 'offline') return { name: 'disconnected', reason: 'offline' };
|
|
41
|
-
if (state === 'error') return { name: 'disconnected', reason: error?.message };
|
|
42
|
-
if (store.isReady) return { name: 'connected', hasUnsyncedChanges: pendingChanges > 0 };
|
|
43
|
-
if (state === 'idle' || state === 'syncing') return { name: 'connecting', progress };
|
|
44
|
-
return { name: 'initial' };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function sameSnapshot(a: SyncStatusSnapshot, b: SyncStatusSnapshot): boolean {
|
|
48
|
-
if (a.name !== b.name) return false;
|
|
49
|
-
if (a.name === 'initial' || a.name === 'needs-auth') return true;
|
|
50
|
-
if (a.name === 'connecting') return b.name === 'connecting' && a.progress === b.progress;
|
|
51
|
-
if (a.name === 'connected') return b.name === 'connected' && a.hasUnsyncedChanges === b.hasUnsyncedChanges;
|
|
52
|
-
return (b.name === 'reconnecting' || b.name === 'disconnected') && a.reason === b.reason;
|
|
53
|
-
}
|
package/src/useReactive.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useCallback, useRef } from 'react';
|
|
4
|
-
import { useSyncExternalStore } from 'react';
|
|
5
|
-
import { reaction } from 'mobx';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Subscribe a React component to a MobX computation with a cached snapshot.
|
|
9
|
-
* This is the framework-level reactive primitive owned by @abloatai/humans;
|
|
10
|
-
* store/model hooks are layered above it.
|
|
11
|
-
*/
|
|
12
|
-
export function useReactive<T>(
|
|
13
|
-
compute: () => T,
|
|
14
|
-
equals: (a: T, b: T) => boolean = defaultEquals,
|
|
15
|
-
): T {
|
|
16
|
-
const computeRef = useRef(compute);
|
|
17
|
-
const equalsRef = useRef(equals);
|
|
18
|
-
const snapshotRef = useRef<{ value: T } | null>(null);
|
|
19
|
-
|
|
20
|
-
equalsRef.current = equals;
|
|
21
|
-
if (snapshotRef.current === null) {
|
|
22
|
-
snapshotRef.current = { value: compute() };
|
|
23
|
-
} else if (computeRef.current !== compute) {
|
|
24
|
-
const next = compute();
|
|
25
|
-
if (!equals(snapshotRef.current.value, next)) {
|
|
26
|
-
snapshotRef.current = { value: next };
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
computeRef.current = compute;
|
|
30
|
-
|
|
31
|
-
const subscribe = useCallback((onChange: () => void) => reaction(
|
|
32
|
-
() => computeRef.current(),
|
|
33
|
-
(next) => {
|
|
34
|
-
const current = snapshotRef.current!.value;
|
|
35
|
-
if (!equalsRef.current(current, next)) {
|
|
36
|
-
snapshotRef.current = { value: next };
|
|
37
|
-
onChange();
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
), [compute]);
|
|
41
|
-
const getSnapshot = useCallback(() => snapshotRef.current!.value, []);
|
|
42
|
-
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function defaultEquals<T>(a: T, b: T): boolean {
|
|
46
|
-
if (Object.is(a, b)) return true;
|
|
47
|
-
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
48
|
-
return a.every((value, index) => Object.is(value, b[index]));
|
|
49
|
-
}
|