@abloatai/humans 0.59.2 → 0.61.0

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 (96) hide show
  1. package/README.md +1 -1
  2. package/dist/Ablo.d.ts +2 -10
  3. package/dist/Ablo.js +0 -1
  4. package/dist/client.d.ts +1 -48
  5. package/dist/humans.d.ts +1 -1
  6. package/dist/local/BaseSyncedStore.d.ts +5 -5
  7. package/dist/local/BaseSyncedStore.js +7 -7
  8. package/dist/local/Database.d.ts +2 -2
  9. package/dist/local/LazyReferenceCollection.d.ts +1 -1
  10. package/dist/local/Model.js +46 -56
  11. package/dist/local/NetworkMonitor.js +2 -0
  12. package/dist/local/RuntimeContext.js +2 -0
  13. package/dist/local/SyncClient.d.ts +12 -36
  14. package/dist/local/SyncClient.js +50 -108
  15. package/dist/local/client/createModelOperations.d.ts +3 -27
  16. package/dist/local/client/createModelOperations.js +20 -21
  17. package/dist/local/client/options.d.ts +14 -39
  18. package/dist/local/client/reactiveEngine.d.ts +3 -9
  19. package/dist/local/client/reactiveEngine.js +6 -151
  20. package/dist/local/client/storeLifecycle.js +5 -1
  21. package/dist/local/fileUploads.d.ts +27 -0
  22. package/dist/local/fileUploads.js +55 -0
  23. package/dist/local/storeContract.d.ts +5 -5
  24. package/dist/local/stores/syncAction.d.ts +4 -4
  25. package/dist/local/sync/contextOnChange.js +1 -1
  26. package/dist/local/sync/createClaimStream.js +1 -1
  27. package/dist/local/sync/credentialLifecycle.d.ts +4 -5
  28. package/dist/local/sync/credentialLifecycle.js +4 -5
  29. package/dist/local/sync/deltaPipeline.js +12 -6
  30. package/dist/local/sync/schemas.d.ts +10 -10
  31. package/dist/local/sync/scopeGroups.d.ts +11 -0
  32. package/dist/local/sync/scopeGroups.js +75 -0
  33. package/dist/local/sync/wsFrameHandlers.d.ts +1 -1
  34. package/dist/local/transactions/localMutation.js +3 -3
  35. package/dist/local/transactions/mutations/MutationQueue.d.ts +4 -5
  36. package/dist/local/transactions/mutations/MutationQueue.js +25 -51
  37. package/dist/local/transactions/mutations/batchProcessing.js +23 -10
  38. package/dist/local/transactions/mutations/commitPayload.d.ts +9 -2
  39. package/dist/local/transactions/mutations/commitTransport.js +3 -1
  40. package/dist/local/transactions/mutations/executionSelection.d.ts +0 -1
  41. package/dist/local/transactions/mutations/executionSelection.js +9 -17
  42. package/dist/local/transactions/mutations/failureHandling.js +9 -0
  43. package/dist/local/transactions/mutations/localMutation.js +3 -3
  44. package/dist/local/transactions/mutations/queueCoalescing.js +8 -0
  45. package/dist/local/transactions/mutations/replayValidation.d.ts +15 -15
  46. package/dist/react/AbloProvider.d.ts +11 -86
  47. package/dist/react/AbloProvider.js +11 -163
  48. package/dist/react/ClientSideSuspense.d.ts +1 -1
  49. package/dist/react/DefaultFallback.d.ts +1 -1
  50. package/dist/react/createAbloReact.js +1 -1
  51. package/dist/react/useErrorListener.js +1 -1
  52. package/dist/react/useMutationFailureListener.js +1 -1
  53. package/dist/react.d.ts +1 -1
  54. package/dist/react.js +1 -1
  55. package/dist/surface.d.ts +3 -3
  56. package/dist/surface.js +1 -4
  57. package/package.json +3 -3
  58. package/src/Ablo.ts +5 -17
  59. package/src/client.ts +0 -51
  60. package/src/local/BaseSyncedStore.ts +14 -14
  61. package/src/local/Model.ts +45 -55
  62. package/src/local/NetworkMonitor.ts +2 -0
  63. package/src/local/RuntimeContext.ts +2 -0
  64. package/src/local/SyncClient.ts +73 -140
  65. package/src/local/client/createModelOperations.ts +30 -64
  66. package/src/local/client/options.ts +20 -43
  67. package/src/local/client/reactiveEngine.ts +7 -179
  68. package/src/local/client/storeLifecycle.ts +6 -1
  69. package/src/local/fileUploads.ts +97 -0
  70. package/src/local/storeContract.ts +5 -5
  71. package/src/local/sync/contextOnChange.ts +1 -1
  72. package/src/local/sync/createClaimStream.ts +1 -1
  73. package/src/local/sync/credentialLifecycle.ts +4 -5
  74. package/src/local/sync/deltaPipeline.ts +10 -6
  75. package/src/local/sync/scopeGroups.ts +91 -0
  76. package/src/local/sync/wsFrameHandlers.ts +0 -1
  77. package/src/local/transactions/localMutation.ts +3 -3
  78. package/src/local/transactions/mutations/MutationQueue.ts +24 -53
  79. package/src/local/transactions/mutations/batchProcessing.ts +25 -10
  80. package/src/local/transactions/mutations/commitPayload.ts +11 -1
  81. package/src/local/transactions/mutations/commitTransport.ts +2 -2
  82. package/src/local/transactions/mutations/executionSelection.ts +9 -15
  83. package/src/local/transactions/mutations/failureHandling.ts +10 -0
  84. package/src/local/transactions/mutations/localMutation.ts +3 -3
  85. package/src/local/transactions/mutations/queueCoalescing.ts +6 -0
  86. package/src/react/AbloProvider.tsx +17 -249
  87. package/src/react/useErrorListener.ts +1 -1
  88. package/src/react/useMutationFailureListener.ts +1 -1
  89. package/src/react.ts +1 -5
  90. package/src/surface.ts +1 -4
  91. package/dist/local/sync/participants.d.ts +0 -132
  92. package/dist/local/sync/participants.js +0 -342
  93. package/dist/local/transactions/mutations/pendingDrain.d.ts +0 -33
  94. package/dist/local/transactions/mutations/pendingDrain.js +0 -117
  95. package/src/local/sync/participants.ts +0 -564
  96. package/src/local/transactions/mutations/pendingDrain.ts +0 -169
