@byline/client 2.7.0 → 3.0.1

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.
@@ -57,6 +57,7 @@ export class CollectionHandle {
57
57
  pageSize,
58
58
  fields: select,
59
59
  readMode,
60
+ onMissingLocale: options.onMissingLocale ?? 'fallback',
60
61
  });
61
62
  await this.populateIfRequested(collectionId, result.documents, locale, readMode, requestContext, {
62
63
  ...options,
@@ -96,6 +97,7 @@ export class CollectionHandle {
96
97
  populate: options.populate,
97
98
  depth: options.depth,
98
99
  status: options.status,
100
+ onMissingLocale: options.onMissingLocale,
99
101
  _readContext: options._readContext,
100
102
  _bypassBeforeRead: options._bypassBeforeRead,
101
103
  });
@@ -119,6 +121,7 @@ export class CollectionHandle {
119
121
  readMode,
120
122
  filters,
121
123
  lenient: options.lenient,
124
+ onMissingLocale: options.onMissingLocale ?? 'fallback',
122
125
  });
123
126
  if (raw == null)
124
127
  return null;
@@ -158,6 +161,7 @@ export class CollectionHandle {
158
161
  reconstruct: true,
159
162
  readMode,
160
163
  filters,
164
+ onMissingLocale: options.onMissingLocale ?? 'fallback',
161
165
  });
162
166
  if (raw == null)
163
167
  return null;
@@ -202,6 +206,7 @@ export class CollectionHandle {
202
206
  locale: options.locale,
203
207
  status: options.status,
204
208
  path: options.path,
209
+ availableLocales: options.availableLocales,
205
210
  });
206
211
  }
207
212
  /**
@@ -216,6 +221,7 @@ export class CollectionHandle {
216
221
  data,
217
222
  locale: options.locale,
218
223
  path: options.path,
224
+ availableLocales: options.availableLocales,
219
225
  });
220
226
  }
221
227
  /**
package/dist/response.js CHANGED
@@ -35,6 +35,25 @@ export function shapeDocument(raw) {
35
35
  if (Array.isArray(raw.restoreWarnings) && raw.restoreWarnings.length > 0) {
36
36
  shaped._restoreWarnings = raw.restoreWarnings;
37
37
  }
38
+ // The per-document content source locale (storage key `source_locale`).
39
+ if (typeof raw.source_locale === 'string') {
40
+ shaped.sourceLocale = raw.source_locale;
41
+ }
42
+ // Locale advertising + availability metadata. Present on
43
+ // find / findById / findByPath; absent on version/history reads.
44
+ // Storage raw keys already match the surface names (passthrough) —
45
+ // `availableLocales` is the editorial advertised set (document-grain,
46
+ // stored), `_availableVersionLocales` is the structural ledger fact
47
+ // (version-grain, computed). See docs/I18N.md.
48
+ if (Array.isArray(raw.availableLocales)) {
49
+ shaped.availableLocales = raw.availableLocales;
50
+ }
51
+ if (Array.isArray(raw._availableVersionLocales)) {
52
+ shaped._availableVersionLocales = raw._availableVersionLocales;
53
+ }
54
+ if (typeof raw._localeAgnostic === 'boolean') {
55
+ shaped._localeAgnostic = raw._localeAgnostic;
56
+ }
38
57
  return shaped;
39
58
  }
40
59
  /**
package/dist/types.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
  import type { RequestContext } from '@byline/auth';
9
- import type { BylineLogger, CollectionDefinition, IDbAdapter, IStorageProvider, PopulateSpec, PredicateValue, QueryPredicate, ReadContext, ReadMode, RichTextPopulateFn, ServerConfig, SlugifierFn, SortSpec } from '@byline/core';
9
+ import type { BylineLogger, CollectionDefinition, IDbAdapter, IStorageProvider, MissingLocalePolicy, PopulateSpec, PredicateValue, QueryPredicate, ReadContext, ReadMode, RichTextPopulateFn, ServerConfig, SlugifierFn, SortSpec } from '@byline/core';
10
10
  export type { FilterOperators, PredicateValue, QueryPredicate, SortDirection, SortSpec, } from '@byline/core';
11
11
  export interface BylineClientConfig {
12
12
  /**
@@ -138,6 +138,31 @@ interface PopulateControls {
138
138
  interface StatusControls {
139
139
  status?: ReadMode;
140
140
  }
141
+ /**
142
+ * What a read does when the requested content locale is missing. Shared by
143
+ * every read method. `@byline/client` defaults this to `'fallback'`.
144
+ *
145
+ * - `'fallback'` (client default) — always return content. A document
146
+ * requested in a locale it has not been translated into falls back through
147
+ * the locale chain to the default content locale: a detail read still
148
+ * returns the document (rendered in the default locale); a list read still
149
+ * includes it.
150
+ * - `'empty'` — restore the requested locale exactly, leaving untranslated
151
+ * localized fields empty (the raw per-locale view; what the admin editor
152
+ * uses). The document is still returned.
153
+ * - `'omit'` — only surface documents available in the requested locale. A
154
+ * detail read returns `null` (so callers can 404); a list read excludes
155
+ * untranslated documents (filtered at the SQL layer, so the page count and
156
+ * `total` stay correct). A document with no localized content is available
157
+ * in every locale.
158
+ *
159
+ * Availability is the version-grain ledger described in
160
+ * `docs/I18N.md`. Relationship population always behaves
161
+ * as `'fallback'` so a populated tree never has holes.
162
+ */
163
+ interface MissingLocaleControls {
164
+ onMissingLocale?: MissingLocalePolicy;
165
+ }
141
166
  /**
142
167
  * Read-side access-control escape hatch shared by every read method.
143
168
  *
@@ -153,7 +178,7 @@ interface StatusControls {
153
178
  interface BeforeReadControls {
154
179
  _bypassBeforeRead?: true;
155
180
  }
156
- export interface FindOptions<F = Record<string, any>> extends PopulateControls, StatusControls, BeforeReadControls {
181
+ export interface FindOptions<F = Record<string, any>> extends PopulateControls, StatusControls, MissingLocaleControls, BeforeReadControls {
157
182
  /** Filter documents. Keys are field names or reserved names (status, path). */
