@byline/db-postgres 3.19.0 → 3.20.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.
|
@@ -51,6 +51,17 @@ function* flattenFieldSetDataGen(fields, data, locale, parent_path = []) {
|
|
|
51
51
|
* @returns
|
|
52
52
|
*/
|
|
53
53
|
function* flattenFieldDataGen(field, data, locale, field_path) {
|
|
54
|
+
// Virtual fields are never persisted. The value participated in the write
|
|
55
|
+
// pipeline (patches applied it, lifecycle hooks saw and could act on it),
|
|
56
|
+
// but no store_* row is written, so reads reconstruct the field as absent
|
|
57
|
+
// and the next editing session starts clean. This is the single
|
|
58
|
+
// enforcement point for the `virtual` contract — every nesting level
|
|
59
|
+
// (top-level, group children, array items, block fields) funnels through
|
|
60
|
+
// this function, and a virtual structure field omits its whole subtree.
|
|
61
|
+
// See `BaseField.virtual` in @byline/core.
|
|
62
|
+
if (field.virtual === true) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
54
65
|
// Treat `null` the same as `undefined` — a removed/cleared field emits no
|
|
55
66
|
// storage rows for the new version, so reads reconstruct as absent. Without
|
|
56
67
|
// this guard, downstream value extractors (relation, file/image, group,
|
|
@@ -324,3 +324,82 @@ describe('reserved field-name tolerance on restore', () => {
|
|
|
324
324
|
expect(warnings, 'a reserved-name orphan must not be treated as an unknown field').toEqual([]);
|
|
325
325
|
});
|
|
326
326
|
});
|
|
327
|
+
describe('virtual fields', () => {
|
|
328
|
+
// Mirrors the publications shape that motivated `virtual`: a per-item
|
|
329
|
+
// "generate thumbnail" checkbox + page number inside an array group,
|
|
330
|
+
// plus a top-level virtual scalar. Virtual values must flow through the
|
|
331
|
+
// write pipeline (hooks see them) but emit NO store rows — reads
|
|
332
|
+
// reconstruct them as absent, so the next editing session starts clean.
|
|
333
|
+
const VirtualCollectionConfig = defineCollection({
|
|
334
|
+
path: 'virtual-docs',
|
|
335
|
+
labels: { singular: 'Virtual Doc', plural: 'Virtual Docs' },
|
|
336
|
+
fields: [
|
|
337
|
+
{ name: 'title', type: 'text' },
|
|
338
|
+
{ name: 'regenerateAll', type: 'checkbox', virtual: true, optional: true },
|
|
339
|
+
{
|
|
340
|
+
name: 'files',
|
|
341
|
+
type: 'array',
|
|
342
|
+
fields: [
|
|
343
|
+
{
|
|
344
|
+
name: 'filesGroup',
|
|
345
|
+
type: 'group',
|
|
346
|
+
fields: [
|
|
347
|
+
{ name: 'label', type: 'text' },
|
|
348
|
+
{ name: 'generateThumbnail', type: 'checkbox', virtual: true, optional: true },
|
|
349
|
+
{ name: 'thumbnailPage', type: 'integer', virtual: true, defaultValue: 1 },
|
|
350
|
+
],
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
name: 'scratch',
|
|
356
|
+
type: 'group',
|
|
357
|
+
virtual: true,
|
|
358
|
+
optional: true,
|
|
359
|
+
fields: [{ name: 'note', type: 'text', optional: true }],
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
});
|
|
363
|
+
const virtualDocument = {
|
|
364
|
+
title: 'Publication-ish',
|
|
365
|
+
regenerateAll: true,
|
|
366
|
+
files: [
|
|
367
|
+
{
|
|
368
|
+
_id: 'item-1',
|
|
369
|
+
filesGroup: { label: 'English PDF', generateThumbnail: true, thumbnailPage: 3 },
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
_id: 'item-2',
|
|
373
|
+
filesGroup: { label: 'Thai PDF', generateThumbnail: false, thumbnailPage: 1 },
|
|
374
|
+
},
|
|
375
|
+
],
|
|
376
|
+
scratch: { note: 'never stored' },
|
|
377
|
+
};
|
|
378
|
+
it('emits no store rows for virtual fields at any nesting depth', () => {
|
|
379
|
+
const flattened = flattenFieldSetData(VirtualCollectionConfig.fields, virtualDocument, 'all');
|
|
380
|
+
const paths = flattened.map((row) => row.field_path.join('.'));
|
|
381
|
+
// Persisted values survive…
|
|
382
|
+
expect(paths).toContain('title');
|
|
383
|
+
expect(paths).toContain('files.0.filesGroup.label');
|
|
384
|
+
expect(paths).toContain('files.1.filesGroup.label');
|
|
385
|
+
// …virtual leaves do not (top-level, nested in array group)…
|
|
386
|
+
expect(paths.some((p) => p.includes('regenerateAll'))).toBe(false);
|
|
387
|
+
expect(paths.some((p) => p.includes('generateThumbnail'))).toBe(false);
|
|
388
|
+
expect(paths.some((p) => p.includes('thumbnailPage'))).toBe(false);
|
|
389
|
+
// …and a virtual structure field omits its whole subtree.
|
|
390
|
+
expect(paths.some((p) => p.startsWith('scratch'))).toBe(false);
|
|
391
|
+
});
|
|
392
|
+
it('round-trip reconstruction leaves virtual fields structurally absent', () => {
|
|
393
|
+
const flattened = flattenFieldSetData(VirtualCollectionConfig.fields, virtualDocument, 'all');
|
|
394
|
+
const { data: restored, warnings } = restoreFieldSetData(VirtualCollectionConfig.fields, flattened);
|
|
395
|
+
expect(warnings).toEqual([]);
|
|
396
|
+
expect(restored.title).toBe('Publication-ish');
|
|
397
|
+
expect(restored.regenerateAll).toBeUndefined();
|
|
398
|
+
expect(restored.scratch).toBeUndefined();
|
|
399
|
+
const items = restored.files;
|
|
400
|
+
expect(items).toHaveLength(2);
|
|
401
|
+
expect(items[0]?.filesGroup.label).toBe('English PDF');
|
|
402
|
+
expect(items[0]?.filesGroup.generateThumbnail).toBeUndefined();
|
|
403
|
+
expect(items[0]?.filesGroup.thumbnailPage).toBeUndefined();
|
|
404
|
+
});
|
|
405
|
+
});
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@byline/db-postgres",
|
|
3
3
|
"private": false,
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.20.1",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20.9.0"
|
|
8
8
|
},
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"npm-run-all": "^4.1.5",
|
|
57
57
|
"pg": "^8.22.0",
|
|
58
58
|
"uuid": "^14.0.1",
|
|
59
|
-
"@byline/admin": "3.
|
|
60
|
-
"@byline/auth": "3.
|
|
61
|
-
"@byline/core": "3.
|
|
59
|
+
"@byline/admin": "3.20.1",
|
|
60
|
+
"@byline/auth": "3.20.1",
|
|
61
|
+
"@byline/core": "3.20.1"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@biomejs/biome": "2.5.2",
|