mikser-io 9.22.0 → 9.23.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/docs/diagnostics.md +36 -5
- package/package.json +1 -1
- package/src/engine.js +14 -1
- package/src/manifest.js +45 -5
- package/src/report.js +14 -4
package/docs/diagnostics.md
CHANGED
|
@@ -115,8 +115,34 @@ Four buckets, and the distinction between them is the point:
|
|
|
115
115
|
`never-rendered`, `inputs-changed`, `ref-changed`, `query-matched`,
|
|
116
116
|
`cache-disabled`, `postprocessor`, `force`, `no-manifest`.
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
Each reason carries the detail behind it, so the answer does not require a
|
|
119
|
+
database query. The key is per-reason — `changed`, `matched`,
|
|
120
|
+
`dependency` — because each means something specific:
|
|
121
|
+
|
|
122
|
+
| reason | detail | says |
|
|
123
|
+
| --- | --- | --- |
|
|
124
|
+
| `inputs-changed` | `changed: ["meta.title"]` | which of the entity's own inputs moved |
|
|
125
|
+
| `query-matched` | `matched: { filter, by }` | which query fired, and which mutated entity tripped it |
|
|
126
|
+
| `ref-changed` | `dependency: { kind, target, key, cause }` | which dependency, and whether it `changed`, was `deleted`, or was `unhashed` (nothing resolved when the edge was recorded) |
|
|
127
|
+
|
|
128
|
+
`query-matched` is the one worth reading. It fires on pages you were not
|
|
129
|
+
thinking about — a listing whose filter happens to cover the document you
|
|
130
|
+
just edited — and on a page with a dozen query edges, *which* one fired is
|
|
131
|
+
the whole question:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{ "destination": "/index.html", "reason": "query-matched",
|
|
135
|
+
"matched": { "filter": { "id": { "$regex": "^/documents/devices/" } },
|
|
136
|
+
"by": "/documents/devices/hera.md" } }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
A `matched.filter` of `null` is a different statement: the page's predicate
|
|
140
|
+
could not be serialized (`findEntities()` with no argument, or a function
|
|
141
|
+
filter), so it re-renders on **any** mutation. That is not a query that
|
|
142
|
+
matched — it is a page with no filter, and narrowing it is the fix the
|
|
143
|
+
catalog already warns about at record time.
|
|
144
|
+
|
|
145
|
+
`inputs-changed` carries a `changed` array naming **which** input moved:
|
|
120
146
|
|
|
121
147
|
```json
|
|
122
148
|
{ "id": "/files/hero.jpg", "reason": "inputs-changed", "changed": ["checksum"] }
|
|
@@ -132,9 +158,14 @@ reads as `meta.weight (added)` / `(removed)`, which is the answer when a
|
|
|
132
158
|
document gains or loses front-matter.
|
|
133
159
|
|
|
134
160
|
The array is absent on a first render — there is no prior snapshot to
|
|
135
|
-
compare against
|
|
136
|
-
|
|
137
|
-
|
|
161
|
+
compare against. It never appears alongside `matched` or `dependency`
|
|
162
|
+
either: a consumer switches on `reason` and reads one field, so a stray
|
|
163
|
+
key from another branch would make that switch wrong.
|
|
164
|
+
|
|
165
|
+
The same detail appears at `--debug` for a watch run, one line per render.
|
|
166
|
+
It is deliberately not in the build's normal output — the counts are the
|
|
167
|
+
summary and `--json` is the record — but when you are watching one page
|
|
168
|
+
misbehave, the trigger is the point.
|
|
138
169
|
|
|
139
170
|
`unchanged` is the interesting one. It means invalidation was coarser
|
|
140
171
|
than it needed to be — the render was scheduled, ran, and produced
|
package/package.json
CHANGED
package/src/engine.js
CHANGED
|
@@ -374,7 +374,20 @@ export async function setup(options) {
|
|
|
374
374
|
logger.debug('Manifest skip: %s → %s', entity.name || entity.id, entity.destination)
|
|
375
375
|
return
|
|
376
376
|
}
|
|
377
|
-
reportRendered(entity, decision.reason, decision
|
|
377
|
+
reportRendered(entity, decision.reason, decision)
|
|
378
|
+
// Same detail at debug, for tailing a watch run. One line per
|
|
379
|
+
// render is too much for a build's normal output — the counts
|
|
380
|
+
// are the summary and --json is the record — but when you are
|
|
381
|
+
// watching one page misbehave, the trigger is the whole point.
|
|
382
|
+
if (logger.isLevelEnabled?.('debug') ?? true) {
|
|
383
|
+
logger.debug('Render %s: %s%s', entity.id, decision.reason,
|
|
384
|
+
decision.changed?.length ? ` (${decision.changed.join(', ')})`
|
|
385
|
+
: decision.matched ? ` (${JSON.stringify(decision.matched.filter)}`
|
|
386
|
+
+ `${decision.matched.by ? ` ← ${decision.matched.by}` : ''})`
|
|
387
|
+
: decision.dependency ? ` (${decision.dependency.kind} `
|
|
388
|
+
+ `${decision.dependency.target} ${decision.dependency.cause})`
|
|
389
|
+
: '')
|
|
390
|
+
}
|
|
378
391
|
// Project reference-marker keys (`$author`, `$hero`, …)
|
|
379
392
|
// into their normalized form (`author`, `hero`) before
|
|
380
393
|
// the entity crosses into the renderer — applies whether
|
package/src/manifest.js
CHANGED
|
@@ -389,11 +389,36 @@ export function createManifest(db) {
|
|
|
389
389
|
const sourceLang = entity?.meta?.lang ?? null
|
|
390
390
|
for (const entry of snapshot.refClosure) {
|
|
391
391
|
if (entry.kind === 'query') {
|
|
392
|
-
|
|
392
|
+
// A null filter is the sentinel for a predicate that
|
|
393
|
+
// could not be serialized — findEntities() with no
|
|
394
|
+
// argument, or a function filter. It re-renders on ANY
|
|
395
|
+
// mutation, which is a materially different situation
|
|
396
|
+
// from "this specific query matched" and is reported as
|
|
397
|
+
// such: an aggregate page that always re-renders is a
|
|
398
|
+
// filter worth narrowing, which is what catalog.js
|
|
399
|
+
// already warns about at record time.
|
|
400
|
+
if (!entry.filter) {
|
|
401
|
+
return {
|
|
402
|
+
skip: false,
|
|
403
|
+
reason: 'query-matched',
|
|
404
|
+
matched: { filter: null, by: null },
|
|
405
|
+
}
|
|
406
|
+
}
|
|
393
407
|
if (!mutatedEntities?.size) continue
|
|
394
408
|
const matcher = sift(entry.filter)
|
|
395
409
|
for (const mutated of mutatedEntities.values()) {
|
|
396
|
-
|
|
410
|
+
// Both the filter and the entity that tripped it are
|
|
411
|
+
// live here. Reporting only "a query matched" throws
|
|
412
|
+
// away the two facts the reader needs — on a page with
|
|
413
|
+
// eighteen query edges, which one fired and what set
|
|
414
|
+
// it off is the entire question.
|
|
415
|
+
if (matcher(mutated)) {
|
|
416
|
+
return {
|
|
417
|
+
skip: false,
|
|
418
|
+
reason: 'query-matched',
|
|
419
|
+
matched: { filter: entry.filter, by: mutated.id ?? null },
|
|
420
|
+
}
|
|
421
|
+
}
|
|
397
422
|
}
|
|
398
423
|
continue
|
|
399
424
|
}
|
|
@@ -433,11 +458,26 @@ export function createManifest(db) {
|
|
|
433
458
|
continue
|
|
434
459
|
}
|
|
435
460
|
}
|
|
436
|
-
|
|
461
|
+
// Which dependency, and why. Three distinct causes reach
|
|
462
|
+
// the same reason, and the difference is what the reader
|
|
463
|
+
// is after: a layout whose bytes moved, a $-ref whose
|
|
464
|
+
// target was deleted, and an edge recorded with no hash
|
|
465
|
+
// are three different things to go and look at.
|
|
466
|
+
//
|
|
467
|
+
// unhashed nothing resolved when the edge was recorded,
|
|
468
|
+
// so any mutation of that name re-renders
|
|
469
|
+
// deleted the target is gone from the catalog
|
|
470
|
+
// changed the target's own input hash moved
|
|
471
|
+
const dependency = (cause) => ({
|
|
472
|
+
skip: false,
|
|
473
|
+
reason: 'ref-changed',
|
|
474
|
+
dependency: { kind: entry.kind, target: entry.target, key, cause },
|
|
475
|
+
})
|
|
476
|
+
if (!entry.hash) return dependency('unhashed')
|
|
437
477
|
const currentHash = currentHashes?.get(key)
|
|
438
478
|
if (currentHash === undefined) continue
|
|
439
|
-
if (currentHash === null) return
|
|
440
|
-
if (currentHash !== entry.hash) return
|
|
479
|
+
if (currentHash === null) return dependency('deleted')
|
|
480
|
+
if (currentHash !== entry.hash) return dependency('changed')
|
|
441
481
|
}
|
|
442
482
|
}
|
|
443
483
|
return { skip: true, reason: 'unchanged' }
|
package/src/report.js
CHANGED
|
@@ -28,15 +28,25 @@ export function reportGated(count = 1) {
|
|
|
28
28
|
store().gated += count
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
// `decision` is the skipDecision that led here. Its detail travels with the
|
|
32
|
+
// reason, because a reason on its own answers a question nobody asked: WHICH
|
|
33
|
+
// input moved, WHICH query matched and what tripped it, WHICH dependency
|
|
34
|
+
// changed and how. All of it is in hand where the decision is made, and a
|
|
35
|
+
// consumer that has to go to the database for it is one the report failed.
|
|
36
|
+
//
|
|
37
|
+
// Detail keys are per-reason rather than one generic field — `changed`,
|
|
38
|
+
// `matched`, `dependency` each mean something specific, and a single
|
|
39
|
+
// polymorphic key would push the type switch onto every consumer.
|
|
40
|
+
export function reportRendered(entity, reason, decision = {}) {
|
|
32
41
|
if (!runtime.options?.json) return
|
|
33
42
|
store().rendered.push({
|
|
34
43
|
id: entity?.id,
|
|
35
44
|
destination: entity?.destination ?? null,
|
|
36
45
|
reason,
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
...(
|
|
46
|
+
// Omitted rather than empty, so presence is meaningful.
|
|
47
|
+
...(decision.changed?.length ? { changed: decision.changed } : {}),
|
|
48
|
+
...(decision.matched ? { matched: decision.matched } : {}),
|
|
49
|
+
...(decision.dependency ? { dependency: decision.dependency } : {}),
|
|
40
50
|
})
|
|
41
51
|
}
|
|
42
52
|
|