@@ -1,7 +1,7 @@
1
1
  'use client';
2
- import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useCallback, useContext, useEffect, useMemo, useRef, useState, createContext, } from 'react';
4
- import { createParticipantClaimId, parseParticipantTtlSeconds, resolveParticipantSyncGroups, } from '../local/sync/participants.js';
4
+ import { resolveScopeGroups } from '../local/sync/scopeGroups.js';
5
5
  import { SyncContext } from './context.js';
6
6
  import { AbloInternalContext } from './internalContext.js';
7
7
  import { AbloValidationError } from '@abloatai/transaction/errors';
@@ -200,158 +200,10 @@ function BootstrapGate({ fallback, children, }) {
200
200
  return _jsx(_Fragment, { children: showFallback ? fallback : children });
201
201
  }
202
202
  const EMPTY_PRESENCE = Object.freeze([]);
203
- const EMPTY_INTENTS = Object.freeze([]);
204
- /**
205
- * Join multiplayer for a given scope. Returns the participant and its
206
- * lifecycle status. Auto-cleans up on unmount or when `paused`
207
- * flips to true.
208
- *
209
- * `useJoin` is the React form of `ablo.<model>.join` — scope-level
210
- * read-interest + presence; returns the reactive participant facade
211
- * (peers/claims/status).
212
- *
213
- * The returned `participant` is an `EngineParticipant` — `.presence`
214
- * + `.claims` only — backed by the engine's existing socket. For
215
- * headless-bot patterns (a separate identity in the same browser
216
- * tab), construct a second `Ablo({ kind: 'agent', ... })` directly.
217
- */
218
- export function useJoin(opts) {
219
- const ctx = useContext(AbloInternalContext);
220
- const engine = ctx?.engine ?? null;
221
- const { paused = false } = opts;
222
- // Resolve the model-form scope ({ reports: id } / refs) THROUGH the schema, so a
223
- // model's declared `scope` kind is honored (typename `Report` → `report:<id>`,
224
- // not the `type:id` string fallback). Schema appears once the engine is ready;
225
- // until then refs resolve by convention, then re-resolve when it arrives.
226
- const scopeKey = JSON.stringify(resolveParticipantSyncGroups(opts.scope, engine?.schema).sort());
227
- const scopedSyncGroups = useMemo(() => JSON.parse(scopeKey), [scopeKey]);
228
- const [claimError, setClaimError] = useState(null);
229
- const [claimConnected, setClaimConnected] = useState(false);
230
- // Reference-stable participant facade — same socket as entity sync,
231
- // so there is no `connect()` / `disconnect()` lifecycle here. The
232
- // engine manages the connection; the hook is a thin window onto its
233
- // already-attached presence + claim streams.
234
- const participant = useMemo(() => {
235
- if (!engine)
236
- return null;
237
- return { presence: engine.presence, claims: engine.claims };
238
- }, [engine]);
239
- // Status maps to the engine's sync state. `connecting` while the
240
- // engine bootstraps; `connected` once `engine.ready()` resolves and
241
- // any scoped participant claim has acked; `error` if the claim
242
- // fails; `disconnected` while paused or before the engine exists.
243
- const syncStatus = useSyncStatus();
244
- // Only a write-claim participant waits on a claim ack. A pure reader
245
- // (the default) is `connected` as soon as the engine is — its read
246
- // interest is fire-and-forget `update_subscription`, not a claim.
247
- const needsClaim = !!opts.claim && scopedSyncGroups.length > 0;
248
- const status = paused || !engine
249
- ? 'disconnected'
250
- : claimError
251
- ? 'error'
252
- : syncStatus.name === 'connected'
253
- ? needsClaim && !claimConnected
254
- ? 'connecting'
255
- : 'connected'
256
- : syncStatus.name === 'disconnected' || syncStatus.name === 'needs-auth'
257
- ? 'disconnected'
258
- : 'connecting';
259
- const error = claimError;
260
- // ── Read interest (always) ───────────────────────────────────────
261
- // Subscribe the connection to the scope's sync groups while mounted +
262
- // connected — the area-of-interest navigation primitive. No claim, no
263
- // TTL: a viewer just receives the scope's deltas. Hysteresis (warm TTL)
264
- // lives in the store's SubscriptionManager, so a quick unmount/remount
265
- // (tab flip) doesn't re-bootstrap.
266
- useEffect(() => {
267
- const scope = opts.scope;
268
- if (paused || !engine || !scope || scopedSyncGroups.length === 0)
269
- return;
270
- if (syncStatus.name !== 'connected')
271
- return;
272
- const store = engine._store;
273
- // `hydrate` backfills the scope's current state after subscribing
274
- // (store handles subscribe-first ordering + single-flight). leaveScope
275
- // only moves read interest; the hydrated rows stay in the pool.
276
- void store.enterScope?.(scope, { hydrate: opts.hydrate });
277
- return () => {
278
- void store.leaveScope?.(scope);
279
- };
280
- // scopeKey is the stable proxy for the resolved groups; same idiom as
281
- // the claim effect below.
282
- }, [engine, paused, scopeKey, syncStatus.name, opts.hydrate]);
283
- // ── Write claim (opt-in: `claim: true`) ─────────────────────────
284
- // A claim is the write-claim primitive — distinct from read interest
285
- // above. Only sent when the caller opts in; it makes peers observe the
286
- // claim and pins the scope so it never warms while held.
287
- useEffect(() => {
288
- setClaimError(null);
289
- setClaimConnected(false);
290
- const scope = opts.scope;
291
- if (paused || !engine || !opts.claim || !scope || scopedSyncGroups.length === 0)
292
- return;
293
- if (syncStatus.name !== 'connected')
294
- return;
295
- const ws = engine._ws;
296
- const store = engine._store;
297
- let cancelled = false;
298
- const claimId = createParticipantClaimId();
299
- ws.sendClaim(claimId, scopedSyncGroups, {
300
- // Reading the retired spelling IS the compatibility path; it goes at 0.37.0.
301
- // eslint-disable-next-line @typescript-eslint/no-deprecated
302
- ttlSeconds: parseParticipantTtlSeconds(opts.ttl ?? opts.ttlSeconds),
303
- })
304
- .then(() => {
305
- if (!cancelled)
306
- setClaimConnected(true);
307
- })
308
- .catch((err) => {
309
- if (!cancelled) {
310
- setClaimError(err instanceof Error ? err : new Error(String(err)));
311
- }
312
- });
313
- // Prominence: hold the scope subscribed for as long as the claim lives.
314
- void store.pinScope?.(scope);
315
- return () => {
316
- cancelled = true;
317
- ws.sendRelease(claimId);
318
- void store.unpinScope?.(scope);
319
- };
320
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- same compatibility read as above.
321
- }, [engine, paused, scopeKey, syncStatus.name, opts.ttl, opts.ttlSeconds, opts.claim]);
322
- // Bridge the engine's presence + claims streams into React state.
323
- // Plain useState + useEffect is sufficient — mid-frame tearing on a
324
- // peer list is harmless (users won't notice one frame of stale
325
- // presence). Queries and sync status use useSyncExternalStore
326
- // because transactions CAN tear visibly; presence can't.
327
- const [peers, setPeers] = useState(EMPTY_PRESENCE);
328
- const [claims, setClaims] = useState(EMPTY_INTENTS);
329
- useEffect(() => {
330
- if (!participant || paused) {
331
- setPeers(EMPTY_PRESENCE);
332
- setClaims(EMPTY_INTENTS);
333
- return;
334
- }
335
- setPeers(participant.presence.others);
336
- setClaims(participant.claims.others);
337
- const unsubPresence = participant.presence.onChange(() => {
338
- setPeers(participant.presence.others);
339
- });
340
- const unsubClaims = participant.claims.onChange(() => {
341
- setClaims(participant.claims.others);
342
- });
343
- return () => {
344
- unsubPresence();
345
- unsubClaims();
346
- };
347
- }, [participant, paused]);
348
- return { participant, peers, claims, status, error };
349
- }
350
203
  /**
351
204
  * Read-only presence: the OTHER participants currently visible to this
352
- * connection, bridged to React. Unlike {@link useJoin}, this does
353
- * NOT enter/leave a scope (no `update_subscription`, no warm-TTL churn) —
354
- * it is a pure reader of the engine's already-flowing presence stream.
205
+ * connection, bridged to React. This is a pure reader of the engine's
206
+ * already-flowing presence stream; it does not mutate connection groups.
355
207
  *
356
208
  * Pass `scope` to narrow to the peers on that scope's sync group(s); omit
357
209
  * it to get everyone on the engine's groups. Membership is driven entirely
@@ -359,11 +211,8 @@ export function useJoin(opts) {
359
211
  * cursor/collaboration traffic), so reading it never affects what the
360
212
  * connection is subscribed to and can't deadlock against a gated channel.
361
213
  *
362
- * Use this to answer "is anyone else here?" e.g. suppressing live-cursor
363
- * broadcasts while alone — when some OTHER mount already owns the scope's
364
- * read interest (scope `leave` is not reference-counted, so a second
365
- * `useJoin` on the same scope would warm-drop the owner's
366
- * subscription on unmount).
214
+ * Use this to answer "is anyone else here?", for example to suppress
215
+ * live-cursor broadcasts while alone.
367
216
  *
368
217
  * ```ts
369
218
  * const peers = usePeers({ reports: reportId });
@@ -373,9 +222,9 @@ export function useJoin(opts) {
373
222
  export function usePeers(scope) {
374
223
  const ctx = useContext(AbloInternalContext);
375
224
  const engine = ctx?.engine ?? null;
376
- // Resolve scope → groups through the schema (same idiom as useJoin).
225
+ // Resolve scope → groups through the schema.
377
226
  // The stringified, sorted key is the stable effect dependency.
378
- const scopeKey = JSON.stringify(resolveParticipantSyncGroups(scope, engine?.schema).sort());
227
+ const scopeKey = JSON.stringify(resolveScopeGroups(scope, engine?.schema).sort());
379
228
  const groups = useMemo(() => JSON.parse(scopeKey), [scopeKey]);
380
229
  const [peers, setPeers] = useState(EMPTY_PRESENCE);
381
230
  useEffect(() => {
@@ -387,13 +236,12 @@ export function usePeers(scope) {
387
236
  const compute = () => groups.length === 0
388
237
  ? presence.others
389
238
  : presence.others.filter((p) => p.syncGroups.some((g) => groups.includes(g)));
390
- // Plain useState + onChange — presence changes on join/leave/activity
239
+ // Plain useState + onChange — presence changes on connect/disconnect/activity
391
240
  // only (never on cursor traffic, a separate channel), so this fires
392
- // rarely; a frame of stale presence is harmless (same rationale as
393
- // useJoin's peers bridge).
241
+ // rarely; a frame of stale presence is harmless.
394
242
  setPeers(compute());
395
243
  return presence.onChange(() => { setPeers(compute()); });
396
- }, [engine, scopeKey]);
244
+ }, [engine, groups, scopeKey]);
397
245
  return peers;
398
246
  }
399
247
  // ── Escape-hatches: raw engine/store access ──────────────────────────
@@ -33,4 +33,4 @@ export interface ClientSideSuspenseProps {
33
33
  /** What to render once the subtree is cleared to render. */
