@abloatai/humans 0.59.2 → 0.60.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 (56) 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 +2 -2
  8. package/dist/local/Database.d.ts +2 -2
  9. package/dist/local/LazyReferenceCollection.d.ts +1 -1
  10. package/dist/local/SyncClient.d.ts +7 -7
  11. package/dist/local/SyncClient.js +25 -10
  12. package/dist/local/client/createModelOperations.d.ts +3 -27
  13. package/dist/local/client/createModelOperations.js +14 -17
  14. package/dist/local/client/options.d.ts +14 -39
  15. package/dist/local/client/reactiveEngine.d.ts +3 -9
  16. package/dist/local/client/reactiveEngine.js +6 -151
  17. package/dist/local/client/storeLifecycle.js +5 -1
  18. package/dist/local/storeContract.d.ts +5 -5
  19. package/dist/local/stores/syncAction.d.ts +4 -4
  20. package/dist/local/sync/credentialLifecycle.d.ts +4 -5
  21. package/dist/local/sync/credentialLifecycle.js +4 -5
  22. package/dist/local/sync/schemas.d.ts +10 -10
  23. package/dist/local/sync/scopeGroups.d.ts +11 -0
  24. package/dist/local/sync/scopeGroups.js +75 -0
  25. package/dist/local/sync/wsFrameHandlers.d.ts +1 -1
  26. package/dist/local/transactions/mutations/MutationQueue.d.ts +3 -3
  27. package/dist/local/transactions/mutations/commitPayload.d.ts +1 -1
  28. package/dist/local/transactions/mutations/replayValidation.d.ts +15 -15
  29. package/dist/react/AbloProvider.d.ts +11 -86
  30. package/dist/react/AbloProvider.js +11 -163
  31. package/dist/react/ClientSideSuspense.d.ts +1 -1
  32. package/dist/react/DefaultFallback.d.ts +1 -1
  33. package/dist/react/createAbloReact.js +1 -1
  34. package/dist/react.d.ts +1 -1
  35. package/dist/react.js +1 -1
  36. package/dist/surface.d.ts +3 -3
  37. package/dist/surface.js +1 -4
  38. package/package.json +3 -2
  39. package/src/Ablo.ts +5 -17
  40. package/src/client.ts +0 -51
  41. package/src/local/BaseSyncedStore.ts +9 -9
  42. package/src/local/SyncClient.ts +41 -14
  43. package/src/local/client/createModelOperations.ts +23 -60
  44. package/src/local/client/options.ts +20 -43
  45. package/src/local/client/reactiveEngine.ts +7 -179
  46. package/src/local/client/storeLifecycle.ts +6 -1
  47. package/src/local/storeContract.ts +5 -5
  48. package/src/local/sync/credentialLifecycle.ts +4 -5
  49. package/src/local/sync/scopeGroups.ts +91 -0
  50. package/src/local/sync/wsFrameHandlers.ts +0 -1
  51. package/src/react/AbloProvider.tsx +17 -249
  52. package/src/react.ts +1 -5
  53. package/src/surface.ts +1 -4
  54. package/dist/local/sync/participants.d.ts +0 -132
  55. package/dist/local/sync/participants.js +0 -342
  56. package/src/local/sync/participants.ts +0 -564
@@ -2,9 +2,9 @@
2
2
  * The reactive engine assembly (ADR 0016). `Ablo({ ... })` resolves auth and
3
3
  * capabilities; `humans().init` constructs the store cluster; the lifecycle
4
4
  * — first mint, identity, ready() — lives in `./storeLifecycle.ts`. What
5
- * remains here is assembly around those parts: the claim stream and
6
- * participant manager, options validation, the typed model proxies, and the
7
- * commit/claim/session resources — composed into the reactive client.
5
+ * remains here is assembly around those parts: the claim and presence streams,
6
+ * options validation, the typed model proxies, and the
7
+ * commit and claim resources — composed into the reactive client.
8
8
  *
9
9
  * Extracted from the factory so the composition root stays a root: resolve,
10
10
  * dispatch, return. The remaining assembly converts to decoration of a
@@ -13,17 +13,13 @@
13
13
  */
14
14
  import { omittedModelError } from '@abloatai/transaction/schema/select';
15
15
  import { durableCommitOperationSchema, } from '@abloatai/transaction/commit';
