@byline/client 6.6.0 → 6.7.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.
- package/dist/client.d.ts +5 -0
- package/dist/client.js +5 -0
- package/dist/collection-handle.d.ts +31 -13
- package/dist/collection-handle.js +80 -34
- package/dist/response.js +7 -0
- package/dist/search.d.ts +31 -5
- package/dist/search.js +63 -5
- 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,11 @@ 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. The provider `total`
|
|
53
|
+
* and facets pass through unless an actor-specific restriction applies
|
|
54
|
+
* (unreadable members or `beforeRead` scoping), as `CollectionHandle.search()`
|
|
55
|
+
* does.
|
|
51
56
|
* For homogeneous, single-collection search use
|
|
52
57
|
* `client.collection(path).search()`.
|
|
53
58
|
*/
|
package/dist/client.js
CHANGED
|
@@ -122,6 +122,11 @@ 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. The provider `total`
|
|
127
|
+
* and facets pass through unless an actor-specific restriction applies
|
|
128
|
+
* (unreadable members or `beforeRead` scoping), as `CollectionHandle.search()`
|
|
129
|
+
* does.
|
|
125
130
|
* For homogeneous, single-collection search use
|
|
126
131
|
* `client.collection(path).search()`.
|
|
127
132
|
*/
|
|
@@ -45,25 +45,32 @@ 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 leaking
|
|
62
|
+
* - under `beforeRead` row scoping, `total` is the surviving hit count
|
|
63
|
+
* for this page and facets are omitted rather than leaking
|
|
64
|
+
* provider-wide aggregates of rows this actor can't see;
|
|
65
|
+
* - otherwise — including every unscoped public search — the provider
|
|
66
|
+
* `total` and facets pass through unchanged (public locale eligibility
|
|
67
|
+
* is actor-independent and applied at indexing, so a hit the re-check
|
|
68
|
+
* drops is a stale index entry, logged at warn level);
|
|
62
69
|
* - a page of hits can come back shorter than `limit` when candidates
|
|
63
70
|
* are dropped; paginate on `offset`, not on received length.
|
|
64
71
|
*
|
|
65
72
|
* `_bypassBeforeRead: true` is the same system-operation escape hatch the
|
|
66
|
-
* read methods take.
|
|
73
|
+
* read methods take; it skips `beforeRead` scoping, never the locale check.
|
|
67
74
|
*
|
|
68
75
|
* Throws `ERR_VALIDATION` when no provider is registered.
|
|
69
76
|
*/
|
|
@@ -309,6 +316,17 @@ export declare class CollectionHandle<TFields extends Record<string, any> = Reco
|
|
|
309
316
|
* Returns the resolved context so callers can thread `readMode` and
|
|
310
317
|
* other per-request state into the adapter without re-resolving.
|
|
311
318
|
*/
|
|
319
|
+
/**
|
|
320
|
+
* Resolve the request context for a read and authorize it: the collection
|
|
321
|
+
* read ability (`assertActorCanPerform`) and the read's locale visibility
|
|
322
|
+
* (`assertLocaleVisibility`). Visibility defaults from `readMode`, so the
|
|
323
|
+
* editing, history and version reads (`readMode: 'any'`) are editorial.
|
|
324
|
+
*
|
|
325
|
+
* The visibility check validates the *effective* locale, the value every
|
|
326
|
+
* read method sends to storage (`locale ?? client.defaultLocale`), so a
|
|
327
|
+
* client configured with `defaultLocale: 'all'` cannot turn an omitted
|
|
328
|
+
* locale into a public all-locale read.
|
|
329
|
+
*/
|
|
312
330
|
private resolveAndAssertRead;
|
|
313
331
|
/**
|
|
314
332
|
* 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,32 @@ 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 leaking
|
|
183
|
+
* - under `beforeRead` row scoping, `total` is the surviving hit count
|
|
184
|
+
* for this page and facets are omitted rather than leaking
|
|
185
|
+
* provider-wide aggregates of rows this actor can't see;
|
|
186
|
+
* - otherwise — including every unscoped public search — the provider
|
|
187
|
+
* `total` and facets pass through unchanged (public locale eligibility
|
|
188
|
+
* is actor-independent and applied at indexing, so a hit the re-check
|
|
189
|
+
* drops is a stale index entry, logged at warn level);
|
|
180
190
|
* - a page of hits can come back shorter than `limit` when candidates
|
|
181
191
|
* are dropped; paginate on `offset`, not on received length.
|
|
182
192
|
*
|
|
183
193
|
* `_bypassBeforeRead: true` is the same system-operation escape hatch the
|
|
184
|
-
* read methods take.
|
|
194
|
+
* read methods take; it skips `beforeRead` scoping, never the locale check.
|
|
185
195
|
*
|
|
186
196
|
* Throws `ERR_VALIDATION` when no provider is registered.
|
|
187
197
|
*/
|
|
@@ -212,9 +222,13 @@ export class CollectionHandle {
|
|
|
212
222
|
limit: options.limit,
|
|
213
223
|
offset: options.offset,
|
|
214
224
|
});
|
|
215
|
-
// Row-level authorization (+ optional
|
|
216
|
-
// pipeline the zone entry point uses
|
|
217
|
-
//
|
|
225
|
+
// Row-level authorization, exact-locale eligibility (+ optional
|
|
226
|
+
// hydration) — the shared finishing pipeline the zone entry point uses
|
|
227
|
+
// too. Public searches re-check every hit; an editorial search without a
|
|
228
|
+
// beforeRead predicate or hydration passes hits through untouched. Only
|
|
229
|
+
// actor-specific restriction (beforeRead) replaces the provider
|
|
230
|
+
// aggregate — public locale eligibility is actor-independent and already
|
|
231
|
+
// applied at indexing, so provider total and facets pass through.
|
|
218
232
|
const hits = await finalizeSearchHits({
|
|
219
233
|
client: this.client,
|
|
220
234
|
requestContext,
|
|
@@ -223,6 +237,7 @@ export class CollectionHandle {
|
|
|
223
237
|
status: options.status,
|
|
224
238
|
hydrate: options.hydrate,
|
|
225
239
|
bypassBeforeRead: options._bypassBeforeRead,
|
|
240
|
+
localeVisibility: resolveLocaleVisibility(readMode),
|
|
226
241
|
readContext: readCtx,
|
|
227
242
|
});
|
|
228
243
|
return aggregateRestricted
|
|
@@ -251,9 +266,13 @@ export class CollectionHandle {
|
|
|
251
266
|
return;
|
|
252
267
|
const populate = this.buildSearchFacetPopulateMap();
|
|
253
268
|
for (const locale of this.client.contentLocales) {
|
|
269
|
+
// Public delivery, pinned explicitly: the system client indexing here
|
|
270
|
+
// is authenticated, but only publicly eligible locale slices (source,
|
|
271
|
+
// or checked and complete) may enter the index.
|
|
254
272
|
const view = await this.findById(documentId, {
|
|
255
273
|
locale,
|
|
256
274
|
status: 'published',
|
|
275
|
+
localeVisibility: 'public',
|
|
257
276
|
onMissingLocale: 'omit',
|
|
258
277
|
populate,
|
|
259
278
|
_bypassBeforeRead: true,
|
|
@@ -360,7 +379,8 @@ export class CollectionHandle {
|
|
|
360
379
|
async findById(documentId, options = {}) {
|
|
361
380
|
const readMode = resolveReadMode(options.status);
|
|
362
381
|
const readCtx = options._readContext ?? createReadContext();
|
|
363
|
-
const
|
|
382
|
+
const localeVisibility = resolveLocaleVisibility(options.status, options.localeVisibility);
|
|
383
|
+
const requestContext = await this.resolveAndAssertRead(readMode, readCtx, localeVisibility, options.locale);
|
|
364
384
|
const { locale = this.client.defaultLocale } = options;
|
|
365
385
|
const filters = await this.resolveBeforeReadFilters(requestContext, readCtx, options._bypassBeforeRead);
|
|
366
386
|
const collectionId = await this.client.resolveCollectionId(this.definition.path);
|
|
@@ -373,6 +393,7 @@ export class CollectionHandle {
|
|
|
373
393
|
filters,
|
|
374
394
|
lenient: options.lenient,
|
|
375
395
|
onMissingLocale: options.onMissingLocale ?? 'fallback',
|
|
396
|
+
localeVisibility,
|
|
376
397
|
});
|
|
377
398
|
if (raw == null)
|
|
378
399
|
return null;
|
|
@@ -385,7 +406,7 @@ export class CollectionHandle {
|
|
|
385
406
|
...options,
|
|
386
407
|
_readContext: readCtx,
|
|
387
408
|
});
|
|
388
|
-
await this.finishReadDocuments([raw], readCtx, requestContext, locale, readMode, options._bypassBeforeRead, options.select, readMaterialization(options.populate, options.depth));
|
|
409
|
+
await this.finishReadDocuments([raw], readCtx, requestContext, locale, readMode, options._bypassBeforeRead, options.select, readMaterialization(options.populate, options.depth), localeVisibility);
|
|
389
410
|
return this.shapeWithPopulated(raw);
|
|
390
411
|
}
|
|
391
412
|
async findByIdForEdit(documentId, options = {}) {
|
|
@@ -453,7 +474,8 @@ export class CollectionHandle {
|
|
|
453
474
|
async findByPath(path, options = {}) {
|
|
454
475
|
const readMode = resolveReadMode(options.status);
|
|
455
476
|
const readCtx = options._readContext ?? createReadContext();
|
|
456
|
-
const
|
|
477
|
+
const localeVisibility = resolveLocaleVisibility(options.status, options.localeVisibility);
|
|
478
|
+
const requestContext = await this.resolveAndAssertRead(readMode, readCtx, localeVisibility, options.locale);
|
|
457
479
|
const { locale = this.client.defaultLocale } = options;
|
|
458
480
|
const filters = await this.resolveBeforeReadFilters(requestContext, readCtx, options._bypassBeforeRead);
|
|
459
481
|
const collectionId = await this.client.resolveCollectionId(this.definition.path);
|
|
@@ -465,6 +487,7 @@ export class CollectionHandle {
|
|
|
465
487
|
readMode,
|
|
466
488
|
filters,
|
|
467
489
|
onMissingLocale: options.onMissingLocale ?? 'fallback',
|
|
490
|
+
localeVisibility,
|
|
468
491
|
});
|
|
469
492
|
if (raw == null)
|
|
470
493
|
return null;
|
|
@@ -475,7 +498,7 @@ export class CollectionHandle {
|
|
|
475
498
|
...options,
|
|
476
499
|
_readContext: readCtx,
|
|
477
500
|
});
|
|
478
|
-
await this.finishReadDocuments([raw], readCtx, requestContext, locale, readMode, options._bypassBeforeRead, options.select, readMaterialization(options.populate, options.depth));
|
|
501
|
+
await this.finishReadDocuments([raw], readCtx, requestContext, locale, readMode, options._bypassBeforeRead, options.select, readMaterialization(options.populate, options.depth), localeVisibility);
|
|
479
502
|
return this.shapeWithPopulated(raw);
|
|
480
503
|
}
|
|
481
504
|
// -------------------------------------------------------------------------
|
|
@@ -817,7 +840,8 @@ export class CollectionHandle {
|
|
|
817
840
|
this.assertTreeCollection();
|
|
818
841
|
const readMode = resolveReadMode(options.status);
|
|
819
842
|
const readCtx = options._readContext ?? createReadContext();
|
|
820
|
-
const
|
|
843
|
+
const localeVisibility = resolveLocaleVisibility(options.status, options.localeVisibility);
|
|
844
|
+
const requestContext = await this.resolveAndAssertRead(readMode, readCtx, localeVisibility, options.locale);
|
|
821
845
|
const locale = options.locale ?? this.client.defaultLocale;
|
|
822
846
|
const filters = await this.resolveBeforeReadFilters(requestContext, readCtx, options._bypassBeforeRead);
|
|
823
847
|
const collectionId = await this.client.resolveCollectionId(this.definition.path);
|
|
@@ -828,10 +852,11 @@ export class CollectionHandle {
|
|
|
828
852
|
readMode,
|
|
829
853
|
locale,
|
|
830
854
|
filters,
|
|
855
|
+
localeVisibility,
|
|
831
856
|
});
|
|
832
857
|
if (structure.length === 0)
|
|
833
858
|
return [];
|
|
834
|
-
const shapedById = await this.hydrateTreeNodes(collectionId, structure.map((n) => n.document_id), locale, readMode, options.select, filters, readCtx, requestContext, options._bypassBeforeRead);
|
|
859
|
+
const shapedById = await this.hydrateTreeNodes(collectionId, structure.map((n) => n.document_id), locale, readMode, options.select, filters, readCtx, requestContext, options._bypassBeforeRead, localeVisibility);
|
|
835
860
|
// Assemble the nested forest. Rows arrive pre-order, so a parent is always
|
|
836
861
|
// materialised before its children; a row whose parent is not in the result
|
|
837
862
|
// set (null parent, or the requested root's own parent) is a top-level node.
|
|
@@ -960,7 +985,8 @@ export class CollectionHandle {
|
|
|
960
985
|
this.assertTreeCollection();
|
|
961
986
|
const readMode = resolveReadMode(options.status);
|
|
962
987
|
const readCtx = options._readContext ?? createReadContext();
|
|
963
|
-
const
|
|
988
|
+
const localeVisibility = resolveLocaleVisibility(options.status, options.localeVisibility);
|
|
989
|
+
const requestContext = await this.resolveAndAssertRead(readMode, readCtx, localeVisibility, options.locale);
|
|
964
990
|
const locale = options.locale ?? this.client.defaultLocale;
|
|
965
991
|
const filters = await this.resolveBeforeReadFilters(requestContext, readCtx, options._bypassBeforeRead);
|
|
966
992
|
const collectionId = await this.client.resolveCollectionId(this.definition.path);
|
|
@@ -969,11 +995,12 @@ export class CollectionHandle {
|
|
|
969
995
|
readMode,
|
|
970
996
|
locale,
|
|
971
997
|
filters,
|
|
998
|
+
localeVisibility,
|
|
972
999
|
});
|
|
973
1000
|
if (ancestors.length === 0)
|
|
974
1001
|
return [];
|
|
975
1002
|
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);
|
|
1003
|
+
const shapedById = await this.hydrateTreeNodes(collectionId, ids, locale, readMode, options.select, filters, readCtx, requestContext, options._bypassBeforeRead, localeVisibility);
|
|
977
1004
|
// A hydration-time miss truncates the edge rather than compacting past a
|
|
978
1005
|
// newly-hidden ancestor.
|
|
979
1006
|
const visible = [];
|
|
@@ -1001,13 +1028,15 @@ export class CollectionHandle {
|
|
|
1001
1028
|
this.assertTreeCollection();
|
|
1002
1029
|
const readMode = resolveReadMode(options.status);
|
|
1003
1030
|
const readCtx = options._readContext ?? createReadContext();
|
|
1004
|
-
const
|
|
1031
|
+
const localeVisibility = resolveLocaleVisibility(options.status, options.localeVisibility);
|
|
1032
|
+
const requestContext = await this.resolveAndAssertRead(readMode, readCtx, localeVisibility, options.locale);
|
|
1005
1033
|
const filters = await this.resolveBeforeReadFilters(requestContext, readCtx, options._bypassBeforeRead);
|
|
1006
1034
|
const result = await this.client.db.queries.documents.getTreeParent({
|
|
1007
1035
|
document_id: documentId,
|
|
1008
1036
|
readMode,
|
|
1009
1037
|
locale: options.locale ?? this.client.defaultLocale,
|
|
1010
1038
|
filters,
|
|
1039
|
+
localeVisibility,
|
|
1011
1040
|
});
|
|
1012
1041
|
return {
|
|
1013
1042
|
placed: result.placed,
|
|
@@ -1085,9 +1114,21 @@ export class CollectionHandle {
|
|
|
1085
1114
|
* Returns the resolved context so callers can thread `readMode` and
|
|
1086
1115
|
* other per-request state into the adapter without re-resolving.
|
|
1087
1116
|
*/
|
|
1088
|
-
|
|
1117
|
+
/**
|
|
1118
|
+
* Resolve the request context for a read and authorize it: the collection
|
|
1119
|
+
* read ability (`assertActorCanPerform`) and the read's locale visibility
|
|
1120
|
+
* (`assertLocaleVisibility`). Visibility defaults from `readMode`, so the
|
|
1121
|
+
* editing, history and version reads (`readMode: 'any'`) are editorial.
|
|
1122
|
+
*
|
|
1123
|
+
* The visibility check validates the *effective* locale, the value every
|
|
1124
|
+
* read method sends to storage (`locale ?? client.defaultLocale`), so a
|
|
1125
|
+
* client configured with `defaultLocale: 'all'` cannot turn an omitted
|
|
1126
|
+
* locale into a public all-locale read.
|
|
1127
|
+
*/
|
|
1128
|
+
async resolveAndAssertRead(readMode, readContext, localeVisibility = resolveLocaleVisibility(readMode), locale) {
|
|
1089
1129
|
const requestContext = await resolveReadRequestContext(this.client, readContext, readMode);
|
|
1090
1130
|
assertActorCanPerform(requestContext, this.definition, 'read');
|
|
1131
|
+
assertLocaleVisibility(requestContext, this.definition.path, localeVisibility, locale ?? this.client.defaultLocale);
|
|
1091
1132
|
return requestContext;
|
|
1092
1133
|
}
|
|
1093
1134
|
/**
|
|
@@ -1113,7 +1154,7 @@ export class CollectionHandle {
|
|
|
1113
1154
|
* `afterRead` per node (deduped within one shared `ReadContext`) to stay
|
|
1114
1155
|
* consistent with the other client read paths.
|
|
1115
1156
|
*/
|
|
1116
|
-
async hydrateTreeNodes(collectionId, documentIds, locale, readMode, select, filters, readContext, requestContext, bypassBeforeRead) {
|
|
1157
|
+
async hydrateTreeNodes(collectionId, documentIds, locale, readMode, select, filters, readContext, requestContext, bypassBeforeRead, localeVisibility = resolveLocaleVisibility(readMode)) {
|
|
1117
1158
|
const shapedById = new Map();
|
|
1118
1159
|
if (documentIds.length === 0)
|
|
1119
1160
|
return shapedById;
|
|
@@ -1124,8 +1165,9 @@ export class CollectionHandle {
|
|
|
1124
1165
|
readMode,
|
|
1125
1166
|
fields: select,
|
|
1126
1167
|
filters,
|
|
1168
|
+
localeVisibility,
|
|
1127
1169
|
});
|
|
1128
|
-
await this.finishReadDocuments(rawDocs, readContext, requestContext, locale, readMode, bypassBeforeRead, select, 'tree');
|
|
1170
|
+
await this.finishReadDocuments(rawDocs, readContext, requestContext, locale, readMode, bypassBeforeRead, select, 'tree', localeVisibility);
|
|
1129
1171
|
for (const raw of rawDocs) {
|
|
1130
1172
|
const doc = raw;
|
|
1131
1173
|
shapedById.set(doc.document_id, this.shapeWithPopulated(doc));
|
|
@@ -1153,6 +1195,9 @@ export class CollectionHandle {
|
|
|
1153
1195
|
depth: options.depth,
|
|
1154
1196
|
locale,
|
|
1155
1197
|
readMode,
|
|
1198
|
+
// Equals the entry point's resolved visibility: an explicit option wins,
|
|
1199
|
+
// otherwise the default follows the same status that selected `readMode`.
|
|
1200
|
+
localeVisibility: options.localeVisibility ?? resolveLocaleVisibility(readMode),
|
|
1156
1201
|
readContext: options._readContext,
|
|
1157
1202
|
requestContext,
|
|
1158
1203
|
securityDomain: getReadSecurityDomain(this.client),
|
|
@@ -1169,7 +1214,7 @@ export class CollectionHandle {
|
|
|
1169
1214
|
* `populateRelationsOnRead` inside the service. No-ops when no adapter
|
|
1170
1215
|
* is registered.
|
|
1171
1216
|
*/
|
|
1172
|
-
async richTextPopulateSources(rawDocs, readContext, requestContext, locale, readMode, bypassBeforeRead) {
|
|
1217
|
+
async richTextPopulateSources(rawDocs, readContext, requestContext, locale, readMode, bypassBeforeRead, localeVisibility) {
|
|
1173
1218
|
const populate = this.client.richTextPopulate;
|
|
1174
1219
|
if (!populate || rawDocs.length === 0)
|
|
1175
1220
|
return;
|
|
@@ -1187,6 +1232,7 @@ export class CollectionHandle {
|
|
|
1187
1232
|
requestContext,
|
|
1188
1233
|
readContext,
|
|
1189
1234
|
readMode,
|
|
1235
|
+
localeVisibility,
|
|
1190
1236
|
locale,
|
|
1191
1237
|
bypassBeforeRead,
|
|
1192
1238
|
richTextPopulate: populate,
|
|
@@ -1195,10 +1241,10 @@ export class CollectionHandle {
|
|
|
1195
1241
|
});
|
|
1196
1242
|
}
|
|
1197
1243
|
/** Complete the common post-reconstruction pipeline in security order. */
|
|
1198
|
-
async finishReadDocuments(rawDocs, readContext, requestContext, locale, readMode, bypassBeforeRead, projection, materialization) {
|
|
1244
|
+
async finishReadDocuments(rawDocs, readContext, requestContext, locale, readMode, bypassBeforeRead, projection, materialization, localeVisibility = resolveLocaleVisibility(readMode)) {
|
|
1199
1245
|
// Rich-text targets must be authorised and refreshed before user hooks see
|
|
1200
1246
|
// the document, regardless of which read entry point materialised it.
|
|
1201
|
-
await this.richTextPopulateSources(rawDocs, readContext, requestContext, locale, readMode, bypassBeforeRead);
|
|
1247
|
+
await this.richTextPopulateSources(rawDocs, readContext, requestContext, locale, readMode, bypassBeforeRead, localeVisibility);
|
|
1202
1248
|
for (const doc of rawDocs) {
|
|
1203
1249
|
await applyAfterRead({
|
|
1204
1250
|
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,21 @@
|
|
|
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.
|
|
20
|
+
*
|
|
21
|
+
* Aggregates: the provider's `total` and facets pass through unless an
|
|
22
|
+
* *actor-specific* restriction applies (a `beforeRead` predicate, or zone
|
|
23
|
+
* members the actor cannot read) — then the restricted-result convention
|
|
24
|
+
* (retained-hit `total`, no facets) prevents leaking counts of rows this
|
|
25
|
+
* reader can't see. Public locale eligibility is actor-independent and is
|
|
26
|
+
* the same rule indexing applies, so the provider aggregate already is the
|
|
27
|
+
* public aggregate; a stale entry the re-check drops is an index-maintenance
|
|
28
|
+
* lag, logged at warn level, not a reason to degrade the response.
|
|
18
29
|
*/
|
|
19
30
|
import type { RequestContext } from '@byline/auth';
|
|
20
|
-
import type { ReadContext, ReadMode, SearchHit, SearchProvider } from '@byline/core';
|
|
31
|
+
import type { LocaleVisibility, ReadContext, ReadMode, SearchHit, SearchProvider } from '@byline/core';
|
|
21
32
|
import type { BylineClient } from './client.js';
|
|
22
33
|
import type { ClientSearchResults, HydratedSearchHit, ZoneSearchOptions } from './types.js';
|
|
23
34
|
/** Validate the optional logical body-field scope before calling a provider. */
|
|
@@ -30,6 +41,11 @@ export interface FinalizeSearchHitsParams {
|
|
|
30
41
|
status?: ReadMode;
|
|
31
42
|
hydrate?: boolean;
|
|
32
43
|
bypassBeforeRead?: true;
|
|
44
|
+
/**
|
|
45
|
+
* The search's locale visibility (derived from `status`). Under `'public'`
|
|
46
|
+
* each hit's document must be publicly eligible in the hit's locale.
|
|
47
|
+
*/
|
|
48
|
+
localeVisibility: LocaleVisibility;
|
|
33
49
|
/** Shared per-request read context (beforeRead cache). Created when omitted. */
|
|
34
50
|
readContext?: ReadContext;
|
|
35
51
|
}
|
|
@@ -42,9 +58,19 @@ export interface FinalizeSearchHitsParams {
|
|
|
42
58
|
* hydration cost one query per collection. Hits whose document doesn't
|
|
43
59
|
* come back — dropped by scoping, or a stale index entry whose document
|
|
44
60
|
* no longer resolves — are removed; the rest carry `hit.document`.
|
|
45
|
-
* - `hydrate` off
|
|
46
|
-
*
|
|
47
|
-
*
|
|
61
|
+
* - `hydrate` off, public visibility — every collection's hit ids are
|
|
62
|
+
* re-resolved through the normal read path with `onMissingLocale:
|
|
63
|
+
* 'omit'` (identity-field projection), which applies both exact-locale
|
|
64
|
+
* eligibility and any `beforeRead` scoping. An index entry can outlive
|
|
65
|
+
* the checkbox that allowed it (until reindexing catches up), so this
|
|
66
|
+
* check runs even without a hook.
|
|
67
|
+
* - `hydrate` off, editorial visibility — collections with a `beforeRead`
|
|
68
|
+
* predicate get the trimmed id re-resolution; collections without one
|
|
69
|
+
* pass through untouched (no second query).
|
|
70
|
+
*
|
|
71
|
+
* Hydration also reads with `'omit'`: a hit is for one locale slice, and a
|
|
72
|
+
* source-language document substituted by fallback would not make that hit
|
|
73
|
+
* safe.
|
|
48
74
|
*
|
|
49
75
|
* Hits whose `collectionPath` isn't registered in the runtime config are
|
|
50
76
|
* dropped with a debug log (an index can outlive a collection). Original
|
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,16 +30,26 @@ 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;
|
|
43
53
|
if (hits.length === 0)
|
|
44
54
|
return hits;
|
|
45
55
|
const readCtx = params.readContext ?? createReadContext();
|
|
@@ -78,12 +88,35 @@ export async function finalizeSearchHits(params) {
|
|
|
78
88
|
select,
|
|
79
89
|
locale,
|
|
80
90
|
status,
|
|
91
|
+
onMissingLocale: 'omit',
|
|
92
|
+
localeVisibility,
|
|
81
93
|
page: 1,
|
|
82
94
|
pageSize: ids.length,
|
|
83
95
|
_readContext: readCtx,
|
|
84
96
|
...(bypassBeforeRead ? { _bypassBeforeRead: true } : {}),
|
|
85
97
|
});
|
|
86
98
|
documentsByCollection.set(collectionPath, new Map(result.docs.map((d) => [d.id, d])));
|
|
99
|
+
await warnOnStaleHits(params, readCtx, collectionPath, group, new Set(result.docs.map((d) => d.id)));
|
|
100
|
+
continue;
|
|
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
|
+
const allowed = new Set(result.docs.map((d) => d.id));
|
|
118
|
+
allowedByCollection.set(collectionPath, allowed);
|
|
119
|
+
await warnOnStaleHits(params, readCtx, collectionPath, group, allowed);
|
|
87
120
|
continue;
|
|
88
121
|
}
|
|
89
122
|
if (bypassBeforeRead) {
|
|
@@ -129,6 +162,30 @@ export async function finalizeSearchHits(params) {
|
|
|
129
162
|
}
|
|
130
163
|
return finished;
|
|
131
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Warn about hits the re-check dropped for a reason other than `beforeRead`
|
|
167
|
+
* scoping — a stale index entry (deleted document, withdrawn translation,
|
|
168
|
+
* unpublished version) that index maintenance hasn't caught up with yet.
|
|
169
|
+
* Drops under an active `beforeRead` predicate are expected and not logged.
|
|
170
|
+
*/
|
|
171
|
+
async function warnOnStaleHits(params, readCtx, collectionPath, group, resolved) {
|
|
172
|
+
const dropped = group.filter((h) => !resolved.has(h.documentId));
|
|
173
|
+
if (dropped.length === 0)
|
|
174
|
+
return;
|
|
175
|
+
const { client, requestContext, bypassBeforeRead } = params;
|
|
176
|
+
if (!bypassBeforeRead) {
|
|
177
|
+
const definition = client.collections.find((c) => c.path === collectionPath);
|
|
178
|
+
const filters = definition == null
|
|
179
|
+
? null
|
|
180
|
+
: await resolveReadSecurityFilters(client, definition, requestContext, readCtx);
|
|
181
|
+
if (filters != null)
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
client.logger.warn({
|
|
185
|
+
collectionPath,
|
|
186
|
+
dropped: dropped.map((h) => ({ documentId: h.documentId, locale: h.locale })),
|
|
187
|
+
}, 'search: dropped index entries that no longer resolve for this read; reindex the affected documents');
|
|
188
|
+
}
|
|
132
189
|
// ---------------------------------------------------------------------------
|
|
133
190
|
// Zone (cross-collection) entry point
|
|
134
191
|
// ---------------------------------------------------------------------------
|
|
@@ -211,6 +268,7 @@ export async function zoneSearch(client, options) {
|
|
|
211
268
|
status: options.status,
|
|
212
269
|
hydrate: options.hydrate,
|
|
213
270
|
bypassBeforeRead: options._bypassBeforeRead,
|
|
271
|
+
localeVisibility: resolveLocaleVisibility(readMode),
|
|
214
272
|
readContext: readCtx,
|
|
215
273
|
});
|
|
216
274
|
return aggregateRestricted
|
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.1",
|
|
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.1",
|
|
48
|
+
"@byline/core": "6.7.1"
|
|
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/db-postgres": "6.
|
|
61
|
-
"@byline/search-postgres": "6.
|
|
60
|
+
"@byline/db-postgres": "6.7.1",
|
|
61
|
+
"@byline/search-postgres": "6.7.1"
|
|
62
62
|
},
|
|
63
63
|
"publishConfig": {
|
|
64
64
|
"access": "public",
|