@relyio/sdk 0.7.0 → 0.8.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.
@@ -1,4 +1,4 @@
1
- import { aJ as paths, Q as EntryMetadata, aK as components } from './define-content-type-DiVpbwNs.cjs';
1
+ import { aJ as paths, Q as EntryMetadata, aK as components } from './define-content-type-cEGjIwT5.cjs';
2
2
 
3
3
  /**
4
4
  * Where the SDK's transport gets its truth.
@@ -197,6 +197,34 @@ interface RelyIOConfig {
197
197
  * })
198
198
  */
199
199
  fetch?: typeof globalThis.fetch;
200
+ /**
201
+ * RelyIO's live edge, which `liveChanges()` — and `<RelyIOProvider live>` from `@relyio/react`
202
+ * with a publishable key — follows a dataset's published changes at. An `https:` or `wss:` URL
203
+ * (`http:` or `ws:` for a local one); the socket and the poll each use the scheme they need.
204
+ *
205
+ * Omitted, it is derived from `baseUrl` for RelyIO's own API only: `https://api.relyio.com`
206
+ * (the default) follows `https://live.relyio.com`. For any other `baseUrl` there is no address to
207
+ * derive, so set this, or `liveChanges()` ends at once with a `LiveEdgeError` naming it and sends
208
+ * nothing.
209
+ *
210
+ * @example
211
+ * const relyio = createClient({
212
+ * apiKey: 'rly_pub_…',
213
+ * baseUrl: 'http://localhost:3000',
214
+ * liveUrl: 'http://localhost:8787',
215
+ * })
216
+ */
217
+ liveUrl?: string;
218
+ /**
219
+ * The `WebSocket` class `liveChanges()` opens its socket with. Omit it and the client uses the
220
+ * runtime's global `WebSocket` — every browser, and Node 22 or later — looked up per connection.
221
+ * Where there is none, as in Node 18 and 20, it polls the edge instead, about every fifteen
222
+ * seconds.
223
+ *
224
+ * Supply one to use a library's socket where the runtime has none, or to add what your runtime
225
+ * lets a socket carry.
226
+ */
227
+ webSocket?: LiveWebSocketConstructor;
200
228
  /** Browser-specific configuration */
201
229
  browser?: BrowserConfig;
202
230
  /**
@@ -213,6 +241,19 @@ interface RelyIOConfig {
213
241
  */
214
242
  dev?: boolean;
215
243
  }
244
+ /**
245
+ * The part of a `WebSocket` the live edge transport uses. A browser's `WebSocket` and Node's (22 and
246
+ * later) are assignable as they are; `src/__tests__/changes.types.ts` holds the platform's to it.
247
+ * Each event reaches its listener as `unknown` and is read field by field.
248
+ */
249
+ interface LiveWebSocket {
250
+ /** `0` connecting, `1` open, `2` closing, `3` closed. */
251
+ readonly readyState: number;
252
+ send(data: string): void;
253
+ close(code?: number, reason?: string): void;
254
+ addEventListener(type: 'open' | 'message' | 'close' | 'error', listener: (event: unknown) => void): void;
255
+ }
256
+ type LiveWebSocketConstructor = new (url: string) => LiveWebSocket;
216
257
  /**
217
258
  * What `updateConfig()` takes: any option of {@link RelyIOConfig}, each laid over the value the
218
259
  * client holds rather than over its default.
@@ -378,10 +419,11 @@ type Environment = 'development' | 'staging' | 'production';
378
419
  type WithDefaults<T, NoDefault extends keyof T = never> = Required<Omit<T, NoDefault>> & Pick<T, NoDefault>;
379
420
  /**
380
421
  * The options with no default, absent from a merged configuration until the caller gives one.
381
- * `fetch` among them: absent means the runtime's own, and that is read per request rather than
382
- * captured here.
422
+ * `fetch` and `webSocket` among them: absent means the runtime's own, and that is read per request
423
+ * or per connection rather than captured here. `liveUrl` too: absent is derived from `baseUrl` when
424
+ * the live transport opens, where RelyIO's own API names its edge, and is otherwise no address.
383
425
  */
384
- type UndefaultedOption = 'apiKey' | 'dataset' | 'locale' | 'dev' | 'fetch';
426
+ type UndefaultedOption = 'apiKey' | 'dataset' | 'locale' | 'dev' | 'fetch' | 'liveUrl' | 'webSocket';
385
427
  /** The options that are themselves bags of options, each merged field by field. */
386
428
  type OptionBag = 'browser' | 'cache' | 'realtime' | 'debug';