16
- import { AbloAuthenticationError, AbloConnectionError, AbloValidationError, claimedError } from '@abloatai/transaction/errors';
16
+ import { AbloConnectionError, AbloValidationError, claimedError } from '@abloatai/transaction/errors';
17
17
  import { batchFence, claimIdFor, fenceTokenFor, modelTarget, streamTarget, subTarget, } from '@abloatai/transaction/coordination';
18
18
  import { validateAbloOptions } from './validateAbloOptions.js';
19
- import { mintSession } from '@abloatai/transaction/auth/sessionMint';
20
- import { revokeCapability, rotateCapability, } from '@abloatai/transaction/auth/capabilityLifecycle';
21
- import { modelWireNames } from '@abloatai/transaction/auth/capability';
22
19
  import { startStoreLifecycle } from './storeLifecycle.js';
23
20
  import { createClaimStream } from '../sync/createClaimStream.js';
24
21
  import { awaitClaimGrant } from '@abloatai/transaction/claims';
25
22
  import { bindClaimLifetime, claimLifetimeOf, } from '@abloatai/transaction/claims/lifetime';
26
- import { createParticipantManager } from '../sync/participants.js';
27
23
  import { resolveApiKeyValue, resolveBootstrapBaseUrl } from '@abloatai/transaction/auth/apiKey';
28
24
  import { claimAttemptFailure, emitClaimStatus, } from '@abloatai/transaction/client/resources/modelOperations';
29
25
  import { createModelOperations } from './createModelOperations.js';
@@ -33,7 +29,7 @@ import { translateHttpError, } from '@abloatai/transaction/errors';
33
29
  import { kReadEvidence, prepareReadSet, } from '@abloatai/transaction/internal/read-set';
34
30
  import { contextOnChange } from '../sync/contextOnChange.js';
35
31
  export function buildReactiveEngine(inputs) {
36
- const { options, internalOptions, url, logger, configuredApiKey, configuredAuthToken, credentialResolver, authCredentials, transport, participantId, kind, presence, cluster, createSibling, } = inputs;
32
+ const { options, internalOptions, url, logger, configuredApiKey, configuredAuthToken, credentialResolver, authCredentials, transport, participantId, kind, presence, cluster, } = inputs;
37
33
  const schema = options.schema;
38
34
  const pointReadBaseUrl = resolveBootstrapBaseUrl({
39
35
  url,
@@ -109,7 +105,7 @@ export function buildReactiveEngine(inputs) {
109
105
  if (configuredApiKey && (internalOptions.kind || internalOptions.agentId)) {
110
106
  logger.warn('Ablo: `kind` / `agentId` are ignored when an `apiKey` is configured — ' +
111
107
  'the server derives participant identity from the key’s scope. Remove ' +
112
- 'them (or mint a scoped session via `ablo.sessions.create({ agent })` ' +
108
+ 'them (or mint a scoped session with `Sessions({ schema, apiKey }).create({ agent })` ' +
113
109
  'for a distinct agent identity). They apply only to the self-hosted ' +
114
110
  '`capabilityToken` path.');
115
111
  }
@@ -149,13 +145,6 @@ export function buildReactiveEngine(inputs) {
149
145
  },
150
146
  });
151
147
  const ready = lifecycle.ready;
152
- const participantManager = createParticipantManager({
153
- ready,
154
- transport,
155
- presence: presenceStream,
156
- claims: claimStream,
157
- schema,
158
- });
159
148
  // 9b. waitForFlush — drains pending mutations using the store's
160
149
  // pendingChanges counter (already maintained by BaseSyncedStore based
161
150
  // on MutationQueue events). Polls every 50ms; uses the existing
@@ -448,16 +437,6 @@ export function buildReactiveEngine(inputs) {
448
437
  // reconcile errors so read interest never makes a read reject or stall.
449
438
  enterScope: (scope) => store.enterScope(scope),
450
439
  pinScope: (scope) => store.pinScope(scope),
451
- // `ablo.<model>.join(ids, { ttl })` performs a scoped participant join
452
- // on this model's sync group(s). WebSocket only — `join` throws
453
- // `AbloConnectionError` if the socket isn't ready.
454
- // `ttl` passes straight through — both surfaces spell the lease the
455
- // same way now, so there is no rename here to make a field's name
456
- // disagree with the value it carries.
457
- createJoin: (modelKey, ids, options) => participantManager.join({
458
- scope: { [modelKey]: ids },
459
- ...(options?.ttl !== undefined ? { ttl: options.ttl } : {}),
460
- }),
461
440
  }, readSetContext);
462
441
  }
