@foir/sdk 0.9.0 → 0.11.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 (63) hide show
  1. package/CHANGELOG.md +719 -27
  2. package/README.md +121 -2
  3. package/dist/auth/auth.d.ts +72 -1
  4. package/dist/auth/hosted.d.ts +71 -4
  5. package/dist/auth/index.d.ts +3 -3
  6. package/dist/auth/operations.d.ts +84 -0
  7. package/dist/auth/ops.d.ts +18 -0
  8. package/dist/auth.js +209 -47
  9. package/dist/client.js +101 -13
  10. package/dist/codegen/block-registry.d.ts +1 -1
  11. package/dist/codegen/block-scaffold.d.ts +1 -1
  12. package/dist/codegen/index.d.ts +1 -1
  13. package/dist/codegen/item-registry.d.ts +1 -1
  14. package/dist/codegen/item-scaffold.d.ts +1 -1
  15. package/dist/codegen/preset.d.ts +3 -2
  16. package/dist/content/content-renderer.d.ts +4 -5
  17. package/dist/content/index.d.ts +1 -1
  18. package/dist/content/richtext.d.ts +1 -1
  19. package/dist/data/builtins.d.ts +81 -13
  20. package/dist/data/client.d.ts +92 -9
  21. package/dist/data/decimal.d.ts +212 -0
  22. package/dist/data/document.d.ts +14 -1
  23. package/dist/data/index.d.ts +22 -10
  24. package/dist/data/manifest.d.ts +40 -4
  25. package/dist/data/schema.d.ts +18 -11
  26. package/dist/data/selection.d.ts +15 -0
  27. package/dist/data/subscriptions.d.ts +36 -10
  28. package/dist/data/transport.d.ts +363 -24
  29. package/dist/data/ws-transport.d.ts +8 -2
  30. package/dist/data.js +591 -64
  31. package/dist/graphql/cdn-route.d.ts +1 -1
  32. package/dist/graphql/foir-request.d.ts +1 -1
  33. package/dist/index.js +0 -9
  34. package/dist/live/coalesce.d.ts +57 -0
  35. package/dist/live/drift.d.ts +58 -0
  36. package/dist/live/events.d.ts +13 -0
  37. package/dist/live/headers.d.ts +53 -0
  38. package/dist/live/index.d.ts +54 -0
  39. package/dist/live/keys.d.ts +72 -0
  40. package/dist/live/manager.d.ts +77 -0
  41. package/dist/live/react/index.d.ts +143 -0
  42. package/dist/live/reconcile.d.ts +82 -0
  43. package/dist/live/resync.d.ts +59 -0
  44. package/dist/live/store.d.ts +134 -0
  45. package/dist/live/tagIndex.d.ts +29 -0
  46. package/dist/live/tanstack.d.ts +127 -0
  47. package/dist/live/writetoken.d.ts +86 -0
  48. package/dist/live-react.js +395 -0
  49. package/dist/live.js +845 -0
  50. package/dist/next/cacheTags.d.ts +245 -0
  51. package/dist/next/index.d.ts +7 -0
  52. package/dist/next.js +260 -34
  53. package/dist/push.js +163 -36
  54. package/dist/react.js +157 -33
  55. package/dist/realtime/index.d.ts +4 -5
  56. package/dist/server/events-client.d.ts +54 -4
  57. package/dist/server/index.d.ts +4 -2
  58. package/dist/server/operation-types.d.ts +86 -58
  59. package/dist/server/read-dispatch.d.ts +53 -0
  60. package/dist/server/require-scoped-token.d.ts +20 -1
  61. package/dist/server.js +602 -10
  62. package/dist/transport/foir-http.d.ts +168 -15
  63. package/package.json +10 -2
@@ -37,21 +37,31 @@
37
37
  * const anything = await foir.raw('{ designTokens { name value } }');
