@byline/core 3.20.4 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/@types/collection-types.d.ts +72 -31
  2. package/dist/@types/db-types.d.ts +93 -47
  3. package/dist/@types/field-data-types.d.ts +8 -10
  4. package/dist/@types/field-data-types.js +1 -1
  5. package/dist/@types/field-data-types.test.node.d.ts +1 -0
  6. package/dist/@types/field-data-types.test.node.js +70 -0
  7. package/dist/@types/field-types.d.ts +25 -1
  8. package/dist/@types/index.d.ts +1 -0
  9. package/dist/@types/index.js +1 -0
  10. package/dist/@types/query-predicate.d.ts +3 -3
  11. package/dist/@types/relation-types.d.ts +38 -0
  12. package/dist/@types/relation-types.js +8 -0
  13. package/dist/@types/search-types.d.ts +3 -4
  14. package/dist/@types/site-config.d.ts +52 -13
  15. package/dist/auth/apply-before-read.d.ts +23 -11
  16. package/dist/auth/apply-before-read.js +139 -33
  17. package/dist/auth/apply-before-read.test.node.js +241 -3
  18. package/dist/auth/index.d.ts +1 -1
  19. package/dist/auth/index.js +1 -1
  20. package/dist/auth/read-context-scope.d.ts +20 -0
  21. package/dist/auth/read-context-scope.js +48 -0
  22. package/dist/codegen/fixtures/all-fields.d.ts +342 -0
  23. package/dist/codegen/fixtures/all-fields.expected.d.ts +120 -0
  24. package/dist/codegen/fixtures/all-fields.expected.js +1 -0
  25. package/dist/codegen/fixtures/all-fields.js +94 -0
  26. package/dist/codegen/index.d.ts +16 -0
  27. package/dist/codegen/index.js +431 -0
  28. package/dist/codegen/index.test.node.d.ts +1 -0
  29. package/dist/codegen/index.test.node.js +230 -0
  30. package/dist/config/attach-hooks.d.ts +25 -0
  31. package/dist/config/attach-hooks.js +130 -0
  32. package/dist/config/attach-hooks.test.node.d.ts +1 -0
  33. package/dist/config/attach-hooks.test.node.js +173 -0
  34. package/dist/config/config-hooks.test.node.d.ts +1 -0
  35. package/dist/config/config-hooks.test.node.js +56 -0
  36. package/dist/config/config.d.ts +9 -5
  37. package/dist/config/config.js +20 -2
  38. package/dist/config/routes.d.ts +5 -5
  39. package/dist/config/routes.js +42 -9
  40. package/dist/config/routes.test.node.d.ts +1 -0
  41. package/dist/config/routes.test.node.js +152 -0
  42. package/dist/core.d.ts +3 -3
  43. package/dist/core.js +20 -14
  44. package/dist/core.test.node.d.ts +1 -0
  45. package/dist/core.test.node.js +28 -0
  46. package/dist/index.d.ts +4 -3
  47. package/dist/index.js +4 -3
  48. package/dist/lib/errors.d.ts +13 -0
  49. package/dist/lib/errors.js +14 -0
  50. package/dist/query/parse-where.d.ts +10 -1
  51. package/dist/query/parse-where.js +146 -2
  52. package/dist/query/parse-where.test.node.js +60 -1
  53. package/dist/schemas/zod/builder.js +7 -3
  54. package/dist/schemas/zod/builder.test.node.d.ts +1 -0
  55. package/dist/schemas/zod/builder.test.node.js +49 -0
  56. package/dist/services/collection-bootstrap.d.ts +1 -1
  57. package/dist/services/collection-bootstrap.test.node.js +30 -59
  58. package/dist/services/discover-counter-groups.d.ts +1 -1
  59. package/dist/services/discover-counter-groups.test.node.js +23 -0
  60. package/dist/services/document-lifecycle/audit.d.ts +22 -1
  61. package/dist/services/document-lifecycle/audit.js +32 -1
  62. package/dist/services/document-lifecycle/create.d.ts +2 -2
  63. package/dist/services/document-lifecycle/create.js +18 -8
  64. package/dist/services/document-lifecycle/delete.d.ts +17 -1
  65. package/dist/services/document-lifecycle/delete.js +91 -26
  66. package/dist/services/document-lifecycle/index.d.ts +1 -1
  67. package/dist/services/document-lifecycle/internals.d.ts +0 -20
  68. package/dist/services/document-lifecycle/internals.js +22 -46
  69. package/dist/services/document-lifecycle/status.d.ts +1 -1
  70. package/dist/services/document-lifecycle/status.js +20 -3
  71. package/dist/services/document-lifecycle/system-fields.d.ts +19 -6
  72. package/dist/services/document-lifecycle/system-fields.js +112 -74
  73. package/dist/services/document-lifecycle/tree.d.ts +22 -24
  74. package/dist/services/document-lifecycle/tree.js +244 -123
  75. package/dist/services/document-lifecycle/tree.test.node.d.ts +8 -0
  76. package/dist/services/document-lifecycle/tree.test.node.js +663 -0
  77. package/dist/services/document-lifecycle/update.d.ts +4 -4
  78. package/dist/services/document-lifecycle/update.js +11 -5
  79. package/dist/services/document-lifecycle.test.node.js +426 -16
  80. package/dist/services/document-read.d.ts +14 -6
  81. package/dist/services/document-read.js +47 -9
  82. package/dist/services/field-upload.test.node.js +70 -0
  83. package/dist/services/index.d.ts +3 -2
  84. package/dist/services/index.js +3 -2
  85. package/dist/services/normalize-numeric-fields.d.ts +23 -0
  86. package/dist/services/normalize-numeric-fields.js +89 -0
  87. package/dist/services/normalize-numeric-fields.test.node.d.ts +8 -0
  88. package/dist/services/normalize-numeric-fields.test.node.js +99 -0
  89. package/dist/services/populate.d.ts +9 -23
  90. package/dist/services/populate.js +180 -28
  91. package/dist/services/populate.test.node.js +59 -0
  92. package/dist/services/richtext-embed.d.ts +39 -2
  93. package/dist/services/richtext-embed.js +4 -34
  94. package/dist/services/richtext-embed.test.node.js +15 -0
  95. package/dist/services/richtext-populate.d.ts +21 -3
  96. package/dist/services/richtext-populate.js +160 -19
  97. package/dist/services/richtext-populate.test.node.js +523 -2
  98. package/dist/services/validate-search-config.d.ts +1 -1
  99. package/dist/storage/collection-fingerprint.js +6 -0
  100. package/dist/storage/collection-fingerprint.test.node.js +21 -0
  101. package/dist/utils/root-relative-redirect.d.ts +6 -0
  102. package/dist/utils/root-relative-redirect.js +37 -0
  103. package/package.json +7 -2