387
429
  /**
@@ -815,4 +857,4 @@ interface PerformanceMetrics {
815
857
  cacheHitRatio?: number;
816
858
  }
817
859
 
818
- export { type ApiPath as A, type BaseContent as B, type CreateInput as C, type DebugConfig as D, type Environment as E, type FieldTypeName as F, ValidationError as G, type MergedRelyIOConfig as H, InvalidPayloadError as I, type PathParamsOf as J, type FieldDefinition as K, type LogLevel as L, type MatchRouteOptions as M, type PerformanceMetrics as P, type QueryOptions as Q, type RelyIOConfig as R, SDKError as S, type UpdateInput as U, type ValidationRule as V, type Wire as W, type RelyIOConfigUpdate as a, type ApiMethod as b, type ApiSuccessBody as c, type RouteMatch as d, type WhereScalar as e, type WhereFieldCondition as f, type WhereClause as g, type WhereOperator as h, type SortOptions as i, type ContentQueryResult as j, type SingletonContent as k, type CustomQuery as l, matchRoute as m, type ContentTypeSchema as n, type FieldOptions as o, type RealtimeEventType as p, type RealtimeEvent as q, type SubscriptionOptions as r, type CacheConfig as s, type RealtimeConfig as t, type RequestConfig as u, type ApiResponse as v, type RetryConfig as w, type CacheStrategy as x, type ValidationViolation as y, BaseValidationError as z };
860
+ export { type ApiPath as A, type BaseContent as B, type CreateInput as C, type DebugConfig as D, type Environment as E, type FieldTypeName as F, type ValidationViolation as G, BaseValidationError as H, ValidationError as I, InvalidPayloadError as J, type MergedRelyIOConfig as K, type LiveWebSocket as L, type MatchRouteOptions as M, type PathParamsOf as N, type FieldDefinition as O, type PerformanceMetrics as P, type QueryOptions as Q, type RelyIOConfig as R, SDKError as S, type UpdateInput as U, type ValidationRule as V, type Wire as W, type RelyIOConfigUpdate as a, type LiveWebSocketConstructor as b, type ApiMethod as c, type ApiSuccessBody as d, type RouteMatch as e, type WhereScalar as f, type WhereFieldCondition as g, type WhereClause as h, type WhereOperator as i, type SortOptions as j, type ContentQueryResult as k, type SingletonContent as l, matchRoute as m, type CustomQuery as n, type ContentTypeSchema as o, type FieldOptions as p, type RealtimeEventType as q, type RealtimeEvent as r, type SubscriptionOptions as s, type CacheConfig as t, type RealtimeConfig as u, type RequestConfig as v, type ApiResponse as w, type RetryConfig as x, type CacheStrategy as y, type LogLevel as z };
@@ -1,4 +1,4 @@
1
- import { aJ as paths, Q as EntryMetadata, aK as components } from './define-content-type-DiVpbwNs.js';
1
+ import { aJ as paths, Q as EntryMetadata, aK as components } from './define-content-type-cEGjIwT5.js';
2
2
 
3
3
  /**
4
4
  * Where the SDK's transport gets its truth.
@@ -197,6 +197,34 @@ interface RelyIOConfig {
197
197
  * })
198
198
  */
199
199
  fetch?: typeof globalThis.fetch;
200
+ /**
201
+ * RelyIO's live edge, which `liveChanges()` — and `<RelyIOProvider live>` from `@relyio/react`
202
+ * with a publishable key — follows a dataset's published changes at. An `https:` or `wss:` URL
203
+ * (`http:` or `ws:` for a local one); the socket and the poll each use the scheme they need.
204
+ *
205
+ * Omitted, it is derived from `baseUrl` for RelyIO's own API only: `https://api.relyio.com`
206
+ * (the default) follows `https://live.relyio.com`. For any other `baseUrl` there is no address to
207
+ * derive, so set this, or `liveChanges()` ends at once with a `LiveEdgeError` naming it and sends
208
+ * nothing.
209
+ *
210
+ * @example
211
+ * const relyio = createClient({
212
+ * apiKey: 'rly_pub_…',
213
+ * baseUrl: 'http://localhost:3000',
214
+ * liveUrl: 'http://localhost:8787',
215
+ * })
216
+ */
217
+ liveUrl?: string;
218
+ /**
219
+ * The `WebSocket` class `liveChanges()` opens its socket with. Omit it and the client uses the
220
+ * runtime's global `WebSocket` — every browser, and Node 22 or later — looked up per connection.
221
+ * Where there is none, as in Node 18 and 20, it polls the edge instead, about every fifteen
222
+ * seconds.
223
+ *
224
+ * Supply one to use a library's socket where the runtime has none, or to add what your runtime
225
+ * lets a socket carry.
226
+ */
227
+ webSocket?: LiveWebSocketConstructor;
200
228
  /** Browser-specific configuration */
201
229
  browser?: BrowserConfig;
202
230
  /**
@@ -213,6 +241,19 @@ interface RelyIOConfig {
213
241
  */
214
242
  dev?: boolean;
215
243
  }
244
+ /**
245
+ * The part of a `WebSocket` the live edge transport uses. A browser's `WebSocket` and Node's (22 and
246
+ * later) are assignable as they are; `src/__tests__/changes.types.ts` holds the platform's to it.
247
+ * Each event reaches its listener as `unknown` and is read field by field.
248
+ */
249
+ interface LiveWebSocket {
250
+ /** `0` connecting, `1` open, `2` closing, `3` closed. */
251
+ readonly readyState: number;
252
+ send(data: string): void;
253
+ close(code?: number, reason?: string): void;
254
+ addEventListener(type: 'open' | 'message' | 'close' | 'error', listener: (event: unknown) => void): void;
255
+ }
256
+ type LiveWebSocketConstructor = new (url: string) => LiveWebSocket;
216
257
  /**
217
258
  * What `updateConfig()` takes: any option of {@link RelyIOConfig}, each laid over the value the
218
259
  * client holds rather than over its default.
@@ -378,10 +419,11 @@ type Environment = 'development' | 'staging' | 'production';
378
419
  type WithDefaults<T, NoDefault extends keyof T = never> = Required<Omit<T, NoDefault>> & Pick<T, NoDefault>;
379
420
  /**
380
421
  * The options with no default, absent from a merged configuration until the caller gives one.
381
- * `fetch` among them: absent means the runtime's own, and that is read per request rather than
382
- * captured here.
422
+ * `fetch` and `webSocket` among them: absent means the runtime's own, and that is read per request
423
+ * or per connection rather than captured here. `liveUrl` too: absent is derived from `baseUrl` when
424
+ * the live transport opens, where RelyIO's own API names its edge, and is otherwise no address.
383
425
  */