38
38
  * ```
39
39
  *
40
- * `FoirSchema` comes from `foir types` (TTS-875). The contract it generates
40
+ * Inside an operation endpoint the client is built from the dispatch instead of
41
+ * a key, and calls Foir as the operation, with the module `foir types
42
+ * --operation <key>` generated for that operation:
43
+ *
44
+ * ```ts
45
+ * const { dispatch } = await requireScopedToken(request, { expectedProjectId });
46
+ * const foir = createFoirClient({ apiUrl, dispatch, generated });
47
+ * ```
48
+ *
49
+ * `FoirSchema` comes from `foir types`. The contract it generates
41
50
  * against is written down in `schema.ts` — read that file before changing
42
51
  * anything here, because a generator is written against it.
43
52
  */
44
- export { createFoirClient, createFoirClientFromEnv } from './client';
45
- export type { FoirClient, FoirClientCore, FoirModelClient, FoirNamespaceClient } from './client';
53
+ export { createFoirClient, createFoirClientFromEnv, graphql } from './client';
54
+ export type { FoirClient, FoirClientCore, FoirModelClient, FoirNamespaceClient, FoirRawOptions, TypedFoirDocument, } from './client';
46
55
  export { fetchFoirManifest } from './manifest';
56
+ export type { FoirManifestRequestOptions } from './manifest';
47
57
  export type { FoirManifest, FoirManifestComposite, FoirManifestModel, FoirManifestNamespace, FoirManifestOp, FoirManifestOpPayload, FoirManifestRelation, FoirManifestScalar, FoirManifestSelection, FoirManifestSelectionField, FoirManifestSelectionMember, FoirManifestSelectionUnion, FoirManifestStructural, FoirManifestSubscription, FoirManifestSubscriptionArgScope, FoirManifestUnclassifiedField, } from './manifest';
48
58
  export { planFoirModelCall, planFoirNamespaceCall } from './document';
49
59
  export type { FoirCall, FoirDocumentPlan, FoirNamespaceCall, FoirNamespaceSelections, FoirRecordPath, FoirSelectionModel, FoirSelectionNamespace, FoirSelections, FoirSelectionSource, FoirSelectionUnions, } from './document';
50
60
  export { createFoirSubjectRegistry, recordFoirSubjects } from './subjects';
51
61
  export type { FoirSubject, FoirSubjectEntry, FoirSubjectOptions, FoirSubjectOrigin, FoirSubjectRegistry, } from './subjects';
52
- export type { CollabInfo, FileValue, FoirAccessGrant, FoirAccessLevel, FoirAccessSource, FoirAccessor, FoirCreateMyNoteArgs, FoirCustomerNote, FoirCustomerNoteAttachment, FoirDegraded, FoirDepthBelow, FoirDepthLevel, FoirDeviceType, FoirGranteeKind, FoirJsonValue, FoirMyAccess, FoirMyNotesArgs, FoirMyNotesResult, FoirNoteConfig, FoirNoteSubjectType, FoirPlatformType, FoirResolveContext, FoirResourceAccess, FoirUnexpandedRecord, FoirUpdateManyPayload, FoirVisibility, GeoPoint, Image, ImageFormat, ImageVariant, ImageVariantSet, MediaValue, Passage, Prose, ProseSpan, RecordAncestor, Reference, Richtext, Video, } from './builtins';
62
+ export type { CollabInfo, FileValue, FoirAccessGrant, FoirAccessLevel, FoirAccessSource, FoirAccessor, FoirCreateMyNoteArgs, FoirSubmitAnonymousNoteArgs, FoirAnonymousNoteReceipt, FoirCustomerNote, FoirCustomerNoteAttachment, FoirDegraded, FoirDepthBelow, FoirDepthLevel, FoirDeviceType, FoirGranteeKind, FoirJsonValue, FoirMyAccess, FoirMyNotesArgs, FoirMyNotesResult, FoirNoteConfig, FoirNoteSubjectType, FoirPlatformType, FoirRecordOwner, FoirResolveContext, FoirResourceAccess, FoirUnexpandedRecord, FoirUpdateManyPayload, FoirVisibility, GeoPoint, Image, ImageFormat, ImageVariant, ImageVariantSet, MediaValue, Passage, Prose, ProseSpan, RecordAncestor, Reference, Richtext, Video, } from './builtins';
53
63
  /**
54
- * Pre-TTS-1391 spellings. The `<Name>Block` wrappers and the `<Name>Value`
64
+ * Pre-rename spellings. The `<Name>Block` wrappers and the `<Name>Value`
55
65
  * split are the same thing as the types above — an item IS its value — so each
56
66
  * of these is an alias, not a separate shape.
57
67
  *
@@ -60,14 +70,16 @@ export type { CollabInfo, FileValue, FoirAccessGrant, FoirAccessLevel, FoirAcces
60
70
  * emits, so there is nothing for them to alias. See `builtins.ts`.
61
71
  */
62
72
  export type { FileBlock, GeoPointValue, ImageBlock, ImageValue, ReferenceBlock, ReferenceValue, RichtextBlock, RichtextValue, VideoBlock, VideoValue, } from './builtins';
63
- export { foirDataEndpoint, foirRoute } from './transport';
64
- export type { FoirClientConfig, FoirGeneratedModule, FoirLane, FoirLaneReason, FoirRouteDecision, FoirRouteInput, FoirSchemaDrift, } from './transport';
73
+ export { FoirSchemaDriftError, foirDataEndpoint, foirRoute } from './transport';
74
+ export type { FoirClientConfig, FoirDispatchClientConfig, FoirDispatchCredential, FoirGeneratedModule, FoirKeyClientConfig, FoirKeyGeneratedModule, FoirLane, FoirLaneReason, FoirOperationCredential, FoirOperationGeneratedModule, FoirRequestConfig, FoirRouteDecision, FoirRouteInput, FoirSchemaDrift, } from './transport';
65
75
  export { foirSocketEndpoint, FoirSocketTransport } from './ws-transport';
66
76
  export type { FoirReconnectPolicy, FoirSubscribeRequest, FoirSubscription, FoirSubscriptionHandlers, FoirWebSocketFactory, FoirWebSocketLike, } from './ws-transport';
67
77
  export { printSubscriptionDocument } from './subscription-document';
68
78
  export type { FoirSubscriptionArgument, PrintSubscriptionDocumentInput, } from './subscription-document';
69
- export type { FoirExecutionSubscribeOptions, FoirJobProgressEvent, FoirModelSubscriptions, FoirNamespaceSubscriptions, FoirNotificationEvent, FoirOperationExecutionEvent, FoirRecordChangedEvent, } from './subscriptions';
79
+ export type { FoirChangeSubscribeOptions, FoirExecutionSubscribeOptions, FoirJobProgressEvent, FoirModelSubscriptions, FoirNamespaceSubscriptions, FoirNotificationEvent, FoirOperationExecutionEvent, FoirRecordChangedEvent, } from './subscriptions';
70
80
  export type { FoirConnection, FoirFieldsShape, FoirModelDefinition, FoirNamespaceDefinition, FoirNamespaceOpDefinition, FoirOpDefinition, FoirRelationDefinition, FoirReturnDefinition, FoirSchemaDefinition, } from './schema';
71
- export type { FoirInclude, FoirModelOperation, FoirNamespaceOperation, FoirOmittableKey, FoirOpResult, FoirQuery, FoirRecordResult, } from './selection';
81
+ export type { FoirInclude, FoirModelOperation, FoirNamespaceOperation, FoirOmittableKey, FoirOpResult, FoirQuery, FoirRecordResult, FoirWriteResult, } from './selection';
82
+ export { FoirDecimal, decimal } from './decimal';
83
+ export type { FoirDecimalDivOptions, FoirDecimalInput, FoirDecimalRounding, } from './decimal';
72
84
  export { FoirRequestError } from '../transport/foir-http';
73
- export type { FoirEnv, FoirGraphQLErrorItem, FoirTokenProvider, } from '../transport/foir-http';
85
+ export type { FoirEnv, FoirGraphQLErrorItem, FoirTokenProvider, FoirAuthDowngrade, } from '../transport/foir-http';
@@ -2,7 +2,7 @@
2
2
  * `GET /v1/manifest` — the per-caller surface descriptor, as a runtime type.
3
3
  *
4
4
  * This is the *runtime* mirror of `schema.ts`'s compile-time contract: what the
5
- * server actually sends, verbatim, so `foir types` (TTS-875) has a typed thing
5
+ * server actually sends, verbatim, so `foir types` has a typed thing
6
6
  * to read and the drift check has a version to compare. The manifest is already
7
7
  * the projection of `CachedSchema.SchemaForCaller(scopes)`, so what it lists is
8
8
  * exactly what this credential may do — no client-side filtering anywhere.
@@ -14,7 +14,7 @@
14
14
  * (`api-public/internal/schema/manifest.go`) field for field; where the two
15
15
  * disagree the platform is right and this file is the bug.
16
16
  */
17
- import { type FoirClientConfig } from './transport';
17
+ import { type FoirRequestConfig } from './transport';
18
18
  export interface FoirManifestScalar {
19
19
  key: string;
20
20
  /**
@@ -273,8 +273,34 @@ export interface FoirManifestNamespace {
273
273
  ops?: Record<string, FoirManifestOp>;
274
274
  }
275
275
  export interface FoirManifest {
276
+ /**
277
+ * The invalidation counter for this project and channel. Every write that
278
+ * could change a response moves it, including the many that change nothing
279
+ * here, so it keys the ETag and the server's caches and nothing on the client
280
+ * is pinned to it.
281
+ */
276
282
  schemaVersion: number;
283
+ /**
284
+ * A hash over exactly the sections `foir types` reads, so it moves if and only
285
+ * if regenerating the types would write a different file. The generated
286
+ * module records it, and the drift check compares the two.
287
+ */
288
+ surfaceVersion: string;
289
+ /**
290
+ * The components `surfaceVersion` is folded from, keyed by where each sits in
291
+ * this body: `models.<key>`, `namespaces.<name>`, `selectionUnions.<name>`,
292
+ * `subscriptions`, `composedFor`. A drift names what moved by comparing these.
293
+ */
294
+ surfaceHashes: Record<string, string>;
277
295
  channel: 'published' | 'draft';
296
+ /**
297
+ * The organisation whose composition this surface is, absent for the
298
+ * project's own. A credential confined to an organisation is served that
299
+ * organisation's surface without naming it, so this is how a reader of the
300
+ * manifest, or of a module generated from it, tells whose definitions it
301
+ * describes.
302
+ */
303
+ composedFor?: string;
278
304
  models: Record<string, FoirManifestModel>;
279
305
  /**
280
306
  * Keyed by namespace name (`auth`, `customers`, `files`, `notifications`,
@@ -433,11 +459,21 @@ export interface FoirManifestSelectionMember {
433
459
  typeName: string;
434
460
  fields: FoirManifestSelectionField[];
435
461
  }
462
+ /** What a manifest request asks for beyond the credential's own surface. */
463
+ export interface FoirManifestRequestOptions {
464
+ /**
465
+ * Describe the surface this operation's dispatch token is served instead of
466
+ * the credential's own (`GET /v1/manifest?operation=<key>`). Answered to a
467
+ * credential that can author the operation (one holding `operations:write`)
468
+ * and refused to any other. `foir types --operation` sends it.
469
+ */
470
+ operation?: string;
471
+ }
436
472
  /**
437
473
  * Fetch the manifest for the credential in `config`.
438
474
  *
439
475
  * Exported because two things outside this file need it: `foir types` reads it
440
476
  * to generate `FoirSchema`, and the client's drift check reads its
441
- * `schemaVersion`. Both would otherwise hand-roll the same fetch.
477
+ * `surfaceVersion`. Both would otherwise hand-roll the same fetch.
442
478
  */
443
- export declare function fetchFoirManifest(config: FoirClientConfig): Promise<FoirManifest>;
479
+ export declare function fetchFoirManifest(config: FoirRequestConfig, options?: FoirManifestRequestOptions): Promise<FoirManifest>;
@@ -2,7 +2,7 @@
2
2
  * THE `FoirSchema` CONTRACT.
3
3
  *
4
4
  * `createFoirClient<FoirSchema>()` is parameterised by a type that `foir types`
5
- * (TTS-875) emits from `GET /v1/manifest`. This file is that contract: the
5
+ * emits from `GET /v1/manifest`. This file is that contract: the
6
6
  * shape the generator writes and the shape the client reads. It is the only
7
7
  * coupling between the two, so it is written down here rather than living in
8
8
  * the generator's head.
@@ -19,7 +19,7 @@
19
19
  * ```