34
34
  children: ReactNode;
35
35
  }
36
- export declare function ClientSideSuspense({ fallback, children }: ClientSideSuspenseProps): import("react/jsx-runtime").JSX.Element;
36
+ export declare function ClientSideSuspense({ fallback, children }: ClientSideSuspenseProps): import("react").JSX.Element;
@@ -21,4 +21,4 @@
21
21
  * pass `fallback={null}`. Consumers who want to skip the gate entirely
22
22
  * pass `fallback="passthrough"`.
23
23
  */
24
- export declare function DefaultFallback(): import("react/jsx-runtime").JSX.Element;
24
+ export declare function DefaultFallback(): import("react").JSX.Element;
@@ -38,7 +38,7 @@ export function createAbloReact(schema) {
38
38
  // implementation's internal-context fallback.
39
39
  const BoundClientContext = createContext(null);
40
40
  function BoundAbloProvider(props) {
41
- return createElement(BoundClientContext.Provider, { value: props.client }, createElement(AbloProvider, props));
41
+ return createElement(BoundClientContext.Provider, { value: props.client }, createElement((AbloProvider), props));
42
42
  }
43
43
  function useBoundAblo(modelOrSelect, id, options) {
44
44
  const bound = useContext(BoundClientContext);
@@ -10,5 +10,5 @@ export function useErrorListener(listener) {
10
10
  }
11
11
  const listenerRef = useRef(listener);
12
12
  listenerRef.current = listener;
13
- useEffect(() => context.subscribeError((error) => listenerRef.current(error)), [context]);
13
+ useEffect(() => context.subscribeError((error) => { listenerRef.current(error); }), [context]);
14
14
  }
@@ -14,6 +14,6 @@ export function useMutationFailureListener(listener) {
14
14
  const engine = context.engine;
15
15
  if (!engine)
16
16
  return;
17
- return engine.onMutationFailure((payload) => listenerRef.current(payload));
17
+ return engine.onMutationFailure((payload) => { listenerRef.current(payload); });
18
18
  }, [context, context.engine]);