384
- type UndefaultedOption = 'apiKey' | 'dataset' | 'locale' | 'dev' | 'fetch';
426
+ type UndefaultedOption = 'apiKey' | 'dataset' | 'locale' | 'dev' | 'fetch' | 'liveUrl' | 'webSocket';
385
427
  /** The options that are themselves bags of options, each merged field by field. */
386
428
  type OptionBag = 'browser' | 'cache' | 'realtime' | 'debug';
387
429
  /**
@@ -815,4 +857,4 @@ interface PerformanceMetrics {
815
857
  cacheHitRatio?: number;
816
858
  }
817
859
 
818
- export { type ApiPath as A, type BaseContent as B, type CreateInput as C, type DebugConfig as D, type Environment as E, type FieldTypeName as F, ValidationError as G, type MergedRelyIOConfig as H, InvalidPayloadError as I, type PathParamsOf as J, type FieldDefinition as K, type LogLevel as L, type MatchRouteOptions as M, type PerformanceMetrics as P, type QueryOptions as Q, type RelyIOConfig as R, SDKError as S, type UpdateInput as U, type ValidationRule as V, type Wire as W, type RelyIOConfigUpdate as a, type ApiMethod as b, type ApiSuccessBody as c, type RouteMatch as d, type WhereScalar as e, type WhereFieldCondition as f, type WhereClause as g, type WhereOperator as h, type SortOptions as i, type ContentQueryResult as j, type SingletonContent as k, type CustomQuery as l, matchRoute as m, type ContentTypeSchema as n, type FieldOptions as o, type RealtimeEventType as p, type RealtimeEvent as q, type SubscriptionOptions as r, type CacheConfig as s, type RealtimeConfig as t, type RequestConfig as u, type ApiResponse as v, type RetryConfig as w, type CacheStrategy as x, type ValidationViolation as y, BaseValidationError as z };
860
+ export { type ApiPath as A, type BaseContent as B, type CreateInput as C, type DebugConfig as D, type Environment as E, type FieldTypeName as F, type ValidationViolation as G, BaseValidationError as H, ValidationError as I, InvalidPayloadError as J, type MergedRelyIOConfig as K, type LiveWebSocket as L, type MatchRouteOptions as M, type PathParamsOf as N, type FieldDefinition as O, type PerformanceMetrics as P, type QueryOptions as Q, type RelyIOConfig as R, SDKError as S, type UpdateInput as U, type ValidationRule as V, type Wire as W, type RelyIOConfigUpdate as a, type LiveWebSocketConstructor as b, type ApiMethod as c, type ApiSuccessBody as d, type RouteMatch as e, type WhereScalar as f, type WhereFieldCondition as g, type WhereClause as h, type WhereOperator as i, type SortOptions as j, type ContentQueryResult as k, type SingletonContent as l, matchRoute as m, type CustomQuery as n, type ContentTypeSchema as o, type FieldOptions as p, type RealtimeEventType as q, type RealtimeEvent as r, type SubscriptionOptions as s, type CacheConfig as t, type RealtimeConfig as u, type RequestConfig as v, type ApiResponse as w, type RetryConfig as x, type CacheStrategy as y, type LogLevel as z };
@@ -1,6 +1,7 @@
1
- import { H as MergedRelyIOConfig, A as ApiPath, b as ApiMethod, J as PathParamsOf, v as ApiResponse, W as Wire, u as RequestConfig, B as BaseContent, g as WhereClause, Q as QueryOptions, i as SortOptions, j as ContentQueryResult, C as CreateInput, U as UpdateInput, S as SDKError, z as BaseValidationError, y as ValidationViolation, R as RelyIOConfig, a as RelyIOConfigUpdate } from './index-DbsTUw3Q.cjs';
2
- import { aK as components, aJ as paths, W as InferContentType, aa as InferAssetType, a3 as InferSingleton } from './define-content-type-DiVpbwNs.cjs';
3
- import { a as SchemaDefinition, b as SchemaContentTypeNames, j as SchemaEntryOf, ay as FilterOperator, h as SchemaAssetTypeNames, c as SchemaSingletonNames, g as SchemaSettingsGroupNames, I as InferSettingsGroup, e as SchemaQueryNames, f as SchemaLocaleCodes } from './define-schema-D7MButVt.cjs';
1
+ import { K as MergedRelyIOConfig, A as ApiPath, c as ApiMethod, N as PathParamsOf, w as ApiResponse, W as Wire, v as RequestConfig, B as BaseContent, h as WhereClause, Q as QueryOptions, j as SortOptions, k as ContentQueryResult, C as CreateInput, U as UpdateInput, S as SDKError, H as BaseValidationError, G as ValidationViolation, R as RelyIOConfig, a as RelyIOConfigUpdate } from './index-B_rIWO_l.cjs';
2
+ import { aK as components, aJ as paths, W as InferContentType, aa as InferAssetType, a3 as InferSingleton } from './define-content-type-cEGjIwT5.cjs';
3
+ import { a as SchemaDefinition, b as SchemaContentTypeNames, j as SchemaEntryOf, ay as FilterOperator, h as SchemaAssetTypeNames, c as SchemaSingletonNames, g as SchemaSettingsGroupNames, I as InferSettingsGroup, e as SchemaQueryNames, f as SchemaLocaleCodes } from './define-schema-DgbJ5Kwn.cjs';
4
+ import { VisitorResyncReason } from './live-visitor.js';
4
5
  import { DevUnreadableReason } from './dev-handshake/index.cjs';
5
6
 
6
7
  /**
@@ -301,11 +302,20 @@ declare class ContentQueryBuilder<T extends BaseContent = BaseContent, F extends
301
302
  private requestHeaders?;
302
303
  /** The client's locale, asked when the query is sent rather than copied when it was built. */