@@ -5,25 +5,12 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- /**
9
- * Richtext populate service — walks a reconstructed document, finds every
10
- * rich-text leaf (including those nested inside `group` / `array` /
11
- * `blocks` structures), gates each leaf by its `populateRelationsOnRead`
12
- * flag, and dispatches to the registered richtext populate adapter.
13
- *
14
- * Slots into the read pipeline alongside `populateDocuments`:
15
- *
16
- * findDocuments → reconstruct → populateDocuments → populateRichTextFields → afterRead
17
- *
18
- * The same `ReadContext` flows through both populate phases, so dedup /
19
- * cycle protection / read-budget enforcement covers rich-text fan-out
20
- * automatically and any nested reads the adapter performs.
21
- *
22
- * The adapter is invoked once per leaf rather than once per document so
23
- * each call has a precise `fieldPath` for error messages and so future
24
- * adapters can implement per-leaf caching if needed.
25
- */
26
8
  import { isArrayField, isBlocksField, isGroupField, } from '../@types/field-types.js';
9
+ import { bindReadContextAuthority, compileBeforeReadFilters } from '../auth/apply-before-read.js';
10
+ import { assertActorCanPerform } from '../auth/assert-actor-can-perform.js';
11
+ import { resolveReadContextRoot } from '../auth/read-context-scope.js';
12
+ import { ERR_READ_BUDGET_EXCEEDED, ERR_VALIDATION } from '../lib/errors.js';
13
+ import { applyAfterRead } from './document-read.js';
27
14
  import { walkFieldTree } from './walk-field-tree.js';
