@abloatai/humans 0.62.0 → 0.63.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.
Files changed (47) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/local/InstanceCache.js +1 -1
  3. package/dist/local/client/createModelOperations.d.ts +21 -0
  4. package/dist/local/client/createModelOperations.js +84 -0
  5. package/dist/local/client/reactiveEngine.js +16 -0
  6. package/dist/local/transactions/mutations/MutationQueue.d.ts +3 -0
  7. package/dist/local/transactions/mutations/MutationQueue.js +36 -2
  8. package/dist/local/transactions/mutations/deltaConfirmation.d.ts +1 -0
  9. package/dist/local/transactions/mutations/deltaConfirmation.js +4 -0
  10. package/dist/local/transactions/mutations/failureHandling.d.ts +1 -0
  11. package/dist/local/transactions/mutations/failureHandling.js +1 -1
  12. package/dist/presence/index.d.ts +7 -2
  13. package/dist/presence/index.js +18 -0
  14. package/dist/presence/readActivity.d.ts +19 -0
  15. package/dist/presence/readActivity.js +122 -0
  16. package/dist/react/AbloProvider.d.ts +2 -18
  17. package/dist/react/AbloProvider.js +11 -27
  18. package/dist/react/createAbloReact.d.ts +4 -0
  19. package/dist/react/createAbloReact.js +12 -2
  20. package/dist/react/useAblo.d.ts +3 -1
  21. package/dist/react/useAblo.js +13 -11
  22. package/dist/react/usePresence.d.ts +19 -0
  23. package/dist/react/usePresence.js +28 -0
  24. package/dist/react/useSyncStatus.js +14 -3
  25. package/dist/react.d.ts +1 -0
  26. package/dist/react.js +1 -0
  27. package/dist/surface.d.ts +1 -1
  28. package/dist/surface.js +1 -0
  29. package/dist/useReactive.js +1 -3
  30. package/package.json +2 -2
  31. package/src/index.ts +1 -0
  32. package/src/local/InstanceCache.ts +1 -1
  33. package/src/local/client/createModelOperations.ts +132 -0
  34. package/src/local/client/reactiveEngine.ts +16 -0
  35. package/src/local/transactions/mutations/MutationQueue.ts +26 -2
  36. package/src/local/transactions/mutations/deltaConfirmation.ts +3 -0
  37. package/src/local/transactions/mutations/failureHandling.ts +2 -1
  38. package/src/presence/index.ts +32 -3
  39. package/src/presence/readActivity.ts +149 -0
  40. package/src/react/AbloProvider.tsx +14 -54
  41. package/src/react/createAbloReact.ts +25 -1
  42. package/src/react/useAblo.ts +18 -13
  43. package/src/react/usePresence.ts +88 -0
  44. package/src/react/useSyncStatus.ts +14 -3
  45. package/src/react.ts +4 -0
  46. package/src/surface.ts +1 -0
  47. package/src/useReactive.ts +1 -3