303
304
  private readonly clientLocale;
305
+ private freshValue?;
304
306
  /**
305
307
  * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when the
306
308
  * query names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
307
309
  */
308
310
  constructor(httpClient: HttpClient, contentType: string, locale?: ClientLocale);
311
+ /**
312
+ * The feed position this read follows — a change's `fresh` (`FeedChange`), or a resync's. Sent as
313
+ * `?_fresh`, the one term the API declares for it: no cache — the Content API's edge or the
314
+ * browser's own — holds an answer for that position from before it, so the read is answered after
315
+ * the change, and every read naming the same position shares that answer. The answer is the
316
+ * read's own; omit it for a read no change prompted.
317
+ */
318
+ fresh(position: string): this;
309
319
  /**
310
320
  * Headers this query adds to the client's own, e.g. `X-RelyIO-Dataset` to list another dataset
311
321
  * the key can see. A name the client already sends is replaced for this query only; the client
@@ -516,11 +526,20 @@ declare class ContentByIdBuilder<T extends BaseContent = BaseContent> {
516
526
  private requestHeaders?;
517
527
  /** The client's locale, asked when the read is sent rather than copied when it was built. */
518
528
  private readonly clientLocale;
529
+ private freshValue?;
519
530
  /**
520
531
  * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when the
521
532
  * read names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
522
533
  */
523
534
  constructor(httpClient: HttpClient, contentType: string, id: string, locale?: ClientLocale);
535
+ /**
536
+ * The feed position this read follows — a change's `fresh` (`FeedChange`), or a resync's. Sent as
537
+ * `?_fresh`, the one term the API declares for it: no cache — the Content API's edge or the
538
+ * browser's own — holds an answer for that position from before it, so the read is answered after
539
+ * the change, and every read naming the same position shares that answer. The answer is the
540
+ * read's own; omit it for a read no change prompted.
541
+ */
542
+ fresh(position: string): this;
524
543
  /**
525
544
  * Include related content in the response
526
545
  */
@@ -1352,6 +1371,12 @@ interface SingletonFetchOptions {
1352
1371
  locale?: string;
1353
1372
  /** Reference fields to populate */
1354
1373
  include?: string[];
1374
+ /**
1375
+ * The feed position this read follows — a change's `fresh` (`FeedChange`), or a resync's. Sent as
1376
+ * `?_fresh`: no cache holds an answer for it from before that position, so the read is answered
1377
+ * after the change. The answer is the read's own; omit it for a read no change prompted.
1378
+ */
1379
+ fresh?: string;
1355
1380
  }
1356
1381
  /**
1357
1382
  * Singleton client for managing singleton content operations
@@ -1738,6 +1763,7 @@ declare class SearchBuilder<T extends BaseContent = BaseContent, S extends AnySc
1738
1763
  private query;
1739
1764
  private searchOptions;
1740
1765
  private requestHeaders?;
1766
+ private freshValue?;
1741
1767
  /** The client's locale, asked when the query is sent rather than copied when it was built. */
1742
1768
  private readonly clientLocale;
1743
1769
  /**
@@ -1750,6 +1776,12 @@ declare class SearchBuilder<T extends BaseContent = BaseContent, S extends AnySc
1750
1776
  * dataset the key can see. A name the client already sends is replaced for this search only.
1751
1777
  */
1752
1778
  headers(headers: Record<string, string>): this;
1779
+ /**
1780
+ * The feed position this search follows — a change's `fresh` (`FeedChange`), or a resync's. Sent
1781
+ * as `?_fresh` on the query string beside the body: the edge keys a search on both, so no answer
1782
+ * it holds from before that position can answer it. The answer is the search's own.
1783
+ */
1784
+ fresh(position: string): this;
1753
1785
  /**
1754
1786
  * Search within a single content type. Where a schema types the client, the name is one of its
1755
1787
  * content types and the results are that type's entries.
@@ -2833,8 +2865,34 @@ type ChangesPageBody = PollChanges['responses'][200]['content']['application/jso
2833
2865
  * item, and fails if it is ever `unknown` again.
2834
2866
  */
2835
2867
  type Change = ChangesPageBody['data'][number];
