@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.
- package/dist/@types/collection-types.d.ts +72 -31
- package/dist/@types/db-types.d.ts +93 -47
- package/dist/@types/field-data-types.d.ts +8 -10
- package/dist/@types/field-data-types.js +1 -1
- package/dist/@types/field-data-types.test.node.d.ts +1 -0
- package/dist/@types/field-data-types.test.node.js +70 -0
- package/dist/@types/field-types.d.ts +25 -1
- package/dist/@types/index.d.ts +1 -0
- package/dist/@types/index.js +1 -0
- package/dist/@types/query-predicate.d.ts +3 -3
- package/dist/@types/relation-types.d.ts +38 -0
- package/dist/@types/relation-types.js +8 -0
- package/dist/@types/search-types.d.ts +3 -4
- package/dist/@types/site-config.d.ts +52 -13
- package/dist/auth/apply-before-read.d.ts +23 -11
- package/dist/auth/apply-before-read.js +139 -33
- package/dist/auth/apply-before-read.test.node.js +241 -3
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.js +1 -1
- package/dist/auth/read-context-scope.d.ts +20 -0
- package/dist/auth/read-context-scope.js +48 -0
- package/dist/codegen/fixtures/all-fields.d.ts +342 -0
- package/dist/codegen/fixtures/all-fields.expected.d.ts +120 -0
- package/dist/codegen/fixtures/all-fields.expected.js +1 -0
- package/dist/codegen/fixtures/all-fields.js +94 -0
- package/dist/codegen/index.d.ts +16 -0
- package/dist/codegen/index.js +431 -0
- package/dist/codegen/index.test.node.d.ts +1 -0
- package/dist/codegen/index.test.node.js +230 -0
- package/dist/config/attach-hooks.d.ts +25 -0
- package/dist/config/attach-hooks.js +130 -0
- package/dist/config/attach-hooks.test.node.d.ts +1 -0
- package/dist/config/attach-hooks.test.node.js +173 -0
- package/dist/config/config-hooks.test.node.d.ts +1 -0
- package/dist/config/config-hooks.test.node.js +56 -0
- package/dist/config/config.d.ts +9 -5
- package/dist/config/config.js +20 -2
- package/dist/config/routes.d.ts +5 -5
- package/dist/config/routes.js +42 -9
- package/dist/config/routes.test.node.d.ts +1 -0
- package/dist/config/routes.test.node.js +152 -0
- package/dist/core.d.ts +3 -3
- package/dist/core.js +20 -14
- package/dist/core.test.node.d.ts +1 -0
- package/dist/core.test.node.js +28 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +4 -3
- package/dist/lib/errors.d.ts +13 -0
- package/dist/lib/errors.js +14 -0
- package/dist/query/parse-where.d.ts +10 -1
- package/dist/query/parse-where.js +146 -2
- package/dist/query/parse-where.test.node.js +60 -1
- package/dist/schemas/zod/builder.js +7 -3
- package/dist/schemas/zod/builder.test.node.d.ts +1 -0
- package/dist/schemas/zod/builder.test.node.js +49 -0
- package/dist/services/collection-bootstrap.d.ts +1 -1
- package/dist/services/collection-bootstrap.test.node.js +30 -59
- package/dist/services/discover-counter-groups.d.ts +1 -1
- package/dist/services/discover-counter-groups.test.node.js +23 -0
- package/dist/services/document-lifecycle/audit.d.ts +22 -1
- package/dist/services/document-lifecycle/audit.js +32 -1
- package/dist/services/document-lifecycle/create.d.ts +2 -2
- package/dist/services/document-lifecycle/create.js +18 -8
- package/dist/services/document-lifecycle/delete.d.ts +17 -1
- package/dist/services/document-lifecycle/delete.js +91 -26
- package/dist/services/document-lifecycle/index.d.ts +1 -1
- package/dist/services/document-lifecycle/internals.d.ts +0 -20
- package/dist/services/document-lifecycle/internals.js +22 -46
- package/dist/services/document-lifecycle/status.d.ts +1 -1
- package/dist/services/document-lifecycle/status.js +20 -3
- package/dist/services/document-lifecycle/system-fields.d.ts +19 -6
- package/dist/services/document-lifecycle/system-fields.js +112 -74
- package/dist/services/document-lifecycle/tree.d.ts +22 -24
- package/dist/services/document-lifecycle/tree.js +244 -123
- package/dist/services/document-lifecycle/tree.test.node.d.ts +8 -0
- package/dist/services/document-lifecycle/tree.test.node.js +663 -0
- package/dist/services/document-lifecycle/update.d.ts +4 -4
- package/dist/services/document-lifecycle/update.js +11 -5
- package/dist/services/document-lifecycle.test.node.js +426 -16
- package/dist/services/document-read.d.ts +14 -6
- package/dist/services/document-read.js +47 -9
- package/dist/services/field-upload.test.node.js +70 -0
- package/dist/services/index.d.ts +3 -2
- package/dist/services/index.js +3 -2
- package/dist/services/normalize-numeric-fields.d.ts +23 -0
- package/dist/services/normalize-numeric-fields.js +89 -0
- package/dist/services/normalize-numeric-fields.test.node.d.ts +8 -0
- package/dist/services/normalize-numeric-fields.test.node.js +99 -0
- package/dist/services/populate.d.ts +9 -23
- package/dist/services/populate.js +180 -28
- package/dist/services/populate.test.node.js +59 -0
- package/dist/services/richtext-embed.d.ts +39 -2
- package/dist/services/richtext-embed.js +4 -34
- package/dist/services/richtext-embed.test.node.js +15 -0
- package/dist/services/richtext-populate.d.ts +21 -3
- package/dist/services/richtext-populate.js +160 -19
- package/dist/services/richtext-populate.test.node.js +523 -2
- package/dist/services/validate-search-config.d.ts +1 -1
- package/dist/storage/collection-fingerprint.js +6 -0
- package/dist/storage/collection-fingerprint.test.node.js +21 -0
- package/dist/utils/root-relative-redirect.d.ts +6 -0
- package/dist/utils/root-relative-redirect.js +37 -0
- package/package.json +7 -2
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
|
-
import { createRequestContext } from '@byline/auth';
|
|
9
|
-
import { describe, expect, it } from 'vitest';
|
|
8
|
+
import { AdminAuth, createRequestContext } from '@byline/auth';
|
|
9
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
10
10
|
import { defineCollection, defineWorkflow, } from '../@types/collection-types.js';
|
|
11
11
|
import { createReadContext } from '../services/populate.js';
|
|
12
|
-
import { applyBeforeRead } from './apply-before-read.js';
|
|
12
|
+
import { applyBeforeRead, compileBeforeReadFilters } from './apply-before-read.js';
|
|
13
|
+
import { resolveReadContextRoot } from './read-context-scope.js';
|
|
13
14
|
const baseCollection = (hook) => defineCollection({
|
|
14
15
|
path: 'posts',
|
|
15
16
|
labels: { singular: 'Post', plural: 'Posts' },
|
|
@@ -101,6 +102,55 @@ describe('applyBeforeRead', () => {
|
|
|
101
102
|
expect(second).toBeNull();
|
|
102
103
|
expect(callCount).toBe(1);
|
|
103
104
|
});
|
|
105
|
+
it('ignores a caller-preseeded public cache entry', async () => {
|
|
106
|
+
const hook = vi.fn(() => ({ title: 'allowed' }));
|
|
107
|
+
const readContext = createReadContext({
|
|
108
|
+
beforeReadCache: new Map([['posts:any', null]]),
|
|
109
|
+
});
|
|
110
|
+
const result = await applyBeforeRead({
|
|
111
|
+
definition: baseCollection(hook),
|
|
112
|
+
requestContext: createRequestContext(),
|
|
113
|
+
readContext,
|
|
114
|
+
});
|
|
115
|
+
expect(hook).toHaveBeenCalledOnce();
|
|
116
|
+
expect(result).toEqual({ title: 'allowed' });
|
|
117
|
+
});
|
|
118
|
+
it('ignores a caller-preseeded public cache entry on the compiled-filter path', async () => {
|
|
119
|
+
const hook = vi.fn(() => ({ title: 'allowed' }));
|
|
120
|
+
const readContext = createReadContext({
|
|
121
|
+
beforeReadCache: new Map([['posts:any', null]]),
|
|
122
|
+
});
|
|
123
|
+
const filters = await compileBeforeReadFilters({
|
|
124
|
+
definition: baseCollection(hook),
|
|
125
|
+
requestContext: createRequestContext(),
|
|
126
|
+
readContext,
|
|
127
|
+
securityDomain: {},
|
|
128
|
+
parseContext: { collections: [], resolveCollectionId: vi.fn() },
|
|
129
|
+
});
|
|
130
|
+
expect(hook).toHaveBeenCalledOnce();
|
|
131
|
+
expect(filters).toEqual([
|
|
132
|
+
expect.objectContaining({ kind: 'field', fieldName: 'title', value: 'allowed' }),
|
|
133
|
+
]);
|
|
134
|
+
});
|
|
135
|
+
it('rejects reuse under a different actor authority', async () => {
|
|
136
|
+
const hook = vi.fn(({ requestContext }) => ({
|
|
137
|
+
title: requestContext.actor?.id,
|
|
138
|
+
}));
|
|
139
|
+
const definition = baseCollection(hook);
|
|
140
|
+
const readContext = createReadContext();
|
|
141
|
+
const requestId = 'same-logical-request';
|
|
142
|
+
const alice = createRequestContext({
|
|
143
|
+
actor: new AdminAuth({ id: 'alice', abilities: ['collections.posts.read'] }),
|
|
144
|
+
requestId,
|
|
145
|
+
});
|
|
146
|
+
const bob = createRequestContext({
|
|
147
|
+
actor: new AdminAuth({ id: 'bob', abilities: ['collections.posts.read'] }),
|
|
148
|
+
requestId,
|
|
149
|
+
});
|
|
150
|
+
await applyBeforeRead({ definition, requestContext: alice, readContext });
|
|
151
|
+
await expect(applyBeforeRead({ definition, requestContext: bob, readContext })).rejects.toThrow('cannot be reused across request authorities');
|
|
152
|
+
expect(hook).toHaveBeenCalledOnce();
|
|
153
|
+
});
|
|
104
154
|
it('runs the hook again on a fresh ReadContext', async () => {
|
|
105
155
|
let callCount = 0;
|
|
106
156
|
const hook = () => {
|
|
@@ -113,6 +163,32 @@ describe('applyBeforeRead', () => {
|
|
|
113
163
|
await applyBeforeRead({ definition: def, requestContext, readContext: createReadContext() });
|
|
114
164
|
expect(callCount).toBe(2);
|
|
115
165
|
});
|
|
166
|
+
it('caches independently by effective read mode', async () => {
|
|
167
|
+
const modes = [];
|
|
168
|
+
const hook = ({ requestContext }) => {
|
|
169
|
+
modes.push(requestContext.readMode);
|
|
170
|
+
return { status: requestContext.readMode === 'published' ? 'published' : 'draft' };
|
|
171
|
+
};
|
|
172
|
+
const definition = baseCollection(hook);
|
|
173
|
+
const readContext = createReadContext();
|
|
174
|
+
const base = createRequestContext();
|
|
175
|
+
await applyBeforeRead({
|
|
176
|
+
definition,
|
|
177
|
+
requestContext: { ...base, readMode: 'published' },
|
|
178
|
+
readContext,
|
|
179
|
+
});
|
|
180
|
+
await applyBeforeRead({
|
|
181
|
+
definition,
|
|
182
|
+
requestContext: { ...base, readMode: 'any' },
|
|
183
|
+
readContext,
|
|
184
|
+
});
|
|
185
|
+
await applyBeforeRead({
|
|
186
|
+
definition,
|
|
187
|
+
requestContext: { ...base, readMode: 'published' },
|
|
188
|
+
readContext,
|
|
189
|
+
});
|
|
190
|
+
expect(modes).toEqual(['published', 'any']);
|
|
191
|
+
});
|
|
116
192
|
it('passes collectionPath, requestContext, and readContext through to the hook', async () => {
|
|
117
193
|
let received;
|
|
118
194
|
const hook = (ctx) => {
|
|
@@ -125,8 +201,40 @@ describe('applyBeforeRead', () => {
|
|
|
125
201
|
expect(received).toMatchObject({
|
|
126
202
|
collectionPath: 'posts',
|
|
127
203
|
requestContext,
|
|
204
|
+
});
|
|
205
|
+
expect(received.readContext.visited).toBe(readContext.visited);
|
|
206
|
+
});
|
|
207
|
+
it('delegates scoped read-budget state to the logical root context', async () => {
|
|
208
|
+
const readContext = createReadContext({ maxReads: 4 });
|
|
209
|
+
const hook = ({ readContext: scoped }) => {
|
|
210
|
+
scoped.visited.add('posts:nested');
|
|
211
|
+
scoped.readCount += 1;
|
|
212
|
+
scoped.maxReads = 3;
|
|
213
|
+
};
|
|
214
|
+
await applyBeforeRead({
|
|
215
|
+
definition: baseCollection(hook),
|
|
216
|
+
requestContext: createRequestContext(),
|
|
217
|
+
readContext,
|
|
218
|
+
});
|
|
219
|
+
expect(readContext.visited).toContain('posts:nested');
|
|
220
|
+
expect(readContext.readCount).toBe(1);
|
|
221
|
+
expect(readContext.maxReads).toBe(3);
|
|
222
|
+
});
|
|
223
|
+
it('resolves scoped hook contexts to their logical root', async () => {
|
|
224
|
+
const readContext = createReadContext();
|
|
225
|
+
let scopedContext;
|
|
226
|
+
const hook = ({ readContext: scoped }) => {
|
|
227
|
+
scopedContext = scoped;
|
|
228
|
+
};
|
|
229
|
+
await applyBeforeRead({
|
|
230
|
+
definition: baseCollection(hook),
|
|
231
|
+
requestContext: createRequestContext(),
|
|
128
232
|
readContext,
|
|
129
233
|
});
|
|
234
|
+
if (!scopedContext)
|
|
235
|
+
throw new Error('beforeRead did not receive a scoped ReadContext');
|
|
236
|
+
expect(resolveReadContextRoot(scopedContext)).toBe(readContext);
|
|
237
|
+
expect(resolveReadContextRoot(readContext)).toBe(readContext);
|
|
130
238
|
});
|
|
131
239
|
it('supports an async hook', async () => {
|
|
132
240
|
const hook = async () => {
|
|
@@ -140,4 +248,134 @@ describe('applyBeforeRead', () => {
|
|
|
140
248
|
});
|
|
141
249
|
expect(result).toEqual({ tenantId: 't-1' });
|
|
142
250
|
});
|
|
251
|
+
it('compiles security filters and resolves relation collection ids once per read context', async () => {
|
|
252
|
+
const categories = defineCollection({
|
|
253
|
+
path: 'categories',
|
|
254
|
+
labels: { singular: 'Category', plural: 'Categories' },
|
|
255
|
+
fields: [{ name: 'tenant', type: 'text', label: 'Tenant' }],
|
|
256
|
+
});
|
|
257
|
+
const definition = defineCollection({
|
|
258
|
+
path: 'posts',
|
|
259
|
+
labels: { singular: 'Post', plural: 'Posts' },
|
|
260
|
+
fields: [
|
|
261
|
+
{
|
|
262
|
+
name: 'category',
|
|
263
|
+
type: 'relation',
|
|
264
|
+
label: 'Category',
|
|
265
|
+
targetCollection: 'categories',
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
hooks: { beforeRead: () => ({ category: { tenant: 'alice' } }) },
|
|
269
|
+
});
|
|
270
|
+
const readContext = createReadContext();
|
|
271
|
+
const requestContext = createRequestContext({ readMode: 'published' });
|
|
272
|
+
const resolveCollectionId = vi.fn(async () => 'categories-id');
|
|
273
|
+
const params = {
|
|
274
|
+
definition,
|
|
275
|
+
requestContext,
|
|
276
|
+
readContext,
|
|
277
|
+
securityDomain: {},
|
|
278
|
+
parseContext: { collections: [definition, categories], resolveCollectionId },
|
|
279
|
+
};
|
|
280
|
+
const [first, second] = await Promise.all([
|
|
281
|
+
compileBeforeReadFilters(params),
|
|
282
|
+
compileBeforeReadFilters(params),
|
|
283
|
+
]);
|
|
284
|
+
expect(second).toBe(first);
|
|
285
|
+
expect(resolveCollectionId).toHaveBeenCalledOnce();
|
|
286
|
+
});
|
|
287
|
+
it('compiles the same definition independently for different security domains', async () => {
|
|
288
|
+
const categories = defineCollection({
|
|
289
|
+
path: 'categories',
|
|
290
|
+
labels: { singular: 'Category', plural: 'Categories' },
|
|
291
|
+
fields: [{ name: 'tenant', type: 'text', label: 'Tenant' }],
|
|
292
|
+
});
|
|
293
|
+
const hook = vi.fn(() => ({ category: { tenant: 'alice' } }));
|
|
294
|
+
const definition = defineCollection({
|
|
295
|
+
path: 'posts',
|
|
296
|
+
labels: { singular: 'Post', plural: 'Posts' },
|
|
297
|
+
fields: [
|
|
298
|
+
{
|
|
299
|
+
name: 'category',
|
|
300
|
+
type: 'relation',
|
|
301
|
+
label: 'Category',
|
|
302
|
+
targetCollection: 'categories',
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
hooks: { beforeRead: hook },
|
|
306
|
+
});
|
|
307
|
+
const readContext = createReadContext();
|
|
308
|
+
const requestContext = createRequestContext({ readMode: 'published' });
|
|
309
|
+
const resolveA = vi.fn(async () => 'categories-a');
|
|
310
|
+
const resolveB = vi.fn(async () => 'categories-b');
|
|
311
|
+
await compileBeforeReadFilters({
|
|
312
|
+
definition,
|
|
313
|
+
requestContext,
|
|
314
|
+
readContext,
|
|
315
|
+
securityDomain: {},
|
|
316
|
+
parseContext: { collections: [definition, categories], resolveCollectionId: resolveA },
|
|
317
|
+
});
|
|
318
|
+
await compileBeforeReadFilters({
|
|
319
|
+
definition,
|
|
320
|
+
requestContext,
|
|
321
|
+
readContext,
|
|
322
|
+
securityDomain: {},
|
|
323
|
+
parseContext: { collections: [definition, categories], resolveCollectionId: resolveB },
|
|
324
|
+
});
|
|
325
|
+
expect(hook).toHaveBeenCalledTimes(2);
|
|
326
|
+
expect(resolveA).toHaveBeenCalledOnce();
|
|
327
|
+
expect(resolveB).toHaveBeenCalledOnce();
|
|
328
|
+
});
|
|
329
|
+
it('fails closed on a recursive beforeRead for the same collection', async () => {
|
|
330
|
+
const readContext = createReadContext();
|
|
331
|
+
const requestContext = createRequestContext();
|
|
332
|
+
let definition;
|
|
333
|
+
const hook = async (ctx) => {
|
|
334
|
+
await applyBeforeRead({
|
|
335
|
+
definition,
|
|
336
|
+
requestContext: ctx.requestContext,
|
|
337
|
+
readContext: ctx.readContext,
|
|
338
|
+
});
|
|
339
|
+
};
|
|
340
|
+
definition = baseCollection(hook);
|
|
341
|
+
await expect(applyBeforeRead({ definition, requestContext, readContext })).rejects.toMatchObject({ code: 'ERR_READ_RECURSION' });
|
|
342
|
+
});
|
|
343
|
+
it('fails closed on an A to B to A beforeRead cycle', async () => {
|
|
344
|
+
const readContext = createReadContext();
|
|
345
|
+
const requestContext = createRequestContext();
|
|
346
|
+
const securityDomain = {};
|
|
347
|
+
let a;
|
|
348
|
+
let b;
|
|
349
|
+
a = defineCollection({
|
|
350
|
+
path: 'a',
|
|
351
|
+
labels: { singular: 'A', plural: 'As' },
|
|
352
|
+
fields: [{ name: 'title', type: 'text', label: 'Title' }],
|
|
353
|
+
hooks: {
|
|
354
|
+
beforeRead: async (ctx) => {
|
|
355
|
+
await applyBeforeRead({
|
|
356
|
+
definition: b,
|
|
357
|
+
requestContext: ctx.requestContext,
|
|
358
|
+
readContext: ctx.readContext,
|
|
359
|
+
securityDomain,
|
|
360
|
+
});
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
b = defineCollection({
|
|
365
|
+
path: 'b',
|
|
366
|
+
labels: { singular: 'B', plural: 'Bs' },
|
|
367
|
+
fields: [{ name: 'title', type: 'text', label: 'Title' }],
|
|
368
|
+
hooks: {
|
|
369
|
+
beforeRead: async (ctx) => {
|
|
370
|
+
await applyBeforeRead({
|
|
371
|
+
definition: a,
|
|
372
|
+
requestContext: ctx.requestContext,
|
|
373
|
+
readContext: ctx.readContext,
|
|
374
|
+
securityDomain,
|
|
375
|
+
});
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
await expect(applyBeforeRead({ definition: a, requestContext, readContext, securityDomain })).rejects.toMatchObject({ code: 'ERR_READ_RECURSION' });
|
|
380
|
+
});
|
|
143
381
|
});
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
|
-
export { applyBeforeRead } from './apply-before-read.js';
|
|
8
|
+
export { applyBeforeRead, bindReadContextAuthority, compileBeforeReadFilters, } from './apply-before-read.js';
|
|
9
9
|
export { assertActorCanPerform } from './assert-actor-can-perform.js';
|
|
10
10
|
export { COLLECTION_ABILITY_VERBS, type CollectionAbilityVerb, collectionAbilityKey, registerCollectionAbilities, } from './register-collection-abilities.js';
|
package/dist/auth/index.js
CHANGED
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
|
-
export { applyBeforeRead } from './apply-before-read.js';
|
|
8
|
+
export { applyBeforeRead, bindReadContextAuthority, compileBeforeReadFilters, } from './apply-before-read.js';
|
|
9
9
|
export { assertActorCanPerform } from './assert-actor-can-perform.js';
|
|
10
10
|
export { COLLECTION_ABILITY_VERBS, collectionAbilityKey, registerCollectionAbilities, } from './register-collection-abilities.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
import type { ReadContext } from '../@types/db-types.js';
|
|
9
|
+
export interface ReadContextScope {
|
|
10
|
+
root: ReadContext;
|
|
11
|
+
ancestry: readonly object[];
|
|
12
|
+
}
|
|
13
|
+
export declare function getReadContextScope(readContext: ReadContext): ReadContextScope;
|
|
14
|
+
/** Resolve a scoped hook context to the ReadContext for its logical request. */
|
|
15
|
+
export declare function resolveReadContextRoot(readContext: ReadContext): ReadContext;
|
|
16
|
+
/**
|
|
17
|
+
* Carry hook ancestry by object identity, not by a forgeable public property.
|
|
18
|
+
* All mutable read-budget state still delegates to the logical root context.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createHookReadContext(parent: ReadContextScope, entry: object): ReadContext;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
const readContextScopes = new WeakMap();
|
|
9
|
+
export function getReadContextScope(readContext) {
|
|
10
|
+
return readContextScopes.get(readContext) ?? { root: readContext, ancestry: [] };
|
|
11
|
+
}
|
|
12
|
+
/** Resolve a scoped hook context to the ReadContext for its logical request. */
|
|
13
|
+
export function resolveReadContextRoot(readContext) {
|
|
14
|
+
return getReadContextScope(readContext).root;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Carry hook ancestry by object identity, not by a forgeable public property.
|
|
18
|
+
* All mutable read-budget state still delegates to the logical root context.
|
|
19
|
+
*/
|
|
20
|
+
export function createHookReadContext(parent, entry) {
|
|
21
|
+
const root = parent.root;
|
|
22
|
+
const scoped = {};
|
|
23
|
+
Object.defineProperties(scoped, {
|
|
24
|
+
visited: { enumerable: true, get: () => root.visited, set: (value) => (root.visited = value) },
|
|
25
|
+
beforeReadCache: {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
get: () => root.beforeReadCache,
|
|
28
|
+
set: (value) => (root.beforeReadCache = value),
|
|
29
|
+
},
|
|
30
|
+
readCount: {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
get: () => root.readCount,
|
|
33
|
+
set: (value) => (root.readCount = value),
|
|
34
|
+
},
|
|
35
|
+
maxReads: {
|
|
36
|
+
enumerable: true,
|
|
37
|
+
get: () => root.maxReads,
|
|
38
|
+
set: (value) => (root.maxReads = value),
|
|
39
|
+
},
|
|
40
|
+
maxDepth: {
|
|
41
|
+
enumerable: true,
|
|
42
|
+
get: () => root.maxDepth,
|
|
43
|
+
set: (value) => (root.maxDepth = value),
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
readContextScopes.set(scoped, { root, ancestry: [...parent.ancestry, entry] });
|
|
47
|
+
return scoped;
|
|
48
|
+
}
|