158
183
  where?: WhereClause;
159
184
  /** Return only these fields. Omit for all fields. */
@@ -167,12 +192,12 @@ export interface FindOptions<F = Record<string, any>> extends PopulateControls,
167
192
  /** Documents per page. Defaults to 20. */
168
193
  pageSize?: number;
169
194
  }
170
- export interface FindOneOptions<F = Record<string, any>> extends PopulateControls, StatusControls, BeforeReadControls {
195
+ export interface FindOneOptions<F = Record<string, any>> extends PopulateControls, StatusControls, MissingLocaleControls, BeforeReadControls {
171
196
  where?: WhereClause;
172
197
  select?: (keyof F & string)[] | string[];
173
198
  locale?: string;
174
199
  }
175
- export interface FindByIdOptions<F = Record<string, any>> extends PopulateControls, StatusControls, BeforeReadControls {
200
+ export interface FindByIdOptions<F = Record<string, any>> extends PopulateControls, StatusControls, MissingLocaleControls, BeforeReadControls {
176
201
  select?: (keyof F & string)[] | string[];
177
202
  locale?: string;
178
203
  /**
@@ -185,7 +210,7 @@ export interface FindByIdOptions<F = Record<string, any>> extends PopulateContro
185
210
  */
186
211
  lenient?: boolean;
187
212
  }
188
- export interface FindByPathOptions<F = Record<string, any>> extends PopulateControls, StatusControls, BeforeReadControls {
213
+ export interface FindByPathOptions<F = Record<string, any>> extends PopulateControls, StatusControls, MissingLocaleControls, BeforeReadControls {
189
214
  select?: (keyof F & string)[] | string[];
190
215
  locale?: string;
191
216
  }
@@ -228,6 +253,13 @@ export interface CreateOptions {
228
253
  * derives a path from `definition.useAsPath` (or falls back to a UUID).
229
254
  */
230
255
  path?: string;
256
+ /**
257
+ * The editorial advertised-locale set, stored document-grain in
258
+ * `byline_document_available_locales`. When omitted, a new document starts
259
+ * with an empty set; an explicit array (empty included) is stored verbatim.
260
+ * Surfaced on reads as `availableLocales`. See `docs/I18N.md`.
261
+ */
262
+ availableLocales?: string[];
231
263
  }
232
264
  export interface UpdateOptions {
233
265
  /** Locale for field value resolution. Defaults to the client's `defaultLocale`. */
@@ -237,6 +269,13 @@ export interface UpdateOptions {
237
269
  * carries forward unchanged (sticky).
238
270
  */
239
271
  path?: string;
272
+ /**
273
+ * The editorial advertised-locale set. When omitted, the existing set
274
+ * carries forward unchanged (sticky — document-grain, like `path`); an
275
+ * explicit array (empty included) replaces it wholesale. Surfaced on reads
276
+ * as `availableLocales`. See `docs/I18N.md`.
277
+ */
278
+ availableLocales?: string[];
240
279
  }
241
280
  /**
242
281
  * A where clause maps field names (or reserved document-level names like
@@ -292,6 +331,15 @@ export interface ClientDocument<F = Record<string, any>> {
292
331
  path: string;
293
332
  /** Workflow status (e.g. 'draft', 'published'). */
294
333
  status: string;
334
+ /**
335
+ * The document's content **source locale** — the locale it was authored in,
336
+ * and its per-document anchor (fallback floor, path locale, completeness
337
+ * yardstick). Stable, document-grain. Surfaced so consumers / the admin can
338
+ * indicate a document whose primary language differs from the system default.
339
+ * Present on `find` / `findById` / `findByPath`. See
340
+ * `docs/I18N.md`.
341
+ */
342
+ sourceLocale?: string;
295
343
  /** When this version was created. */
296
344
  createdAt: Date;
297
345
  /** When this version was last updated. */
@@ -307,6 +355,33 @@ export interface ClientDocument<F = Record<string, any>> {
307
355
  * normal reads.
308
356
  */
309
357
  _restoreWarnings?: string[];
358
+ /**
359
+ * The editorial *advertised* locale set — the content locales the editor
360
+ * has deliberately elected to promote for this document. Document-grain and
361
+ * stored (in `byline_document_available_locales`), sticky across versions —
362
+ * not derived. The deliberate counterpart to the structural
363
+ * `_availableVersionLocales` fact below; the public advertised set is their
364
+ * intersection (`availableLocales ∩ _availableVersionLocales`), which the
365
+ * host composes for hreflang / sitemap / "Also available in…" menus.
366
+ * Present on `find` / `findById` / `findByPath`. See `docs/I18N.md`.
367
+ */
368
+ availableLocales?: string[];
369
+ /**
370
+ * Content locales this document's resolved version is *structurally*
371
+ * available in — path-coverage against the default content locale, from the
372
+ * version-grain `byline_document_version_locales` ledger. Computed, read-only.
373
+ * Present on `find` / `findById` / `findByPath` (absent on version/history
374
+ * reads); the published-available set on a normal (published) read. The
375
+ * structural fact reconciled against the editorial `availableLocales` above.
376
+ * See `docs/I18N.md`.
377
+ */
378
+ _availableVersionLocales?: string[];
379
+ /**
380
+ * `true` when the document has no localized content — it renders identically
381
+ * in every locale and `_availableVersionLocales` is empty. A per-document
382
+ * language affordance should render nothing in this case.
383
+ */
384
+ _localeAgnostic?: boolean;
310
385
  }
311
386
  export interface FindResult<F = Record<string, any>> {
312
387
  docs: ClientDocument<F>[];
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@byline/client",
3
3
  "private": false,
4
4
  "license": "MPL-2.0",
5
- "version": "2.7.0",
5
+ "version": "3.0.1",
6
6
  "engines": {
7
7
  "node": ">=20.9.0"
8
8
  },
@@ -38,8 +38,8 @@
38
38
  ],
39
39
  "dependencies": {
40
40
  "npm-run-all": "^4.1.5",
41
- "@byline/auth": "2.7.0",
42
- "@byline/core": "2.7.0"
41
+ "@byline/auth": "3.0.1",
42
+ "@byline/core": "3.0.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@biomejs/biome": "2.4.15",
@@ -51,7 +51,7 @@
51
51
  "tsx": "^4.22.3",
52
52
  "typescript": "6.0.3",
53
53
  "vitest": "^4.1.7",
54
- "@byline/db-postgres": "2.7.0"
54
+ "@byline/db-postgres": "3.0.1"
55
55
  },
56
56
  "publishConfig": {
57
57
  "access": "public",