mikser-io 9.39.2 → 9.40.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/package.json +1 -1
- package/src/engine.js +20 -1
- package/src/manifest.js +21 -12
- package/src/track.js +40 -1
package/package.json
CHANGED
package/src/engine.js
CHANGED
|
@@ -541,7 +541,8 @@ export async function setup(options) {
|
|
|
541
541
|
// meta. Opt-out rather than opt-in: a contract that is only
|
|
542
542
|
// correct when someone remembered to enable it is a contract
|
|
543
543
|
// nobody can rely on. Set `metaReads: false` to switch it off.
|
|
544
|
-
const track = createTrack({ meta: runtime.options.metaReads !== false
|
|
544
|
+
const track = createTrack({ meta: runtime.options.metaReads !== false,
|
|
545
|
+
consumed: runtime.options.metaReads !== false })
|
|
545
546
|
const renderOptions = {
|
|
546
547
|
entity: renderEntity,
|
|
547
548
|
options: {
|
|
@@ -639,10 +640,28 @@ export async function setup(options) {
|
|
|
639
640
|
...(track?.metaReads ?? []),
|
|
640
641
|
...(context?.sidecarMetaReads ?? []),
|
|
641
642
|
]
|
|
643
|
+
// Which keys of OTHER entities this render read, keyed
|
|
644
|
+
// by the entity they belong to. Merged from the same two
|
|
645
|
+
// places: the render's own track, and the sidecar's,
|
|
646
|
+
// which runs earlier on the main thread.
|
|
647
|
+
const consumedReads = new Map()
|
|
648
|
+
for (const [cid, paths] of [
|
|
649
|
+
...(track?.consumedReads ?? []),
|
|
650
|
+
...(context?.sidecarConsumedReads ?? []),
|
|
651
|
+
]) {
|
|
652
|
+
const into = consumedReads.get(cid) ?? new Set()
|
|
653
|
+
for (const path of paths) into.add(path)
|
|
654
|
+
consumedReads.set(cid, into)
|
|
655
|
+
}
|
|
642
656
|
entry.output = {
|
|
643
657
|
success: true,
|
|
644
658
|
result,
|
|
645
659
|
...(metaReads.length ? { metaReads: [...new Set(metaReads)].sort() } : {}),
|
|
660
|
+
...(consumedReads.size ? {
|
|
661
|
+
consumedReads: [...consumedReads]
|
|
662
|
+
.map(([cid, paths]) => [cid, [...paths].sort()])
|
|
663
|
+
.sort(([a], [b]) => a.localeCompare(b)),
|
|
664
|
+
} : {}),
|
|
646
665
|
}
|
|
647
666
|
// A render that produced nothing at all. Distinct from
|
|
648
667
|
// a render that THREW, which lands in `errors` and
|
package/src/manifest.js
CHANGED
|
@@ -86,6 +86,11 @@ export const SNAPSHOTS_SCHEMA = `
|
|
|
86
86
|
-- JavaScript, and no parser for any template engine will ever find
|
|
87
87
|
-- an optional chain like row.meta?.hero?.tags.
|
|
88
88
|
metaReads TEXT,
|
|
89
|
+
-- Keys of OTHER entities this render read, keyed by the entity they
|
|
90
|
+
-- belong to. The counterpart to metaReads, and the only record of what
|
|
91
|
+
-- a document that never renders is actually needed FOR: the engine knew
|
|
92
|
+
-- that a page queried it, never which of its fields mattered.
|
|
93
|
+
consumedReads TEXT,
|
|
89
94
|
renderedAt INTEGER,
|
|
90
95
|
parent TEXT,
|
|
91
96
|
PRIMARY KEY (id, destination)
|
|
@@ -206,7 +211,7 @@ function buildRefClosure(entity, deps) {
|
|
|
206
211
|
return closure
|
|
207
212
|
}
|
|
208
213
|
|
|
209
|
-
function buildSnapshot(entity, deps, outputHash, metaReads) {
|
|
214
|
+
function buildSnapshot(entity, deps, outputHash, metaReads, consumedReads) {
|
|
210
215
|
const snapshot = {
|
|
211
216
|
id: entity.id,
|
|
212
217
|
destination: entity.destination,
|
|
@@ -222,6 +227,7 @@ function buildSnapshot(entity, deps, outputHash, metaReads) {
|
|
|
222
227
|
// Sorted and deduped: this is a SET of paths, and a stable order keeps a
|
|
223
228
|
// snapshot comparable between runs.
|
|
224
229
|
if (metaReads?.length) snapshot.metaReads = [...new Set(metaReads)].sort()
|
|
230
|
+
if (consumedReads?.length) snapshot.consumedReads = consumedReads
|
|
225
231
|
if (entity.parent) snapshot.parent = entity.parent
|
|
226
232
|
if (outputHash) snapshot.outputHash = outputHash
|
|
227
233
|
return snapshot
|
|
@@ -239,6 +245,7 @@ function rowToSnap(row) {
|
|
|
239
245
|
outputHash: row.outputHash ?? undefined,
|
|
240
246
|
refClosure: row.refClosure ? JSON.parse(row.refClosure) : undefined,
|
|
241
247
|
metaReads: row.metaReads ? JSON.parse(row.metaReads) : undefined,
|
|
248
|
+
consumedReads: row.consumedReads ? JSON.parse(row.consumedReads) : undefined,
|
|
242
249
|
renderedAt: row.renderedAt ?? undefined,
|
|
243
250
|
parent: row.parent ?? undefined,
|
|
244
251
|
}
|
|
@@ -253,6 +260,7 @@ function snapToRow(snap) {
|
|
|
253
260
|
outputHash: snap.outputHash ?? null,
|
|
254
261
|
refClosure: snap.refClosure ? JSON.stringify(snap.refClosure) : null,
|
|
255
262
|
metaReads: snap.metaReads ? JSON.stringify(snap.metaReads) : null,
|
|
263
|
+
consumedReads: snap.consumedReads ? JSON.stringify(snap.consumedReads) : null,
|
|
256
264
|
renderedAt: snap.renderedAt ?? null,
|
|
257
265
|
parent: snap.parent ?? null,
|
|
258
266
|
}
|
|
@@ -376,7 +384,7 @@ export function createManifest(db) {
|
|
|
376
384
|
SELECT id FROM mikser_snapshots WHERE destination = ?
|
|
377
385
|
`)
|
|
378
386
|
const stmtLookupByDestination = db.prepare(`
|
|
379
|
-
SELECT id, destination, inputHash, inputParts, outputHash, refClosure, metaReads, renderedAt, parent
|
|
387
|
+
SELECT id, destination, inputHash, inputParts, outputHash, refClosure, metaReads, consumedReads, renderedAt, parent
|
|
380
388
|
FROM mikser_snapshots WHERE destination = ?
|
|
381
389
|
`)
|
|
382
390
|
const stmtDeleteByDestination = db.prepare(`
|
|
@@ -413,18 +421,18 @@ export function createManifest(db) {
|
|
|
413
421
|
`)
|
|
414
422
|
|
|
415
423
|
const stmtLookupById = db.prepare(`
|
|
416
|
-
SELECT id, destination, inputHash, inputParts, outputHash, refClosure, metaReads, renderedAt, parent
|
|
424
|
+
SELECT id, destination, inputHash, inputParts, outputHash, refClosure, metaReads, consumedReads, renderedAt, parent
|
|
417
425
|
FROM mikser_snapshots WHERE id = ? ORDER BY destination
|
|
418
426
|
`)
|
|
419
427
|
const stmtLookup = db.prepare(`
|
|
420
|
-
SELECT id, destination, inputHash, inputParts, outputHash, refClosure, metaReads, renderedAt, parent
|
|
428
|
+
SELECT id, destination, inputHash, inputParts, outputHash, refClosure, metaReads, consumedReads, renderedAt, parent
|
|
421
429
|
FROM mikser_snapshots WHERE id = ? AND destination = ?
|
|
422
430
|
`)
|
|
423
431
|
const stmtUpsert = db.prepare(`
|
|
424
432
|
INSERT OR REPLACE INTO mikser_snapshots
|
|
425
|
-
(id, destination, inputHash, inputParts, outputHash, refClosure, metaReads, renderedAt, parent)
|
|
433
|
+
(id, destination, inputHash, inputParts, outputHash, refClosure, metaReads, consumedReads, renderedAt, parent)
|
|
426
434
|
VALUES
|
|
427
|
-
(@id, @destination, @inputHash, @inputParts, @outputHash, @refClosure, @metaReads, @renderedAt, @parent)
|
|
435
|
+
(@id, @destination, @inputHash, @inputParts, @outputHash, @refClosure, @metaReads, @consumedReads, @renderedAt, @parent)
|
|
428
436
|
`)
|
|
429
437
|
const stmtDeleteByPK = db.prepare(`
|
|
430
438
|
DELETE FROM mikser_snapshots WHERE id = ? AND destination = ?
|
|
@@ -440,7 +448,7 @@ export function createManifest(db) {
|
|
|
440
448
|
SELECT id, destination FROM mikser_snapshots WHERE parent = ?
|
|
441
449
|
`)
|
|
442
450
|
const stmtSelectAll = db.prepare(`
|
|
443
|
-
SELECT id, destination, inputHash, inputParts, outputHash, refClosure, metaReads, renderedAt, parent
|
|
451
|
+
SELECT id, destination, inputHash, inputParts, outputHash, refClosure, metaReads, consumedReads, renderedAt, parent
|
|
444
452
|
FROM mikser_snapshots
|
|
445
453
|
`)
|
|
446
454
|
const stmtCount = db.prepare(`SELECT COUNT(*) AS c FROM mikser_snapshots`)
|
|
@@ -859,8 +867,8 @@ export function createManifest(db) {
|
|
|
859
867
|
},
|
|
860
868
|
|
|
861
869
|
// Record a successful render. Single INSERT OR REPLACE.
|
|
862
|
-
record(entity, deps, metaReads) {
|
|
863
|
-
stmtUpsert.run(snapToRow(buildSnapshot(entity, deps, undefined, metaReads)))
|
|
870
|
+
record(entity, deps, metaReads, consumedReads) {
|
|
871
|
+
stmtUpsert.run(snapToRow(buildSnapshot(entity, deps, undefined, metaReads, consumedReads)))
|
|
864
872
|
},
|
|
865
873
|
|
|
866
874
|
// Return the set of entity ids whose recorded snapshots have a
|
|
@@ -1154,7 +1162,8 @@ onFinalize(async () => {
|
|
|
1154
1162
|
|
|
1155
1163
|
for await (const { output, entity, deps } of useJournal('Output', [OPERATION.RENDER])) {
|
|
1156
1164
|
if (output?.success && !output.skipped) {
|
|
1157
|
-
renderedEntries.push({ entity, deps, metaReads: output.metaReads
|
|
1165
|
+
renderedEntries.push({ entity, deps, metaReads: output.metaReads,
|
|
1166
|
+
consumedReads: output.consumedReads })
|
|
1158
1167
|
if (entity.pages > 1) {
|
|
1159
1168
|
const parentId = entity.parent ?? entity.id
|
|
1160
1169
|
if (!newDestinationsByParent.has(parentId)) {
|
|
@@ -1287,10 +1296,10 @@ onFinalize(async () => {
|
|
|
1287
1296
|
// `parent` is checked too, mirroring _stmtDeleteByIdOrParent — a deleted
|
|
1288
1297
|
// parent takes its paginated children's snapshots with it.
|
|
1289
1298
|
const recordedSnapshots = []
|
|
1290
|
-
for (const { entity, deps, metaReads } of renderedEntries) {
|
|
1299
|
+
for (const { entity, deps, metaReads, consumedReads } of renderedEntries) {
|
|
1291
1300
|
if (deleted.has(entity.id) || (entity.parent && deleted.has(entity.parent))) continue
|
|
1292
1301
|
const outputHash = await hashOutputFile(entity.destination)
|
|
1293
|
-
recordedSnapshots.push(buildSnapshot(entity, deps, outputHash, metaReads))
|
|
1302
|
+
recordedSnapshots.push(buildSnapshot(entity, deps, outputHash, metaReads, consumedReads))
|
|
1294
1303
|
}
|
|
1295
1304
|
|
|
1296
1305
|
// Apply all DB mutations atomically.
|
package/src/track.js
CHANGED
|
@@ -40,7 +40,7 @@ export function filterKey(filter) {
|
|
|
40
40
|
// The returned object exposes `partials: Set<string>` and
|
|
41
41
|
// `queries: Array<filter | null>` directly. Consumers iterate either
|
|
42
42
|
// shape; both are owned by the track for the lifetime of the run.
|
|
43
|
-
export function createTrack({ partial = true, query = true, lookup = true, meta = false } = {}) {
|
|
43
|
+
export function createTrack({ partial = true, query = true, lookup = true, meta = false, consumed = false } = {}) {
|
|
44
44
|
const track = {}
|
|
45
45
|
if (lookup) {
|
|
46
46
|
// Lookups a TEMPLATE made by name: runtime.href('/contacts'),
|
|
@@ -100,6 +100,28 @@ export function createTrack({ partial = true, query = true, lookup = true, meta
|
|
|
100
100
|
track.metaReads = metaReads
|
|
101
101
|
track.metaRead = (path) => { if (path) metaReads.add(path) }
|
|
102
102
|
}
|
|
103
|
+
if (consumed) {
|
|
104
|
+
// Which fields of OTHER entities this render read.
|
|
105
|
+
//
|
|
106
|
+
// `metaReads` covers the entity being rendered. This covers the ones it
|
|
107
|
+
// pulled in — a page reads a navigation document through a query and
|
|
108
|
+
// then takes `items[].label` off it, and until now nothing recorded
|
|
109
|
+
// that. The consequence is that a document which never renders had no
|
|
110
|
+
// derivable contract at all: the engine knew THAT a page queried it,
|
|
111
|
+
// never which of its keys mattered.
|
|
112
|
+
//
|
|
113
|
+
// Keyed by the consumed entity's id, because the answer is per-entity:
|
|
114
|
+
// "what does anything read from navigation.yml" is the question, and it
|
|
115
|
+
// is asked from the other side.
|
|
116
|
+
const consumedReads = new Map()
|
|
117
|
+
track.consumedReads = consumedReads
|
|
118
|
+
track.consumedRead = (id, path) => {
|
|
119
|
+
if (!id || !path) return
|
|
120
|
+
let paths = consumedReads.get(id)
|
|
121
|
+
if (!paths) consumedReads.set(id, paths = new Set())
|
|
122
|
+
paths.add(path)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
103
125
|
if (query) {
|
|
104
126
|
const queries = []
|
|
105
127
|
const queryKeys = new Set()
|
|
@@ -261,6 +283,8 @@ export function serializeTrack(track) {
|
|
|
261
283
|
queries: track.queries ? [...track.queries] : undefined,
|
|
262
284
|
metaReads: track.metaReads ? [...track.metaReads] : undefined,
|
|
263
285
|
lookups: track.lookups ? [...track.lookups].map(([k, v]) => [k, [...v]]) : undefined,
|
|
286
|
+
consumedReads: track.consumedReads
|
|
287
|
+
? [...track.consumedReads].map(([k, v]) => [k, [...v]]) : undefined,
|
|
264
288
|
}
|
|
265
289
|
}
|
|
266
290
|
|
|
@@ -273,5 +297,20 @@ export function mergeTrack(target, data) {
|
|
|
273
297
|
for (const q of data.queries ?? []) target.query?.(q)
|
|
274
298
|
for (const m of data.metaReads ?? []) target.metaRead?.(m)
|
|
275
299
|
for (const [name, ids] of data.lookups ?? []) target.lookup?.(name, ids)
|
|
300
|
+
for (const [id, paths] of data.consumedReads ?? []) {
|
|
301
|
+
for (const path of paths) target.consumedRead?.(id, path)
|
|
302
|
+
}
|
|
276
303
|
return target
|
|
277
304
|
}
|
|
305
|
+
|
|
306
|
+
// A queried entity, wrapped so what a render reads off it is recorded against
|
|
307
|
+
// THAT entity rather than the one being rendered.
|
|
308
|
+
//
|
|
309
|
+
// Only `meta` is wrapped: id, name and uri are how the caller identifies the
|
|
310
|
+
// row, not content, and recording them would bury the keys that matter. Returns
|
|
311
|
+
// the entity untouched when there is nothing to record into, so a call site can
|
|
312
|
+
// wrap unconditionally.
|
|
313
|
+
export function observeConsumed(entity, track) {
|
|
314
|
+
if (!track?.consumedRead || !entity?.id || !entity.meta || typeof entity.meta !== 'object') return entity
|
|
315
|
+
return { ...entity, meta: recordReads(entity.meta, '', (path) => track.consumedRead(entity.id, path)) }
|
|
316
|
+
}
|