@@ -0,0 +1,88 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import type { PresenceSession } from '@abloatai/transaction/presence';
5
+ import { AbloValidationError } from '@abloatai/transaction/errors';
6
+ import {
7
+ getModelClientMeta,
8
+ type ModelOperations,
9
+ } from '../local/client/createModelOperations.js';
10
+ import type { AbloClient as Ablo } from '../client.js';
11
+ import type { SchemaRecord } from '@abloatai/transaction/schema/schema';
12
+ import type { ResolveSchema } from '@abloatai/transaction/types/global';
13
+ import { useAbloClientImpl } from './useAblo.js';
14
+
15
+ type DefaultModels = ResolveSchema extends { models: infer M }
16
+ ? M extends SchemaRecord
17
+ ? M
18
+ : SchemaRecord
19
+ : SchemaRecord;
20
+
21
+ export type PresenceModelSelector<R extends SchemaRecord, T, C> =
22
+ (ablo: Ablo<R>) => ModelOperations<T, C>;
23
+
24
+ /**
25
+ * Declare that this component is reading one row and return every live session
26
+ * reading or otherwise acting on that row. Ablo owns the lease, refresh,
27
+ * reconnect, and cleanup mechanics for the component's lifetime.
28
+ */
29
+ export function usePresence<T, C>(
30
+ modelClient: ModelOperations<T, C>,
31
+ recordId: string,
32
+ ): readonly PresenceSession[];
33
+ export function usePresence<
34
+ R extends SchemaRecord = DefaultModels,
35
+ T = Record<string, unknown>,
36
+ C = unknown,
37
+ >(
38
+ select: PresenceModelSelector<R, T, C>,
39
+ recordId: string,
40
+ ): readonly PresenceSession[];
41
+ export function usePresence<
42
+ R extends SchemaRecord = DefaultModels,
43
+ T = Record<string, unknown>,
44
+ C = unknown,
45
+ >(
46
+ modelOrSelect: ModelOperations<T, C> | PresenceModelSelector<R, T, C>,
47
+ recordId: string,
48
+ ): readonly PresenceSession[] {
49
+ const engine = useAbloClientImpl<R>(null);
50
+ return usePresenceImpl(engine, modelOrSelect, recordId);
51
+ }
52
+
53
+ /** @internal Shared by the global hook and schema-bound React factory. */
54
+ export function usePresenceImpl<R extends SchemaRecord, T, C>(
55
+ engine: Ablo<R> | null,
56
+ modelOrSelect: ModelOperations<T, C> | PresenceModelSelector<R, T, C>,
57
+ recordId: string,
58
+ ): readonly PresenceSession[] {
59
+ if (recordId.length === 0) {
60
+ throw new AbloValidationError(
61
+ 'usePresence requires a non-empty record id.',
62
+ { code: 'invalid_request', param: 'recordId' },
63
+ );
64
+ }
65
+ const modelClient = typeof modelOrSelect === 'function'
66
+ ? engine
67
+ ? modelOrSelect(engine)
68
+ : null
69
+ : modelOrSelect;
70
+ const presence = modelClient ? getModelClientMeta(modelClient)?.presence : undefined;
71
+ if (modelClient !== null && presence === undefined) {
72
+ throw new AbloValidationError(
73
+ 'usePresence requires a model from the reactive Ablo client.',
74
+ { code: 'invalid_request', param: 'modelClient' },
75
+ );
76
+ }
77
+
78
+ const [, render] = useState(0);
79
+
80
+ useEffect(
81
+ () => presence?.subscribe(() => { render((version) => version + 1); }),
82
+ [presence],
83
+ );
84
+
85
+ useEffect(() => presence?.read(recordId), [presence, recordId]);
86
+
87
+ return presence?.get(recordId) ?? [];
88
+ }
@@ -1,10 +1,12 @@
1
1
  'use client';
2
2
 
3
- import { useCallback } from 'react';
3
+ import { useCallback, useContext } from 'react';
4
4
  import {
5
- useSyncContext,
5
+ SyncContext,
6
6
  type SyncStoreContract,
7
7
  } from './context.js';
8
+ import { AbloInternalContext } from './internalContext.js';
9
+ import { AbloValidationError } from '@abloatai/transaction/errors';
8
10
  import { useReactive } from '../useReactive.js';
9
11
 
10
12
  export type SyncStatusSnapshot =
@@ -17,7 +19,16 @@ export type SyncStatusSnapshot =
17
19
 
18
20
  /** Reactively exposes the local store's connection and confirmation status. */
19
21
  export function useSyncStatus(): SyncStatusSnapshot {
20
- const { store } = useSyncContext();
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
+ }
21
32
  const compute = useCallback(() => deriveStatus(store), [store]);
22
33
  return useReactive(compute, sameSnapshot);
23
34
  }
package/src/react.ts CHANGED
@@ -3,6 +3,10 @@ export { useReactive } from './useReactive.js';
3
3
  export { useCurrentUserId } from './react/useCurrentUserId.js';
4
4
  export { useErrorListener } from './react/useErrorListener.js';
5
5
  export { useSyncStatus, type SyncStatusSnapshot } from './react/useSyncStatus.js';
6
+ export {
7
+ usePresence,
8
+ type PresenceModelSelector,
9
+ } from './react/usePresence.js';
6
10
  export {
7
11
  useMutationFailureListener,
8
12
  type MutationFailurePayload,
package/src/surface.ts CHANGED
@@ -38,6 +38,7 @@ export const PUBLIC_MODEL_VERBS = [
38
38
  'listAll',
39
39
  'local',
40
40
  'presence',
41
+ 'events',
41
42
  'create',
42
43
  'update',
43
44
  'delete',
@@ -16,7 +16,6 @@ export function useReactive<T>(
16
16
  const computeRef = useRef(compute);
17
17
  const equalsRef = useRef(equals);
18
18
  const snapshotRef = useRef<{ value: T } | null>(null);
19
- const versionRef = useRef(0);
20
19
 
21
20
  equalsRef.current = equals;
22
21
  if (snapshotRef.current === null) {
@@ -25,7 +24,6 @@ export function useReactive<T>(
25
24
  const next = compute();
26
25
  if (!equals(snapshotRef.current.value, next)) {
27
26
  snapshotRef.current = { value: next };
28
- versionRef.current++;
29
27
  }
30
28
  }
31
29
  computeRef.current = compute;
@@ -39,7 +37,7 @@ export function useReactive<T>(
39
37
  onChange();
40
38
  }
41
39
  },
42
- ), [versionRef.current]);
40
+ ), [compute]);
43
41
  const getSnapshot = useCallback(() => snapshotRef.current!.value, []);
44
42
  return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
45
43
  }