463
442
  const commits = {
@@ -532,45 +511,6 @@ export function buildReactiveEngine(inputs) {
532
511
  return commitRecordListSchema.parse(body);
533
512
  },
534
513
  };
535
- /**
536
- * The control-plane credential: always the original configured secret key.
537
- * Never reads `authCredentials` — that holds the exchanged sync credential
538
- * (a wide-scope `rk_` on the hosted path), which control-plane routes
539
- * rightly refuse (e.g. the user-session mint is sk_-gated). Counterpart to
540
- * `getAuthToken()`, which resolves the sync-plane token.
541
- *
542
- * The secret-key-only rule is enforced on the server; the credential-kind taxonomy
543
- * (secret/restricted/ephemeral/publishable) lives in `auth/credentialPolicy`.
544
- */
545
- async function controlPlaneApiKey() {
546
- return resolveApiKeyValue(configuredApiKey);
547
- }
548
- /**
549
- * Resolve the control-plane context a session/agent mint needs (sk_ +
550
- * bootstrap base URL + the schema-key→typename map the server gates on).
551
- * Shared by `sessions.create` and `agents.create` so the two mint doors
552
- * can never drift on how a token is minted. Throws if no `sk_` is present —
553
- * minting is a backend-only operation.
554
- */
555
- async function buildMintContext(resource) {
556
- const apiKey = await controlPlaneApiKey();
557
- if (!apiKey) {
558
- throw new AbloAuthenticationError(`${resource} requires a secret (sk_) API key — call it from your backend, not the browser.`, { code: 'apikey_missing' });
559
- }
560
- return {
561
- apiKey,
562
- baseUrl: resolveBootstrapBaseUrl({
563
- url,
564
- bootstrapBaseUrl: internalOptions.bootstrapBaseUrl,
565
- }),
566
- ...(internalOptions.fetch ? { fetch: internalOptions.fetch } : {}),
567
- // Map every `can` schema-key to the wire typename the server gates on, so a
568
- // typename override (`documents` → `Document`) doesn't mint a capability
569
- // the server then denies. Derived from this client's schema by the one rule
570
- // the HTTP client and the mint route also read. See `MintSessionContext`.
571
- modelTypenames: modelWireNames(schema.models),
572
- };
573
- }
574
514
  const engine = {
575
515
  ...modelProxies,
576
516
  ready,
@@ -594,11 +534,6 @@ export function buildReactiveEngine(inputs) {
594
534
  // is the canonical credential; fall back to a configured API key.
595
535
  //
596
536
  // This is the sync-plane token (bootstrap, WebSocket, query HTTP). Control-plane
597
- // calls (sessions.create, datasource registration) never use it — they
598
- // present the original secret key via `controlPlaneApiKey()` below. The
599
- // split matters: after the startup exchange this resolver returns the
600
- // derived wide-scope `rk_`, a credential the control-plane routes
601
- // correctly refuse (an agent token must never mint humans).
602
537
  return (authCredentials.getAuthToken() ??
603
538
  (await resolveApiKeyValue(configuredApiKey)) ??
604
539
  configuredAuthToken ??
@@ -618,86 +553,6 @@ export function buildReactiveEngine(inputs) {
618
553
  nudgeReconnect() {
619
554
  store.nudgeReconnect();
620
555
  },
621
- sessions: {
622
- // A backend (holding `sk_`) mints a short-lived scoped token for one end
623
- // user or one agent.
624
- //
625
- // Both arms authenticate with the original secret key
626
- // (`controlPlaneApiKey()`), never the wide-scope `rk_` the startup exchange
627
- // installed as the sync credential. A derived agent credential silently
628
- // replacing the secret key on control-plane calls is how humans would get
629
- // minted as agents — and correct attribution is the point.
630
- async create(params) {
631
- // Both mint paths (`{ user }` → /v1/ephemeral_keys → `ek_`,
632
- // `{ agent, can }` → /v1/capabilities → scoped `rk_`) resolve their
633
- // control-plane context through the shared `buildMintContext`, so this
634
- // client, `agents.create`, and the stateless HTTP client can't drift on
635
- // how a token is minted.
636
- return mintSession(params, await buildMintContext('sessions.create'));
637
- },
638
- async revoke({ id }) {
639
- const context = await buildMintContext('sessions.revoke');
640
- return revokeCapability({
641
- apiKey: context.apiKey,
642
- baseUrl: context.baseUrl,
643
- id,
644
- ...(context.fetch ? { fetch: context.fetch } : {}),
645
- });
646
- },
647
- async rotate({ id, graceSeconds, ttlSeconds }) {
648
- const context = await buildMintContext('sessions.rotate');
649
- return rotateCapability({
650
- apiKey: context.apiKey,
651
- baseUrl: context.baseUrl,
652
- id,
653
- ...(graceSeconds !== undefined ? { graceSeconds } : {}),
654
- ...(ttlSeconds !== undefined ? { ttlSeconds } : {}),
655
- ...(context.fetch ? { fetch: context.fetch } : {}),
656
- });
657
- },
658
- },
659
- // Mint a scoped agent identity and hand back a connected client bound to it —
660
- // `sessions.create({ agent })` plus a typed `Ablo({ schema, apiKey })` client,
661
- // for agents that run in this (secret-key-holding) process. Omitting `id`
662
- // yields a fresh uuid per call, so concurrent agents are distinct participants
663
- // that queue behind each other (even when they share a `name`). Humans don't
664
- // get a server-built client — ship them a token via `sessions.create({ user })`.
665
- agents: {
666
- async create(params) {
667
- // Distinct participant by default: omit `id` → a fresh uuid, so even two
668
- // agents that share a `name` are independent participants and queue
669
- // behind one another. `name` is display only (→ userMeta.name); it never
670
- // derives the id. Pass an explicit `id` only to re-attach an agent to
671
- // its own held claims.
672
- const id = params.id ?? globalThis.crypto.randomUUID();
673
- const userMeta = params.name !== undefined ? { ...params.userMeta, name: params.name } : params.userMeta;
674
- const sessionParams = {
675
- agent: { id },
676
- can: params.can,
677
- ...(params.onBehalfOf ? { onBehalfOf: params.onBehalfOf } : {}),
678
- ...(params.syncGroups ? { syncGroups: params.syncGroups } : {}),
679
- ...(params.ttlSeconds !== undefined ? { ttlSeconds: params.ttlSeconds } : {}),
680
- ...(userMeta ? { userMeta } : {}),
681
- };
682
- // Re-mint the `rk_` on every resolver call so a long-lived agent client
683
- // never hits token expiry; the `sk_` stays in this process — the child
684
- // only ever sees its own short-lived `rk_`.
685
- const mintToken = async () => (await mintSession(sessionParams, await buildMintContext('agents.create')))
686
- .token;
687
- // Mint once up front so a bad key / denied scope throws HERE, not later
688
- // inside the child's bootstrap; reuse that first token, re-mint on refresh.
689
- let pending = await mintToken();
690
- const apiKey = async () => {
691
- if (pending !== null) {
692
- const token = pending;
693
- pending = null;
694
- return token;
695
- }
696
- return mintToken();
697
- };
698
- return createSibling({ ...internalOptions, apiKey });
699
- },
700
- },
701
556
  async dispose() {
702
557
  lifecycle.dispose();
703
558
  try {
@@ -60,7 +60,11 @@ export function startStoreLifecycle(deps) {
60
60
  // unambiguously a deliberate server client). User-kind clients in Node (an
61
61
  // SSR/RSC module evaluating scaffolded browser code) stay reactive-only.
62
62
  if (credentialResolver) {
63
- const rawEndpoint = internalOptions.authEndpoint ?? internalOptions.apiKey;
63
+ const rawEndpoint = internalOptions.session &&
64
+ typeof internalOptions.session === 'object' &&
65
+ 'endpoint' in internalOptions.session
66
+ ? internalOptions.session.endpoint
67
+ : internalOptions.apiKey;
64
68
  const absoluteEndpoint = typeof rawEndpoint === 'string' && /^https?:\/\//i.test(rawEndpoint);
65
69
  store.startCredentialLifecycle(credentialResolver, {
66
70
  /* eslint-disable @typescript-eslint/no-deprecated -- `kind` gates the self-hosted proactive pre-roll; hosted path derives it from the apiKey scope */
@@ -13,7 +13,7 @@ import type { Model } from './Model.js';
13
13
  import type { ModelScope } from '@abloatai/transaction/types';
14
14
  import type { QueryView, QueryViewOptions } from './views/QueryView.js';
15
15
  import type { ViewRegistry } from './views/ViewRegistry.js';
16
- import type { ParticipantScope } from './sync/participants.js';
16
+ import type { GroupScope } from './sync/scopeGroups.js';
17
17
  /**
18
18
  * A snapshot of the client's synchronization state, shaped for binding to UI.
19
19
  * {@link SyncStoreContract.syncStatus} exposes a reactive instance of this, and
@@ -129,12 +129,12 @@ export interface SyncStoreContract {
129
129
  * read subscriptions and write claims always agree on which group they refer
130
130
  * to. These are optional and do nothing until the connection is open.
131
131
  */
132
- enterScope?(scope: ParticipantScope, opts?: {
132
+ enterScope?(scope: GroupScope, opts?: {
133
133
  hydrate?: boolean;
134
134
  }): Promise<void>;
135
- leaveScope?(scope: ParticipantScope): Promise<void>;
136
- pinScope?(scope: ParticipantScope): Promise<void>;
137
- unpinScope?(scope: ParticipantScope): Promise<void>;
135
+ leaveScope?(scope: GroupScope): Promise<void>;
136
+ pinScope?(scope: GroupScope): Promise<void>;
137
+ unpinScope?(scope: GroupScope): Promise<void>;
138
138
  /**
139
139
  * The full reactive {@link SyncStatus} record. The `useSyncStatus()` hook
140
140
  * reads its fields — `state`, `progress`, `pendingChanges`, `isSessionError`,
@@ -11,13 +11,13 @@ export declare const syncActionSchema: z.ZodObject<{
11
11
  modelId: z.ZodString;
12
12
  action: z.ZodEnum<{
13
13
  A: "A";
14
- C: "C";
15
- D: "D";
16
- G: "G";
17
14
  I: "I";
18
- S: "S";
19
15
  U: "U";
16
+ D: "D";
20
17
  V: "V";
18
+ C: "C";
19
+ G: "G";
20
+ S: "S";
21
21
  }>;
22
22
  data: z.ZodUnknown;
23
23
  __class: z.ZodDefault<z.ZodLiteral<"SyncAction">>;
@@ -1,7 +1,6 @@
1
1
  /**
2
- * Moved to the confirmation core with the duplex transport (ADR 0016): keeping
3
- * a long-lived socket's credential fresh is connection plumbing an agent needs
4
- * as much as a browser does. This path re-exports it so existing importers
5
- * stay unchanged.
2
+ * The shared session subsystem owns renewal for both browser and agent
3
+ * sessions. This local boundary keeps the reactive store pointed downward at
4
+ * that one lifecycle implementation.
6
5
  */
7
- export { DEFAULT_PREROLL_INTERVAL_MS, MIN_PREROLL_DELAY_MS, computePrerollDelayMs, CredentialLifecycle, type CredentialRefreshOutcome, type CredentialRecoveryOutcome, type CredentialRefreshResult, type CredentialRefresher, type CredentialLifecycleContext, } from '@abloatai/transaction/transport/connection';
6
+ export { DEFAULT_PREROLL_INTERVAL_MS, MIN_PREROLL_DELAY_MS, computePrerollDelayMs, CredentialLifecycle, type CredentialRefreshOutcome, type CredentialRecoveryOutcome, type CredentialRefreshResult, type CredentialRefresher, type CredentialLifecycleContext, } from '@abloatai/transaction/sessions';
@@ -1,7 +1,6 @@
1
1
  /**
2
- * Moved to the confirmation core with the duplex transport (ADR 0016): keeping
3
- * a long-lived socket's credential fresh is connection plumbing an agent needs
4
- * as much as a browser does. This path re-exports it so existing importers
5
- * stay unchanged.
2
+ * The shared session subsystem owns renewal for both browser and agent
3
+ * sessions. This local boundary keeps the reactive store pointed downward at
4
+ * that one lifecycle implementation.
6
5
  */
7
- export { DEFAULT_PREROLL_INTERVAL_MS, MIN_PREROLL_DELAY_MS, computePrerollDelayMs, CredentialLifecycle, } from '@abloatai/transaction/transport/connection';
6
+ export { DEFAULT_PREROLL_INTERVAL_MS, MIN_PREROLL_DELAY_MS, computePrerollDelayMs, CredentialLifecycle, } from '@abloatai/transaction/sessions';
@@ -18,19 +18,19 @@ import type { RuntimeContext } from "../RuntimeContext.js";
18
18
  */
19
19
  export declare const ServerDeltaSchema: z.ZodObject<{
20
20
  id: z.ZodNumber;
21
+ data: z.ZodNullable<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodString]>>;
21
22
  actionType: z.ZodEnum<{
22
23
  A: "A";
23
- C: "C";
24
- D: "D";
25
- G: "G";
26
24
  I: "I";
27
- S: "S";
28
25
  U: "U";
26
+ D: "D";
29
27
  V: "V";
28
+ C: "C";
29
+ G: "G";
30
+ S: "S";
30
31
  }>;
31
32
  modelName: z.ZodString;
32
33
  modelId: z.ZodString;
33
- data: z.ZodNullable<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodString]>>;
34
34
  }, z.core.$loose>;
35
35
  export type ValidatedServerDelta = z.infer<typeof ServerDeltaSchema>;
36
36
  export declare const BootstrapResponseSchema: z.ZodObject<{
@@ -42,19 +42,19 @@ export declare const BootstrapResponseSchema: z.ZodObject<{
42
42
  models: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodPipe<z.ZodUnion<readonly [z.ZodArray<z.ZodUnknown>, z.ZodString, z.ZodNull]>, z.ZodTransform<unknown[], string | unknown[] | null>>>>;
43
43
  deltas: z.ZodOptional<z.ZodArray<z.ZodObject<{
44
44
  id: z.ZodNumber;
45
+ data: z.ZodNullable<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodString]>>;
45
46
  actionType: z.ZodEnum<{
46
47
  A: "A";
47
- C: "C";
48
- D: "D";
49
- G: "G";
50
48
  I: "I";
51
- S: "S";
52
49
  U: "U";
50
+ D: "D";
53
51
  V: "V";
52
+ C: "C";
53
+ G: "G";
54
+ S: "S";
54
55
  }>;
55
56
  modelName: z.ZodString;
56
57
  modelId: z.ZodString;
57
- data: z.ZodNullable<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodString]>>;
58
58
  }, z.core.$loose>>>;
59
59
  deltaCount: z.ZodOptional<z.ZodNumber>;
60
60
  failedModels: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -0,0 +1,11 @@
1
+ import type { ClaimTarget } from '@abloatai/transaction/types/streams';
2
+ import type { Schema } from '@abloatai/transaction/schema/schema';
3
+ /** A schema-shaped selector used to narrow connection groups and presence reads. */
4
+ export type GroupScope = ClaimTarget | readonly ClaimTarget[] | string | readonly string[] | {
5
+ readonly syncGroup: string;
6
+ } | {
7
+ readonly syncGroups: readonly string[];
8
+ } | Record<string, string | readonly string[] | undefined>;
9
+ /** Resolve an application-shaped scope into the wire groups owned by the schema. */
10
+ export declare function resolveScopeGroups(scope: GroupScope | undefined, schema?: Schema): string[];
11
+ export declare function groupFromEntityRef(ref: ClaimTarget, schema?: Schema): string;
@@ -0,0 +1,75 @@
1
+ import { scopeKindOf } from '@abloatai/transaction/schema/model';
2
+ /** Resolve an application-shaped scope into the wire groups owned by the schema. */
3
+ export function resolveScopeGroups(scope, schema) {
4
+ if (!scope)
5
+ return [];
6
+ if (typeof scope === 'string')
7
+ return [scope];
8
+ if (Array.isArray(scope)) {
9
+ const groups = [];
10
+ for (const entry of scope) {
11
+ if (typeof entry === 'string')
12
+ groups.push(entry);
13
+ else if (isEntityScope(entry))
14
+ groups.push(groupFromEntityRef(entry, schema));
15
+ }
16
+ return groups;
17
+ }
18
+ const direct = scope;
19
+ if (isEntityScope(scope))
20
+ return [groupFromEntityRef(scope, schema)];
21
+ if (typeof direct.syncGroup === 'string')
22
+ return [direct.syncGroup];
23
+ if (Array.isArray(direct.syncGroups)) {
24
+ return direct.syncGroups.filter((group) => typeof group === 'string');
25
+ }
26
+ const groups = [];
27
+ for (const [key, value] of Object.entries(scope)) {
28
+ if (value === undefined)
29
+ continue;
30
+ if (Array.isArray(value)) {
31
+ for (const id of value) {
32
+ if (typeof id === 'string')
33
+ groups.push(groupFromSchemaKey(key, id, schema));
34
+ }
35
+ }
36
+ else if (typeof value === 'string') {
37
+ groups.push(groupFromSchemaKey(key, value, schema));
38
+ }
39
+ }
40
+ return groups;
41
+ }
42
+ export function groupFromEntityRef(ref, schema) {
43
+ const match = findModelForEntityRef(ref, schema);
44
+ const kind = match
45
+ ? groupKindForModel(match.def, match.key)
46
+ : ref.type.toLowerCase();
47
+ return `${kind}:${ref.id}`;
48
+ }
49
+ function groupFromSchemaKey(schemaKey, id, schema) {
50
+ const def = schema?.models[schemaKey];
51
+ const kind = def ? groupKindForModel(def, schemaKey) : schemaKey.toLowerCase();
52
+ return `${kind}:${id}`;
53
+ }
54
+ function groupKindForModel(def, key) {
55
+ return scopeKindOf(def, key) ?? (def.typename ?? key).toLowerCase();
56
+ }
57
+ function findModelForEntityRef(ref, schema) {
58
+ if (!schema?.models)
59
+ return null;
60
+ const wanted = ref.type.toLowerCase();
61
+ for (const [key, def] of Object.entries(schema.models)) {
62
+ const typename = def.typename ?? key;
63
+ if (typename.toLowerCase() === wanted || key.toLowerCase() === wanted) {
64
+ return { key, def };
65
+ }
66
+ }
67
+ return null;
68
+ }
69
+ function isEntityScope(scope) {
70
+ return (typeof scope === 'object' &&
71
+ scope !== null &&
72
+ !Array.isArray(scope) &&
73
+ typeof scope.type === 'string' &&
74
+ typeof scope.id === 'string');
75
+ }
@@ -5,4 +5,4 @@
5
5
  * the transport object. This path re-exports it so existing importers stay
6
6
  * unchanged.
7
7
  */
8
- export { isRecord, readWsInboundFrame, wsFrameHandlers, dispatchWsFrame, type PendingCommit, type PendingClaim, type PendingSubscription, type WsInboundFrame, type WsSession, type WsFrameHandler, } from '@abloatai/transaction/transport/websocket';
8
+ export { isRecord, readWsInboundFrame, wsFrameHandlers, dispatchWsFrame, type PendingCommit, type PendingSubscription, type WsInboundFrame, type WsSession, type WsFrameHandler, } from '@abloatai/transaction/transport/websocket';
@@ -477,7 +477,7 @@ export declare class MutationQueue extends EventEmitter {
477
477
  awaitingDeltaCount: number;
478
478
  awaitingDeltaTransactions: {
479
479
  id: string;
480
- type: "archive" | "create" | "delete" | "unarchive" | "update";
480
+ type: "update" | "create" | "delete" | "archive" | "unarchive";
481
481
  modelName: string;
482
482
  modelId: string;
483
483
  syncIdNeeded: number | undefined;
@@ -486,13 +486,13 @@ export declare class MutationQueue extends EventEmitter {
486
486
  }[];
487
487
  pendingTransactions: {
488
488
  id: string;
489
- type: "archive" | "create" | "delete" | "unarchive" | "update";
489
+ type: "update" | "create" | "delete" | "archive" | "unarchive";
490
490
  modelName: string;
491
491
  modelId: string;
492
492
  }[];
493
493
  executingTransactions: {
494
494
  id: string;
495
- type: "archive" | "create" | "delete" | "unarchive" | "update";
495
+ type: "update" | "create" | "delete" | "archive" | "unarchive";
496
496
  modelName: string;
497
497
  modelId: string;
498
498
  }[];
@@ -122,7 +122,7 @@ export declare const stripModelSuffix: (modelName: string) => string;
122
122
  * foreign-key ordering because the row already exists, so they all share the
123
123
  * configured default non-create priority.
124
124
  */
125
- export declare const computePriorityScore: (type: QueuedMutation['type'], modelName: string, runtime?: RuntimeContext) => number;
125
+ export declare const computePriorityScore: (type: QueuedMutation["type"], modelName: string, runtime?: RuntimeContext) => number;
126
126
  export declare const TX_TYPE_TO_MUTATION_OP: Record<QueuedMutation['type'], MutationOperationType>;
127
127
  export declare function hasStaleWriteOptions(options?: WriteOptions): boolean;
128
128
  /** Options whose identity/audit semantics forbid merging two caller writes. */
@@ -27,11 +27,11 @@ import type { RuntimeContext } from '../../RuntimeContext.js';
27
27
  export declare const persistedTransactionSchema: z.ZodObject<{
28
28
  id: z.ZodString;
29
29
  type: z.ZodEnum<{
30
- archive: "archive";
30
+ update: "update";
31
31
  create: "create";
32
32
  delete: "delete";
33
+ archive: "archive";
33
34
  unarchive: "unarchive";
34
- update: "update";
35
35
  }>;
36
36
  modelName: z.ZodString;
37
37
  modelId: z.ZodString;
@@ -95,10 +95,10 @@ export declare function deserializePersistedTransaction(row: unknown, runtime?:
95
95
  export declare const persistedMutationSchema: z.ZodObject<{
96
96
  mutationId: z.ZodOptional<z.ZodString>;
97
97
  type: z.ZodEnum<{
98
- archive: "archive";
98
+ update: "update";
99
99
  create: "create";
100
100
  delete: "delete";
101
- update: "update";
101
+ archive: "archive";
102
102
  }>;
103
103
  modelData: z.ZodRecord<z.ZodString, z.ZodUnknown>;
104
104
  modelName: z.ZodString;
@@ -133,14 +133,15 @@ export declare const PENDING_MUTATION_RECORD_PREFIX = "pending-mutation:";
133
133
  export declare const PENDING_MUTATION_REPLAY_WINDOW_MS: number;
134
134
  /** Scope-less records written by the first aggregate-journal release. */
135
135
  export declare const legacyPendingMutationRecordSchema: z.ZodObject<{
136
+ storageVersion: z.ZodLiteral<1>;
136
137
  id: z.ZodString;
137
138
  type: z.ZodLiteral<"pending_mutation">;
138
139
  mutation: z.ZodObject<{
139
140
  type: z.ZodEnum<{
140
- archive: "archive";
141
+ update: "update";
141
142
  create: "create";
142
143
  delete: "delete";
143
- update: "update";
144
+ archive: "archive";
144
145
  }>;
145
146
  modelData: z.ZodRecord<z.ZodString, z.ZodUnknown>;
146
147
  modelName: z.ZodString;
@@ -167,17 +168,22 @@ export declare const legacyPendingMutationRecordSchema: z.ZodObject<{
167
168
  mutationId: z.ZodString;
168
169
  }, z.core.$loose>;
169
170
  timestamp: z.ZodNumber;
170
- storageVersion: z.ZodLiteral<1>;
171
171
  }, z.core.$strict>;
172
172
  export declare const pendingMutationRecordSchema: z.ZodObject<{
173
+ storageVersion: z.ZodLiteral<2>;
174
+ scope: z.ZodObject<{
175
+ organizationId: z.ZodString;
176
+ participantId: z.ZodString;
177
+ namespace: z.ZodString;
178
+ }, z.core.$strict>;
173
179
  id: z.ZodString;
174
180
  type: z.ZodLiteral<"pending_mutation">;
175
181
  mutation: z.ZodObject<{
176
182
  type: z.ZodEnum<{
177
- archive: "archive";
183
+ update: "update";
178
184
  create: "create";
179
185
  delete: "delete";
180
- update: "update";
186
+ archive: "archive";
181
187
  }>;
182
188
  modelData: z.ZodRecord<z.ZodString, z.ZodUnknown>;
183
189
  modelName: z.ZodString;
@@ -204,12 +210,6 @@ export declare const pendingMutationRecordSchema: z.ZodObject<{
204
210
  mutationId: z.ZodString;
205
211
  }, z.core.$loose>;
206
212
  timestamp: z.ZodNumber;
207
- storageVersion: z.ZodLiteral<2>;
208
- scope: z.ZodObject<{
209
- organizationId: z.ZodString;
210
- participantId: z.ZodString;
211
- namespace: z.ZodString;
212
- }, z.core.$strict>;
213
213
  }, z.core.$strict>;
214
214
  export type PendingMutationRecord = z.infer<typeof pendingMutationRecordSchema>;
215
215
  export declare function pendingMutationRecordId(mutationId: string): string;