19
19
  }
package/dist/react.d.ts CHANGED
@@ -4,7 +4,7 @@ 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
6
  export { useMutationFailureListener, type MutationFailurePayload, } from './react/useMutationFailureListener.js';
7
- export { AbloProvider, useJoin, usePeers, useSync, useSyncStore, type AbloProviderProps, type ParticipantScope, type ParticipantStatus, type UseJoinOptions, type UseJoinReturn, } from './react/AbloProvider.js';
7
+ export { AbloProvider, usePeers, useSync, useSyncStore, type AbloProviderProps, type GroupScope, } from './react/AbloProvider.js';
8
8
  export { ClientSideSuspense, type ClientSideSuspenseProps, } from './react/ClientSideSuspense.js';
9
9
  export { DefaultFallback } from './react/DefaultFallback.js';
10
10
  export { createAbloReact, type AbloReactBinding, } from './react/createAbloReact.js';
package/dist/react.js CHANGED
@@ -4,7 +4,7 @@ export { useCurrentUserId } from './react/useCurrentUserId.js';
4
4
  export { useErrorListener } from './react/useErrorListener.js';
5
5
  export { useSyncStatus } from './react/useSyncStatus.js';
6
6
  export { useMutationFailureListener, } from './react/useMutationFailureListener.js';
