@byline/core 3.15.2 → 3.16.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/@types/collection-types.d.ts +7 -7
- package/dist/@types/db-types.d.ts +46 -24
- package/dist/@types/query-predicate.d.ts +25 -3
- package/dist/@types/search-types.d.ts +8 -1
- package/dist/@types/site-config.d.ts +2 -2
- package/dist/auth/assert-actor-can-perform.d.ts +1 -1
- package/dist/auth/assert-actor-can-perform.js +1 -1
- package/dist/auth/register-collection-abilities.d.ts +1 -1
- package/dist/auth/register-collection-abilities.js +1 -1
- package/dist/config/config.d.ts +1 -1
- package/dist/config/config.js +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.js +1 -1
- package/dist/lib/errors.d.ts +1 -1
- package/dist/lib/errors.js +1 -1
- package/dist/query/parse-where.js +71 -20
- package/dist/query/parse-where.test.node.js +93 -0
- package/dist/schemas/zod/builder.js +2 -2
- package/dist/services/build-search-document.d.ts +9 -0
- package/dist/services/build-search-document.js +15 -1
- package/dist/services/document-lifecycle/audit.d.ts +1 -1
- package/dist/services/document-lifecycle/audit.js +3 -3
- package/dist/services/document-lifecycle/context.d.ts +1 -1
- package/dist/services/document-lifecycle/create.d.ts +1 -1
- package/dist/services/document-lifecycle/create.js +1 -1
- package/dist/services/document-lifecycle/delete.js +3 -3
- package/dist/services/document-lifecycle/internals.d.ts +2 -2
- package/dist/services/document-lifecycle/internals.js +3 -3
- package/dist/services/document-lifecycle/status.js +1 -1
- package/dist/services/document-lifecycle/system-fields.d.ts +2 -2
- package/dist/services/document-lifecycle/system-fields.js +3 -3
- package/dist/services/document-lifecycle/tree.js +1 -1
- package/dist/services/document-lifecycle/update.d.ts +2 -2
- package/dist/services/document-lifecycle.test.node.js +3 -3
- package/dist/services/document-to-markdown.d.ts +1 -1
- package/dist/services/document-to-markdown.js +1 -1
- package/dist/services/document-to-markdown.test.node.js +1 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.js +1 -1
- package/dist/services/populate.d.ts +1 -1
- package/package.json +3 -5
|
@@ -474,7 +474,7 @@ export interface DeleteContext {
|
|
|
474
474
|
}
|
|
475
475
|
/**
|
|
476
476
|
* Context passed to the `afterTreeChange` hook — the structural-change
|
|
477
|
-
* invalidation event for `tree: true` collections (docs/
|
|
477
|
+
* invalidation event for `tree: true` collections (docs/04-collections/03-document-trees.md).
|
|
478
478
|
*
|
|
479
479
|
* Tree mutations are document-grain and **unversioned**, so the normal
|
|
480
480
|
* version-write invalidation (`afterCreate` / `afterUpdate` / `afterStatusChange`)
|
|
@@ -676,7 +676,7 @@ export declare function resolveUploadHooks(hooks: UploadHooks | UploadHooksLoade
|
|
|
676
676
|
* by populate. A hook that performs its own reads should thread this
|
|
677
677
|
* context back in via `client.collection(...).findById(id, { _readContext:
|
|
678
678
|
* readContext })` so the visited set and read budget are preserved —
|
|
679
|
-
* essential to foreclose the A→B→A loop (see `docs/
|
|
679
|
+
* essential to foreclose the A→B→A loop (see `docs/04-collections/02-relationships.md`).
|
|
680
680
|
*/
|
|
681
681
|
export interface AfterReadContext {
|
|
682
682
|
/** The raw reconstructed document. Mutate in place — changes persist. */
|
|
@@ -709,7 +709,7 @@ export interface AfterReadContext {
|
|
|
709
709
|
* - `collectionPath` — the collection being queried (useful when the
|
|
710
710
|
* same hook function is reused across collections).
|
|
711
711
|
*
|
|
712
|
-
* See `docs/
|
|
712
|
+
* See `docs/06-auth-and-security/01-authn-authz.md` for the strategic rationale; the Quick
|
|
713
713
|
* Reference there carries six worked recipes.
|
|
714
714
|
*/
|
|
715
715
|
export interface BeforeReadContext {
|
|
@@ -786,7 +786,7 @@ export interface CollectionHooks {
|
|
|
786
786
|
* (`removeFromTree`), or the promote-children-to-root that accompanies a
|
|
787
787
|
* delete. Tree writes mint no document version, so this is the only
|
|
788
788
|
* invalidation signal for them. Fires once per write with the full affected
|
|
789
|
-
* set ({@link TreeChangeContext}). See docs/
|
|
789
|
+
* set ({@link TreeChangeContext}). See docs/04-collections/03-document-trees.md.
|
|
790
790
|
*/
|
|
791
791
|
afterTreeChange?: CollectionHookSlot<TreeChangeContext>;
|
|
792
792
|
/**
|
|
@@ -795,7 +795,7 @@ export interface CollectionHooks {
|
|
|
795
795
|
* that the query layer ANDs onto the caller's `where` to enforce
|
|
796
796
|
* read-side row scoping (multi-tenant, owner-only-drafts, soft-delete
|
|
797
797
|
* hide, etc). Returning `void` applies no scoping. Multiple functions
|
|
798
|
-
* combine with implicit AND. See `docs/
|
|
798
|
+
* combine with implicit AND. See `docs/06-auth-and-security/01-authn-authz.md` (Read-side
|
|
799
799
|
* scoping + Quick Reference recipes).
|
|
800
800
|
*/
|
|
801
801
|
beforeRead?: BeforeReadHookSlot;
|
|
@@ -970,7 +970,7 @@ export interface CollectionDefinition {
|
|
|
970
970
|
* collection has at least one `localized` field, so the validator rejects
|
|
971
971
|
* `advertiseLocales: true` on a collection with none.
|
|
972
972
|
*
|
|
973
|
-
* See `docs/
|
|
973
|
+
* See `docs/07-internationalization/index.md`.
|
|
974
974
|
*/
|
|
975
975
|
advertiseLocales?: boolean;
|
|
976
976
|
/**
|
|
@@ -1061,7 +1061,7 @@ export interface CollectionDefinition {
|
|
|
1061
1061
|
* touch no user fields. Do **not** also declare a `parent` relation field; the
|
|
1062
1062
|
* tree owns structure (a topic that genuinely belongs in two places is a
|
|
1063
1063
|
* cross-link relation field, never a second tree edge). See
|
|
1064
|
-
* docs/
|
|
1064
|
+
* docs/04-collections/03-document-trees.md.
|
|
1065
1065
|
*/
|
|
1066
1066
|
tree?: boolean;
|
|
1067
1067
|
/**
|
|
@@ -39,7 +39,7 @@ export type ReadMode = 'any' | 'published';
|
|
|
39
39
|
* safe default for internal/direct reads); `@byline/client` defaults it to
|
|
40
40
|
* `'fallback'` for application reads. Availability follows path-coverage against
|
|
41
41
|
* the default content locale; a document with no localized content is available
|
|
42
|
-
* in every locale. See `docs/
|
|
42
|
+
* in every locale. See `docs/07-internationalization/index.md`.
|
|
43
43
|
*/
|
|
44
44
|
export type MissingLocalePolicy = 'empty' | 'fallback' | 'omit';
|
|
45
45
|
/**
|
|
@@ -128,6 +128,28 @@ export interface RelationFilter {
|
|
|
128
128
|
targetCollectionId: string;
|
|
129
129
|
/** Filters applied to the target document. Recursive. */
|
|
130
130
|
nested: DocumentFilter[];
|
|
131
|
+
/**
|
|
132
|
+
* Whether the source field is a multi-target (`hasMany`) relation. hasMany
|
|
133
|
+
* items are stored as indexed `store_relation` rows (`<field>.0`,
|
|
134
|
+
* `<field>.1`, …), so the adapter matches `field_name` by prefix instead
|
|
135
|
+
* of exact equality. Set at parse time from the field definition.
|
|
136
|
+
*/
|
|
137
|
+
hasMany?: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* Quantifier over the relation's target set, for `hasMany` relations
|
|
140
|
+
* (single relations are a set of ≤ 1 and compose the same way):
|
|
141
|
+
*
|
|
142
|
+
* - `'some'` (default) — at least one target satisfies `nested`.
|
|
143
|
+
* - `'every'` — no target fails `nested`. Vacuously true when the
|
|
144
|
+
* document has no (resolving) targets on the field.
|
|
145
|
+
* - `'none'` — no target satisfies `nested`. With empty `nested`,
|
|
146
|
+
* matches documents that have no resolving targets at all.
|
|
147
|
+
*
|
|
148
|
+
* Targets that do not resolve in the selected read view (deleted, or
|
|
149
|
+
* unpublished under `readMode: 'published'`) are ignored by all three —
|
|
150
|
+
* the same visibility rule populate applies.
|
|
151
|
+
*/
|
|
152
|
+
quantifier?: 'some' | 'every' | 'none';
|
|
131
153
|
}
|
|
132
154
|
/**
|
|
133
155
|
* A boolean combinator group. Wraps a list of child filters with explicit
|
|
@@ -204,7 +226,7 @@ export interface IDbAdapter {
|
|
|
204
226
|
documents: IDocumentCommands;
|
|
205
227
|
counters: ICounterCommands;
|
|
206
228
|
/**
|
|
207
|
-
* Append-only audit-log writes (docs/
|
|
229
|
+
* Append-only audit-log writes (docs/06-auth-and-security/02-auditability.md — Workstream 2). Optional
|
|
208
230
|
* capability, paired with `withTransaction`: a consumer that records audit
|
|
209
231
|
* entries asserts both are present and throws otherwise (it must never
|
|
210
232
|
* silently skip the audit row). Adapters that model the audit log
|
|
@@ -215,14 +237,14 @@ export interface IDbAdapter {
|
|
|
215
237
|
queries: {
|
|
216
238
|
collections: ICollectionQueries;
|
|
217
239
|
documents: IDocumentQueries;
|
|
218
|
-
/** Audit-log reads — per-document history, system-wide report. See docs/
|
|
240
|
+
/** Audit-log reads — per-document history, system-wide report. See docs/06-auth-and-security/02-auditability.md. */
|
|
219
241
|
audit?: IAuditQueries;
|
|
220
242
|
};
|
|
221
243
|
/**
|
|
222
244
|
* Optional capability: run `fn` inside a single database transaction so the
|
|
223
245
|
* writes it performs commit or roll back atomically. The adapter propagates
|
|
224
246
|
* the transaction to every `commands.*` call made within `fn` (see
|
|
225
|
-
* docs/
|
|
247
|
+
* docs/03-architecture/03-transactions.md — AsyncLocalStorage propagation), so a service can
|
|
226
248
|
* compose multiple commands into one unit of work without threading a
|
|
227
249
|
* transaction handle through their signatures.
|
|
228
250
|
*
|
|
@@ -232,7 +254,7 @@ export interface IDbAdapter {
|
|
|
232
254
|
* method** (or implement it to throw); a consumer that requires atomicity
|
|
233
255
|
* (e.g. the audit log) MUST assert its presence and throw — never silently
|
|
234
256
|
* run non-atomically, which would defeat the very guarantee it provides. See
|
|
235
|
-
* docs/
|
|
257
|
+
* docs/03-architecture/03-transactions.md ("Serverless / HTTP-gateway databases — the contract
|
|
236
258
|
* seam").
|
|
237
259
|
*/
|
|
238
260
|
withTransaction?: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
@@ -243,7 +265,7 @@ export interface IDbAdapter {
|
|
|
243
265
|
* boot by `initBylineCore` so in-place upgrades self-heal without a manual
|
|
244
266
|
* step or a migrate-ordering constraint — a no-op (zero rows) once every
|
|
245
267
|
* document is stamped. Optional so adapters that don't model `source_locale`
|
|
246
|
-
* need not implement it. See docs/
|
|
268
|
+
* need not implement it. See docs/07-internationalization/index.md.
|
|
247
269
|
*/
|
|
248
270
|
backfillSourceLocales?: () => Promise<{
|
|
249
271
|
rowsUpdated: number;
|
|
@@ -252,7 +274,7 @@ export interface IDbAdapter {
|
|
|
252
274
|
/**
|
|
253
275
|
* The realm of the actor that performed an audited change. `'admin'` for
|
|
254
276
|
* admin-user actions, `'user'` reserved for the end-user realm, `'system'`
|
|
255
|
-
* for deliberate internal-tooling writes. See docs/
|
|
277
|
+
* for deliberate internal-tooling writes. See docs/06-auth-and-security/02-auditability.md.
|
|
256
278
|
*/
|
|
257
279
|
export type AuditActorRealm = 'admin' | 'user' | 'system';
|
|
258
280
|
/** Input to `IAuditCommands.append` — one audit-log row. */
|
|
@@ -297,7 +319,7 @@ export interface AuditLogPage {
|
|
|
297
319
|
}
|
|
298
320
|
/**
|
|
299
321
|
* Append-only audit-log writes. The companion read interface is
|
|
300
|
-
* `IAuditQueries`. See docs/
|
|
322
|
+
* `IAuditQueries`. See docs/06-auth-and-security/02-auditability.md — Workstream 2.
|
|
301
323
|
*/
|
|
302
324
|
export interface IAuditCommands {
|
|
303
325
|
/**
|
|
@@ -310,7 +332,7 @@ export interface IAuditCommands {
|
|
|
310
332
|
id: string;
|
|
311
333
|
}>;
|
|
312
334
|
}
|
|
313
|
-
/** Audit-log reads. See docs/
|
|
335
|
+
/** Audit-log reads. See docs/06-auth-and-security/02-auditability.md — Workstreams 3 & 4. */
|
|
314
336
|
export interface IAuditQueries {
|
|
315
337
|
/**
|
|
316
338
|
* The audit history for one document, newest first, paged. Backs the
|
|
@@ -324,7 +346,7 @@ export interface IAuditQueries {
|
|
|
324
346
|
page_size?: number;
|
|
325
347
|
}): Promise<AuditLogPage>;
|
|
326
348
|
/**
|
|
327
|
-
* The system-wide activity feed (docs/
|
|
349
|
+
* The system-wide activity feed (docs/06-auth-and-security/02-auditability.md — Workstream 4), newest
|
|
328
350
|
* first, paged and filterable. A read-time **union** of two disjoint event
|
|
329
351
|
* sources, normalised onto the `AuditLogEntry` shape:
|
|
330
352
|
*
|
|
@@ -364,7 +386,7 @@ export interface IAuditQueries {
|
|
|
364
386
|
/**
|
|
365
387
|
* Adapter capability for the shared-pool counter mechanism backing the
|
|
366
388
|
* `counter` field type. See `packages/core/src/@types/field-types.ts`
|
|
367
|
-
* (CounterField) for the field-level contract and `docs/
|
|
389
|
+
* (CounterField) for the field-level contract and `docs/04-collections/index.md`
|
|
368
390
|
* (Counter fields) for the conceptual overview.
|
|
369
391
|
*
|
|
370
392
|
* Both methods are keyed by the developer-facing `groupName` (the value
|
|
@@ -455,7 +477,7 @@ export interface IDocumentCommands {
|
|
|
455
477
|
* editorial advertised-locale set. `undefined` leaves the existing set
|
|
456
478
|
* untouched (sticky across versions, like `path`); `[]` clears it. The
|
|
457
479
|
* locale values are the advertised content locales themselves, not the
|
|
458
|
-
* write locale. See `docs/
|
|
480
|
+
* write locale. See `docs/07-internationalization/index.md`.
|
|
459
481
|
*/
|
|
460
482
|
availableLocales?: string[];
|
|
461
483
|
locale?: string;
|
|
@@ -473,7 +495,7 @@ export interface IDocumentCommands {
|
|
|
473
495
|
* Only set on the initial create (when `documentId` is undefined) for
|
|
474
496
|
* collections with `orderable: true`. Ignored on subsequent versions of
|
|
475
497
|
* an existing document — order is admin metadata on the logical document,
|
|
476
|
-
* not per-version content. See docs/
|
|
498
|
+
* not per-version content. See docs/04-collections/index.md (Orderable collections).
|
|
477
499
|
*/
|
|
478
500
|
orderKey?: string;
|
|
479
501
|
}): Promise<{
|
|
@@ -487,7 +509,7 @@ export interface IDocumentCommands {
|
|
|
487
509
|
* the change is immediate and applies across every version. Backs the admin
|
|
488
510
|
* path widget's direct-write Save. The unique constraint on
|
|
489
511
|
* `(collection_id, locale, path)` may surface as `ERR_PATH_CONFLICT` from the
|
|
490
|
-
* lifecycle layer. See `docs/
|
|
512
|
+
* lifecycle layer. See `docs/07-internationalization/index.md`.
|
|
491
513
|
*/
|
|
492
514
|
updateDocumentPath(params: {
|
|
493
515
|
documentId: string;
|
|
@@ -501,7 +523,7 @@ export interface IDocumentCommands {
|
|
|
501
523
|
* wholesale **without** minting a new version or touching workflow status —
|
|
502
524
|
* the change is immediate and applies across every version. `[]` clears it.
|
|
503
525
|
* Backs the admin available-locales widget's direct-write Save. See
|
|
504
|
-
* `docs/
|
|
526
|
+
* `docs/07-internationalization/index.md`.
|
|
505
527
|
*/
|
|
506
528
|
setDocumentAvailableLocales(params: {
|
|
507
529
|
documentId: string;
|
|
@@ -555,7 +577,7 @@ export interface IDocumentCommands {
|
|
|
555
577
|
documentId: string;
|
|
556
578
|
locale: string;
|
|
557
579
|
status?: string;
|
|
558
|
-
/** Acting user id for the version audit trail (`created_by`). See docs/
|
|
580
|
+
/** Acting user id for the version audit trail (`created_by`). See docs/06-auth-and-security/02-auditability.md. */
|
|
559
581
|
createdBy?: string;
|
|
560
582
|
}): Promise<{
|
|
561
583
|
newVersionId: string;
|
|
@@ -598,7 +620,7 @@ export interface IDocumentCommands {
|
|
|
598
620
|
* **before** (its right neighbour). Either may be null — `{}` appends as the
|
|
599
621
|
* only/last child, `beforeDocumentId` alone appends after it, `afterDocumentId`
|
|
600
622
|
* alone prepends before it. Both are resolved within the *target* parent
|
|
601
|
-
* group. See docs/
|
|
623
|
+
* group. See docs/04-collections/03-document-trees.md.
|
|
602
624
|
*/
|
|
603
625
|
placeTreeNode(params: {
|
|
604
626
|
collectionId: string;
|
|
@@ -613,7 +635,7 @@ export interface IDocumentCommands {
|
|
|
613
635
|
* Remove a document's edge row, returning it to the *unplaced* state (in the
|
|
614
636
|
* collection, but not in the tree). Distinct from document deletion — the
|
|
615
637
|
* document and its content are untouched. No-op when the node is already
|
|
616
|
-
* unplaced. See docs/
|
|
638
|
+
* unplaced. See docs/04-collections/03-document-trees.md.
|
|
617
639
|
*/
|
|
618
640
|
removeFromTree(params: {
|
|
619
641
|
documentId: string;
|
|
@@ -642,7 +664,7 @@ export interface IDocumentQueries {
|
|
|
642
664
|
/**
|
|
643
665
|
* Request-scoped auth context. Adapters thread it through to
|
|
644
666
|
* `assertActorCanPerform` for ability assertion and to `beforeRead`
|
|
645
|
-
* hooks for query scoping. See docs/
|
|
667
|
+
* hooks for query scoping. See docs/06-auth-and-security/01-authn-authz.md.
|
|
646
668
|
*/
|
|
647
669
|
requestContext?: RequestContext;
|
|
648
670
|
/**
|
|
@@ -696,7 +718,7 @@ export interface IDocumentQueries {
|
|
|
696
718
|
*
|
|
697
719
|
* Source-locale only: this resolves the single canonical slug, which is the
|
|
698
720
|
* only path row a document has today. When per-locale paths land (see
|
|
699
|
-
* docs/
|
|
721
|
+
* docs/04-collections/04-document-paths.md → "Phase — per-locale paths"), the write-side hook
|
|
700
722
|
* contexts that consume this must be enriched to carry the locale each path
|
|
701
723
|
* was derived under (or the full `locale → path` set) — a single canonical
|
|
702
724
|
* `path` is no longer sufficient for per-localised-URL cache/CDN purges.
|
|
@@ -925,7 +947,7 @@ export interface IDocumentQueries {
|
|
|
925
947
|
* parent, increasing toward the root). Empty for a root or unplaced node.
|
|
926
948
|
*
|
|
927
949
|
* Backs breadcrumbs and the read-time hierarchical-URL canonicalization
|
|
928
|
-
* (docs/
|
|
950
|
+
* (docs/04-collections/03-document-trees.md). Depth-bounded as a backstop against pathological
|
|
929
951
|
* key state even though the write-path cycle guard prevents true cycles.
|
|
930
952
|
*
|
|
931
953
|
* **Status-at-edge.** `readMode: 'published'` joins
|
|
@@ -950,7 +972,7 @@ export interface IDocumentQueries {
|
|
|
950
972
|
* `order_key`. `parentDocumentId: null` returns the collection's root nodes.
|
|
951
973
|
* Scoped to `collectionId` so root reads (which have no parent to scope by)
|
|
952
974
|
* stay within the collection. One level only — the recursive subtree read is
|
|
953
|
-
* a separate query. See docs/
|
|
975
|
+
* a separate query. See docs/04-collections/03-document-trees.md.
|
|
954
976
|
*/
|
|
955
977
|
getTreeChildren(params: {
|
|
956
978
|
collectionId: string;
|
|
@@ -961,7 +983,7 @@ export interface IDocumentQueries {
|
|
|
961
983
|
}>>;
|
|
962
984
|
/**
|
|
963
985
|
* Resolve a single node's placement state in the tree, distinguishing the
|
|
964
|
-
* three tri-states (docs/
|
|
986
|
+
* three tri-states (docs/04-collections/03-document-trees.md → "Node placement"):
|
|
965
987
|
*
|
|
966
988
|
* - **Unplaced** (no edge row) → `{ placed: false, parentDocumentId: null }`
|
|
967
989
|
* - **Root** (edge row, null parent) → `{ placed: true, parentDocumentId: null }`
|
|
@@ -993,7 +1015,7 @@ export interface IDocumentQueries {
|
|
|
993
1015
|
* `byline_current_published_documents` and drops any node without a current
|
|
994
1016
|
* published version; because the walk only recurses through *included*
|
|
995
1017
|
* nodes, an unpublished node's entire subtree is omitted — the spine is
|
|
996
|
-
* broken and descendants are not promoted (see docs/
|
|
1018
|
+
* broken and descendants are not promoted (see docs/04-collections/03-document-trees.md).
|
|
997
1019
|
* `readMode: 'any'` (the default) includes every current, non-deleted node.
|
|
998
1020
|
* Depth-bounded by `maxDepth` as a backstop.
|
|
999
1021
|
*
|
|
@@ -21,13 +21,35 @@ export interface FilterOperators {
|
|
|
21
21
|
$in?: Array<string | number>;
|
|
22
22
|
$nin?: Array<string | number>;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Quantifiers usable on a relation field's predicate value — the multi-target
|
|
26
|
+
* (`hasMany`) analogue of a nested sub-where. Each key carries a nested
|
|
27
|
+
* `QueryPredicate` evaluated against the relation's *targets*:
|
|
28
|
+
*
|
|
29
|
+
* - `$some` — at least one target matches (a bare nested sub-where on a
|
|
30
|
+
* relation field is shorthand for this).
|
|
31
|
+
* - `$every` — no target fails the predicate. Vacuously true when the
|
|
32
|
+
* document has no targets on the field (Prisma-style semantics).
|
|
33
|
+
* - `$none` — no target matches. `$none: {}` matches documents with no
|
|
34
|
+
* (resolving) targets at all.
|
|
35
|
+
*
|
|
36
|
+
* Multiple quantifier keys on one field AND together. Also valid on single
|
|
37
|
+
* relations (a set of ≤ 1). Targets that don't resolve in the selected read
|
|
38
|
+
* view (deleted, or unpublished under published-mode reads) are ignored.
|
|
39
|
+
*/
|
|
40
|
+
export interface RelationQuantifiers {
|
|
41
|
+
$some?: QueryPredicate;
|
|
42
|
+
$every?: QueryPredicate;
|
|
43
|
+
$none?: QueryPredicate;
|
|
44
|
+
}
|
|
24
45
|
/**
|
|
25
46
|
* The value side of a predicate entry. Either a bare value (interpreted as
|
|
26
47
|
* `$eq`), an operator object, a nested `QueryPredicate` (for cross-collection
|
|
27
|
-
* relation filters or combinator children),
|
|
28
|
-
* (
|
|
48
|
+
* relation filters or combinator children), a `RelationQuantifiers` object
|
|
49
|
+
* (relation fields only), or an array of `QueryPredicate` (the value side of
|
|
50
|
+
* `$and` / `$or`).
|
|
29
51
|
*/
|
|
30
|
-
export type PredicateValue = string | number | boolean | null | FilterOperators | QueryPredicate | QueryPredicate[];
|
|
52
|
+
export type PredicateValue = string | number | boolean | null | FilterOperators | RelationQuantifiers | QueryPredicate | QueryPredicate[];
|
|
31
53
|
/**
|
|
32
54
|
* Structured predicate language used by:
|
|
33
55
|
* - the client API's `where` clause (re-exported as `WhereClause`),
|
|
@@ -200,7 +200,14 @@ export interface SearchFacetBucket {
|
|
|
200
200
|
/** The result envelope returned by `SearchProvider.search`. */
|
|
201
201
|
export interface SearchResults {
|
|
202
202
|
hits: SearchHit[];
|
|
203
|
-
/**
|
|
203
|
+
/**
|
|
204
|
+
* Total matches across all pages (not just the returned `hits`).
|
|
205
|
+
*
|
|
206
|
+
* This is the *provider's* count. When the querying collection applies
|
|
207
|
+
* `beforeRead` row scoping, unauthorized hits are dropped **after**
|
|
208
|
+
* ranking (see `CollectionHandle.search`), so `total` — like facet
|
|
209
|
+
* counts — is approximate under scoping and exact without it.
|
|
210
|
+
*/
|
|
204
211
|
total: number;
|
|
205
212
|
/**
|
|
206
213
|
* Facet buckets keyed by field name, when `facets` was requested and the
|
|
@@ -64,7 +64,7 @@ export interface BaseConfig {
|
|
|
64
64
|
* its path locale, and the completeness ledger — so changing this value
|
|
65
65
|
* is safe for existing data: they keep reading against the locale they
|
|
66
66
|
* were authored in. New documents created after the change anchor to the
|
|
67
|
-
* new value. See docs/
|
|
67
|
+
* new value. See docs/07-internationalization/index.md.
|
|
68
68
|
*
|
|
69
69
|
* (Switching this on a live system still needs the one-time
|
|
70
70
|
* `backfillSourceLocales()` maintenance step to have stamped any rows
|
|
@@ -103,7 +103,7 @@ export interface BaseConfig {
|
|
|
103
103
|
*
|
|
104
104
|
* Optional at the type level so `BaseConfig` stays loose for tests
|
|
105
105
|
* and seed scripts; required at runtime via `validateTranslations`
|
|
106
|
-
* whenever `interface.locales` is non-empty. See `docs/
|
|
106
|
+
* whenever `interface.locales` is non-empty. See `docs/07-internationalization/index.md` for
|
|
107
107
|
* the design.
|
|
108
108
|
*
|
|
109
109
|
* The shape is declared inline (rather than imported from
|
|
@@ -33,6 +33,6 @@ import { type CollectionAbilityVerb } from './register-collection-abilities.js';
|
|
|
33
33
|
* bypass this helper — the same escape hatch that skips collection
|
|
34
34
|
* hooks. Seeds, migrations, and internal tooling live there.
|
|
35
35
|
*
|
|
36
|
-
* See docs/
|
|
36
|
+
* See docs/06-auth-and-security/01-authn-authz.md.
|
|
37
37
|
*/
|
|
38
38
|
export declare function assertActorCanPerform(context: RequestContext | undefined, collectionPath: string, verb: CollectionAbilityVerb): void;
|
|
@@ -33,7 +33,7 @@ import { collectionAbilityKey, } from './register-collection-abilities.js';
|
|
|
33
33
|
* bypass this helper — the same escape hatch that skips collection
|
|
34
34
|
* hooks. Seeds, migrations, and internal tooling live there.
|
|
35
35
|
*
|
|
36
|
-
* See docs/
|
|
36
|
+
* See docs/06-auth-and-security/01-authn-authz.md.
|
|
37
37
|
*/
|
|
38
38
|
export function assertActorCanPerform(context, collectionPath, verb) {
|
|
39
39
|
if (!context) {
|
|
@@ -32,7 +32,7 @@ import type { CollectionDefinition } from '../@types/index.js';
|
|
|
32
32
|
* predictable and avoids hidden conditional logic downstream.
|
|
33
33
|
*
|
|
34
34
|
* Called from `initBylineCore()` for each declared collection. See
|
|
35
|
-
* docs/
|
|
35
|
+
* docs/06-auth-and-security/01-authn-authz.md.
|
|
36
36
|
*/
|
|
37
37
|
export declare function registerCollectionAbilities(registry: AbilityRegistry, definition: CollectionDefinition): void;
|
|
38
38
|
/** The ability suffixes that every collection contributes. Exposed for contract tests. */
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* predictable and avoids hidden conditional logic downstream.
|
|
31
31
|
*
|
|
32
32
|
* Called from `initBylineCore()` for each declared collection. See
|
|
33
|
-
* docs/
|
|
33
|
+
* docs/06-auth-and-security/01-authn-authz.md.
|
|
34
34
|
*/
|
|
35
35
|
export function registerCollectionAbilities(registry, definition) {
|
|
36
36
|
const path = definition.path;
|
package/dist/config/config.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ export declare function getServerConfig(): ServerConfig;
|
|
|
47
47
|
* config-driven at the read source. The payoff is canonical downstream
|
|
48
48
|
* ordering (display switcher, hreflang `alternates`, sitemap) regardless of
|
|
49
49
|
* the order a document declared its locales in. Read-time projection only;
|
|
50
|
-
* nothing persisted changes. See docs/
|
|
50
|
+
* nothing persisted changes. See docs/07-internationalization/index.md.
|
|
51
51
|
*/
|
|
52
52
|
export declare function orderByContentLocale(codes: string[]): string[];
|
|
53
53
|
export declare function defineBylineCore(core: unknown): void;
|
package/dist/config/config.js
CHANGED
|
@@ -124,7 +124,7 @@ export function getServerConfig() {
|
|
|
124
124
|
* config-driven at the read source. The payoff is canonical downstream
|
|
125
125
|
* ordering (display switcher, hreflang `alternates`, sitemap) regardless of
|
|
126
126
|
* the order a document declared its locales in. Read-time projection only;
|
|
127
|
-
* nothing persisted changes. See docs/
|
|
127
|
+
* nothing persisted changes. See docs/07-internationalization/index.md.
|
|
128
128
|
*/
|
|
129
129
|
export function orderByContentLocale(codes) {
|
|
130
130
|
const content = getServerConfigInstance()?.i18n?.content;
|
package/dist/core.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export interface BylineCore<TAdminStore = unknown> {
|
|
|
38
38
|
* during server bootstrap and before any admin UI renders.
|
|
39
39
|
*
|
|
40
40
|
* Consumed at runtime by `AdminAuth.assertAbility()` and at design
|
|
41
|
-
* time by the admin role-editor UI. See docs/
|
|
41
|
+
* time by the admin role-editor UI. See docs/06-auth-and-security/01-authn-authz.md.
|
|
42
42
|
*/
|
|
43
43
|
abilities: AbilityRegistry;
|
|
44
44
|
/** Convenience wrapper around `abilities.register()`. */
|
package/dist/core.js
CHANGED
|
@@ -103,7 +103,7 @@ export const initBylineCore = async (config, pinoLogger) => {
|
|
|
103
103
|
// and is a no-op (zero rows) once every document is stamped. Self-heals
|
|
104
104
|
// in-place upgrades without a manual maintenance step. The write path stamps
|
|
105
105
|
// new documents directly, so steady-state boots touch nothing. See
|
|
106
|
-
// docs/
|
|
106
|
+
// docs/07-internationalization/index.md.
|
|
107
107
|
if (typeof composed.db.backfillSourceLocales === 'function') {
|
|
108
108
|
const { rowsUpdated } = await composed.db.backfillSourceLocales();
|
|
109
109
|
if (rowsUpdated > 0) {
|
package/dist/lib/errors.d.ts
CHANGED
|
@@ -111,6 +111,6 @@ export declare const ERR_PATH_CONFLICT: (opts: BylineErrorOptions, errorConstruc
|
|
|
111
111
|
* `commands.audit` / `queries.audit` surfaces. A misconfiguration, not a
|
|
112
112
|
* user error: an auditability guarantee cannot be honoured non-atomically, so
|
|
113
113
|
* the write is refused loudly rather than recorded with a gap. See
|
|
114
|
-
* docs/
|
|
114
|
+
* docs/03-architecture/03-transactions.md and docs/06-auth-and-security/02-auditability.md.
|
|
115
115
|
*/
|
|
116
116
|
export declare const ERR_AUDIT_UNSUPPORTED: (opts: BylineErrorOptions, errorConstructor?: any) => BylineError;
|
package/dist/lib/errors.js
CHANGED
|
@@ -147,6 +147,6 @@ export const ERR_PATH_CONFLICT = createErrorType(ErrorCodes.PATH_CONFLICT, 'warn
|
|
|
147
147
|
* `commands.audit` / `queries.audit` surfaces. A misconfiguration, not a
|
|
148
148
|
* user error: an auditability guarantee cannot be honoured non-atomically, so
|
|
149
149
|
* the write is refused loudly rather than recorded with a gap. See
|
|
150
|
-
* docs/
|
|
150
|
+
* docs/03-architecture/03-transactions.md and docs/06-auth-and-security/02-auditability.md.
|
|
151
151
|
*/
|
|
152
152
|
export const ERR_AUDIT_UNSUPPORTED = createErrorType(ErrorCodes.AUDIT_UNSUPPORTED);
|
|
@@ -249,12 +249,19 @@ async function parseWhereInternal(where, definition, ctx, { isNested, inCombinat
|
|
|
249
249
|
const field = definition.fields.find((f) => f.name === key);
|
|
250
250
|
if (!field)
|
|
251
251
|
continue; // Unknown field — skip silently
|
|
252
|
-
// Relation field with a
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
//
|
|
252
|
+
// Relation field with a quantifier object (`$some` / `$every` / `$none`)
|
|
253
|
+
// or a plain-object sub-clause → cross-collection filter(s).
|
|
254
|
+
//
|
|
255
|
+
// - `{ gallery: { $some: { … } } }` — explicit quantifier(s) over the
|
|
256
|
+
// relation's target set; multiple quantifier keys AND together.
|
|
257
|
+
// - `{ category: { path: 'news' } }` — a plain object with no
|
|
258
|
+
// $-prefixed top-level keys is unambiguously a nested where,
|
|
259
|
+
// shorthand for `$some`.
|
|
260
|
+
//
|
|
261
|
+
// Anything else (bare value, operator object) stays in the ordinary
|
|
262
|
+
// field-filter path below and matches the relation's
|
|
256
263
|
// target_document_id column directly.
|
|
257
|
-
if (field.type === 'relation' && isPlainSubWhere(rawValue)) {
|
|
264
|
+
if (field.type === 'relation' && (isQuantifierObject(rawValue) || isPlainSubWhere(rawValue))) {
|
|
258
265
|
if (!ctx) {
|
|
259
266
|
// No way to resolve the target without a ParseContext. Direct
|
|
260
267
|
// callers of `parseWhere` (tests, tooling) can legitimately hit
|
|
@@ -273,21 +280,35 @@ async function parseWhereInternal(where, definition, ctx, { isNested, inCombinat
|
|
|
273
280
|
continue;
|
|
274
281
|
}
|
|
275
282
|
const targetCollectionId = await ctx.resolveCollectionId(targetPath);
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
nested
|
|
290
|
-
|
|
283
|
+
// Normalise both forms to a list of (quantifier, sub-where) pairs —
|
|
284
|
+
// the plain sub-where is shorthand for a single `$some`.
|
|
285
|
+
const quantified = isQuantifierObject(rawValue)
|
|
286
|
+
? Object.entries(rawValue).map(([qKey, sub]) => ({
|
|
287
|
+
quantifier: qKey.slice(1),
|
|
288
|
+
sub: sub,
|
|
289
|
+
}))
|
|
290
|
+
: [{ quantifier: 'some', sub: rawValue }];
|
|
291
|
+
for (const { quantifier, sub } of quantified) {
|
|
292
|
+
const nested = await parseWhereInternal(sub, targetDef, ctx, {
|
|
293
|
+
isNested: true,
|
|
294
|
+
inCombinator: false,
|
|
295
|
+
});
|
|
296
|
+
// Splice nested filters straight in. The nested parse runs with
|
|
297
|
+
// `isNested: true`, which promotes `status` / `path` into
|
|
298
|
+
// `DocumentColumnFilter` entries the adapter resolves against the
|
|
299
|
+
// target version's columns (`td${depth}.status` / `td${depth}.path`)
|
|
300
|
+
// via the inner relation scope. `query` is dropped one level up.
|
|
301
|
+
// `hasMany: false` / `quantifier: 'some'` are the adapter defaults —
|
|
302
|
+
// omit them so the plain single-relation filter shape is unchanged.
|
|
303
|
+
result.filters.push({
|
|
304
|
+
kind: 'relation',
|
|
305
|
+
fieldName: key,
|
|
306
|
+
targetCollectionId,
|
|
307
|
+
nested: nested.filters,
|
|
308
|
+
...(relation.hasMany === true ? { hasMany: true } : {}),
|
|
309
|
+
...(quantifier === 'some' ? {} : { quantifier }),
|
|
310
|
+
});
|
|
311
|
+
}
|
|
291
312
|
continue;
|
|
292
313
|
}
|
|
293
314
|
const storeInfo = fieldTypeToStore[field.type];
|
|
@@ -350,6 +371,36 @@ export function parseSort(sort, definition) {
|
|
|
350
371
|
* than a comparison value.
|
|
351
372
|
*/
|
|
352
373
|
const COMBINATOR_KEYS = new Set(['$and', '$or']);
|
|
374
|
+
/**
|
|
375
|
+
* Relation quantifier keys (`{ gallery: { $some: { … } } }`). Only
|
|
376
|
+
* meaningful on relation fields; each key's value is a nested sub-where
|
|
377
|
+
* evaluated against the relation's targets.
|
|
378
|
+
*/
|
|
379
|
+
const QUANTIFIER_KEYS = new Set(['$some', '$every', '$none']);
|
|
380
|
+
/**
|
|
381
|
+
* A quantifier object is a non-null, non-array object whose keys are *all*
|
|
382
|
+
* quantifier keys (`$some` / `$every` / `$none`), each carrying a nested
|
|
383
|
+
* sub-where object (empty allowed — `$none: {}` means "no targets at all").
|
|
384
|
+
* Mixing quantifier keys with field names or operators is not a recognised
|
|
385
|
+
* shape and falls through to the ordinary operator path (which rejects it).
|
|
386
|
+
*/
|
|
387
|
+
function isQuantifierObject(raw) {
|
|
388
|
+
if (raw === null || typeof raw !== 'object')
|
|
389
|
+
return false;
|
|
390
|
+
if (Array.isArray(raw))
|
|
391
|
+
return false;
|
|
392
|
+
const keys = Object.keys(raw);
|
|
393
|
+
if (keys.length === 0)
|
|
394
|
+
return false;
|
|
395
|
+
for (const k of keys) {
|
|
396
|
+
if (!QUANTIFIER_KEYS.has(k))
|
|
397
|
+
return false;
|
|
398
|
+
const v = raw[k];
|
|
399
|
+
if (v === null || typeof v !== 'object' || Array.isArray(v))
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
353
404
|
/**
|
|
354
405
|
* A "plain sub-where" is a non-null, non-array object whose top-level keys
|
|
355
406
|
* are field names or predicate-level combinators (`$and` / `$or`) — but
|
|
@@ -29,6 +29,14 @@ const testCollection = defineCollection({
|
|
|
29
29
|
targetCollection: 'test-categories',
|
|
30
30
|
optional: true,
|
|
31
31
|
},
|
|
32
|
+
{
|
|
33
|
+
name: 'tags',
|
|
34
|
+
type: 'relation',
|
|
35
|
+
label: 'Tags',
|
|
36
|
+
targetCollection: 'test-categories',
|
|
37
|
+
hasMany: true,
|
|
38
|
+
optional: true,
|
|
39
|
+
},
|
|
32
40
|
],
|
|
33
41
|
});
|
|
34
42
|
const categoriesCollection = defineCollection({
|
|
@@ -715,3 +723,88 @@ describe('mergePredicates', () => {
|
|
|
715
723
|
expect(parsed.filters[1]).toMatchObject({ kind: 'field', fieldName: 'title' });
|
|
716
724
|
});
|
|
717
725
|
});
|
|
726
|
+
// ---------------------------------------------------------------------------
|
|
727
|
+
// parseWhere — relation quantifiers ($some / $every / $none)
|
|
728
|
+
// ---------------------------------------------------------------------------
|
|
729
|
+
describe('parseWhere — relation quantifiers', () => {
|
|
730
|
+
it('emits a $some RelationFilter with hasMany flag for a hasMany field', async () => {
|
|
731
|
+
const result = await parseWhere({ tags: { $some: { slug: 'news' } } }, testCollection, ctx);
|
|
732
|
+
expect(result.filters).toHaveLength(1);
|
|
733
|
+
expect(result.filters[0]).toEqual({
|
|
734
|
+
kind: 'relation',
|
|
735
|
+
fieldName: 'tags',
|
|
736
|
+
targetCollectionId: 'id-test-categories',
|
|
737
|
+
hasMany: true,
|
|
738
|
+
nested: [
|
|
739
|
+
{
|
|
740
|
+
kind: 'field',
|
|
741
|
+
fieldName: 'slug',
|
|
742
|
+
storeType: 'text',
|
|
743
|
+
valueColumn: 'value',
|
|
744
|
+
operator: '$eq',
|
|
745
|
+
value: 'news',
|
|
746
|
+
},
|
|
747
|
+
],
|
|
748
|
+
});
|
|
749
|
+
});
|
|
750
|
+
it('treats a plain sub-where on a hasMany field as implicit $some', async () => {
|
|
751
|
+
const result = await parseWhere({ tags: { slug: 'news' } }, testCollection, ctx);
|
|
752
|
+
expect(result.filters).toHaveLength(1);
|
|
753
|
+
expect(result.filters[0]).toMatchObject({
|
|
754
|
+
kind: 'relation',
|
|
755
|
+
fieldName: 'tags',
|
|
756
|
+
hasMany: true,
|
|
757
|
+
});
|
|
758
|
+
// Default quantifier is omitted from the wire shape.
|
|
759
|
+
expect(result.filters[0]).not.toHaveProperty('quantifier');
|
|
760
|
+
});
|
|
761
|
+
it('emits an $every RelationFilter', async () => {
|
|
762
|
+
const result = await parseWhere({ tags: { $every: { status: 'published' } } }, testCollection, ctx);
|
|
763
|
+
expect(result.filters).toHaveLength(1);
|
|
764
|
+
expect(result.filters[0]).toEqual({
|
|
765
|
+
kind: 'relation',
|
|
766
|
+
fieldName: 'tags',
|
|
767
|
+
targetCollectionId: 'id-test-categories',
|
|
768
|
+
hasMany: true,
|
|
769
|
+
quantifier: 'every',
|
|
770
|
+
nested: [{ kind: 'docColumn', column: 'status', operator: '$eq', value: 'published' }],
|
|
771
|
+
});
|
|
772
|
+
});
|
|
773
|
+
it('emits a $none RelationFilter with empty nested for `$none: {}`', async () => {
|
|
774
|
+
const result = await parseWhere({ tags: { $none: {} } }, testCollection, ctx);
|
|
775
|
+
expect(result.filters).toHaveLength(1);
|
|
776
|
+
expect(result.filters[0]).toEqual({
|
|
777
|
+
kind: 'relation',
|
|
778
|
+
fieldName: 'tags',
|
|
779
|
+
targetCollectionId: 'id-test-categories',
|
|
780
|
+
hasMany: true,
|
|
781
|
+
quantifier: 'none',
|
|
782
|
+
nested: [],
|
|
783
|
+
});
|
|
784
|
+
});
|
|
785
|
+
it('ANDs multiple quantifier keys on one field into separate filters', async () => {
|
|
786
|
+
const result = await parseWhere({ tags: { $some: { slug: 'a' }, $none: { slug: 'b' } } }, testCollection, ctx);
|
|
787
|
+
expect(result.filters).toHaveLength(2);
|
|
788
|
+
expect(result.filters[0]).toMatchObject({ kind: 'relation', fieldName: 'tags', hasMany: true });
|
|
789
|
+
expect(result.filters[0]).not.toHaveProperty('quantifier');
|
|
790
|
+
expect(result.filters[1]).toMatchObject({
|
|
791
|
+
kind: 'relation',
|
|
792
|
+
fieldName: 'tags',
|
|
793
|
+
quantifier: 'none',
|
|
794
|
+
});
|
|
795
|
+
});
|
|
796
|
+
it('supports quantifiers on single (non-hasMany) relation fields without the hasMany flag', async () => {
|
|
797
|
+
const result = await parseWhere({ category: { $none: { slug: 'hidden' } } }, testCollection, ctx);
|
|
798
|
+
expect(result.filters).toHaveLength(1);
|
|
799
|
+
expect(result.filters[0]).toMatchObject({
|
|
800
|
+
kind: 'relation',
|
|
801
|
+
fieldName: 'category',
|
|
802
|
+
quantifier: 'none',
|
|
803
|
+
});
|
|
804
|
+
expect(result.filters[0]).not.toHaveProperty('hasMany');
|
|
805
|
+
});
|
|
806
|
+
it('skips quantifier objects when ctx is not provided', async () => {
|
|
807
|
+
const result = await parseWhere({ tags: { $some: { slug: 'news' } } }, testCollection);
|
|
808
|
+
expect(result.filters).toHaveLength(0);
|
|
809
|
+
});
|
|
810
|
+
});
|
|
@@ -208,7 +208,7 @@ export const createBaseSchema = (collection) => {
|
|
|
208
208
|
id: z.uuid(),
|
|
209
209
|
versionId: z.uuid().optional(),
|
|
210
210
|
path: z.string().optional(),
|
|
211
|
-
// The document's content source-locale anchor (see docs/
|
|
211
|
+
// The document's content source-locale anchor (see docs/07-internationalization/index.md).
|
|
212
212
|
// Carried through list/get responses so the admin can badge it; Zod would
|
|
213
213
|
// otherwise strip it as an undeclared key.
|
|
214
214
|
sourceLocale: z.string().optional(),
|
|
@@ -216,7 +216,7 @@ export const createBaseSchema = (collection) => {
|
|
|
216
216
|
hasPublishedVersion: z.boolean().optional(),
|
|
217
217
|
createdAt: z.iso.datetime(),
|
|
218
218
|
updatedAt: z.iso.datetime(),
|
|
219
|
-
// Version audit metadata — acting user + action (see docs/
|
|
219
|
+
// Version audit metadata — acting user + action (see docs/06-auth-and-security/02-auditability.md — Workstream 1).
|
|
220
220
|
// Declared so list/get/history responses carry them through the
|
|
221
221
|
// server-fn parse; Zod would otherwise strip them as undeclared keys.
|
|
222
222
|
createdBy: z.uuid().optional(),
|
|
@@ -72,4 +72,13 @@ export interface BuildSearchDocumentOptions {
|
|
|
72
72
|
* Assemble one type-enriched `SearchDocument` from a locale-resolved
|
|
73
73
|
* document and its collection's role-based `search` config.
|
|
74
74
|
*/
|
|
75
|
+
/**
|
|
76
|
+
* Resolve the zone set a collection indexes into, or `null` when the
|
|
77
|
+
* collection doesn't opt into search at all. A collection with a `search`
|
|
78
|
+
* config but no explicit `zones` belongs to a single implicit zone equal to
|
|
79
|
+
* its own path. Shared by the assembler (below) and the client's
|
|
80
|
+
* cross-collection `search({ zone })` membership check so the two can't
|
|
81
|
+
* drift.
|
|
82
|
+
*/
|
|
83
|
+
export declare function resolveSearchZones(definition: CollectionDefinition): string[] | null;
|
|
75
84
|
export declare function buildSearchDocument(doc: SearchSourceDocument, definition: CollectionDefinition, options?: BuildSearchDocumentOptions): SearchDocument;
|
|
@@ -40,12 +40,26 @@ import { resolveIdentityField } from './populate.js';
|
|
|
40
40
|
* Assemble one type-enriched `SearchDocument` from a locale-resolved
|
|
41
41
|
* document and its collection's role-based `search` config.
|
|
42
42
|
*/
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the zone set a collection indexes into, or `null` when the
|
|
45
|
+
* collection doesn't opt into search at all. A collection with a `search`
|
|
46
|
+
* config but no explicit `zones` belongs to a single implicit zone equal to
|
|
47
|
+
* its own path. Shared by the assembler (below) and the client's
|
|
48
|
+
* cross-collection `search({ zone })` membership check so the two can't
|
|
49
|
+
* drift.
|
|
50
|
+
*/
|
|
51
|
+
export function resolveSearchZones(definition) {
|
|
52
|
+
const search = definition.search;
|
|
53
|
+
if (search == null)
|
|
54
|
+
return null;
|
|
55
|
+
return search.zones != null && search.zones.length > 0 ? search.zones : [definition.path];
|
|
56
|
+
}
|
|
43
57
|
export function buildSearchDocument(doc, definition, options = {}) {
|
|
44
58
|
const locale = options.locale ?? doc.locale;
|
|
45
59
|
const search = definition.search ?? {};
|
|
46
60
|
const fieldsData = doc.fields ?? {};
|
|
47
61
|
const title = stringValue(resolveLocalized(fieldsData[resolveIdentityField(definition) ?? ''], locale)) ?? '';
|
|
48
|
-
const zones =
|
|
62
|
+
const zones = resolveSearchZones(definition) ?? [definition.path];
|
|
49
63
|
const fields = [];
|
|
50
64
|
// --- body: searchable text -------------------------------------------------
|
|
51
65
|
for (const decl of search.body ?? []) {
|
|
@@ -38,7 +38,7 @@ export interface AuditCapability {
|
|
|
38
38
|
* **both** `withTransaction` and `commands.audit`. Returns a non-null
|
|
39
39
|
* capability the caller composes; throws `ERR_AUDIT_UNSUPPORTED` otherwise,
|
|
40
40
|
* rather than silently skipping the audit row or running it non-atomically.
|
|
41
|
-
* See docs/
|
|
41
|
+
* See docs/03-architecture/03-transactions.md and docs/06-auth-and-security/02-auditability.md.
|
|
42
42
|
*/
|
|
43
43
|
export declare function requireAuditCapability(db: IDbAdapter): AuditCapability;
|
|
44
44
|
/** Order-insensitive equality for the advertised-locale set. */
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
9
|
* Audit-log write helpers for the document-grain lifecycle write-points
|
|
10
|
-
* (docs/
|
|
10
|
+
* (docs/06-auth-and-security/02-auditability.md — Workstream 2). The audit log records the changes the
|
|
11
11
|
* immutable version stream does NOT capture an actor for: non-versioned
|
|
12
12
|
* system-field writes (path, available-locales), in-place status transitions,
|
|
13
13
|
* and deletions. Each such mutation and its audit row commit atomically inside
|
|
14
14
|
* `withTransaction` — a silently-unwritten audit row is the one unacceptable
|
|
15
|
-
* outcome (see docs/
|
|
15
|
+
* outcome (see docs/03-architecture/03-transactions.md).
|
|
16
16
|
*/
|
|
17
17
|
import { ERR_AUDIT_UNSUPPORTED } from '../../lib/errors.js';
|
|
18
18
|
import { actorId } from './internals.js';
|
|
@@ -42,7 +42,7 @@ export function auditActor(ctx) {
|
|
|
42
42
|
* **both** `withTransaction` and `commands.audit`. Returns a non-null
|
|
43
43
|
* capability the caller composes; throws `ERR_AUDIT_UNSUPPORTED` otherwise,
|
|
44
44
|
* rather than silently skipping the audit row or running it non-atomically.
|
|
45
|
-
* See docs/
|
|
45
|
+
* See docs/03-architecture/03-transactions.md and docs/06-auth-and-security/02-auditability.md.
|
|
46
46
|
*/
|
|
47
47
|
export function requireAuditCapability(db) {
|
|
48
48
|
const withTransaction = db.withTransaction;
|
|
@@ -73,7 +73,7 @@ export interface DocumentLifecycleContext {
|
|
|
73
73
|
* — `assertActorCanPerform` runs at every lifecycle entry and rejects
|
|
74
74
|
* a missing context.
|
|
75
75
|
*
|
|
76
|
-
* See docs/
|
|
76
|
+
* See docs/06-auth-and-security/01-authn-authz.md.
|
|
77
77
|
*/
|
|
78
78
|
requestContext?: RequestContext;
|
|
79
79
|
}
|
|
@@ -39,7 +39,7 @@ export declare function createDocument(ctx: DocumentLifecycleContext, params: {
|
|
|
39
39
|
* sidebar widget). Document-grain and sticky like `path`: passed straight
|
|
40
40
|
* to the storage primitive, which replaces the document's rows wholesale.
|
|
41
41
|
* `undefined` writes nothing (a new document starts with an empty set —
|
|
42
|
-
* the safe opt-in default); `[]` clears it. See docs/
|
|
42
|
+
* the safe opt-in default); `[]` clears it. See docs/07-internationalization/index.md.
|
|
43
43
|
*/
|
|
44
44
|
availableLocales?: string[];
|
|
45
45
|
}): Promise<CreateDocumentResult>;
|
|
@@ -87,7 +87,7 @@ export async function createDocument(ctx, params) {
|
|
|
87
87
|
// command directly — no `update` re-assertion, no separate tree event
|
|
88
88
|
// (afterCreate covers invalidation). Post-version and best-effort: a
|
|
89
89
|
// failure leaves the document created-but-unplaced and is logged, not
|
|
90
|
-
// thrown. See docs/
|
|
90
|
+
// thrown. See docs/04-collections/03-document-trees.md.
|
|
91
91
|
if (definition.tree === true) {
|
|
92
92
|
try {
|
|
93
93
|
await appendTreeRoot(ctx, documentId);
|
|
@@ -96,9 +96,9 @@ export async function deleteDocument(ctx, params) {
|
|
|
96
96
|
// 3. Soft-delete all versions, atomically with the audit record. A
|
|
97
97
|
// whole-document delete mints no new version, so the version stream
|
|
98
98
|
// never records it — the audit log is the only place a deletion is
|
|
99
|
-
// accountable (docs/
|
|
99
|
+
// accountable (docs/06-auth-and-security/02-auditability.md). Storage-file cleanup (step 4) is a
|
|
100
100
|
// DB↔external side-effect and stays OUTSIDE the transaction — it is
|
|
101
|
-
// post-commit, best-effort compensation (docs/
|
|
101
|
+
// post-commit, best-effort compensation (docs/03-architecture/03-transactions.md).
|
|
102
102
|
const audit = requireAuditCapability(db);
|
|
103
103
|
const actor = auditActor(ctx);
|
|
104
104
|
let deletedVersionCount = 0;
|
|
@@ -132,7 +132,7 @@ export async function deleteDocument(ctx, params) {
|
|
|
132
132
|
// structural-change invalidation event. Post-commit and best-effort,
|
|
133
133
|
// like file cleanup: a failure here leaves the soft-delete intact
|
|
134
134
|
// (status-at-edge already hides the deleted node's subtree from
|
|
135
|
-
// reads) and is logged rather than thrown. See docs/
|
|
135
|
+
// reads) and is logged rather than thrown. See docs/04-collections/03-document-trees.md.
|
|
136
136
|
if (definition.tree === true) {
|
|
137
137
|
try {
|
|
138
138
|
await promoteChildrenAndRemove(ctx, { documentId: params.documentId });
|
|
@@ -28,7 +28,7 @@ import type { DocumentLifecycleContext } from './context.js';
|
|
|
28
28
|
* (the seeds/migrations escape hatch) — both yield `undefined` → NULL
|
|
29
29
|
* `created_by`, which the history strip renders as "unknown". Real
|
|
30
30
|
* `AdminAuth` / `UserAuth` actors always carry UUID ids, so their attribution
|
|
31
|
-
* is unaffected. See docs/
|
|
31
|
+
* is unaffected. See docs/06-auth-and-security/02-auditability.md — Workstream 1.
|
|
32
32
|
*/
|
|
33
33
|
export declare function actorId(ctx: DocumentLifecycleContext): string | undefined;
|
|
34
34
|
/**
|
|
@@ -76,7 +76,7 @@ export declare function appendTreeRoot(ctx: DocumentLifecycleContext, documentId
|
|
|
76
76
|
*
|
|
77
77
|
* No-op for non-tree collections. Best-effort and post-version: a failure leaves
|
|
78
78
|
* the document saved-but-unplaced and is logged, never thrown. See
|
|
79
|
-
* docs/
|
|
79
|
+
* docs/04-collections/03-document-trees.md.
|
|
80
80
|
*/
|
|
81
81
|
export declare function selfHealTreePlacement(ctx: DocumentLifecycleContext, documentId: string): Promise<void>;
|
|
82
82
|
/** Extract `id` from the document object returned by `createDocumentVersion`. */
|
|
@@ -35,7 +35,7 @@ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
|
|
|
35
35
|
* (the seeds/migrations escape hatch) — both yield `undefined` → NULL
|
|
36
36
|
* `created_by`, which the history strip renders as "unknown". Real
|
|
37
37
|
* `AdminAuth` / `UserAuth` actors always carry UUID ids, so their attribution
|
|
38
|
-
* is unaffected. See docs/
|
|
38
|
+
* is unaffected. See docs/06-auth-and-security/02-auditability.md — Workstream 1.
|
|
39
39
|
*/
|
|
40
40
|
export function actorId(ctx) {
|
|
41
41
|
const id = ctx.requestContext?.actor?.id;
|
|
@@ -131,7 +131,7 @@ export async function appendTreeRoot(ctx, documentId) {
|
|
|
131
131
|
*
|
|
132
132
|
* No-op for non-tree collections. Best-effort and post-version: a failure leaves
|
|
133
133
|
* the document saved-but-unplaced and is logged, never thrown. See
|
|
134
|
-
* docs/
|
|
134
|
+
* docs/04-collections/03-document-trees.md.
|
|
135
135
|
*/
|
|
136
136
|
export async function selfHealTreePlacement(ctx, documentId) {
|
|
137
137
|
if (ctx.definition.tree !== true)
|
|
@@ -210,7 +210,7 @@ export function resolvePathForUpdate(args) {
|
|
|
210
210
|
// skip the write (existing path row stays as-is — sticky). The path row
|
|
211
211
|
// lives under the document's source_locale (its anchor), not the mutable
|
|
212
212
|
// global default — so this stays correct after the global default is
|
|
213
|
-
// switched. See docs/
|
|
213
|
+
// switched. See docs/07-internationalization/index.md.
|
|
214
214
|
return explicitPath ?? undefined;
|
|
215
215
|
}
|
|
216
216
|
// Non-source-locale (translation) write: reject any path change with a warn
|
|
@@ -89,7 +89,7 @@ export async function changeDocumentStatus(ctx, params) {
|
|
|
89
89
|
// 4–5. Mutate status in-place + auto-archive, atomically with the audit
|
|
90
90
|
// record. Status mutates the version row rather than minting a new
|
|
91
91
|
// version, so the version stream never captures *who* changed it —
|
|
92
|
-
// the audit log is its only accountability home (docs/
|
|
92
|
+
// the audit log is its only accountability home (docs/06-auth-and-security/02-auditability.md).
|
|
93
93
|
const audit = requireAuditCapability(db);
|
|
94
94
|
const actor = auditActor(ctx);
|
|
95
95
|
await audit.withTransaction(async () => {
|
|
@@ -25,7 +25,7 @@ export interface UpdateDocumentSystemFieldsResult {
|
|
|
25
25
|
* version. This service backs the admin path / available-locales widgets'
|
|
26
26
|
* direct-write Save (the `direct-write` and `both` dirty-reason cases). The
|
|
27
27
|
* public *advertised* set remains the intersection of `availableLocales` with
|
|
28
|
-
* the resolved version's completeness ledger. See docs/
|
|
28
|
+
* the resolved version's completeness ledger. See docs/07-internationalization/index.md.
|
|
29
29
|
*
|
|
30
30
|
* Flow:
|
|
31
31
|
* 1. `assertActorCanPerform('update')` — same auth gate as content writes.
|
|
@@ -40,7 +40,7 @@ export interface UpdateDocumentSystemFieldsResult {
|
|
|
40
40
|
* No content hooks fire — these are not content writes. Accountability for
|
|
41
41
|
* these mutations is the document-grain audit log: each field that actually
|
|
42
42
|
* changes records a `document.path.changed` / `document.locales.changed` row
|
|
43
|
-
* atomically with the write (docs/
|
|
43
|
+
* atomically with the write (docs/06-auth-and-security/02-auditability.md — Workstream 2).
|
|
44
44
|
*
|
|
45
45
|
* @throws {BylineError} ERR_NOT_FOUND if the document does not exist.
|
|
46
46
|
* @throws {BylineError} ERR_PATH_CONFLICT if the path is already in use.
|
|
@@ -22,7 +22,7 @@ import { resolvePathForUpdate, rethrowPathConflict } from './internals.js';
|
|
|
22
22
|
* version. This service backs the admin path / available-locales widgets'
|
|
23
23
|
* direct-write Save (the `direct-write` and `both` dirty-reason cases). The
|
|
24
24
|
* public *advertised* set remains the intersection of `availableLocales` with
|
|
25
|
-
* the resolved version's completeness ledger. See docs/
|
|
25
|
+
* the resolved version's completeness ledger. See docs/07-internationalization/index.md.
|
|
26
26
|
*
|
|
27
27
|
* Flow:
|
|
28
28
|
* 1. `assertActorCanPerform('update')` — same auth gate as content writes.
|
|
@@ -37,7 +37,7 @@ import { resolvePathForUpdate, rethrowPathConflict } from './internals.js';
|
|
|
37
37
|
* No content hooks fire — these are not content writes. Accountability for
|
|
38
38
|
* these mutations is the document-grain audit log: each field that actually
|
|
39
39
|
* changes records a `document.path.changed` / `document.locales.changed` row
|
|
40
|
-
* atomically with the write (docs/
|
|
40
|
+
* atomically with the write (docs/06-auth-and-security/02-auditability.md — Workstream 2).
|
|
41
41
|
*
|
|
42
42
|
* @throws {BylineError} ERR_NOT_FOUND if the document does not exist.
|
|
43
43
|
* @throws {BylineError} ERR_PATH_CONFLICT if the path is already in use.
|
|
@@ -78,7 +78,7 @@ export async function updateDocumentSystemFields(ctx, params) {
|
|
|
78
78
|
// Both document-grain writes and their audit rows commit atomically.
|
|
79
79
|
// These fields are non-versioned, so the version stream never records
|
|
80
80
|
// them — the audit log is their only accountability home. One audit row
|
|
81
|
-
// per field that actually changed (docs/
|
|
81
|
+
// per field that actually changed (docs/06-auth-and-security/02-auditability.md).
|
|
82
82
|
const currentPath = originalData.path;
|
|
83
83
|
const currentLocales = originalData.availableLocales ?? [];
|
|
84
84
|
const availableLocalesWritten = params.availableLocales !== undefined;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
9
|
* Document-tree lifecycle service — the unversioned structural mutations for
|
|
10
|
-
* `tree: true` collections (docs/
|
|
10
|
+
* `tree: true` collections (docs/04-collections/03-document-trees.md). Wraps the storage adapter's
|
|
11
11
|
* tree commands so that, like the versioned lifecycle services, they enforce the
|
|
12
12
|
* actor ability and fire a collection hook. Tree writes mint no document version
|
|
13
13
|
* and touch no user fields, so the `afterTreeChange` hook is the *only*
|
|
@@ -43,7 +43,7 @@ export declare function updateDocument(ctx: DocumentLifecycleContext, params: {
|
|
|
43
43
|
* The editorial advertised-locale set. `undefined` leaves the existing
|
|
44
44
|
* set untouched (sticky — document-grain, like `path`); an explicit array
|
|
45
45
|
* (empty included) replaces it wholesale. Driven by the admin
|
|
46
|
-
* available-locales sidebar widget. See docs/
|
|
46
|
+
* available-locales sidebar widget. See docs/07-internationalization/index.md.
|
|
47
47
|
*/
|
|
48
48
|
availableLocales?: string[];
|
|
49
49
|
}): Promise<UpdateDocumentResult>;
|
|
@@ -78,7 +78,7 @@ export declare function updateDocumentWithPatches(ctx: DocumentLifecycleContext,
|
|
|
78
78
|
* The editorial advertised-locale set (typically supplied alongside
|
|
79
79
|
* patches when the admin available-locales widget has been edited).
|
|
80
80
|
* `undefined` leaves the existing set untouched (sticky); an explicit
|
|
81
|
-
* array replaces it wholesale. See docs/
|
|
81
|
+
* array replaces it wholesale. See docs/07-internationalization/index.md.
|
|
82
82
|
*/
|
|
83
83
|
availableLocales?: string[];
|
|
84
84
|
}): Promise<UpdateDocumentWithPatchesResult>;
|
|
@@ -36,7 +36,7 @@ function createMockDb() {
|
|
|
36
36
|
const getDocumentById = vi.fn().mockResolvedValue(null);
|
|
37
37
|
const getCurrentVersionMetadata = vi.fn().mockResolvedValue(null);
|
|
38
38
|
const getCurrentPath = vi.fn().mockResolvedValue('current-path');
|
|
39
|
-
// Audit capability (docs/
|
|
39
|
+
// Audit capability (docs/06-auth-and-security/02-auditability.md — W2). `withTransaction` is a passthrough
|
|
40
40
|
// in unit tests (runs the unit of work immediately, no real tx); `append`
|
|
41
41
|
// records the calls so write-point tests can assert the audit rows emitted.
|
|
42
42
|
const auditAppend = vi.fn().mockResolvedValue({ id: 'audit-1' });
|
|
@@ -165,7 +165,7 @@ describe('Document lifecycle service', () => {
|
|
|
165
165
|
data: { title: 'Hello' },
|
|
166
166
|
locale: 'en',
|
|
167
167
|
});
|
|
168
|
-
// Audit contract (docs/
|
|
168
|
+
// Audit contract (docs/06-auth-and-security/02-auditability.md — W1): every version row
|
|
169
169
|
// records the actor that created it.
|
|
170
170
|
expect(createDocumentVersion.mock.calls[0]?.[0].createdBy).toBe(TEST_ACTOR_ID);
|
|
171
171
|
});
|
|
@@ -703,7 +703,7 @@ describe('Document lifecycle service', () => {
|
|
|
703
703
|
getCurrentVersionMetadata.mockResolvedValue({ ...metadataRow });
|
|
704
704
|
const ctx = buildCtx(db);
|
|
705
705
|
await changeDocumentStatus(ctx, { documentId: 'doc-1', nextStatus: 'published' });
|
|
706
|
-
// The mutation + audit row run inside one withTransaction (docs/
|
|
706
|
+
// The mutation + audit row run inside one withTransaction (docs/06-auth-and-security/02-auditability.md).
|
|
707
707
|
expect(withTransaction).toHaveBeenCalledOnce();
|
|
708
708
|
expect(auditAppend).toHaveBeenCalledWith(expect.objectContaining({
|
|
709
709
|
documentId: 'doc-1',
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
/**
|
|
9
9
|
* `documentToMarkdown` — the document-grain markdown assembler for the
|
|
10
10
|
* agent-readable export surface (`.md` routes, `llms.txt`). See
|
|
11
|
-
*
|
|
11
|
+
* TODO-INTERNAL.md → "Markdown export".
|
|
12
12
|
*
|
|
13
13
|
* A page is a composite (text, richtext, blocks, arrays, relations); this
|
|
14
14
|
* walks the collection's schema and the locale-resolved field data in
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
/**
|
|
9
9
|
* `documentToMarkdown` — the document-grain markdown assembler for the
|
|
10
10
|
* agent-readable export surface (`.md` routes, `llms.txt`). See
|
|
11
|
-
*
|
|
11
|
+
* TODO-INTERNAL.md → "Markdown export".
|
|
12
12
|
*
|
|
13
13
|
* A page is a composite (text, richtext, blocks, arrays, relations); this
|
|
14
14
|
* walks the collection's schema and the locale-resolved field data in
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Contract tests for the document-grain markdown assembler. The expected
|
|
10
10
|
* strings ARE the format contract — agents build on this shape, so a
|
|
11
11
|
* change here is a consumer-visible format change and should be
|
|
12
|
-
* deliberate (
|
|
12
|
+
* deliberate (TODO-INTERNAL.md → "The output is a contract surface").
|
|
13
13
|
*/
|
|
14
14
|
import { describe, expect, it } from 'vitest';
|
|
15
15
|
import { documentToMarkdown } from './document-to-markdown.js';
|
package/dist/services/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { ERR_CONFLICT, ERR_INVALID_TRANSITION, ERR_NOT_FOUND, ERR_PATCH_FAILED, ERR_READ_BUDGET_EXCEEDED, ERR_VALIDATION, } from '../lib/errors.js';
|
|
2
2
|
export { normaliseDateFields } from '../utils/normalise-dates.js';
|
|
3
3
|
export { type AssignCounterValuesInput, assignCounterValues } from './assign-counter-values.js';
|
|
4
|
-
export { type BuildSearchDocumentOptions, buildSearchDocument, type SearchSourceDocument, } from './build-search-document.js';
|
|
4
|
+
export { type BuildSearchDocumentOptions, buildSearchDocument, resolveSearchZones, type SearchSourceDocument, } from './build-search-document.js';
|
|
5
5
|
export { type CollectionRecord, type EnsureCollectionsInput, ensureCollections, } from './collection-bootstrap.js';
|
|
6
6
|
export { type DiscoverCounterGroupsInput, discoverCounterGroups, } from './discover-counter-groups.js';
|
|
7
7
|
export * from './document-lifecycle/index.js';
|
package/dist/services/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export { ERR_CONFLICT, ERR_INVALID_TRANSITION, ERR_NOT_FOUND, ERR_PATCH_FAILED, ERR_READ_BUDGET_EXCEEDED, ERR_VALIDATION, } from '../lib/errors.js';
|
|
3
3
|
export { normaliseDateFields } from '../utils/normalise-dates.js';
|
|
4
4
|
export { assignCounterValues } from './assign-counter-values.js';
|
|
5
|
-
export { buildSearchDocument, } from './build-search-document.js';
|
|
5
|
+
export { buildSearchDocument, resolveSearchZones, } from './build-search-document.js';
|
|
6
6
|
export { ensureCollections, } from './collection-bootstrap.js';
|
|
7
7
|
export { discoverCounterGroups, } from './discover-counter-groups.js';
|
|
8
8
|
export * from './document-lifecycle/index.js';
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* documents. The guard is in place from day one so that the hook work
|
|
26
26
|
* in Phase 4+ cannot reintroduce the problem.
|
|
27
27
|
*
|
|
28
|
-
* See docs/
|
|
28
|
+
* See docs/04-collections/02-relationships.md for the full design rationale.
|
|
29
29
|
*
|
|
30
30
|
* ---------------------------------------------------------------------
|
|
31
31
|
* DSL summary
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@byline/core",
|
|
3
3
|
"private": false,
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.16.1",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20.9.0"
|
|
8
8
|
},
|
|
@@ -73,19 +73,17 @@
|
|
|
73
73
|
],
|
|
74
74
|
"dependencies": {
|
|
75
75
|
"dotenv": "^17.4.2",
|
|
76
|
-
"jose": "^6.2.3",
|
|
77
|
-
"npm-run-all": "^4.1.5",
|
|
78
76
|
"pino": "^10.3.1",
|
|
79
77
|
"sharp": "^0.34.5",
|
|
80
|
-
"uuid": "^14.0.0",
|
|
81
78
|
"zod": "^4.4.3",
|
|
82
|
-
"@byline/auth": "3.
|
|
79
|
+
"@byline/auth": "3.16.1"
|
|
83
80
|
},
|
|
84
81
|
"devDependencies": {
|
|
85
82
|
"@biomejs/biome": "2.4.15",
|
|
86
83
|
"@types/node": "^25.9.1",
|
|
87
84
|
"chokidar": "^5.0.0",
|
|
88
85
|
"chokidar-cli": "^3.0.0",
|
|
86
|
+
"npm-run-all": "^4.1.5",
|
|
89
87
|
"tsc-alias": "^1.8.17",
|
|
90
88
|
"tsx": "^4.22.3",
|
|
91
89
|
"typescript": "6.0.3",
|