2836
- /** Why a cursor could no longer be replayed. `unknown` when the message named neither. */
2837
- type ChangesExpiredReason = 'retention' | 'dataset-replaced' | 'unknown';
2868
+ /**
2869
+ * A change as `relyio.changes()` and `relyio.liveChanges()` deliver it: the document's
2870
+ * {@link Change}, and `fresh`.
2871
+ *
2872
+ * `fresh` is the feed position a refetch for this change names, as the one query term the API
2873
+ * declares on every read its edge may keep (`?_fresh`, `sdk-surfaces-spec.md` decision 32): hand it
2874
+ * to the read — `.fresh(change.fresh)` on `findById()`, `query()` and `search()`, `{ fresh }` to a
2875
+ * singleton's `fetch()`. A position no request named before the change is a key no cache holds a
2876
+ * body under, so the read is answered after the change — at the edge and in the browser, whether
2877
+ * or not the change's purge has reached them — and every later read naming it shares that answer.
2878
+ *
2879
+ * It is at or after the change, never before: on `/v1/changes` the change's own cursor, the ledger
2880
+ * row it came from; at the live edge the cursor of the batch it arrived in. It is not always
2881
+ * `cursor`: at the edge an earlier change of a batch carries the position **before** the batch as
2882
+ * its `cursor`, so a failure part-way through replays the batch, and a refetch naming that position
2883
+ * would be answered from before the change.
2884
+ */
2885
+ type FeedChange = Change & {
2886
+ /** The feed position a refetch for this change names (`?_fresh`); at or after the change. */
2887
+ readonly fresh: string;
2888
+ };
2889
+ /**
2890
+ * Why a cursor could no longer be replayed, or why everything must be refetched. `/v1/changes`
2891
+ * names `retention` and `dataset-replaced`; the live edge's `feed.resync` may name any reason its
2892
+ * wire declares (`VisitorResyncReason` in `@relyio/api-contract`, read from there rather than
2893
+ * restated). `unknown` when the message named none.
2894
+ */
2895
+ type ChangesExpiredReason = VisitorResyncReason | 'unknown';
2838
2896
  /**
2839
2897
  * `RESOURCE_EXPIRED` from the changes feed: the cursor names a position the
2840
2898
  * ledger can no longer replay from (`INV-SS-CURSOR-REPLAYS`). The consumer
@@ -2843,12 +2901,28 @@ type ChangesExpiredReason = 'retention' | 'dataset-replaced' | 'unknown';
2843
2901
  * A `ResourceExpiredError`, so code that already catches that still does. The
2844
2902
  * refusal declares no details, so `reason` is read from the message the api
2845
2903
  * writes (`apps/services/api/src/changes/changes-request.ts` `refusalOf`).
2904
+ *
2905
+ * The live edge's `feed.resync` — a socket's frame or a poll's answer — is the
2906
+ * same instruction and arrives as the same error ({@link ChangesExpiredError.resync}),
2907
+ * so `onExpired` means one thing on both transports.
2846
2908
  */
2847
2909
  declare class ChangesExpiredError extends ResourceExpiredError {
2848
2910
  readonly reason: ChangesExpiredReason;
2849
- /** The base class's signature, so `withContext` can rebuild one. */
2850
- constructor(message: string, statusCode?: number, cause?: Error, context?: Record<string, unknown>);
2851
- static of(expired: ResourceExpiredError): ChangesExpiredError;
2911
+ /**
2912
+ * The position the feed resynced to, which a refetch of everything names as `?_fresh`, as a
2913
+ * change's {@link FeedChange.fresh} is named: the head a `/v1/changes` resync took, or the cursor
2914
+ * the edge's `feed.resync` sent. `undefined` when there was no resync to take one — the error was
2915
+ * thrown (`onExpired: 'throw'`) — and on an error a caller built itself.
2916
+ */
2917
+ readonly fresh: string | undefined;
2918
+ /** The base class's signature, so `withContext` can rebuild one; then the position resynced to. */
2919
+ constructor(message: string, statusCode?: number, cause?: Error, context?: Record<string, unknown>, fresh?: string);
2920
+ /** The base class rebuilds from its four arguments, which would drop the position resynced to. */
2921
+ withContext(context: Record<string, unknown>): this;
2922
+ /** `/v1/changes`' refusal, with the head the feed resynced to when it did. */
2923
+ static of(expired: ResourceExpiredError, fresh?: string): ChangesExpiredError;
2924
+ /** The live edge's `feed.resync`: refetch everything, then continue from the cursor it sent. */
2925
+ static resync(reason: VisitorResyncReason, cursor: string): ChangesExpiredError;
2852
2926
  }
2853
2927
  interface ChangesOptions {
2854
2928
  /**
@@ -2882,7 +2956,8 @@ interface ChangesOptions {
2882
2956
  * - a function — resync, and call it with the error once the new position is
2883
2957
  * taken and before any change after it is yielded. This is the observable
2884
2958
  * form: whatever the consumer derived from the feed may be stale, so the
2885
- * function is where it invalidates everything. The iterator waits for it.
2959
+ * function is where it invalidates everything — naming the error's `fresh`,
2960
+ * the new position, on every refetch. The iterator waits for it.
2886
2961
  */
2887
2962
  onExpired?: 'throw' | 'resync' | ((expired: ChangesExpiredError) => void | Promise<void>);
2888
2963
  }
@@ -2891,6 +2966,81 @@ interface ChangesHeadOptions {
2891
2966
  signal?: AbortSignal;
2892
2967
  }