28
15
  /**
29
16
  * Walk a field set and a matching reconstructed data tree in lockstep,
@@ -66,7 +53,7 @@ export function resolvePopulateOnRead(field) {
66
53
  * is `true`. Mutates document `fields` in place.
67
54
  */
68
55
  export async function populateRichTextFields(options) {
69
- const { fields, collectionPath, documents, populate, readContext } = options;
56
+ const { fields, collectionPath, documents, populate, readContext, requestContext, readMode, readDocuments, } = options;
70
57
  for (const doc of documents) {
71
58
  const docFields = (doc.fields ?? {});
72
59
  for (const leaf of collectRichTextLeaves(fields, docFields)) {
@@ -77,10 +64,164 @@ export async function populateRichTextFields(options) {
77
64
  fieldPath: leaf.fieldPath,
78
65
  collectionPath,
79
66
  readContext,
67
+ requestContext,
68
+ readMode,
69
+ readDocuments,
80
70
  });
81
71
  }
82
72
  }
83
73
  }
74
+ /** Build the secure batch reader exposed to editor-agnostic adapters. */
75
+ export function createRichTextDocumentReader(options) {
76
+ const { db, collections, requestContext, readContext, readMode, locale, bypassBeforeRead, richTextPopulate, } = options;
77
+ const securityDomain = options.securityDomain ?? {};
78
+ const state = getRichTextReaderState(readContext);
79
+ const read = async ({ collectionPath, documentIds, fields }) => {
80
+ bindReadContextAuthority(readContext, requestContext);
81
+ if (documentIds.length === 0)
82
+ return [];
83
+ const definition = collections.find((candidate) => candidate.path === collectionPath);
84
+ if (definition == null) {
85
+ throw ERR_VALIDATION({
86
+ message: `richtext target collection '${collectionPath}' is not registered`,
87
+ details: { collectionPath },
88
+ });
89
+ }
90
+ assertActorCanPerform(requestContext, collectionPath, 'read');
91
+ let filters;
92
+ if (!bypassBeforeRead) {
93
+ filters = await compileBeforeReadFilters({
94
+ definition,
95
+ requestContext,
96
+ readContext,
97
+ securityDomain,
98
+ parseContext: {
99
+ collections,
100
+ resolveCollectionId: async (path) => {
101
+ const row = await db.queries.collections.getCollectionByPath(path);
102
+ return row?.id ?? '';
103
+ },
104
+ },
105
+ });
106
+ }
107
+ const collection = await db.queries.collections.getCollectionByPath(collectionPath);
108
+ if (collection?.id == null) {
109
+ throw ERR_VALIDATION({
110
+ message: `richtext target collection '${collectionPath}' is not available`,
111
+ details: { collectionPath },
112
+ });
113
+ }
114
+ const collectionId = collection.id;
115
+ const projection = fields == null ? '*' : [...fields].sort().join(',');
116
+ const resultById = new Map();
117
+ const idsToFetch = [];
118
+ for (const documentId of new Set(documentIds)) {
119
+ const key = richTextMaterializationKey(collectionId, documentId, requestContext.requestId, locale, readMode, projection);
120
+ if (state.cache.has(key)) {
121
+ const cached = state.cache.get(key);
122
+ if (cached != null)
123
+ resultById.set(documentId, cached);
124
+ }
125
+ else if (!state.active.has(`${collectionId}:${documentId}`)) {
126
+ idsToFetch.push(documentId);
127
+ }
128
+ }
129
+ if (idsToFetch.length > 0) {
130
+ const fetched = (await db.queries.documents.getDocumentsByDocumentIds({
131
+ collection_id: collectionId,
132
+ document_ids: idsToFetch,
133
+ fields,
134
+ readMode,
135
+ locale,
136
+ filters: filters && filters.length > 0 ? filters : undefined,
137
+ }));
138
+ const fetchedById = new Map(fetched
139
+ .filter((doc) => typeof doc.document_id === 'string')
140
+ .map((doc) => [doc.document_id, doc]));
141
+ for (const documentId of idsToFetch) {
142
+ const materializationKey = richTextMaterializationKey(collectionId, documentId, requestContext.requestId, locale, readMode, projection);
143
+ // Earlier items in this batch may recursively populate a later target.
144
+ // Honour that completed/active state rather than reprocessing the stale
145
+ // raw row captured by the outer batch and overwriting its redacted cache.
146
+ if (state.cache.has(materializationKey)) {
147
+ const cached = state.cache.get(materializationKey);
148
+ if (cached != null)
149
+ resultById.set(documentId, cached);
150
+ continue;
151
+ }
152
+ const documentKey = `${collectionId}:${documentId}`;
153
+ if (state.active.has(documentKey))
154
+ continue;
155
+ const doc = fetchedById.get(documentId);
156
+ if (doc == null) {
157
+ state.cache.set(materializationKey, null);
158
+ continue;
159
+ }
160
+ state.active.add(documentKey);
161
+ readContext.visited.add(documentKey);
162
+ try {
163
+ readContext.readCount += 1;
164
+ if (readContext.readCount > readContext.maxReads) {
165
+ throw ERR_READ_BUDGET_EXCEEDED({
166
+ message: `richtext populate exceeded read budget (maxReads=${readContext.maxReads})`,
167
+ details: {
168
+ readCount: readContext.readCount,
169
+ maxReads: readContext.maxReads,
170
+ targetCollectionId: collectionId,
171
+ targetDocumentId: documentId,
172
+ },
173
+ });
174
+ }
175
+ if (richTextPopulate) {
176
+ await populateRichTextFields({
177
+ fields: definition.fields,
178
+ collectionPath,
179
+ documents: [doc],
180
+ populate: richTextPopulate,
181
+ readContext,
182
+ requestContext,
183
+ readMode,
184
+ readDocuments: read,
185
+ });
186
+ }
187
+ await applyAfterRead({
188
+ doc,
189
+ definition,
190
+ readContext,
191
+ requestContext,
192
+ locale,
193
+ readMode,
194
+ projection: fields,
195
+ materialization: 'richtext-target',
196
+ });
197
+ state.cache.set(materializationKey, doc);
198
+ resultById.set(documentId, doc);
199
+ }
200
+ finally {
201
+ state.active.delete(documentKey);
202
+ }
203
+ }
204
+ }
205
+ return documentIds.flatMap((documentId) => {
206
+ const doc = resultById.get(documentId);
207
+ return doc == null ? [] : [doc];
208
+ });
209
+ };
210
+ return read;
211
+ }
212
+ const richTextReaderStates = new WeakMap();
213
+ function getRichTextReaderState(readContext) {
214
+ const root = resolveReadContextRoot(readContext);
215
+ const existing = richTextReaderStates.get(root);
216
+ if (existing)
217
+ return existing;
218
+ const state = { active: new Set(), cache: new Map() };
219
+ richTextReaderStates.set(root, state);
220
+ return state;
221
+ }
222
+ function richTextMaterializationKey(collectionId, documentId, requestId, locale, readMode, projection) {
223
+ return `${collectionId}:${documentId}:${requestId}:${locale ?? 'all'}:${readMode}:${projection}`;
224
+ }
84
225
  // ---------------------------------------------------------------------------
85
226
  // Boot-time validation
86
227
  // ---------------------------------------------------------------------------