20
20
  *
21
21
  * A `.ts` and not a `.d.ts` because the file has a runtime half — the selection
22
- * trees, `foirSchemaVersion`, and `foirSchemaType`, which is what carries
22
+ * trees, `foirSurfaceVersion`, and `foirSchemaType`, which is what carries
23
23
  * `FoirSchema` across the type/value boundary so the client can infer it (a
24
24
  * namespace import carries values; an interface erases). `FoirGeneratedModule`
25
25
  * in `transport.ts` is the shape those exports have to satisfy, and it requires
@@ -30,8 +30,9 @@
30
30
  * // foir.generated.ts — DO NOT EDIT. Generated by `foir types`.
31
31
  * import type { FoirSchemaDefinition } from '@foir/sdk/data';
32
32
  *
33
- * /** `schemaVersion` from the manifest these types were generated from. *\/
34
- * export const foirSchemaVersion = 317;
33
+ * /** The surface these types were generated from. *\/
34
+ * export const foirSurfaceVersion = '6f1c0d2a9b8e4f7c3a5d1e0b2c4f6a8d';
35
+ * export const foirSurfaceHashes: Record<string, string> = { 'models.tilly_block': '…' };
35
36
  * export const foirSchemaChannel = 'published';
36
37
  *
37
38
  * // `<D>` ONLY on the types between the record and a union stopping point —
@@ -71,7 +72,7 @@
71
72
  * }
72
73
  *
73
74
  * export interface FoirSchema extends FoirSchemaDefinition {
74
- * schemaVersion: typeof foirSchemaVersion;
75
+ * surfaceVersion: typeof foirSurfaceVersion;
75
76
  * channel: 'published';
76
77
  * models: {
77
78
  * tilly_block: {
@@ -161,7 +162,7 @@
161
162
  * 8. **`raw` and `ns` are reserved top-level names.** They are the escape hatch
162
163
  * and the namespace accessor on the client object. A model whose key is
163
164
  * `raw` or `ns` would collide; the server should reject those model keys at
164
- * create time (noted in TTS-868's "Known limits").
165
+ * create time (a known limit).
165
166
  *
166
167
  * 9. **`models` and `namespaces` must be object type LITERALS, not named
167
168
  * `interface`s.** TypeScript gives an implicit index signature to object
@@ -211,10 +212,16 @@ export interface FoirRelationDefinition {
211
212
  /** True when the relation itself can resolve null. */