7
- export { AbloProvider, useJoin, usePeers, useSync, useSyncStore, } from './react/AbloProvider.js';
7
+ export { AbloProvider, usePeers, useSync, useSyncStore, } from './react/AbloProvider.js';
8
8
  export { ClientSideSuspense, } from './react/ClientSideSuspense.js';
9
9
  export { DefaultFallback } from './react/DefaultFallback.js';
10
10
  export { createAbloReact, } from './react/createAbloReact.js';
package/dist/surface.d.ts CHANGED
@@ -19,18 +19,18 @@
19
19
  * tuple, so it is the one list of model-verb names a generated summary can
20
20
  * describe.
21
21
  */
22
- export declare const PUBLIC_MODEL_VERBS: readonly ['get', 'read', 'list', 'listAll', 'local', 'create', 'update', 'delete', 'claim', 'join', 'onChange'];
22
+ export declare const PUBLIC_MODEL_VERBS: readonly ["get", "read", "list", "listAll", "local", "create", "update", "delete", "claim", "onChange"];
23
23
  /**
24
24
  * The option keys accepted by `local.list` and `onChange`, matching the
25
25
  * keys of {@link LocalReadOptions}. Note that the lifecycle filter is named
26
26
  * `state`, not `scope`.
27
27
  */
28
- export declare const PUBLIC_LIST_OPTION_KEYS: readonly ['where', 'filter', 'orderBy', 'limit', 'offset', 'state'];
28
+ export declare const PUBLIC_LIST_OPTION_KEYS: readonly ["where", "filter", "orderBy", "limit", "offset", "state"];
29
29
  /**
30
30
  * The keys of the client constructor options, {@link AbloOptions}. Only
31
31
  * `schema` is required; every other key is optional.
32
32
  */
33
- export declare const PUBLIC_ABLO_OPTION_KEYS: readonly ['schema', 'apiKey', 'projectId', 'branchId', 'authEndpoint', 'authTimeoutMs', 'allowCrossOriginAuthEndpoint', 'persistence', 'durableWrites', 'commitOutbox', 'commitOutboxScope', 'debug', 'logLevel', 'logger', 'authToken', 'baseURL', 'fetch', 'defaultHeaders', 'defaultQuery', 'dangerouslyAllowBrowser', 'collaborationEvents', 'plugins'];
33
+ export declare const PUBLIC_ABLO_OPTION_KEYS: readonly ["schema", "apiKey", "session", "projectId", "branchId", "persistence", "durableWrites", "commitOutbox", "commitOutboxScope", "debug", "logLevel", "logger", "authToken", "baseURL", "fetch", "defaultHeaders", "defaultQuery", "dangerouslyAllowBrowser", "collaborationEvents", "plugins"];
34
34
  export type ModelVerb = (typeof PUBLIC_MODEL_VERBS)[number];