2893
2968
 
2969
+ /**
2970
+ * `relyio.liveChanges()` — a dataset's published changes at RelyIO's live edge, for a publishable key
2971
+ * (`live-layer-spec.md` decision 20; LV5.2, REL-1158).
2972
+ *
2973
+ * The wire is `@relyio/api-contract`'s `live-visitor.ts`, read from there and never restated: the
2974
+ * path and query names, the frames and their reader, the keepalive, the poll's answer and its reader,
2975
+ * the poll interval, and what each close code and each refused poll means a client does next.
2976
+ *
2977
+ * **One open, two ways in.** A subscription opens a WebSocket to the edge. Where it cannot — the
2978
+ * runtime has no `WebSocket` (Node 18 and 20), constructing one throws, or the socket fails before it
2979
+ * opens (a network that blocks sockets) — it polls the edge instead, from the same cursor, about
2980
+ * every {@link VISITOR_POLL_INTERVAL_MS}, and tries a socket again about once a minute. Both carry
2981
+ * one cursor space, so a switch either way loses nothing.
2982
+ *
2983
+ * **It ends only when its signal aborts, and throws every other end** — a socket closed by a refusal
2984
+ * or a deploy, a refused poll, a keepalive nobody answered — as a `LiveEdgeError` saying what to do
2985
+ * next, so `followChanges()` reconnects, backs off or stops by the edge's own tables. That loop is
2986
+ * the one reconnect policy (`INV-SS-ONE-CORE`, REL-1215); this module keeps no back-off of its own.
2987
+ *
2988
+ * **Cursors.** The edge sends one cursor after a batch of changes. The last change of a batch carries
2989
+ * it; each earlier one carries the position before the batch, so a failure part-way through a batch
2990
+ * replays the whole batch rather than skipping its rest. Delivery is at-least-once: a replay and the
2991
+ * live frames after it may name one change twice, and a refetch makes a repeat cost a read, never a
2992
+ * wrong view (`INV-SS-CHANGE-IS-IDS`).
2993
+ *
2994
+ * **What a refetch names.** Every change of a batch carries the batch's own cursor as `fresh`
2995
+ * (`FeedChange`), the position its refetch names as `?_fresh` — not `cursor`, which for an earlier
2996
+ * change is the position before the batch: the previous batch's refetches were answered under that
2997
+ * one, before this batch, and a cache would hand the same answer back. A `feed.resync`'s error
2998
+ * carries the cursor it sent the same way.
2999
+ */
3000
+
3001
+ /**
3002
+ * How often an open socket sends the keepalive, which the edge's runtime answers without waking the
3003
+ * feed — the Studio's dataset room sends its own on the same interval. A socket that has heard
3004
+ * nothing by the next one, not even the answer, is presumed dead and reconnected: a client that only
3005
+ * receives never learns otherwise that a network change dropped its connection.
3006
+ */
3007
+ declare const LIVE_KEEPALIVE_INTERVAL_MS = 30000;
3008
+ interface LiveChangesOptions {
3009
+ /**
3010
+ * A change's cursor from an earlier subscription. Omitted, the edge answers the dataset's current
3011
+ * position first and replays nothing; fetch what you show **after** that position arrives — with
3012
+ * `followChanges()`, after `onState` reports `following` — so nothing lands in between unseen.
3013
+ */
3014
+ since?: string;
3015
+ /** Content types to keep. Omitted keeps every type; an empty list is refused. At most 50. */
3016
+ types?: readonly string[];
3017
+ /** Entry ids to keep. Omitted keeps every entry; an empty list is refused. At most 100. */
3018
+ ids?: readonly string[];
3019
+ /**
3020
+ * Ends the subscription. An abort — while connected, while polling, or between polls — makes the
3021
+ * `for await` **complete**, with the socket closed, no further request and nothing thrown.
3022
+ */
3023
+ signal?: AbortSignal;
3024
+ /**
3025
+ * What to do when the edge says to refetch everything (`feed.resync`: the cursor is too old, was
3026
+ * never this dataset's, or the dataset was replaced) — as `relyio.changes()` takes it: `'throw'`
3027
+ * (default) throws a `ChangesExpiredError`, `'resync'` carries on from the edge's position, and a
3028
+ * function is awaited with the error before any change after that position is yielded.
3029
+ */
3030
+ onExpired?: ChangesOptions['onExpired'];
3031
+ /**
3032
+ * Called when the edge has the subscription in hand — its `feed.opened` frame, or an answered
3033
+ * poll. `followChanges()` passes it to reset its back-off; see `ChangeSourceOptions.opened`.
3034
+ */
3035
+ opened?: () => void;
3036
+ /**
3037
+ * Called with a position reached without a change to carry it. `followChanges()` passes it so a
3038
+ * connection dropped before its first change reconnects from there; see
3039
+ * `ChangeSourceOptions.reached`.
3040
+ */
3041
+ reached?: (cursor: string) => void;
3042
+ }
3043
+
2894
3044
  /**
2895
3045
  * The key's project, as `GET /v1/project` describes it: organization, project, dataset and branch,
2896
3046
  * each by id and name, and `studioUrl` — the Studio root the preview toolbar deep-links from
@@ -2952,6 +3102,8 @@ declare class TypedRelyIOClient<TSchema extends AnySchemaDefinition | undefined
2952
3102
  * locale, or none after `locale: null` (REL-1453).
2953
3103
  */