212
213
  readonly nullable: boolean;
213
214
  }
214
- /** How an op's result wraps (or does not wrap) the model. */
215
+ /**
216
+ * How an op's result wraps (or does not wrap) the model. `write` is a record
217
+ * write's result: the record, and whether the answer is a replay (see
218
+ * `FoirWriteResult`).
219
+ */
215
220
  export type FoirReturnDefinition = {
216
221
  readonly kind: 'record';
217
222
  readonly nullable: boolean;
223
+ } | {
224
+ readonly kind: 'write';
218
225
  } | {
219
226
  readonly kind: 'connection';
220
227
  } | {
@@ -282,12 +289,12 @@ export interface FoirNamespaceDefinition {
282
289
  */
283
290
  export interface FoirSchemaDefinition {
284
291
  /**
285
- * `schemaVersion` from the manifest these types were generated from. Kept as
292
+ * `surfaceVersion` from the manifest these types were generated from. Kept as
286
293
  * a literal so a human reading the generated file sees it, and mirrored by a
287
- * runtime `foirSchemaVersion` const for drift detection — the type cannot be
288
- * read at runtime.
294
+ * runtime `foirSurfaceVersion` const for drift detection, because the type
295
+ * cannot be read at runtime.
289
296
  */
290
- readonly schemaVersion: number;
297
+ readonly surfaceVersion: string;
291
298
  readonly channel: 'published' | 'draft';
292
299
  readonly models: Readonly<Record<string, FoirModelDefinition>>;
293
300
  readonly namespaces: Readonly<Record<string, FoirNamespaceDefinition>>;
@@ -428,11 +428,26 @@ type WrapRelation<R extends FoirRelationDefinition, T> = R['list'] extends true
428
428
  export type FoirRecordResult<S extends FoirSchemaDefinition, M extends FoirModelDefinition, Q, D extends number = IncludeDepth, Depth extends number = DepthOf<Q>> = Q extends {
429
429
  include: infer I;
430
430
  } ? ApplySelection<M, Q, Depth> & IncludedRelations<S, M, I, D, Depth> : ApplySelection<M, Q, Depth>;
431
+ /**
432
+ * What a record write (`create`, `update`, `rename`) answers with.
433
+ *
434
+ * `replayed` is true when the call carried an `idempotencyKey` an earlier
435
+ * attempt of the same write already used: the platform wrote nothing this time
436
+ * and `record` is as that first attempt left it. A caller that acts on the
437
+ * result (a stock count read back after a take, say) checks it, because a
438
+ * replayed answer looks exactly like a fresh one.
439
+ */
440
+ export interface FoirWriteResult<R> {
441
+ record: R;
442
+ replayed: boolean;
443
+ }
431
444
  /** Apply an op's return wrapper to the shaped record. */
432
445
  export type FoirOpResult<S extends FoirSchemaDefinition, M extends FoirModelDefinition, O extends FoirOpDefinition, Q> = O['returns'] extends {
433
446
  kind: 'opaque';
434
447
  type: infer T;
435
448
  } ? T : O['returns'] extends {
449
+ kind: 'write';
450
+ } ? FoirWriteResult<FoirRecordResult<S, M, Q>> : O['returns'] extends {
436
451
  kind: 'connection';
437
452
  } ? FoirConnection<FoirRecordResult<S, M, Q>> : O['returns'] extends {
438
453
  kind: 'list';
@@ -38,13 +38,17 @@
38
38
  * carries a resolved selection tree, which `subscription-document.ts` prints
39
39
  * with the same walk the query path prints models with.
40
40
  *
41
- * A subscribing client therefore fetches the descriptor once even when
42
- * `config.selections` was supplied, because `selections` carries MODEL trees and
43
- * nothing generates subscription ones yet. That is a real cost and it is stated
44
- * rather than hidden — but it is ONE fetch, memoised across every subscription
45
- * and shared with the query path, sitting behind a long-lived socket. `client.ts`
46
- * rejects a round trip in front of every query for good reason; in front of a
47
- * socket that will live for minutes it is noise.
41
+ * A client built with `foirSubscriptions` makes no request at all: `client.ts`
42
+ * hands this an already-resolved descriptor instead of the fetcher, and the
43
+ * printer cannot tell the difference — it is the same shape either way, which is
44
+ * why nothing in this file changed when that landed.
45
+ *
46
+ * Without them it fetches once, which is what every client did before `foir
47
+ * types` emitted subscription trees and what a module generated before that
48
+ * still does. It is ONE fetch, memoised across every subscription and shared
49
+ * with the query path, sitting behind a long-lived socket. `client.ts` rejects a
50
+ * round trip in front of every query for good reason; in front of a socket that
51
+ * will live for minutes it is noise.
48
52
  */
49
53
  import type { FoirManifest } from './manifest';
50
54
  import type { FoirClientConfig } from './transport';
@@ -88,8 +92,12 @@ export interface FoirJobProgressEvent {
88
92
  /** `operationExecution` / `operation{Op}Execution` — one execution's progress. */
89
93
  export interface FoirOperationExecutionEvent {
90
94
  id: string;
91
- /** `pending`, `dispatched`, `running`, `completed`, `failed`, `cancelled` or `timed_out`. */
92
- status: string;
95
+ /**
96
+ * The same spelling `operationExecution` and `operationExecutions` return for
97
+ * the run, so one comparison serves a read and an event. The socket carries
98
+ * the platform's lowercase value; this client hands it over as the enum name.
99
+ */
100
+ status: 'PENDING' | 'DISPATCHED' | 'RUNNING' | 'COMPLETED' | 'FAILED' | 'CANCELLED' | 'TIMED_OUT';
93
101
  /** 0-100; null on terminal events. */
94
102
  progressPct?: number | null;
95
103
  progressMessage?: string | null;
@@ -117,6 +125,24 @@ export type FoirExecutionSubscribeOptions = FoirSubscriptionHandlers<FoirOperati
117
125
  */
118
126
  operation?: string;
119
127
  };
128
+ /**
129
+ * What `onChange` takes: the handlers, plus the optional resume cursor.
130
+ *
131
+ * `after` is the highest `seq` this consumer has already applied. Passing it
132
+ * makes the stream cursor-resumable — the server replays what came after it
133
+ * from the durable log before continuing live, so a message lost anywhere
134
+ * upstream is repaired by the next connect rather than left as a permanently
135
+ * stale cache entry. Omitting it is the stream as it has always been: live from
136
+ * the moment you connect.
137
+ *
138
+ * The two refusals it can produce — a cursor older than the retention window,
139
+ * or one further behind than the surface will page — arrive on `onError` with a
140
+ * machine-readable code, and both mean the cache must be thrown away rather
141
+ * than partially repaired. `@foir/sdk/live` classifies them.
142
+ */
143
+ export type FoirChangeSubscribeOptions = FoirSubscriptionHandlers<FoirRecordChangedEvent> & {
144
+ after?: number;
145
+ };
120
146
  /** The subscriptions a model accessor carries. */
121
147
  export interface FoirModelSubscriptions {
122
148
  /**
@@ -125,7 +151,7 @@ export interface FoirModelSubscriptions {
125
151
  * The socket refuses a model this credential may not read, but the model is
126
152
  * not in `FoirSchema` in that case, so the refusal is a type error first.
127
153
  */
128
- onChange(handlers: FoirSubscriptionHandlers<FoirRecordChangedEvent>): FoirSubscription;
154
+ onChange(options: FoirChangeSubscribeOptions): FoirSubscription;
129
155
  }
130
156
  /**
131
157
  * The subscriptions one namespace carries, by name.