@byline/client 6.5.0 → 6.7.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.
- package/dist/client.d.ts +3 -0
- package/dist/client.js +3 -0
- package/dist/collection-handle.d.ts +29 -13
- package/dist/collection-handle.js +78 -36
- package/dist/response.js +7 -0
- package/dist/search.d.ts +38 -6
- package/dist/search.js +41 -9
- package/dist/singleton-handle.js +6 -2
- package/dist/types.d.ts +28 -4
- package/package.json +5 -5
package/dist/client.d.ts
CHANGED
|
@@ -48,6 +48,9 @@ export declare class BylineClient<TCollections extends CollectionRegistry = Regi
|
|
|
48
48
|
* across every collection indexed into the named zone. Collections the
|
|
49
49
|
* actor cannot `read` are excluded; `beforeRead` row scoping applies per
|
|
50
50
|
* collection; `hydrate: true` attaches a shaped `ClientDocument` per hit.
|
|
51
|
+
* A public search (the default `status: 'published'`) also re-checks each
|
|
52
|
+
* hit's locale against the advertised-locale policy, and reports a
|
|
53
|
+
* page-local `total` with no facets, as `CollectionHandle.search()` does.
|
|
51
54
|
* For homogeneous, single-collection search use
|
|
52
55
|
* `client.collection(path).search()`.
|
|
53
56
|
*/
|
package/dist/client.js
CHANGED
|
@@ -122,6 +122,9 @@ export class BylineClient {
|
|
|
122
122
|
* across every collection indexed into the named zone. Collections the
|
|
123
123
|
* actor cannot `read` are excluded; `beforeRead` row scoping applies per
|
|
124
124
|
* collection; `hydrate: true` attaches a shaped `ClientDocument` per hit.
|
|
125
|
+
* A public search (the default `status: 'published'`) also re-checks each
|
|
126
|
+
* hit's locale against the advertised-locale policy, and reports a
|
|
127
|
+
* page-local `total` with no facets, as `CollectionHandle.search()` does.
|
|
125
128
|
* For homogeneous, single-collection search use
|
|
126
129
|
* `client.collection(path).search()`.
|
|
127
130
|
*/
|
|
@@ -45,25 +45,30 @@ export declare class CollectionHandle<TFields extends Record<string, any> = Reco
|
|
|
45
45
|
* Asserts the collection `read` ability first (same gate as the other
|
|
46
46
|
* reads), and defaults `status` to `'published'`, so a public viewer only
|
|
47
47
|
* sees published content — which is also all the index holds, since
|
|
48
|
-
* indexing
|
|
48
|
+
* indexing writes only publicly deliverable locales of the published
|
|
49
|
+
* version.
|
|
49
50
|
*
|
|
50
|
-
* **
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
51
|
+
* **Authorization after ranking** — "rank in the provider, authorise in
|
|
52
|
+
* core": the provider's candidate hits are re-resolved through the normal
|
|
53
|
+
* read path (the same predicate merge + SQL compile every other read uses)
|
|
54
|
+
* whenever the search is public (`status: 'published'`, the default) or
|
|
55
|
+
* the collection configures a `beforeRead` hook. A public search re-checks
|
|
56
|
+
* every hit's locale against the advertised-locale policy even without a
|
|
57
|
+
* hook, so a stale index slice for an unchecked translation is dropped;
|
|
58
|
+
* one batched read per page performs the check. `hydrate: true` reads the
|
|
59
|
+
* hits into shaped `ClientDocument`s in their own locale (no source
|
|
60
|
+
* substitution) and attaches them as `hit.document`. Consequences:
|
|
59
61
|
*
|
|
60
|
-
* - under row scoping, `total` is the
|
|
61
|
-
* and facets are omitted rather than
|
|
62
|
+
* - for every public search, and under row scoping, `total` is the
|
|
63
|
+
* surviving hit count for this page and facets are omitted rather than
|
|
64
|
+
* leaking provider-wide aggregates;
|
|
65
|
+
* - only an editorial search (`status: 'any'`) with no `beforeRead` hook
|
|
66
|
+
* passes the provider `total` and facets through unchanged;
|
|
62
67
|
* - a page of hits can come back shorter than `limit` when candidates
|
|
63
68
|
* are dropped; paginate on `offset`, not on received length.
|
|
64
69
|
*
|
|
65
70
|
* `_bypassBeforeRead: true` is the same system-operation escape hatch the
|
|
66
|
-
* read methods take.
|
|
71
|
+
* read methods take; it skips `beforeRead` scoping, never the locale check.
|
|
67
72
|
*
|
|
68
73
|
* Throws `ERR_VALIDATION` when no provider is registered.
|
|
69
74
|
*/
|
|
@@ -309,6 +314,17 @@ export declare class CollectionHandle<TFields extends Record<string, any> = Reco
|
|
|
309
314
|
* Returns the resolved context so callers can thread `readMode` and
|
|
310
315
|
* other per-request state into the adapter without re-resolving.
|
|
311
316
|
*/
|
|
317
|
+
/**
|
|
318
|
+
* Resolve the request context for a read and authorize it: the collection
|
|
319
|
+
* read ability (`assertActorCanPerform`) and the read's locale visibility
|
|
320
|
+
* (`assertLocaleVisibility`). Visibility defaults from `readMode`, so the
|
|
321
|
+
* editing, history and version reads (`readMode: 'any'`) are editorial.
|
|
322
|
+
*
|
|
323
|
+
* The visibility check validates the *effective* locale, the value every
|
|
324
|
+
* read method sends to storage (`locale ?? client.defaultLocale`), so a
|
|
325
|
+
* client configured with `defaultLocale: 'all'` cannot turn an omitted
|
|
326
|
+
* locale into a public all-locale read.
|
|
327
|
+
*/
|
|
312
328
|
private resolveAndAssertRead;
|
|
313
329
|
/**
|
|
314
330
|
* Guard that this collection opts into the document-tree primitive. The
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
|
-
import { applyAfterRead, assertActorCanPerform, buildSearchDocument, cancelDocumentScheduledPublish, changeDocumentStatus, confirmDocumentScheduledPublish, createDocument, createReadContext, createRichTextDocumentReader, deleteDocument, documentAbilityKey, documentRevisionFromDatabase, ERR_VALIDATION, getDocumentScheduledPublish, parseSort, parseWhere, placeTreeNode as placeTreeNodeLifecycle, populateDocuments, populateRichTextFields, removeFromTree as removeFromTreeLifecycle, resolveIdentityField, restoreDocumentVersion, scheduleDocumentPublish, unpublishDocument, updateDocument, } from '@byline/core';
|
|
8
|
+
import { applyAfterRead, assertActorCanPerform, assertLocaleVisibility, buildSearchDocument, cancelDocumentScheduledPublish, changeDocumentStatus, confirmDocumentScheduledPublish, createDocument, createReadContext, createRichTextDocumentReader, deleteDocument, documentAbilityKey, documentRevisionFromDatabase, ERR_VALIDATION, getDocumentScheduledPublish, parseSort, parseWhere, placeTreeNode as placeTreeNodeLifecycle, populateDocuments, populateRichTextFields, removeFromTree as removeFromTreeLifecycle, resolveIdentityField, resolveLocaleVisibility, restoreDocumentVersion, scheduleDocumentPublish, unpublishDocument, updateDocument, } from '@byline/core';
|
|
9
9
|
import { getReadSecurityDomain, resolveReadRequestContext, resolveReadSecurityFilters, } from './read-context.js';
|
|
10
10
|
import { expectedDocumentId, readSingletonForEdit, } from './read-internals.js';
|
|
11
11
|
import { shapeDocument, shapePopulatedInPlace } from './response.js';
|
|
@@ -39,7 +39,8 @@ export class CollectionHandle {
|
|
|
39
39
|
async find(options = {}) {
|
|
40
40
|
const readMode = resolveReadMode(options.status);
|
|
41
41
|
const readCtx = options._readContext ?? createReadContext();
|
|
42
|
-
const
|
|
42
|
+
const localeVisibility = resolveLocaleVisibility(options.status, options.localeVisibility);
|
|
43
|
+
const requestContext = await this.resolveAndAssertRead(readMode, readCtx, localeVisibility, options.locale);
|
|
43
44
|
const { where, select, sort, locale = this.client.defaultLocale, page = 1, pageSize = 20, } = options;
|
|
44
45
|
const securityFilters = await this.resolveBeforeReadFilters(requestContext, readCtx, options._bypassBeforeRead);
|
|
45
46
|
const parsedWhere = await parseWhere(where, this.definition, {
|
|
@@ -66,12 +67,13 @@ export class CollectionHandle {
|
|
|
66
67
|
fields: select,
|
|
67
68
|
readMode,
|
|
68
69
|
onMissingLocale: options.onMissingLocale ?? 'fallback',
|
|
70
|
+
localeVisibility,
|
|
69
71
|
});
|
|
70
72
|
await this.populateIfRequested(collectionId, result.documents, locale, readMode, requestContext, {
|
|
71
73
|
...options,
|
|
72
74
|
_readContext: readCtx,
|
|
73
75
|
});
|
|
74
|
-
await this.finishReadDocuments(result.documents, readCtx, requestContext, locale, readMode, options._bypassBeforeRead, select, readMaterialization(options.populate, options.depth));
|
|
76
|
+
await this.finishReadDocuments(result.documents, readCtx, requestContext, locale, readMode, options._bypassBeforeRead, select, readMaterialization(options.populate, options.depth), localeVisibility);
|
|
75
77
|
return {
|
|
76
78
|
docs: result.documents.map((d) => this.shapeWithPopulated(d)),
|
|
77
79
|
meta: {
|
|
@@ -147,6 +149,7 @@ export class CollectionHandle {
|
|
|
147
149
|
populate: options.populate,
|
|
148
150
|
depth: options.depth,
|
|
149
151
|
status: options.status,
|
|
152
|
+
localeVisibility: options.localeVisibility,
|
|
150
153
|
onMissingLocale: options.onMissingLocale,
|
|
151
154
|
_readContext: options._readContext,
|
|
152
155
|
_bypassBeforeRead: options._bypassBeforeRead,
|
|
@@ -163,25 +166,30 @@ export class CollectionHandle {
|
|
|
163
166
|
* Asserts the collection `read` ability first (same gate as the other
|
|
164
167
|
* reads), and defaults `status` to `'published'`, so a public viewer only
|
|
165
168
|
* sees published content — which is also all the index holds, since
|
|
166
|
-
* indexing
|
|
169
|
+
* indexing writes only publicly deliverable locales of the published
|
|
170
|
+
* version.
|
|
167
171
|
*
|
|
168
|
-
* **
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
172
|
+
* **Authorization after ranking** — "rank in the provider, authorise in
|
|
173
|
+
* core": the provider's candidate hits are re-resolved through the normal
|
|
174
|
+
* read path (the same predicate merge + SQL compile every other read uses)
|
|
175
|
+
* whenever the search is public (`status: 'published'`, the default) or
|
|
176
|
+
* the collection configures a `beforeRead` hook. A public search re-checks
|
|
177
|
+
* every hit's locale against the advertised-locale policy even without a
|
|
178
|
+
* hook, so a stale index slice for an unchecked translation is dropped;
|
|
179
|
+
* one batched read per page performs the check. `hydrate: true` reads the
|
|
180
|
+
* hits into shaped `ClientDocument`s in their own locale (no source
|
|
181
|
+
* substitution) and attaches them as `hit.document`. Consequences:
|
|
177
182
|
*
|
|
178
|
-
* - under row scoping, `total` is the
|
|
179
|
-
* and facets are omitted rather than
|
|
183
|
+
* - for every public search, and under row scoping, `total` is the
|
|
184
|
+
* surviving hit count for this page and facets are omitted rather than
|
|
185
|
+
* leaking provider-wide aggregates;
|
|
186
|
+
* - only an editorial search (`status: 'any'`) with no `beforeRead` hook
|
|
187
|
+
* passes the provider `total` and facets through unchanged;
|
|
180
188
|
* - a page of hits can come back shorter than `limit` when candidates
|
|
181
189
|
* are dropped; paginate on `offset`, not on received length.
|
|
182
190
|
*
|
|
183
191
|
* `_bypassBeforeRead: true` is the same system-operation escape hatch the
|
|
184
|
-
* read methods take.
|
|
192
|
+
* read methods take; it skips `beforeRead` scoping, never the locale check.
|
|
185
193
|
*
|
|
186
194
|
* Throws `ERR_VALIDATION` when no provider is registered.
|
|
187
195
|
*/
|
|
@@ -212,10 +220,12 @@ export class CollectionHandle {
|
|
|
212
220
|
limit: options.limit,
|
|
213
221
|
offset: options.offset,
|
|
214
222
|
});
|
|
215
|
-
// Row-level authorization (+ optional
|
|
216
|
-
// pipeline the zone entry point uses
|
|
217
|
-
//
|
|
218
|
-
|
|
223
|
+
// Row-level authorization, exact-locale eligibility (+ optional
|
|
224
|
+
// hydration) — the shared finishing pipeline the zone entry point uses
|
|
225
|
+
// too. Public searches always re-check every hit and always follow the
|
|
226
|
+
// restricted-result convention; only an editorial search without a
|
|
227
|
+
// beforeRead predicate or hydration passes hits through untouched.
|
|
228
|
+
const { hits, eligibilityRestricted } = await finalizeSearchHits({
|
|
219
229
|
client: this.client,
|
|
220
230
|
requestContext,
|
|
221
231
|
hits: results.hits,
|
|
@@ -223,9 +233,10 @@ export class CollectionHandle {
|
|
|
223
233
|
status: options.status,
|
|
224
234
|
hydrate: options.hydrate,
|
|
225
235
|
bypassBeforeRead: options._bypassBeforeRead,
|
|
236
|
+
localeVisibility: resolveLocaleVisibility(readMode),
|
|
226
237
|
readContext: readCtx,
|
|
227
238
|
});
|
|
228
|
-
return aggregateRestricted
|
|
239
|
+
return aggregateRestricted || eligibilityRestricted
|
|
229
240
|
? { hits, total: hits.length }
|
|
230
241
|
: { hits, total: results.total, facets: results.facets };
|
|
231
242
|
}
|
|
@@ -251,9 +262,13 @@ export class CollectionHandle {
|
|
|
251
262
|
return;
|
|
252
263
|
const populate = this.buildSearchFacetPopulateMap();
|
|
253
264
|
for (const locale of this.client.contentLocales) {
|
|
265
|
+
// Public delivery, pinned explicitly: the system client indexing here
|
|
266
|
+
// is authenticated, but only publicly eligible locale slices (source,
|
|
267
|
+
// or checked and complete) may enter the index.
|
|
254
268
|
const view = await this.findById(documentId, {
|
|
255
269
|
locale,
|
|
256
270
|
status: 'published',
|
|
271
|
+
localeVisibility: 'public',
|
|
257
272
|
onMissingLocale: 'omit',
|
|
258
273
|
populate,
|
|
259
274
|
_bypassBeforeRead: true,
|
|
@@ -360,7 +375,8 @@ export class CollectionHandle {
|
|
|
360
375
|
async findById(documentId, options = {}) {
|
|
361
376
|
const readMode = resolveReadMode(options.status);
|
|
362
377
|
const readCtx = options._readContext ?? createReadContext();
|
|
363
|
-
const
|
|
378
|
+
const localeVisibility = resolveLocaleVisibility(options.status, options.localeVisibility);
|
|
379
|
+
const requestContext = await this.resolveAndAssertRead(readMode, readCtx, localeVisibility, options.locale);
|
|
364
380
|
const { locale = this.client.defaultLocale } = options;
|
|
365
381
|
const filters = await this.resolveBeforeReadFilters(requestContext, readCtx, options._bypassBeforeRead);
|
|
366
382
|
const collectionId = await this.client.resolveCollectionId(this.definition.path);
|
|
@@ -373,6 +389,7 @@ export class CollectionHandle {
|
|
|
373
389
|
filters,
|
|
374
390
|
lenient: options.lenient,
|
|
375
391
|
onMissingLocale: options.onMissingLocale ?? 'fallback',
|
|
392
|
+
localeVisibility,
|
|
376
393
|
});
|
|
377
394
|
if (raw == null)
|
|
378
395
|
return null;
|
|
@@ -385,7 +402,7 @@ export class CollectionHandle {
|
|
|
385
402
|
...options,
|
|
386
403
|
_readContext: readCtx,
|
|
387
404
|
});
|
|
388
|
-
await this.finishReadDocuments([raw], readCtx, requestContext, locale, readMode, options._bypassBeforeRead, options.select, readMaterialization(options.populate, options.depth));
|
|
405
|
+
await this.finishReadDocuments([raw], readCtx, requestContext, locale, readMode, options._bypassBeforeRead, options.select, readMaterialization(options.populate, options.depth), localeVisibility);
|
|
389
406
|
return this.shapeWithPopulated(raw);
|
|
390
407
|
}
|
|
391
408
|
async findByIdForEdit(documentId, options = {}) {
|
|
@@ -453,7 +470,8 @@ export class CollectionHandle {
|
|
|
453
470
|
async findByPath(path, options = {}) {
|
|
454
471
|
const readMode = resolveReadMode(options.status);
|
|
455
472
|
const readCtx = options._readContext ?? createReadContext();
|
|
456
|
-
const
|
|
473
|
+
const localeVisibility = resolveLocaleVisibility(options.status, options.localeVisibility);
|
|
474
|
+
const requestContext = await this.resolveAndAssertRead(readMode, readCtx, localeVisibility, options.locale);
|
|
457
475
|
const { locale = this.client.defaultLocale } = options;
|
|
458
476
|
const filters = await this.resolveBeforeReadFilters(requestContext, readCtx, options._bypassBeforeRead);
|
|
459
477
|
const collectionId = await this.client.resolveCollectionId(this.definition.path);
|
|
@@ -465,6 +483,7 @@ export class CollectionHandle {
|
|
|
465
483
|
readMode,
|
|
466
484
|
filters,
|
|
467
485
|
onMissingLocale: options.onMissingLocale ?? 'fallback',
|
|
486
|
+
localeVisibility,
|
|
468
487
|
});
|
|
469
488
|
if (raw == null)
|
|
470
489
|
return null;
|
|
@@ -475,7 +494,7 @@ export class CollectionHandle {
|
|
|
475
494
|
...options,
|
|
476
495
|
_readContext: readCtx,
|
|
477
496
|
});
|
|
478
|
-
await this.finishReadDocuments([raw], readCtx, requestContext, locale, readMode, options._bypassBeforeRead, options.select, readMaterialization(options.populate, options.depth));
|
|
497
|
+
await this.finishReadDocuments([raw], readCtx, requestContext, locale, readMode, options._bypassBeforeRead, options.select, readMaterialization(options.populate, options.depth), localeVisibility);
|
|
479
498
|
return this.shapeWithPopulated(raw);
|
|
480
499
|
}
|
|
481
500
|
// -------------------------------------------------------------------------
|
|
@@ -817,7 +836,8 @@ export class CollectionHandle {
|
|
|
817
836
|
this.assertTreeCollection();
|
|
818
837
|
const readMode = resolveReadMode(options.status);
|
|
819
838
|
const readCtx = options._readContext ?? createReadContext();
|
|
820
|
-
const
|
|
839
|
+
const localeVisibility = resolveLocaleVisibility(options.status, options.localeVisibility);
|
|
840
|
+
const requestContext = await this.resolveAndAssertRead(readMode, readCtx, localeVisibility, options.locale);
|
|
821
841
|
const locale = options.locale ?? this.client.defaultLocale;
|
|
822
842
|
const filters = await this.resolveBeforeReadFilters(requestContext, readCtx, options._bypassBeforeRead);
|
|
823
843
|
const collectionId = await this.client.resolveCollectionId(this.definition.path);
|
|
@@ -828,10 +848,11 @@ export class CollectionHandle {
|
|
|
828
848
|
readMode,
|
|
829
849
|
locale,
|
|
830
850
|
filters,
|
|
851
|
+
localeVisibility,
|
|
831
852
|
});
|
|
832
853
|
if (structure.length === 0)
|
|
833
854
|
return [];
|
|
834
|
-
const shapedById = await this.hydrateTreeNodes(collectionId, structure.map((n) => n.document_id), locale, readMode, options.select, filters, readCtx, requestContext, options._bypassBeforeRead);
|
|
855
|
+
const shapedById = await this.hydrateTreeNodes(collectionId, structure.map((n) => n.document_id), locale, readMode, options.select, filters, readCtx, requestContext, options._bypassBeforeRead, localeVisibility);
|
|
835
856
|
// Assemble the nested forest. Rows arrive pre-order, so a parent is always
|
|
836
857
|
// materialised before its children; a row whose parent is not in the result
|
|
837
858
|
// set (null parent, or the requested root's own parent) is a top-level node.
|
|
@@ -960,7 +981,8 @@ export class CollectionHandle {
|
|
|
960
981
|
this.assertTreeCollection();
|
|
961
982
|
const readMode = resolveReadMode(options.status);
|
|
962
983
|
const readCtx = options._readContext ?? createReadContext();
|
|
963
|
-
const
|
|
984
|
+
const localeVisibility = resolveLocaleVisibility(options.status, options.localeVisibility);
|
|
985
|
+
const requestContext = await this.resolveAndAssertRead(readMode, readCtx, localeVisibility, options.locale);
|
|
964
986
|
const locale = options.locale ?? this.client.defaultLocale;
|
|
965
987
|
const filters = await this.resolveBeforeReadFilters(requestContext, readCtx, options._bypassBeforeRead);
|
|
966
988
|
const collectionId = await this.client.resolveCollectionId(this.definition.path);
|
|
@@ -969,11 +991,12 @@ export class CollectionHandle {
|
|
|
969
991
|
readMode,
|
|
970
992
|
locale,
|
|
971
993
|
filters,
|
|
994
|
+
localeVisibility,
|
|
972
995
|
});
|
|
973
996
|
if (ancestors.length === 0)
|
|
974
997
|
return [];
|
|
975
998
|
const ids = ancestors.map((a) => a.document_id);
|
|
976
|
-
const shapedById = await this.hydrateTreeNodes(collectionId, ids, locale, readMode, options.select, filters, readCtx, requestContext, options._bypassBeforeRead);
|
|
999
|
+
const shapedById = await this.hydrateTreeNodes(collectionId, ids, locale, readMode, options.select, filters, readCtx, requestContext, options._bypassBeforeRead, localeVisibility);
|
|
977
1000
|
// A hydration-time miss truncates the edge rather than compacting past a
|
|
978
1001
|
// newly-hidden ancestor.
|
|
979
1002
|
const visible = [];
|
|
@@ -1001,13 +1024,15 @@ export class CollectionHandle {
|
|
|
1001
1024
|
this.assertTreeCollection();
|
|
1002
1025
|
const readMode = resolveReadMode(options.status);
|
|
1003
1026
|
const readCtx = options._readContext ?? createReadContext();
|
|
1004
|
-
const
|
|
1027
|
+
const localeVisibility = resolveLocaleVisibility(options.status, options.localeVisibility);
|
|
1028
|
+
const requestContext = await this.resolveAndAssertRead(readMode, readCtx, localeVisibility, options.locale);
|
|
1005
1029
|
const filters = await this.resolveBeforeReadFilters(requestContext, readCtx, options._bypassBeforeRead);
|
|
1006
1030
|
const result = await this.client.db.queries.documents.getTreeParent({
|
|
1007
1031
|
document_id: documentId,
|
|
1008
1032
|
readMode,
|
|
1009
1033
|
locale: options.locale ?? this.client.defaultLocale,
|
|
1010
1034
|
filters,
|
|
1035
|
+
localeVisibility,
|
|
1011
1036
|
});
|
|
1012
1037
|
return {
|
|
1013
1038
|
placed: result.placed,
|
|
@@ -1085,9 +1110,21 @@ export class CollectionHandle {
|
|
|
1085
1110
|
* Returns the resolved context so callers can thread `readMode` and
|
|
1086
1111
|
* other per-request state into the adapter without re-resolving.
|
|
1087
1112
|
*/
|
|
1088
|
-
|
|
1113
|
+
/**
|
|
1114
|
+
* Resolve the request context for a read and authorize it: the collection
|
|
1115
|
+
* read ability (`assertActorCanPerform`) and the read's locale visibility
|
|
1116
|
+
* (`assertLocaleVisibility`). Visibility defaults from `readMode`, so the
|
|
1117
|
+
* editing, history and version reads (`readMode: 'any'`) are editorial.
|
|
1118
|
+
*
|
|
1119
|
+
* The visibility check validates the *effective* locale, the value every
|
|
1120
|
+
* read method sends to storage (`locale ?? client.defaultLocale`), so a
|
|
1121
|
+
* client configured with `defaultLocale: 'all'` cannot turn an omitted
|
|
1122
|
+
* locale into a public all-locale read.
|
|
1123
|
+
*/
|
|
1124
|
+
async resolveAndAssertRead(readMode, readContext, localeVisibility = resolveLocaleVisibility(readMode), locale) {
|
|
1089
1125
|
const requestContext = await resolveReadRequestContext(this.client, readContext, readMode);
|
|
1090
1126
|
assertActorCanPerform(requestContext, this.definition, 'read');
|
|
1127
|
+
assertLocaleVisibility(requestContext, this.definition.path, localeVisibility, locale ?? this.client.defaultLocale);
|
|
1091
1128
|
return requestContext;
|
|
1092
1129
|
}
|
|
1093
1130
|
/**
|
|
@@ -1113,7 +1150,7 @@ export class CollectionHandle {
|
|
|
1113
1150
|
* `afterRead` per node (deduped within one shared `ReadContext`) to stay
|
|
1114
1151
|
* consistent with the other client read paths.
|
|
1115
1152
|
*/
|
|
1116
|
-
async hydrateTreeNodes(collectionId, documentIds, locale, readMode, select, filters, readContext, requestContext, bypassBeforeRead) {
|
|
1153
|
+
async hydrateTreeNodes(collectionId, documentIds, locale, readMode, select, filters, readContext, requestContext, bypassBeforeRead, localeVisibility = resolveLocaleVisibility(readMode)) {
|
|
1117
1154
|
const shapedById = new Map();
|
|
1118
1155
|
if (documentIds.length === 0)
|
|
1119
1156
|
return shapedById;
|
|
@@ -1124,8 +1161,9 @@ export class CollectionHandle {
|
|
|
1124
1161
|
readMode,
|
|
1125
1162
|
fields: select,
|
|
1126
1163
|
filters,
|
|
1164
|
+
localeVisibility,
|
|
1127
1165
|
});
|
|
1128
|
-
await this.finishReadDocuments(rawDocs, readContext, requestContext, locale, readMode, bypassBeforeRead, select, 'tree');
|
|
1166
|
+
await this.finishReadDocuments(rawDocs, readContext, requestContext, locale, readMode, bypassBeforeRead, select, 'tree', localeVisibility);
|
|
1129
1167
|
for (const raw of rawDocs) {
|
|
1130
1168
|
const doc = raw;
|
|
1131
1169
|
shapedById.set(doc.document_id, this.shapeWithPopulated(doc));
|
|
@@ -1153,6 +1191,9 @@ export class CollectionHandle {
|
|
|
1153
1191
|
depth: options.depth,
|
|
1154
1192
|
locale,
|
|
1155
1193
|
readMode,
|
|
1194
|
+
// Equals the entry point's resolved visibility: an explicit option wins,
|
|
1195
|
+
// otherwise the default follows the same status that selected `readMode`.
|
|
1196
|
+
localeVisibility: options.localeVisibility ?? resolveLocaleVisibility(readMode),
|
|
1156
1197
|
readContext: options._readContext,
|
|
1157
1198
|
requestContext,
|
|
1158
1199
|
securityDomain: getReadSecurityDomain(this.client),
|
|
@@ -1169,7 +1210,7 @@ export class CollectionHandle {
|
|
|
1169
1210
|
* `populateRelationsOnRead` inside the service. No-ops when no adapter
|
|
1170
1211
|
* is registered.
|
|
1171
1212
|
*/
|
|
1172
|
-
async richTextPopulateSources(rawDocs, readContext, requestContext, locale, readMode, bypassBeforeRead) {
|
|
1213
|
+
async richTextPopulateSources(rawDocs, readContext, requestContext, locale, readMode, bypassBeforeRead, localeVisibility) {
|
|
1173
1214
|
const populate = this.client.richTextPopulate;
|
|
1174
1215
|
if (!populate || rawDocs.length === 0)
|
|
1175
1216
|
return;
|
|
@@ -1187,6 +1228,7 @@ export class CollectionHandle {
|
|
|
1187
1228
|
requestContext,
|
|
1188
1229
|
readContext,
|
|
1189
1230
|
readMode,
|
|
1231
|
+
localeVisibility,
|
|
1190
1232
|
locale,
|
|
1191
1233
|
bypassBeforeRead,
|
|
1192
1234
|
richTextPopulate: populate,
|
|
@@ -1195,10 +1237,10 @@ export class CollectionHandle {
|
|
|
1195
1237
|
});
|
|
1196
1238
|
}
|
|
1197
1239
|
/** Complete the common post-reconstruction pipeline in security order. */
|
|
1198
|
-
async finishReadDocuments(rawDocs, readContext, requestContext, locale, readMode, bypassBeforeRead, projection, materialization) {
|
|
1240
|
+
async finishReadDocuments(rawDocs, readContext, requestContext, locale, readMode, bypassBeforeRead, projection, materialization, localeVisibility = resolveLocaleVisibility(readMode)) {
|
|
1199
1241
|
// Rich-text targets must be authorised and refreshed before user hooks see
|
|
1200
1242
|
// the document, regardless of which read entry point materialised it.
|
|
1201
|
-
await this.richTextPopulateSources(rawDocs, readContext, requestContext, locale, readMode, bypassBeforeRead);
|
|
1243
|
+
await this.richTextPopulateSources(rawDocs, readContext, requestContext, locale, readMode, bypassBeforeRead, localeVisibility);
|
|
1202
1244
|
for (const doc of rawDocs) {
|
|
1203
1245
|
await applyAfterRead({
|
|
1204
1246
|
doc,
|
package/dist/response.js
CHANGED
|
@@ -39,6 +39,13 @@ export function shapeDocument(raw) {
|
|
|
39
39
|
if (typeof raw.source_locale === 'string') {
|
|
40
40
|
shaped.sourceLocale = raw.source_locale;
|
|
41
41
|
}
|
|
42
|
+
// The read's language decision for this document and selected version
|
|
43
|
+
// (storage key `resolved_locale`). `null` is a real value (withheld exact
|
|
44
|
+
// read, multi-locale read, locale-agnostic version); only a result the
|
|
45
|
+
// adapter did not reconstruct lacks the key.
|
|
46
|
+
if ('resolved_locale' in raw) {
|
|
47
|
+
shaped.resolvedLocale = raw.resolved_locale ?? null;
|
|
48
|
+
}
|
|
42
49
|
// Version audit metadata — acting user + action (storage keys
|
|
43
50
|
// `created_by` / `event_type`).
|
|
44
51
|
// `created_by` is NULL on rows written before audit wiring or by
|
package/dist/search.d.ts
CHANGED
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
* `finalizeSearchHits`, which — per collection represented in the hits —
|
|
15
15
|
* applies `beforeRead` row scoping (re-resolving candidate ids through the
|
|
16
16
|
* normal read path) and, when `hydrate` is requested, batch-reads the hits
|
|
17
|
-
* into shaped `ClientDocument`s in the same query.
|
|
17
|
+
* into shaped `ClientDocument`s in the same query. Under `'public'` locale
|
|
18
|
+
* visibility every hit is also re-checked for exact-locale eligibility, so a
|
|
19
|
+
* stale index entry for a withheld translation is dropped whole.
|
|
18
20
|
*/
|
|
19
21
|
import type { RequestContext } from '@byline/auth';
|
|
20
|
-
import type { ReadContext, ReadMode, SearchHit, SearchProvider } from '@byline/core';
|
|
22
|
+
import type { LocaleVisibility, ReadContext, ReadMode, SearchHit, SearchProvider } from '@byline/core';
|
|
21
23
|
import type { BylineClient } from './client.js';
|
|
22
24
|
import type { ClientSearchResults, HydratedSearchHit, ZoneSearchOptions } from './types.js';
|
|
23
25
|
/** Validate the optional logical body-field scope before calling a provider. */
|
|
@@ -30,9 +32,29 @@ export interface FinalizeSearchHitsParams {
|
|
|
30
32
|
status?: ReadMode;
|
|
31
33
|
hydrate?: boolean;
|
|
32
34
|
bypassBeforeRead?: true;
|
|
35
|
+
/**
|
|
36
|
+
* The search's locale visibility (derived from `status`). Under `'public'`
|
|
37
|
+
* each hit's document must be publicly eligible in the hit's locale.
|
|
38
|
+
*/
|
|
39
|
+
localeVisibility: LocaleVisibility;
|
|
33
40
|
/** Shared per-request read context (beforeRead cache). Created when omitted. */
|
|
34
41
|
readContext?: ReadContext;
|
|
35
42
|
}
|
|
43
|
+
/** Finished hits, plus whether a post-ranking eligibility policy applied. */
|
|
44
|
+
export interface FinalizedSearchHits {
|
|
45
|
+
hits: HydratedSearchHit[];
|
|
46
|
+
/**
|
|
47
|
+
* `true` whenever the public exact-locale eligibility policy applies — that
|
|
48
|
+
* is, for every `'public'` search, including an empty page. The provider's
|
|
49
|
+
* `total` and facets count its whole index, which can hold slices the
|
|
50
|
+
* policy would reject on other pages, so a page-only check cannot certify
|
|
51
|
+
* them. The caller follows the restricted-result convention (retained-hit
|
|
52
|
+
* `total`, no provider facets), exactly as for `beforeRead` row scoping,
|
|
53
|
+
* which also restricts on the presence of the policy, not on an observed
|
|
54
|
+
* removal.
|
|
55
|
+
*/
|
|
56
|
+
eligibilityRestricted: boolean;
|
|
57
|
+
}
|
|
36
58
|
/**
|
|
37
59
|
* Authorise (and optionally hydrate) provider hits, per collection:
|
|
38
60
|
*
|
|
@@ -42,15 +64,25 @@ export interface FinalizeSearchHitsParams {
|
|
|
42
64
|
* hydration cost one query per collection. Hits whose document doesn't
|
|
43
65
|
* come back — dropped by scoping, or a stale index entry whose document
|
|
44
66
|
* no longer resolves — are removed; the rest carry `hit.document`.
|
|
45
|
-
* - `hydrate` off
|
|
46
|
-
*
|
|
47
|
-
*
|
|
67
|
+
* - `hydrate` off, public visibility — every collection's hit ids are
|
|
68
|
+
* re-resolved through the normal read path with `onMissingLocale:
|
|
69
|
+
* 'omit'` (identity-field projection), which applies both exact-locale
|
|
70
|
+
* eligibility and any `beforeRead` scoping. An index entry can outlive
|
|
71
|
+
* the checkbox that allowed it (until reindexing catches up), so this
|
|
72
|
+
* check runs even without a hook.
|
|
73
|
+
* - `hydrate` off, editorial visibility — collections with a `beforeRead`
|
|
74
|
+
* predicate get the trimmed id re-resolution; collections without one
|
|
75
|
+
* pass through untouched (no second query).
|
|
76
|
+
*
|
|
77
|
+
* Hydration also reads with `'omit'`: a hit is for one locale slice, and a
|
|
78
|
+
* source-language document substituted by fallback would not make that hit
|
|
79
|
+
* safe.
|
|
48
80
|
*
|
|
49
81
|
* Hits whose `collectionPath` isn't registered in the runtime config are
|
|
50
82
|
* dropped with a debug log (an index can outlive a collection). Original
|
|
51
83
|
* ranking order is preserved.
|
|
52
84
|
*/
|
|
53
|
-
export declare function finalizeSearchHits(params: FinalizeSearchHitsParams): Promise<
|
|
85
|
+
export declare function finalizeSearchHits(params: FinalizeSearchHitsParams): Promise<FinalizedSearchHits>;
|
|
54
86
|
/**
|
|
55
87
|
* Cross-collection search over a named zone. Membership is resolved from
|
|
56
88
|
* the runtime collection definitions (`search.zones`, defaulting to the
|
package/dist/search.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
|
-
import { assertActorCanPerform, createReadContext, ERR_VALIDATION, getCollectionAdminConfig, MAX_SEARCH_QUERY_LENGTH, resolveSearchZones, } from '@byline/core';
|
|
8
|
+
import { assertActorCanPerform, createReadContext, ERR_VALIDATION, getCollectionAdminConfig, MAX_SEARCH_QUERY_LENGTH, resolveLocaleVisibility, resolveSearchZones, } from '@byline/core';
|
|
9
9
|
import { resolveReadRequestContext, resolveReadSecurityFilters } from './read-context.js';
|
|
10
10
|
/** Validate the optional logical body-field scope before calling a provider. */
|
|
11
11
|
export function assertSearchFieldScope(provider, fieldScope) {
|
|
@@ -30,18 +30,29 @@ export function assertSearchFieldScope(provider, fieldScope) {
|
|
|
30
30
|
* hydration cost one query per collection. Hits whose document doesn't
|
|
31
31
|
* come back — dropped by scoping, or a stale index entry whose document
|
|
32
32
|
* no longer resolves — are removed; the rest carry `hit.document`.
|
|
33
|
-
* - `hydrate` off
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* - `hydrate` off, public visibility — every collection's hit ids are
|
|
34
|
+
* re-resolved through the normal read path with `onMissingLocale:
|
|
35
|
+
* 'omit'` (identity-field projection), which applies both exact-locale
|
|
36
|
+
* eligibility and any `beforeRead` scoping. An index entry can outlive
|
|
37
|
+
* the checkbox that allowed it (until reindexing catches up), so this
|
|
38
|
+
* check runs even without a hook.
|
|
39
|
+
* - `hydrate` off, editorial visibility — collections with a `beforeRead`
|
|
40
|
+
* predicate get the trimmed id re-resolution; collections without one
|
|
41
|
+
* pass through untouched (no second query).
|
|
42
|
+
*
|
|
43
|
+
* Hydration also reads with `'omit'`: a hit is for one locale slice, and a
|
|
44
|
+
* source-language document substituted by fallback would not make that hit
|
|
45
|
+
* safe.
|
|
36
46
|
*
|
|
37
47
|
* Hits whose `collectionPath` isn't registered in the runtime config are
|
|
38
48
|
* dropped with a debug log (an index can outlive a collection). Original
|
|
39
49
|
* ranking order is preserved.
|
|
40
50
|
*/
|
|
41
51
|
export async function finalizeSearchHits(params) {
|
|
42
|
-
const { client, requestContext, hits, locale, status, hydrate, bypassBeforeRead } = params;
|
|
52
|
+
const { client, requestContext, hits, locale, status, hydrate, bypassBeforeRead, localeVisibility, } = params;
|
|
53
|
+
const eligibilityRestricted = localeVisibility === 'public';
|
|
43
54
|
if (hits.length === 0)
|
|
44
|
-
return hits;
|
|
55
|
+
return { hits, eligibilityRestricted };
|
|
45
56
|
const readCtx = params.readContext ?? createReadContext();
|
|
46
57
|
// Group hit ids by collection, preserving overall ranking order for the
|
|
47
58
|
// final reassembly.
|
|
@@ -78,6 +89,8 @@ export async function finalizeSearchHits(params) {
|
|
|
78
89
|
select,
|
|
79
90
|
locale,
|
|
80
91
|
status,
|
|
92
|
+
onMissingLocale: 'omit',
|
|
93
|
+
localeVisibility,
|
|
81
94
|
page: 1,
|
|
82
95
|
pageSize: ids.length,
|
|
83
96
|
_readContext: readCtx,
|
|
@@ -86,6 +99,24 @@ export async function finalizeSearchHits(params) {
|
|
|
86
99
|
documentsByCollection.set(collectionPath, new Map(result.docs.map((d) => [d.id, d])));
|
|
87
100
|
continue;
|
|
88
101
|
}
|
|
102
|
+
if (localeVisibility === 'public') {
|
|
103
|
+
// Exact-locale eligibility (and beforeRead scoping, unless bypassed)
|
|
104
|
+
// for every public hit, hook or no hook.
|
|
105
|
+
const result = await handle.find({
|
|
106
|
+
where: { id: { $in: ids } },
|
|
107
|
+
select: definition.useAsTitle != null ? [definition.useAsTitle] : undefined,
|
|
108
|
+
locale,
|
|
109
|
+
status,
|
|
110
|
+
onMissingLocale: 'omit',
|
|
111
|
+
localeVisibility,
|
|
112
|
+
page: 1,
|
|
113
|
+
pageSize: ids.length,
|
|
114
|
+
_readContext: readCtx,
|
|
115
|
+
...(bypassBeforeRead ? { _bypassBeforeRead: true } : {}),
|
|
116
|
+
});
|
|
117
|
+
allowedByCollection.set(collectionPath, new Set(result.docs.map((d) => d.id)));
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
89
120
|
if (bypassBeforeRead) {
|
|
90
121
|
allowedByCollection.set(collectionPath, null);
|
|
91
122
|
continue;
|
|
@@ -127,7 +158,7 @@ export async function finalizeSearchHits(params) {
|
|
|
127
158
|
finished.push(hit);
|
|
128
159
|
}
|
|
129
160
|
}
|
|
130
|
-
return finished;
|
|
161
|
+
return { hits: finished, eligibilityRestricted };
|
|
131
162
|
}
|
|
132
163
|
// ---------------------------------------------------------------------------
|
|
133
164
|
// Zone (cross-collection) entry point
|
|
@@ -203,7 +234,7 @@ export async function zoneSearch(client, options) {
|
|
|
203
234
|
});
|
|
204
235
|
// Constrain to readable member collections, then authorise / hydrate.
|
|
205
236
|
const scoped = results.hits.filter((h) => readable.has(h.collectionPath));
|
|
206
|
-
const hits = await finalizeSearchHits({
|
|
237
|
+
const { hits, eligibilityRestricted } = await finalizeSearchHits({
|
|
207
238
|
client,
|
|
208
239
|
requestContext,
|
|
209
240
|
hits: scoped,
|
|
@@ -211,9 +242,10 @@ export async function zoneSearch(client, options) {
|
|
|
211
242
|
status: options.status,
|
|
212
243
|
hydrate: options.hydrate,
|
|
213
244
|
bypassBeforeRead: options._bypassBeforeRead,
|
|
245
|
+
localeVisibility: resolveLocaleVisibility(readMode),
|
|
214
246
|
readContext: readCtx,
|
|
215
247
|
});
|
|
216
|
-
return aggregateRestricted
|
|
248
|
+
return aggregateRestricted || eligibilityRestricted
|
|
217
249
|
? { hits, total: hits.length }
|
|
218
250
|
: { hits, total: results.total, facets: results.facets };
|
|
219
251
|
}
|
package/dist/singleton-handle.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
|
-
import { assertActorCanPerform, cancelDocumentScheduledPublish, changeDocumentStatus, confirmDocumentScheduledPublish, copySingletonToLocale, createReadContext, ERR_NOT_FOUND, getDocumentScheduledPublish, resolveSingletonDocumentId, restoreSingletonVersion, scheduleDocumentPublish, unpublishDocument, updateSingleton, } from '@byline/core';
|
|
8
|
+
import { assertActorCanPerform, assertLocaleVisibility, cancelDocumentScheduledPublish, changeDocumentStatus, confirmDocumentScheduledPublish, copySingletonToLocale, createReadContext, ERR_NOT_FOUND, getDocumentScheduledPublish, resolveLocaleVisibility, resolveSingletonDocumentId, restoreSingletonVersion, scheduleDocumentPublish, unpublishDocument, updateSingleton, } from '@byline/core';
|
|
9
9
|
import { CollectionHandle } from './collection-handle.js';
|
|
10
10
|
import { resolveReadRequestContext } from './read-context.js';
|
|
11
11
|
import { expectedDocumentId, readSingletonForEdit, } from './read-internals.js';
|
|
@@ -32,7 +32,11 @@ export class SingletonHandle {
|
|
|
32
32
|
}
|
|
33
33
|
async get(options = {}) {
|
|
34
34
|
const readContext = options._readContext ?? createReadContext();
|
|
35
|
-
await this.authorizeRead(options.status ?? 'published', readContext);
|
|
35
|
+
const requestContext = await this.authorizeRead(options.status ?? 'published', readContext);
|
|
36
|
+
// Validate locale visibility before the slot lookup, so an invalid request
|
|
37
|
+
// is rejected whether or not the slot is materialized, and before any
|
|
38
|
+
// adapter work. The delegated `findById` repeats the same check.
|
|
39
|
+
assertLocaleVisibility(requestContext, this.definition.path, resolveLocaleVisibility(options.status, options.localeVisibility), options.locale ?? this.client.defaultLocale);
|
|
36
40
|
const documentId = await this.resolveMappedDocumentId();
|
|
37
41
|
if (documentId == null)
|
|
38
42
|
return null;
|
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, DocumentPublishSchedule, IDbAdapter, IStorageProvider, MissingLocalePolicy, PopulateSpec, PredicateValue, QueryPredicate, ReadContext, ReadMode, RichTextPopulateFn, RichTextToTextFn, SearchFacetBucket, SearchHit, SearchMatching, SearchProvider, ServerConfig, SingletonSavePrecondition, SlugifierFn, SortSpec } from '@byline/core';
|
|
9
|
+
import type { BylineLogger, CollectionDefinition, DocumentPublishSchedule, IDbAdapter, IStorageProvider, LocaleVisibility, MissingLocalePolicy, PopulateSpec, PredicateValue, QueryPredicate, ReadContext, ReadMode, RichTextPopulateFn, RichTextToTextFn, SearchFacetBucket, SearchHit, SearchMatching, SearchProvider, ServerConfig, SingletonSavePrecondition, SlugifierFn, SortSpec } from '@byline/core';
|
|
10
10
|
export type { FilterOperators, PredicateValue, QueryPredicate, SearchFacetBucket, SearchHit, SearchProvider, SearchResults, SortDirection, SortSpec, } from '@byline/core';
|
|
11
11
|
export interface BylineClientConfig {
|
|
12
12
|
/**
|
|
@@ -165,9 +165,18 @@ interface PopulateControls extends ReadContextControls {
|
|
|
165
165
|
* Distinct from `where.status`, which is an *exact-match filter* on the
|
|
166
166
|
* selected version's status column ("show me all drafts"). `status` is
|
|
167
167
|
* the *source view* selector.
|
|
168
|
+
*
|
|
169
|
+
* `localeVisibility` chooses which of the selected version's languages may be
|
|
170
|
+
* delivered. `'public'` honours the editor's advertised-locale checkboxes (an
|
|
171
|
+
* unchecked translation behaves like a missing one); `'editorial'` ignores them
|
|
172
|
+
* so an authorized reviewer can see a withheld translation. It defaults from
|
|
173
|
+
* `status`: omitted or `'published'` → `'public'`, `'any'` → `'editorial'`.
|
|
174
|
+
* `'editorial'` requires an authenticated actor, and a `'public'` read cannot
|
|
175
|
+
* request `locale: 'all'`. See `LocaleVisibility` in `@byline/core`.
|
|
168
176
|
*/
|
|
169
177
|
interface StatusControls {
|
|
170
178
|
status?: ReadMode;
|
|
179
|
+
localeVisibility?: LocaleVisibility;
|
|
171
180
|
}
|
|
172
181
|
/**
|
|
173
182
|
* Options for `CollectionHandle.search(...)` — a ranked full-text query
|
|
@@ -596,6 +605,21 @@ export interface ClientDocument<F = Record<string, any>> {
|
|
|
596
605
|
* `docs/08-internationalization/index.md`.
|
|
597
606
|
*/
|
|
598
607
|
sourceLocale?: string;
|
|
608
|
+
/**
|
|
609
|
+
* The content locale this read selected for this document's fields, decided
|
|
610
|
+
* from the whole selected version before any field projection.
|
|
611
|
+
*
|
|
612
|
+
* - `'fallback'`: the first eligible locale in the chain, which may be the
|
|
613
|
+
* source rather than the requested locale.
|
|
614
|
+
* - `'omit'`, or an eligible exact read: the requested locale.
|
|
615
|
+
* - `null`: a public exact read that withheld an unavailable translation,
|
|
616
|
+
* a multi-locale (`locale: 'all'`) read, or a locale-agnostic version.
|
|
617
|
+
*
|
|
618
|
+
* It describes which locale was selected, not whether every field is
|
|
619
|
+
* linguistically in that language, and not whether the translation is
|
|
620
|
+
* publicly released. Each populated target and tree node carries its own.
|
|
621
|
+
*/
|
|
622
|
+
resolvedLocale?: string | null;
|
|
599
623
|
/** When this version was created. */
|
|
600
624
|
createdAt: Date;
|
|
601
625
|
/** When this version was last updated. */
|
|
@@ -730,6 +754,6 @@ export interface EditableTreeNode<F = Record<string, any>> {
|
|
|
730
754
|
depth: number;
|
|
731
755
|
children: EditableTreeNode<F>[];
|
|
732
756
|
}
|
|
733
|
-
export type FindByIdForEditOptions<F = Record<string, any>> = Omit<FindByIdOptions<F>, 'status'>;
|
|
734
|
-
export type FindForEditOptions<F = Record<string, any>> = Omit<FindOptions<F>, 'status'>;
|
|
735
|
-
export type GetSubtreeForEditOptions<F = Record<string, any>> = Omit<GetSubtreeOptions<F>, 'status'>;
|
|
757
|
+
export type FindByIdForEditOptions<F = Record<string, any>> = Omit<FindByIdOptions<F>, 'status' | 'localeVisibility'>;
|
|
758
|
+
export type FindForEditOptions<F = Record<string, any>> = Omit<FindOptions<F>, 'status' | 'localeVisibility'>;
|
|
759
|
+
export type GetSubtreeForEditOptions<F = Record<string, any>> = Omit<GetSubtreeOptions<F>, 'status' | 'localeVisibility'>;
|
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": "6.
|
|
5
|
+
"version": "6.7.0",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20.9.0"
|
|
8
8
|
},
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"uuid": "^14.0.2",
|
|
47
|
-
"@byline/auth": "6.
|
|
48
|
-
"@byline/core": "6.
|
|
47
|
+
"@byline/auth": "6.7.0",
|
|
48
|
+
"@byline/core": "6.7.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@biomejs/biome": "2.5.14",
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
"tsc-alias": "^1.9.5",
|
|
58
58
|
"typescript": "^7.0.2",
|
|
59
59
|
"vitest": "^5.0.1",
|
|
60
|
-
"@byline/search-postgres": "6.
|
|
61
|
-
"@byline/db-postgres": "6.
|
|
60
|
+
"@byline/search-postgres": "6.7.0",
|
|
61
|
+
"@byline/db-postgres": "6.7.0"
|
|
62
62
|
},
|
|
63
63
|
"publishConfig": {
|
|
64
64
|
"access": "public",
|