2954
3104
  private readonly currentLocale;
3105
+ /** The live edge's poll client id: chosen once, when first needed, and kept for this client's life. */
3106
+ private _liveClientId;
2955
3107
  constructor(config: TypedClientConfig<TSchema>);
2956
3108
  /**
2957
3109
  * Get the schema definition
@@ -3114,13 +3266,15 @@ declare class TypedRelyIOClient<TSchema extends AnySchemaDefinition | undefined
3114
3266
  * Lazy — nothing is requested until the first `next()`. It runs until
3115
3267
  * `signal` aborts, which ends the `for await` cleanly rather than throwing.
3116
3268
  * A change names what changed; refetch it through the ordinary read path
3117
- * (`INV-SS-CHANGE-IS-IDS`).
3269
+ * (`INV-SS-CHANGE-IS-IDS`), naming its `fresh` so no cache answers with the
3270
+ * body from before it (`FeedChange`).
3118
3271
  *
3119
3272
  * @example
3120
3273
  * ```typescript
3121
3274
  * const controller = new AbortController()
3122
3275
  * for await (const change of relyio.changes({ since: cursor, types: ['post'], signal: controller.signal })) {
3123
- * // { entryId, contentType, locale, kind, cursor }
3276
+ * // { entryId, contentType, locale, kind, cursor, fresh }
3277
+ * await relyio.content(change.contentType).findById(change.entryId).fresh(change.fresh).execute()
3124
3278
  * }
3125
3279
  * ```
3126
3280
  *
@@ -3128,11 +3282,42 @@ declare class TypedRelyIOClient<TSchema extends AnySchemaDefinition | undefined
3128
3282
  * ```typescript
3129
3283
  * for await (const change of relyio.changes({
3130
3284
  * since: cursor,
3131
- * onExpired: async () => invalidateEverything(),
3285
+ * onExpired: async expired => refetchEverything(expired.fresh),
3132
3286
  * })) { … }
3133
3287
  * ```
3134
3288
  */
3135
- changes(options?: ChangesOptions): AsyncIterable<Change>;
3289
+ changes(options?: ChangesOptions): AsyncIterable<FeedChange>;
3290
+ /**
3291
+ * Follow the dataset's **published** changes at RelyIO's live edge, with a publishable key
3292
+ * (`rly_pub_…`): each change as ids, in order — the transport a browser follows changes with.
3293
+ *
3294
+ * A WebSocket to the edge, from `since` or the dataset's current position; where a socket cannot
3295
+ * open — the runtime has none, or the network refuses it — it polls the edge instead, about every
3296
+ * fifteen seconds, from the same cursor. The edge is `liveUrl`, or derived from the default
3297
+ * `baseUrl` (see `RelyIOConfig.liveUrl`).
3298
+ *
3299
+ * One subscription ends only when `signal` aborts, which ends the `for await` cleanly; every other
3300
+ * end — a socket a deploy dropped, a refusal, a refused poll — is thrown as a `LiveEdgeError`
3301
+ * that says whether to stop, retry or back off. Hand it to `followChanges()`, the one loop that
3302
+ * reconnects from the last cursor with jittered back-off and honours those answers:
3303
+ *
3304
+ * @example
3305
+ * ```typescript
3306
+ * const relyio = createClient({ apiKey: 'rly_pub_…' })
3307
+ * const controller = new AbortController()
3308
+ * await followChanges(from => relyio.liveChanges({ ...from, types: ['post'] }), {
3309
+ * signal: controller.signal,
3310
+ * // `fresh` names a position no cache holds a body for from before the change.
3311
+ * onChange: change =>
3312
+ * relyio.content(change.contentType).findById(change.entryId).fresh(change.fresh).execute(),
3313
+ * onResync: resync => refetchEverything(resync.cause === 'expired' ? resync.expired.fresh : undefined),
3314
+ * })
3315
+ * ```
3316
+ *
3317
+ * A client without a publishable key sends nothing: a secret or preview key is never put in the
3318
+ * edge's URL. On a server, follow `changes()` with it instead.
3319
+ */
3320
+ liveChanges(options?: LiveChangesOptions): AsyncIterable<FeedChange>;
3136
3321
  /**
3137
3322
  * The dataset's current position in the changes feed, as a cursor to start
3138
3323
  * `changes({ since })` or `followChanges(…, { since })` from. One
@@ -3414,4 +3599,4 @@ declare function createClient<TSchema extends AnySchemaDefinition>(config: Typed
3414
3599
  */
3415
3600
  type ClientSchema<T extends TypedRelyIOClient<AnySchemaDefinition>> = T extends TypedRelyIOClient<infer TSchema> ? TSchema : never;
3416
3601
 