35
35
  export type ListOptionKey = (typeof PUBLIC_LIST_OPTION_KEYS)[number];
36
36
  export type AbloOptionKey = (typeof PUBLIC_ABLO_OPTION_KEYS)[number];
package/dist/surface.js CHANGED
@@ -30,7 +30,6 @@ export const PUBLIC_MODEL_VERBS = [
30
30
  'update',
31
31
  'delete',
32
32
  'claim',
33
- 'join',
34
33
  'onChange',
35
34
  ];
36
35
  // ── the read/list query option surface ─────────────────────────────────────
@@ -55,11 +54,9 @@ export const PUBLIC_LIST_OPTION_KEYS = [
55
54
  export const PUBLIC_ABLO_OPTION_KEYS = [
56
55
  'schema',
57
56
  'apiKey',
57
+ 'session',
58
58
  'projectId',
59
59
  'branchId',
60
- 'authEndpoint',
61
- 'authTimeoutMs',
62
- 'allowCrossOriginAuthEndpoint',
63
60
  'persistence',
64
61
  'durableWrites',
65
62
  'commitOutbox',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abloatai/humans",
3
- "version": "0.59.2",
3
+ "version": "0.61.0",
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",
@@ -68,7 +68,7 @@
68
68
  "test:property": "jest --testPathPatterns=__tests__/property",
69
69
  "test:migrated-integration": "jest --testPathPatterns=__tests__/integration",
70
70
  "test:e2e": "E2E_TEST=true jest --config jest.e2e.config.ts",
71
- "lint:eslint": "eslint . --cache",
71
+ "lint:eslint": "eslint . --cache --suppressions-location eslint-suppressions.json",
72
72
  "check:boundary": "node scripts/check-boundary.mjs",
73
73
  "test:integration": "npm run build && node scripts/check-package-integration.mjs",
74
74
  "lint:pkg": "publint"
@@ -84,7 +84,7 @@
84
84
  "directory": "packages/humans"
85
85
  },
86
86
  "dependencies": {
87
- "@abloatai/transaction": "^0.59.2",
87
+ "@abloatai/transaction": "^0.61.0",
88
88
  "mobx": "^6.13.7",
89
89
  "uuid": "^11.1.0",
90
90
  "zod": "^4.4.3"
package/src/Ablo.ts CHANGED
@@ -46,9 +46,9 @@ import { resolveClientPrelude } from './local/client/clientPrelude.js';
46
46
  // The option types, the client's public shape, the pass over the options bag,
47
47
  // the resource-type surface, and the default WebSocket mutation executor each
48
48
  // live in their own module. The type-only ones (`options`, `abloClient`,
49
- // `resourceTypes`) carry no runtime imports, which lets the HTTP client and the
50
- // session-mint helpers reference the client types without importing this
51
- // factory back and creating an import cycle.
49
+ // `resourceTypes`) carry no runtime imports, which lets the HTTP client
50
+ // reference the client types without importing this factory back and creating
51
+ // an import cycle.
52
52
  import type { AbloOptions } from './local/client/options.js';
53
53
  // `AbloReads` is named by `Ablo.Reads` in the namespace below as well as
54
54
  // re-exported, so it needs the import too — same reason as `AbloOptions`.
@@ -92,11 +92,11 @@ export type Ablo<S extends SchemaRecord> = AbloClient<S>;
92
92
  * ```
93
93
  *
94
94
  * In the browser (or any client that shouldn't hold a secret key), point
95
- * `authEndpoint` at your session-mint route instead — the SDK fetches it, keeps the
95
+ * `session.endpoint` at your session-mint route instead — the SDK fetches it, keeps the
96
96
  * short-lived token fresh, and re-mints on expiry:
97
97
  *
98
98
  * ```ts
99
- * const ablo = Ablo({ schema, authEndpoint: '/api/ablo-session' });
99
+ * const ablo = Ablo({ schema, session: { endpoint: '/api/ablo-session' } });
100
100
  * ```
101
101
  *
102
102
  * Server-side agents, workers, and services use `@abloatai/transaction`.
@@ -214,7 +214,6 @@ export function Ablo<const S extends SchemaRecord>(
214
214
  transport,
215
215
  presence: humansSurface.presence,
216
216
  cluster,
217
- createSibling: (siblingOptions) => Ablo(siblingOptions),
218
217
  }), installedPlugins);
219
218
  }
220
219
 
@@ -232,7 +231,6 @@ export function Ablo<const S extends SchemaRecord>(
232
231
  // continue to work for callers who prefer them.
233
232
 
234
233
  import type * as _Streams from '@abloatai/transaction/types/streams';
235
- import type * as _Participants from './local/sync/participants.js';
236
234
  import type * as _Mutators from './local/mutators/defineMutators.js';
237
235
  import type * as _Tx from './local/mutators/Transaction.js';
238
236
  import type * as _Undo from './local/mutators/UndoManager.js';
@@ -297,16 +295,6 @@ export namespace Ablo {
297
295
  export type Actor = _Streams.ParticipantRef;
298
296
  }
299
297
 
300
- // ── Participant (sub-namespace — 5 names, shared concept) ─────────
301
- // eslint-disable-next-line @typescript-eslint/no-namespace
302
- export namespace Participant {
303
- export type Manager = _Participants.ParticipantManager;
304
- export type Joined = _Participants.JoinedParticipant;
305
- export type Scope = _Participants.ParticipantScope;
306
- export type Status = _Participants.ParticipantStatus;
307
- export type JoinOptions = _Participants.ParticipantJoinOptions;
308
- }
309
-
310
298
  // ── Schema (type + sub-namespace via declaration merge) ───────────
311
299
  export type Schema<S extends _SchemaTypes.SchemaRecord = _SchemaTypes.SchemaRecord> = _SchemaTypes.Schema<S>;
312
300
  /**
package/src/client.ts CHANGED
@@ -27,8 +27,6 @@ import type { ModelOperations } from './local/client/createModelOperations.js';
27
27
  import type {
28
28
  ClaimResource,
29
29
  CommitResource,
30
- CreateAgentClientParams,
31
- SessionResource,
32
30
  } from '@abloatai/transaction/client/resources/httpResources';
33
31
  import type { EffectiveAuthority } from '@abloatai/transaction/auth';
34
32
  import type { ReadDependency } from '@abloatai/transaction/coordination';
@@ -125,55 +123,6 @@ export type AbloClient<S extends SchemaRecord> = {
125
123
  */
126
124
  nudgeReconnect(): void;
127
125
 
128
- /**
129
- * Mint a short-lived, scoped session token for one end user. Call this on your
130
- * backend, where the `sk_` secret key lives, then hand the returned `token` to
131
- * that user's browser — typically through a token route the browser's `apiKey`
132
- * resolver fetches. The browser presents the token as its bearer, and the
133
- * server verifies it. The browser must never see the `sk_` key, only the
134
- * per-user session token.
135
- *
136
- * Pass `{ user: { id }, can: { items: ['read', 'update'] } }` for an end-user
137
- * session. It mints an `ek_` and attributes writes to a user (recorded as
138
- * `actor_kind` on the delta row). Pass `{ agent: { id }, can: {
139
- * items: ['update'] } }` for a scoped agent session, which mints an `rk_`.
140
- * Both kinds require `can`, typed against your schema's model names. This
141
- * always authenticates with the original `sk_`, never the client's exchanged
142
- * sync credential.
143
- */
144
- sessions: SessionResource<S>;
145
-
146
- /**
147
- * Mint a scoped **agent identity** and return a ready-to-use client bound to
148
- * it — the `ablo.<resource>.<verb>` shape for the agent use case. One call
149
- * replaces `sessions.create({ agent, can })` + constructing a second
150
- * `Ablo({ apiKey: token })`:
151
- *
152
- * ```ts
153
- * const agent = await ablo.agents.create({
154
- * name: 'researcher', // readable label (optional)
155
- * can: { records: ['read', 'update'] },
156
- * // id omitted → a fresh uuid: a distinct, independent participant
157
- * });
158
- * await agent.records.update({ id, data, claim });
159
- * await agent.dispose(); // when the agent is done
160
- * ```
161
- *
162
- * Server-side only: it requires the `sk_` secret key (like `sessions.create`)
163
- * and throws `AbloAuthenticationError` in the browser. The returned client
164
- * holds its own auto-refreshing `rk_`, so a long run never hits token expiry,
165
- * and the `sk_` never leaves this process. Each call is a distinct participant
166
- * by default (omit `id` for a fresh uuid), so even two agents sharing a `name`
167
- * queue behind one another on a contended row — `name` is display only and
168
- * never collapses identity. Humans don't get a server-built client; ship them a
169
- * token via `sessions.create({ user, can })`. If you need the raw token for
170
- * revocation, or a stable re-attachable id, use `sessions.create({ agent, can })`
171
- * or pass `id`.
172
- */
173
- agents: {
174
- create(params: CreateAgentClientParams<S>): Promise<AbloClient<S>>;
175
- };
176
-
177
126
  /**
178
127
  * The organization this client resolved to — `null` until `ready()`
179
128
  * completes. Use it instead of scraping CLI output or hardcoding env vars:
@@ -18,9 +18,9 @@ import { ConnectionManager } from './sync/ConnectionManager.js';
18
18
  import { contextLogger, contextSocketObservability } from './sync/contextPorts.js';
19
19
  import { SubscriptionManager } from './sync/SubscriptionManager.js';
20
20
  import {
21
- resolveParticipantSyncGroups,
22
- type ParticipantScope,
23
- } from './sync/participants.js';
21
+ resolveScopeGroups,
22
+ type GroupScope,
23
+ } from './sync/scopeGroups.js';
24
24
  import type { SyncClient } from './SyncClient.js';
25
25
  import type { Database, BootstrapResult, BootstrapRequirements } from './Database.js';
26
26
  import type { BootstrapData } from './sync/BootstrapFetcher.js';
@@ -394,8 +394,8 @@ export class BaseSyncedStore<
394
394
  // {@link SubscriptionManager.reconcile}); the on-connect `resync` pushes
395
395
  // whatever interest accumulated.
396
396
 
397
- private scopeToGroups(scope: ParticipantScope): string[] {
398
- return resolveParticipantSyncGroups(scope, this.schema);
397
+ private scopeToGroups(scope: GroupScope): string[] {
398
+ return resolveScopeGroups(scope, this.schema);
399
399
  }
400
400
 
401
401
  /**
@@ -406,7 +406,7 @@ export class BaseSyncedStore<
406
406
  * Hydration is best-effort — a failed backfill never rejects `enterScope`,
407
407
  * and the live delta stream keeps flowing regardless.
408
408
  */
409
- enterScope(scope: ParticipantScope, opts?: { hydrate?: boolean }): Promise<void> {
409
+ enterScope(scope: GroupScope, opts?: { hydrate?: boolean }): Promise<void> {
410
410
  const groups = this.scopeToGroups(scope);
411
411
  const subscribed = Promise.all(groups.map((g) => this.areaOfInterest.enter(g))).then(
412
412
  () => undefined,
@@ -456,21 +456,21 @@ export class BaseSyncedStore<
456
456
  }
457
457
 
458
458
  /** Leave a scope → its groups go warm (hysteresis), then drop on sweep. */
459
- leaveScope(scope: ParticipantScope): Promise<void> {
459
+ leaveScope(scope: GroupScope): Promise<void> {
460
460
  return Promise.all(
461
461
  this.scopeToGroups(scope).map((g) => this.areaOfInterest.leave(g)),
462
462
  ).then(() => undefined);
463
463
  }
464
464
 
465
465
  /** Pin a scope (active claim / prominence) → never warms while pinned. */
466
- pinScope(scope: ParticipantScope): Promise<void> {
466
+ pinScope(scope: GroupScope): Promise<void> {
467
467
  return Promise.all(
468
468
  this.scopeToGroups(scope).map((g) => this.areaOfInterest.pin(g)),
469
469
  ).then(() => undefined);
470
470
  }
471
471
 
472
472
  /** Release a pin → the group transitions to warm rather than dropping. */
473
- unpinScope(scope: ParticipantScope): Promise<void> {
473
+ unpinScope(scope: GroupScope): Promise<void> {
474
474
  return Promise.all(
475
475
  this.scopeToGroups(scope).map((g) => this.areaOfInterest.unpin(g)),
476
476
  ).then(() => undefined);
@@ -1759,9 +1759,9 @@ export class BaseSyncedStore<
1759
1759
  const isCreate = !this.objectPool.get(model.id);
1760
1760
  if (isCreate) {
1761
1761
  model.updatedAt = new Date();
1762
- this.syncClient.add(model);
1762
+ await this.syncClient.add(model);
1763
1763
  } else {
1764
- this.syncClient.update(model);
1764
+ await this.syncClient.update(model);
1765
1765
  }
1766
1766
  }
1767
1767
 
@@ -1779,21 +1779,21 @@ export class BaseSyncedStore<
1779
1779
  const model = rowAsModel(entity);
1780
1780
  this.pendingDeletes.add(model.id);
1781
1781
  // SyncClient.delete handles: pool remove, transaction queue
1782
- this.syncClient.delete(model);
1782
+ await this.syncClient.delete(model);
1783
1783
  }
1784
1784
 
1785
1785
  /** Archive a model. Accepts schema-inferred entity shapes (see `save`). */
1786
1786
  async archive<T extends { id: string; archivedAt?: Date | null }>(entity: T): Promise<void> {
1787
1787
  const model = rowAsModel(entity);
1788
1788
  model.archivedAt = new Date();
1789
- this.syncClient.archive(model);
1789
+ await this.syncClient.archive(model);
1790
1790
  }
1791
1791
 
1792
1792
  /** Unarchive a model. Accepts schema-inferred entity shapes (see `save`). */
1793
1793
  async unarchive<T extends { id: string; archivedAt?: Date | null }>(entity: T): Promise<void> {
1794
1794
  const model = rowAsModel(entity);
1795
1795
  model.archivedAt = null;
1796
- this.syncClient.update(model);
1796
+ await this.syncClient.update(model);
1797
1797
  }
1798
1798
 
1799
1799