@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
@@ -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);
@@ -935,7 +935,8 @@ export class SyncClient extends EventEmitter {
935
935
  model: Model,
936
936
  poolAction: () => void,
937
937
  writeOptions?: WriteOptions,
938
- ): void {
938
+ capturedChangesOverride?: Record<string, unknown>,
939
+ ): Promise<void> | undefined {
939
940
  // No-op UPDATE guard (O(1)). An update with no dirty fields would travel
940
941
  // to the server, get dropped by `coalesceOperations` Rule 4 (empty input),
941
942
  // and — if it was the only op — come back as `lastSyncId: 0`. That trips
@@ -949,24 +950,32 @@ export class SyncClient extends EventEmitter {
949
950
  // is false → we fall through to the normal path rather than risk dropping a
950
951
  // real write. Only a genuine Model with an empty dirty-set is skipped.
951
952
  const hasChanges: unknown = model.hasChanges;
952
- if (type === 'update' && hasChanges === false) {
953
- return;
953
+ if (
954
+ type === 'update' &&
955
+ hasChanges === false &&
956
+ capturedChangesOverride === undefined
957
+ ) {
958
+ return Promise.resolve();
954
959
  }
955
960
 
956
961
  // Capture changes before the pool action runs. Pool operations —
957
962
  // upsert in particular — can clear the model's local changes, so
958
963
  // capturing first ensures they are never lost.
959
- const capturedChanges =
960
- type === 'update' || type === 'create' ? this.captureModelChanges(model) : undefined;
964
+ const capturedChanges = capturedChangesOverride !== undefined
965
+ ? Object.freeze({ ...capturedChangesOverride })
966
+ : type === 'update' || type === 'create'
967
+ ? this.captureModelChanges(model)
968
+ : undefined;
961
969
 
962
970
  poolAction();
963
- this.stageMutation(type, model, capturedChanges, writeOptions);
971
+ const confirmation = this.stageMutation(type, model, capturedChanges, writeOptions);
964
972
  this.notifyObservers({
965
973
  type,
966
974
  modelType: model.getModelName(),
967
975
  model: type !== 'delete' ? model : undefined,
968
976
  modelId: model.id,
969
977
  });
978
+ return confirmation;
970
979
 
971
980
  // QueryProcessor uses `models:changed` to invalidate caches. Coalesce
972
981
  // to one event per microtask: a paste of 100 rows should re-run
@@ -1004,13 +1013,23 @@ export class SyncClient extends EventEmitter {
1004
1013
  }
1005
1014
 
1006
1015
  /** Add new model (CREATE) - works offline */
1007
- add(model: Model, options?: WriteOptions): void {
1008
- this.mutate('create', model, () => { this.objectPool.add(model, ModelScope.live); }, options);
1016
+ add(model: Model, options?: WriteOptions): Promise<void> | undefined {
1017
+ return this.mutate('create', model, () => { this.objectPool.add(model, ModelScope.live); }, options);
1009
1018
  }
1010
1019
 
1011
1020
  /** Update existing model (UPDATE) - works offline */
1012
- update(model: Model, options?: WriteOptions): void {
1013
- this.mutate('update', model, () => { this.objectPool.upsert(model, ModelScope.live); }, options);
1021
+ update(
1022
+ model: Model,
1023
+ options?: WriteOptions,
1024
+ capturedChanges?: Record<string, unknown>,
1025
+ ): Promise<void> | undefined {
1026
+ return this.mutate(
1027
+ 'update',
1028
+ model,
1029
+ () => { this.objectPool.upsert(model, ModelScope.live); },
1030
+ options,
1031
+ capturedChanges,
1032
+ );
1014
1033
  }
1015
1034
 
1016
1035
  /**
@@ -1055,10 +1074,10 @@ export class SyncClient extends EventEmitter {
1055
1074
  }
1056
1075
 
1057
1076
  /** Delete model (DELETE) - works offline */
1058
- delete(model: Model, options?: WriteOptions): void {
1077
+ delete(model: Model, options?: WriteOptions): Promise<void> | undefined {
1059
1078
  // Clear pending mutations first to prevent "not found" errors on fast delete
1060
1079
  this.mutationQueue.cancelTransactionsForModel(model.id);
1061
- this.mutate('delete', model, () => this.objectPool.remove(model.id), options);
1080
+ return this.mutate('delete', model, () => this.objectPool.remove(model.id), options);
1062
1081
  }
1063
1082
 
1064
1083
  /**
@@ -1204,8 +1223,8 @@ export class SyncClient extends EventEmitter {
1204
1223
  model: Model,
1205
1224
  capturedChanges?: Record<string, unknown>,
1206
1225
  writeOptions?: WriteOptions,
1207
- ): void {
1208
- if (this.isDisposed) return;
1226
+ ): Promise<void> | undefined {
1227
+ if (this.isDisposed) return Promise.resolve();
1209
1228
  if (!this.userId || !this.organizationId) {
1210
1229
  this.mutationQueue.deferMutation(type, model, capturedChanges, writeOptions);
1211
1230
  return;
@@ -1218,6 +1237,13 @@ export class SyncClient extends EventEmitter {
1218
1237
  capturedChanges,
1219
1238
  writeOptions,
1220
1239
  );
1240
+ const confirmation = staging.then(async (transaction) => {
1241
+ await transaction.confirmation;
1242
+ });
1243
+ // Most internal callers intentionally use fire-and-forget writes. Observe
1244
+ // their rejection without replacing the exact promise returned to model
1245
+ // operations that need authoritative per-transaction confirmation.
1246
+ void confirmation.catch(() => undefined);
1221
1247
  const pending = staging.then(() => undefined).catch((error: Error) => {
1222
1248
  this.runtime.observability.captureMutationFailure({
1223
1249
  context: `stage-mutation-${type}`,
@@ -1228,6 +1254,7 @@ export class SyncClient extends EventEmitter {
1228
1254
  });
1229
1255
  this.pendingStages.add(pending);
1230
1256
  void pending.finally(() => this.pendingStages.delete(pending));
1257
+ return confirmation;
1231
1258
  }
1232
1259
 
1233
1260
  private scheduleSync(): void {
@@ -6,7 +6,7 @@
6
6
  * `read` and `list`, with the same point lookup restricted to the local graph under
7
7
  * `local`, the writes `create`, `update`, and `delete`, the coordination
8
8
  * namespace `claim` (callable as `claim({ id })`, plus `claim.state`,
9
- * `claim.queue`, `claim.release`, and `claim.reorder`), `join`, and `onChange`.
9
+ * `claim.queue`, `claim.release`, and `claim.reorder`), and `onChange`.
10
10
  * The factory returns a plain object; the client assembles the `ablo.<model>`
11
11
  * lookup table from one of these per model.
12
12
  */
@@ -66,7 +66,6 @@ import type { ModelRegistry } from '../ModelRegistry.js';
66
66
  import type { InstanceCache } from '../InstanceCache.js';
67
67
  import type { SyncClient } from '../SyncClient.js';
68
68
  import type { OnDemandLoader } from '../sync/OnDemandLoader.js';
69
- import type { JoinedParticipant } from '../sync/participants.js';
70
69
  import { ModelScope } from '@abloatai/transaction/types';
71
70
  import type {
72
71
  Duration,
@@ -112,7 +111,6 @@ export type {
112
111
  ModelCreateParams,
113
112
  ModelUpdateParams,
114
113
  ModelDeleteParams,
115
- JoinOptions,
116
114
  } from '@abloatai/transaction/client/resources/modelOperations';
117
115
  export type { Claim, ClaimHeartbeat, ClaimHeartbeatOptions, HeldClaim, HeldLease };
118
116
 
@@ -126,7 +124,6 @@ import type {
126
124
  ClaimAttemptEvent,
127
125
  ClaimQueueView,
128
126
  ClaimReorderParams,
129
- JoinOptions,
130
127
  LocalCountOptions,
131
128
  LocalReadOptions,
132
129
  ModelCreateManyParams,
@@ -284,17 +281,6 @@ export interface ModelCollaboration {
284
281
  * fire-and-forget, best-effort semantics as `enterScope`.
285
282
  */
286
283
  pinScope?(scope: Record<string, string>): void | Promise<void>;
287
- /**
288
- * Opens a presence and claim subscription on this model's sync group(s) and
289
- * returns the live participant handle. Backs `ablo.<model>.join(ids)`.
290
- * WebSocket only, since presence needs a live socket; it is absent on other
291
- * client constructions, where the surface throws a clear error.
292
- */
293
- createJoin?(
294
- modelKey: string,
295
- ids: string | readonly string[],
296
- options?: JoinOptions,
297
- ): Promise<JoinedParticipant>;
298
284
  }
299
285
 
300
286
 
@@ -375,26 +361,6 @@ interface ReactiveModelSurface<T, Fields = T> {
375
361
  */
376
362
  claim: ClaimApi<T, Fields>;
377
363
 
378
- /**
379
- * Joins the sync group(s) for one or more rows of this model and returns a
380
- * live participant handle — presence (`.peers`), the scoped claim stream
381
- * (`.claims`), and `.leave()` / `await using` disposal. This is a presence
382
- * subscription: it reports who else is here and what they hold, not row
383
- * values changing — for the latter, use `onChange`.
384
- *
385
- * WebSocket only: presence needs a live socket, so this is absent on HTTP
386
- * clients and throws on any non-WebSocket construction.
387
- *
388
- * ```ts
389
- * await using participant = await ablo.sections.join(sectionIds, { ttl: '5m' });
390
- * participant.peers; // who else is here
391
- * ```
392
- */
393
- join(
394
- ids: string | readonly string[],
395
- options?: JoinOptions,
396
- ): Promise<JoinedParticipant>;
397
-
398
364
  /** Subscribe to changes; the callback runs on every change. */
399
365
  onChange(
400
366
  callback: (entities: T[]) => void,
@@ -546,7 +512,10 @@ export function createModelOperations<T, C>(
546
512
  return rows.map((row) => modelAsRow<T>(row));
547
513
  };
548
514
 
549
- const waitForMutation = async (model: Model): Promise<void> => {
515
+ const waitForMutation = async (
516
+ model: Model,
517
+ exactConfirmation?: Promise<void>,
518
+ ): Promise<void> => {
550
519
  // Model writes are optimistic locally, but their promise has one stable
551
520
  // meaning: authoritative confirmation. Callers that do not need the
552
521
  // barrier can keep using the row immediately and leave the promise to the
@@ -558,7 +527,8 @@ export function createModelOperations<T, C>(
558
527
  // coalescer and producing one SQL transaction per delta.
559
528
  await Promise.resolve();
560
529
  await syncClient.syncNow();
561
- await syncClient.waitForConfirmation(model.getModelName(), model.id);
530
+ if (exactConfirmation) await exactConfirmation;
531
+ else await syncClient.waitForConfirmation(model.getModelName(), model.id);
562
532
  };
563
533
 
564
534
  // Claims this model surface currently holds, keyed by the exact grant id.
@@ -1402,8 +1372,8 @@ export function createModelOperations<T, C>(
1402
1372
  }
1403
1373
  : {}),
1404
1374
  };
1405
- syncClient.add(model, effective);
1406
- await waitForMutation(model);
1375
+ const confirmation = syncClient.add(model, effective);
1376
+ await waitForMutation(model, confirmation);
1407
1377
  return modelAsRow<T>(model);
1408
1378
  } finally {
1409
1379
  await autoLease?.release?.().catch(() => {});
@@ -1528,8 +1498,12 @@ export function createModelOperations<T, C>(
1528
1498
  : {}),
1529
1499
  };
1530
1500
  model.applyChanges(patch);
1531
- syncClient.update(model, effective);
1532
- await waitForMutation(model);
1501
+ const confirmation = syncClient.update(
1502
+ model,
1503
+ effective,
1504
+ patch as Record<string, unknown>,
1505
+ );
1506
+ await waitForMutation(model, confirmation);
1533
1507
  return modelAsRow<T>(model);
1534
1508
  },
1535
1509
  });
@@ -1585,8 +1559,12 @@ export function createModelOperations<T, C>(
1585
1559
  // the server. (`updateFromData` is the hydration path and would discard
1586
1560
  // the tracking, producing an empty `input: {}` no-op mutation.)
1587
1561
  model.applyChanges(params.data);
1588
- syncClient.update(model, effective);
1589
- await waitForMutation(model);
1562
+ const confirmation = syncClient.update(
1563
+ model,
1564
+ effective,
1565
+ params.data as Record<string, unknown>,
1566
+ );
1567
+ await waitForMutation(model, confirmation);
1590
1568
  const updated = modelAsRow<T>(model);
1591
1569
  await settleClaimsAfterWrite(id, handle);
1592
1570
  return updated;
@@ -1658,8 +1636,8 @@ export function createModelOperations<T, C>(
1658
1636
  ...opts,
1659
1637
  ...(selected ? { claimRef: { id: selected.id } } : {}),
1660
1638
  };
1661
- syncClient.delete(model, effective);
1662
- await waitForMutation(model);
1639
+ const confirmation = syncClient.delete(model, effective);
1640
+ await waitForMutation(model, confirmation);
1663
1641
  await settleClaimsAfterWrite(id, handle);
1664
1642
  }),
1665
1643
 
@@ -1667,21 +1645,6 @@ export function createModelOperations<T, C>(
1667
1645
  // readers (`claim.state` / `claim.queue` / `claim.release` / `claim.reorder`).
1668
1646
  claim: claimApi,
1669
1647
 
1670
- join: guard(
1671
- (
1672
- ids: string | readonly string[],
1673
- options?: JoinOptions,
1674
- ): Promise<JoinedParticipant> => {
1675
- if (!collaboration?.createJoin) {
1676
- throw new AbloValidationError(
1677
- `Model "${schemaKey}" was built without a WebSocket runtime, so join() is unavailable here. Presence needs a live socket — use the standard Ablo({ schema, apiKey }) client (not the HTTP transport).`,
1678
- { code: 'model_join_not_configured' },
1679
- );
1680
- }
1681
- return collaboration.createJoin(schemaKey, ids, options);
1682
- },
1683
- ),
1684
-
1685
1648
  onChange(callback, options): () => void {
1686
1649
  return autorun(() => {
1687
1650
  callback(local.list(options));
@@ -32,6 +32,11 @@ import type { CommitOutboxScope } from '@abloatai/transaction/commit';
32
32
  */
33
33
  export type { CredentialProvider } from '@abloatai/transaction/auth/apiKey';
34
34
  import type { CredentialProvider } from '@abloatai/transaction/auth/apiKey';
35
+ import type {
36
+ SessionCredential,
37
+ SessionEndpoint,
38
+ SessionProvider,
39
+ } from '@abloatai/transaction/sessions';
35
40
  import type { AbloPlugin } from '../../plugin.js';
36
41
  import type { ParticipantKind } from '@abloatai/transaction/types/participant';
37
42
 
@@ -74,19 +79,21 @@ export interface AbloOptions<S extends SchemaRecord = SchemaRecord> {
74
79
  * `ABLO_API_KEY` environment variable, so you usually pass nothing. A
75
80
  * long-lived key needs no refresh; the client uses it as-is.
76
81
  *
77
- * - **An async resolver** `() => Promise<string | null>` the escape hatch for
78
- * when the exchange needs custom headers, a request body, or a non-HTTP mint
79
- * (vault rotation, a cloud token service, an existing auth session). It uses
80
- * the same renewal machinery as the endpoint form.
81
- *
82
- * The endpoint and resolver forms share one contract: return a token; return
83
- * `null` when the login itself is gone (terminal — the client signs out and
84
- * fails `ready()` with `session_expired`); or throw on a transient failure, which
85
- * backs off and retries without signing out. The endpoint form maps HTTP onto
86
- * this for you: only a structured `401 session_expired` means signed out.
82
+ * - **An async resolver** for advanced process-owned key rotation, such as a
83
+ * vault or workload-identity exchange. Scoped actor renewal belongs in
84
+ * `session` instead.
87
85
  */
88
86
  apiKey?: string | CredentialProvider | null | undefined;
89
87
 
88
+ /**
89
+ * Scoped actor identity. Pass a session returned by `sessions.create()` for
90
+ * bounded work, a provider that re-mints it for a long-lived client, or
91
+ * `{ endpoint: '/api/ablo-session' }` in a browser. Endpoint responses use
92
+ * the canonical credential protocol; only a structured `401
93
+ * session_expired` ends the underlying login.
94
+ */
95
+ session?: SessionCredential | SessionProvider | SessionEndpoint | null | undefined;
96
+
90
97
  /**
91
98
  * Pins this client to one Ablo project. During `ready()` the server resolves
92
99
  * the API key's actual project and the client refuses to start when it differs.
@@ -105,33 +112,6 @@ export interface AbloOptions<S extends SchemaRecord = SchemaRecord> {
105
112
  */
106
113
  branchId?: string | null | undefined;
107
114
 
108
- /**
109
- * The session-mint endpoint — the browser-side auth field, and the named
110
- * endpoint for the route that mints the signed-in user's short-lived token:
111
- *
112
- * ```ts
113
- * const ablo = Ablo({ schema, authEndpoint: '/api/ablo-session' });
114
- * ```
115
- *
116
- * The client owns the whole exchange: it POSTs the route (same-origin, cookies
117
- * included), validates the canonical auth response contract, keeps it fresh
118
- * ahead of expiry, and re-mints when the server reports the token stale. Only
119
- * a structured `401 session_expired` response means signed out. It also
120
- * accepts an async resolver `() => Promise<string | null>` when the exchange
121
- * needs custom headers or a body — the same contract as the resolver form of
122
- * `apiKey`.
123
- *
124
- * Mutually exclusive with `apiKey`: a server holds a key, a browser holds a mint
125
- * route, and passing both is a validation error.
126
- */
127
- authEndpoint?: string | CredentialProvider | null | undefined;
128
-
129
- /** Timeout for a session-mint request. @default 10000 */
130
- authTimeoutMs?: number | undefined;
131
-
132
- /** Explicit opt-in for a cross-origin session-mint endpoint. */
133
- allowCrossOriginAuthEndpoint?: boolean | undefined;
134
-
135
115
  /**
136
116
  * Local persistence mode. Pass `indexeddb` only when you want offline
137
117
  * queueing and a reload-surviving browser cache.
@@ -264,15 +244,12 @@ export interface InternalAbloOptions<S extends SchemaRecord = SchemaRecord> {
264
244
  */
265
245
  apiKey?: string | CredentialProvider | null | undefined;
266
246
 
247
+ /** A scoped session, or a provider that re-mints it for a long-lived client. */
248
+ session?: SessionCredential | SessionProvider | SessionEndpoint | null | undefined;
249
+
267
250
  /** Expected project assertion; see {@link AbloOptions.projectId}. */
268
251
  projectId?: string | null | undefined;
269
252
 
270
- /**
271
- * Session-mint endpoint (string or async resolver) — see
272
- * {@link AbloOptions.authEndpoint}. Mutually exclusive with `apiKey`.
273
- */
274
- authEndpoint?: string | CredentialProvider | null | undefined;
275
-
276
253
  /**
277
254
  * A bearer auth token, sent as `Authorization: Bearer <token>` on every request.
278
255
  *