3417
- export { AdhocQueryClient as $, type ActiveSandbox as A, type SearchHitMetadata as B, ChangesExpiredError as C, type SearchResponse as D, type ExtendedQueryResult as E, type AutocompleteSuggestion as F, type AutocompleteResponse as G, HttpClient as H, type SimilarResult as I, type SimilarResponse as J, type SearchFilter as K, type SearchFields as L, type SearchSort as M, type SearchTypeName as N, type SearchEntry as O, type Project as P, BatchClient as Q, type Register as R, type SandboxState as S, TypedRelyIOClient as T, BatchBuilder as U, type BatchMethod as V, type BatchOperation as W, type BatchOptions as X, type BatchOperationResult as Y, type BatchSummary as Z, type BatchResponse as _, type Change as a, AdhocQueryBuilder as a0, type FetchBlock as a1, type AggregateBlock as a2, type AggregateMetric as a3, type SearchBlock as a4, type FilterValue as a5, type FullTextConfig as a6, type AdhocQueryDefinition as a7, type AdhocQueryOptions as a8, type QueryMetadata as a9, ResourceNotFoundError as aA, ResourceConflictError as aB, ResourceExpiredError as aC, isResourceNotFound as aD, type FileErrorCode as aE, type FileUploadErrorCode as aF, FileUploadError as aG, FileValidationError as aH, type FileValidationConstraints as aI, validateFile as aJ, type AdhocQueryResult as aa, AssetClient as ab, AssetFileBuilder as ac, AssetUploadBuilder as ad, isAssetEntry as ae, type AssetVisibility as af, type FileStatus as ag, type FileBackend as ah, type AssetFileBlock as ai, type AssetFileExtracted as aj, type FileStatusResult as ak, type AssetUrls as al, type AssetEntryResponse as am, type PopulatedAssetReference as an, type PresignInput as ao, type PresignResult as ap, type ConfirmInput as aq, type ConfirmResult as ar, type ResolveUrlInput as as, type ResolveUrlResult as at, type CreateAssetOptions as au, type WaitUntilReadyOptions as av, type MultipartCreateResult as aw, type MultipartPart as ax, type MultipartTarget as ay, type UploadProgress as az, type ChangesExpiredReason as b, createClient as c, type TypedClientConfig as d, type ClientSchema as e, type RegisteredSchema as f, type SendOptions as g, entityTagOf as h, type ConditionalWriteOptions as i, type RetryableWriteOptions as j, type ChangesHeadOptions as k, type ChangesOptions as l, ContentClient as m, SingletonClient as n, SettingsClient as o, ContentQueryBuilder as p, ContentByIdBuilder as q, type ChildrenOptions as r, type SingletonFetchOptions as s, type SettingsFetchOptions as t, SearchClient as u, SearchBuilder as v, AutocompleteBuilder as w, SimilarBuilder as x, type SearchMode as y, type SearchResultItem as z };
3602
+ export { type BatchOperationResult as $, type ActiveSandbox as A, type SearchResultItem as B, type Change as C, type SearchHitMetadata as D, type ExtendedQueryResult as E, type FeedChange as F, type SearchResponse as G, HttpClient as H, type AutocompleteSuggestion as I, type AutocompleteResponse as J, type SimilarResult as K, LIVE_KEEPALIVE_INTERVAL_MS as L, type SimilarResponse as M, type SearchFilter as N, type SearchFields as O, type Project as P, type SearchSort as Q, type Register as R, type SandboxState as S, TypedRelyIOClient as T, type SearchTypeName as U, type SearchEntry as V, BatchClient as W, BatchBuilder as X, type BatchMethod as Y, type BatchOperation as Z, type BatchOptions as _, ChangesExpiredError as a, type BatchSummary as a0, type BatchResponse as a1, AdhocQueryClient as a2, AdhocQueryBuilder as a3, type FetchBlock as a4, type AggregateBlock as a5, type AggregateMetric as a6, type SearchBlock as a7, type FilterValue as a8, type FullTextConfig as a9, type MultipartPart as aA, type MultipartTarget as aB, type UploadProgress as aC, ResourceNotFoundError as aD, ResourceConflictError as aE, ResourceExpiredError as aF, isResourceNotFound as aG, type FileErrorCode as aH, type FileUploadErrorCode as aI, FileUploadError as aJ, FileValidationError as aK, type FileValidationConstraints as aL, validateFile as aM, type AdhocQueryDefinition as aa, type AdhocQueryOptions as ab, type QueryMetadata as ac, type AdhocQueryResult as ad, AssetClient as ae, AssetFileBuilder as af, AssetUploadBuilder as ag, isAssetEntry as ah, type AssetVisibility as ai, type FileStatus as aj, type FileBackend as ak, type AssetFileBlock as al, type AssetFileExtracted as am, type FileStatusResult as an, type AssetUrls as ao, type AssetEntryResponse as ap, type PopulatedAssetReference as aq, type PresignInput as ar, type PresignResult as as, type ConfirmInput as at, type ConfirmResult as au, type ResolveUrlInput as av, type ResolveUrlResult as aw, type CreateAssetOptions as ax, type WaitUntilReadyOptions as ay, type MultipartCreateResult as az, type ChangesExpiredReason as b, createClient as c, type TypedClientConfig as d, type ClientSchema as e, type RegisteredSchema as f, type SendOptions as g, entityTagOf as h, type ConditionalWriteOptions as i, type RetryableWriteOptions as j, type ChangesHeadOptions as k, type ChangesOptions as l, type LiveChangesOptions as m, ContentClient as n, SingletonClient as o, SettingsClient as p, ContentQueryBuilder as q, ContentByIdBuilder as r, type ChildrenOptions as s, type SingletonFetchOptions as t, type SettingsFetchOptions as u, SearchClient as v, SearchBuilder as w, AutocompleteBuilder as x, SimilarBuilder as y, type SearchMode as z };