@fougere/core 0.4.0-alpha.0 → 0.5.0-alpha.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.
- package/dist/boot/Emissions.js +1 -1
- package/dist/boot/Emissions.js.map +1 -1
- package/dist/boot/binding.d.ts +1 -1
- package/dist/boot/binding.d.ts.map +1 -1
- package/dist/boot/bootstrap.d.ts.map +1 -1
- package/dist/boot/bootstrap.js +12 -4
- package/dist/boot/bootstrap.js.map +1 -1
- package/dist/boot/hosted.d.ts +29 -0
- package/dist/boot/hosted.d.ts.map +1 -0
- package/dist/boot/hosted.js +38 -0
- package/dist/boot/hosted.js.map +1 -0
- package/dist/boot/ownership.d.ts +0 -3
- package/dist/boot/ownership.d.ts.map +1 -1
- package/dist/boot/ownership.js.map +1 -1
- package/dist/boot/remote.d.ts +1 -1
- package/dist/boot/remote.d.ts.map +1 -1
- package/dist/boot/remote.js +1 -1
- package/dist/boot/remote.js.map +1 -1
- package/dist/boot/statement-drift.d.ts +24 -0
- package/dist/boot/statement-drift.d.ts.map +1 -0
- package/dist/boot/statement-drift.js +38 -0
- package/dist/boot/statement-drift.js.map +1 -0
- package/dist/boot/types.d.ts +15 -2
- package/dist/boot/types.d.ts.map +1 -1
- package/dist/contract/CallLog.d.ts +57 -0
- package/dist/contract/CallLog.d.ts.map +1 -0
- package/dist/contract/CallLog.js +2 -0
- package/dist/contract/CallLog.js.map +1 -0
- package/dist/contract.d.ts +8 -3
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +9 -2
- package/dist/contract.js.map +1 -1
- package/dist/dispatch/DispatchLifecycle.d.ts +9 -1
- package/dist/dispatch/DispatchLifecycle.d.ts.map +1 -1
- package/dist/dispatch/DispatchLifecycle.js +17 -2
- package/dist/dispatch/DispatchLifecycle.js.map +1 -1
- package/dist/dispatch/InFlight.d.ts +0 -1
- package/dist/dispatch/InFlight.d.ts.map +1 -1
- package/dist/dispatch/InFlight.js +0 -3
- package/dist/dispatch/InFlight.js.map +1 -1
- package/dist/effective-operation.d.ts.map +1 -1
- package/dist/effective-operation.js +4 -0
- package/dist/effective-operation.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +4 -2
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +4 -2
- package/dist/node.js.map +1 -1
- package/dist/scan/bundling.d.ts +14 -0
- package/dist/scan/bundling.d.ts.map +1 -0
- package/dist/scan/bundling.js +26 -0
- package/dist/scan/bundling.js.map +1 -0
- package/dist/scan/conventions.d.ts +8 -12
- package/dist/scan/conventions.d.ts.map +1 -1
- package/dist/scan/conventions.js +0 -13
- package/dist/scan/conventions.js.map +1 -1
- package/dist/scan/declare.d.ts +71 -0
- package/dist/scan/declare.d.ts.map +1 -0
- package/dist/scan/declare.js +125 -0
- package/dist/scan/declare.js.map +1 -0
- package/dist/scan/handler-parser.js +0 -1
- package/dist/scan/handler-parser.js.map +1 -1
- package/dist/scan/scanner.d.ts +17 -0
- package/dist/scan/scanner.d.ts.map +1 -1
- package/dist/scan/scanner.js +17 -1
- package/dist/scan/scanner.js.map +1 -1
- package/dist/scan/statement.d.ts +4 -0
- package/dist/scan/statement.d.ts.map +1 -0
- package/dist/scan/statement.js +112 -0
- package/dist/scan/statement.js.map +1 -0
- package/dist/wire/call.d.ts +12 -1
- package/dist/wire/call.d.ts.map +1 -1
- package/dist/wire/call.js +11 -0
- package/dist/wire/call.js.map +1 -1
- package/dist/wire/drift.d.ts +55 -0
- package/dist/wire/drift.d.ts.map +1 -0
- package/dist/wire/drift.js +121 -0
- package/dist/wire/drift.js.map +1 -0
- package/dist/wire/middleware.d.ts +1 -1
- package/dist/wire/middleware.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/boot/Emissions.ts +1 -1
- package/src/boot/HandlerFacade.ts +1 -1
- package/src/boot/binding.ts +1 -1
- package/src/boot/bootstrap.ts +12 -4
- package/src/boot/hosted.ts +77 -0
- package/src/boot/ownership.ts +1 -4
- package/src/boot/remote.ts +1 -1
- package/src/boot/statement-drift.ts +60 -0
- package/src/boot/types.ts +15 -2
- package/src/contract/CallLog.ts +55 -0
- package/src/contract.ts +12 -3
- package/src/dispatch/DispatchLifecycle.ts +22 -2
- package/src/dispatch/InFlight.ts +0 -4
- package/src/effective-operation.ts +4 -0
- package/src/index.ts +16 -2
- package/src/node.ts +4 -2
- package/src/scan/bundling.ts +25 -0
- package/src/scan/conventions.ts +0 -18
- package/src/scan/declare.ts +188 -0
- package/src/scan/handler-parser.ts +0 -1
- package/src/scan/scanner.ts +22 -1
- package/src/scan/statement.ts +126 -0
- package/src/wire/call.ts +13 -1
- package/src/wire/drift.ts +131 -0
- package/src/wire/middleware.ts +1 -1
- package/dist/boot/Lifecycle.d.ts +0 -4
- package/dist/boot/Lifecycle.d.ts.map +0 -1
- package/dist/boot/Lifecycle.js +0 -3
- package/dist/boot/Lifecycle.js.map +0 -1
- package/dist/scan/scan-cache.d.ts +0 -16
- package/dist/scan/scan-cache.d.ts.map +0 -1
- package/dist/scan/scan-cache.js +0 -99
- package/dist/scan/scan-cache.js.map +0 -1
- package/dist/wire/invocation.d.ts +0 -4
- package/dist/wire/invocation.d.ts.map +0 -1
- package/dist/wire/invocation.js +0 -3
- package/dist/wire/invocation.js.map +0 -1
- package/src/boot/Lifecycle.ts +0 -3
- package/src/scan/scan-cache.ts +0 -115
- package/src/wire/invocation.ts +0 -10
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { Card, type Change, type SchemaDescriptor } from '@fougere/schema';
|
|
2
|
+
import type { IdentityCard } from './call.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* What separates the copy a consumer holds from what the producer actually serves.
|
|
6
|
+
*
|
|
7
|
+
* It lives beside `IdentityCard` and not in `@fougere/testing` for the reason the card
|
|
8
|
+
* itself does: the subject is the card, and a second reader arrived — the dev panel, which
|
|
9
|
+
* must not drag a 426 KB faker in to compare two of them.
|
|
10
|
+
*/
|
|
11
|
+
export interface CardDrift {
|
|
12
|
+
frond: string;
|
|
13
|
+
/** A door the consumer calls that the producer no longer serves. */
|
|
14
|
+
missingDoors: string[];
|
|
15
|
+
/** An operation the consumer calls that the door no longer has. */
|
|
16
|
+
missingOps: { door: string; ops: string[] }[];
|
|
17
|
+
/** A shape that moved under a door the consumer still calls. */
|
|
18
|
+
shapes: { door: string; changes: Change[] }[];
|
|
19
|
+
/** A fact the consumer subscribes to whose shape moved, or that is gone. */
|
|
20
|
+
facts: { fact: string; changes: Change[] | 'gone' }[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Every door of a card, by name. */
|
|
24
|
+
function doorsOf(card: IdentityCard, frond: string): Map<string, { ops: Set<string>; schema?: SchemaDescriptor }> {
|
|
25
|
+
const found = new Map<string, { ops: Set<string>; schema?: SchemaDescriptor }>();
|
|
26
|
+
for (const one of card.fronds) {
|
|
27
|
+
if (one.name !== frond) continue;
|
|
28
|
+
for (const door of one.doors) {
|
|
29
|
+
found.set(door.name, { ops: new Set(door.ops.map((op) => op.name)), schema: door.schema });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return found;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function factsOf(card: IdentityCard, frond: string): Map<string, SchemaDescriptor | undefined> {
|
|
36
|
+
const found = new Map<string, SchemaDescriptor | undefined>();
|
|
37
|
+
for (const one of card.fronds) {
|
|
38
|
+
if (one.name !== frond) continue;
|
|
39
|
+
for (const fact of one.facts ?? []) found.set(fact.name, fact.schema as SchemaDescriptor | undefined);
|
|
40
|
+
}
|
|
41
|
+
return found;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* What a consumer's synced copy no longer matches in what the producer serves.
|
|
46
|
+
*
|
|
47
|
+
* The gap TypeScript cannot see, and the only place the gradient genuinely lies: the code
|
|
48
|
+
* is identical in-process and split, but one side may have aged. `fougere sync` wrote the
|
|
49
|
+
* consumer's copy three weeks ago, the producer moved on, and it still compiles —
|
|
50
|
+
* production is where that is found today. This is what Pact sells; the material was
|
|
51
|
+
* already here, in `rpc.discover` and in `Card.diff`.
|
|
52
|
+
*
|
|
53
|
+
* Read in ONE direction on purpose: what the consumer holds, checked against what is
|
|
54
|
+
* served. A producer serving MORE than the consumer knows is not drift — it is a producer
|
|
55
|
+
* that moved forward without breaking anyone, which is the whole point of the order the
|
|
56
|
+
* repo already states (re-sync the readers, then deploy the sender).
|
|
57
|
+
*/
|
|
58
|
+
export function driftOf(mine: IdentityCard, theirs: IdentityCard, frond: string): CardDrift {
|
|
59
|
+
const held = doorsOf(mine, frond);
|
|
60
|
+
const served = doorsOf(theirs, frond);
|
|
61
|
+
const drift: CardDrift = { frond, missingDoors: [], missingOps: [], shapes: [], facts: [] };
|
|
62
|
+
|
|
63
|
+
for (const [name, door] of held) {
|
|
64
|
+
const there = served.get(name);
|
|
65
|
+
if (!there) { drift.missingDoors.push(name); continue; }
|
|
66
|
+
|
|
67
|
+
const missing = [...door.ops].filter((op) => !there.ops.has(op));
|
|
68
|
+
if (missing.length > 0) drift.missingOps.push({ door: name, ops: missing.sort() });
|
|
69
|
+
|
|
70
|
+
if (door.schema && there.schema) {
|
|
71
|
+
// `Card.diff` never guesses a rename — a field gone plus a field appeared lands in
|
|
72
|
+
// `ambiguous`, and only a declaration settles it. Here nobody can declare one, so
|
|
73
|
+
// the pair is reported as it is and a human reads it.
|
|
74
|
+
const moved = Card.fromDescriptor(door.schema).diff(Card.fromDescriptor(there.schema));
|
|
75
|
+
if (moved.changes.length > 0) drift.shapes.push({ door: name, changes: moved.changes });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const heldFacts = factsOf(mine, frond);
|
|
80
|
+
const servedFacts = factsOf(theirs, frond);
|
|
81
|
+
for (const [name, shape] of heldFacts) {
|
|
82
|
+
if (!servedFacts.has(name)) { drift.facts.push({ fact: name, changes: 'gone' }); continue; }
|
|
83
|
+
const there = servedFacts.get(name);
|
|
84
|
+
if (!shape || !there) continue;
|
|
85
|
+
const moved = Card.fromDescriptor(shape).diff(Card.fromDescriptor(there));
|
|
86
|
+
if (moved.changes.length > 0) drift.facts.push({ fact: name, changes: moved.changes });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return drift;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Whether anything at all separates the two cards. */
|
|
93
|
+
export function agrees(drift: CardDrift): boolean {
|
|
94
|
+
return drift.missingDoors.length === 0
|
|
95
|
+
&& drift.missingOps.length === 0
|
|
96
|
+
&& drift.shapes.length === 0
|
|
97
|
+
&& drift.facts.length === 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The drift, in the words a deploy needs.
|
|
102
|
+
*
|
|
103
|
+
* A fact says the order out loud, because the repo already states it as a rule and
|
|
104
|
+
* nothing enforced it: a fact is judged strictly, so a reader that has not been re-synced
|
|
105
|
+
* refuses what the sender now announces.
|
|
106
|
+
*/
|
|
107
|
+
export function explain(drift: CardDrift): string[] {
|
|
108
|
+
const lines: string[] = [];
|
|
109
|
+
for (const door of drift.missingDoors) lines.push(`${drift.frond}.${door} — you call it, it is not served`);
|
|
110
|
+
for (const { door, ops } of drift.missingOps) lines.push(`${drift.frond}.${door} — gone: ${ops.join(', ')}`);
|
|
111
|
+
for (const { door, changes } of drift.shapes) {
|
|
112
|
+
for (const change of changes) lines.push(`${drift.frond}.${door} — ${describe(change)}`);
|
|
113
|
+
}
|
|
114
|
+
for (const { fact, changes } of drift.facts) {
|
|
115
|
+
if (changes === 'gone') { lines.push(`${fact} — you subscribe to it, it is no longer announced`); continue; }
|
|
116
|
+
for (const change of changes) lines.push(`${fact} — ${describe(change)} → re-sync and deploy the readers, THEN the sender`);
|
|
117
|
+
}
|
|
118
|
+
return lines;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function describe(change: Change): string {
|
|
122
|
+
switch (change.kind) {
|
|
123
|
+
case 'added': return `+ ${change.field}${change.required ? ' (required)' : ''}`;
|
|
124
|
+
case 'removed': return `- ${change.field}`;
|
|
125
|
+
case 'renamed': return `${change.from} → ${change.to}`;
|
|
126
|
+
case 'retyped': return `${change.field}: ${[...change.from].join('|')} → ${[...change.to].join('|')}`;
|
|
127
|
+
case 'reshaped': return `${change.field}: its bounds moved`;
|
|
128
|
+
case 'required': return `${change.field}: ${change.from ? 'no longer' : 'now'} required`;
|
|
129
|
+
case 'restated': return `${change.field}: its ${change.axis} moved`;
|
|
130
|
+
}
|
|
131
|
+
}
|
package/src/wire/middleware.ts
CHANGED
|
@@ -20,7 +20,7 @@ export interface OperationContext {
|
|
|
20
20
|
/** Extensible bag — middlewares deposit data here (user, permissions, etc.). */
|
|
21
21
|
state: Record<string, unknown>;
|
|
22
22
|
/** Transport-agnostic invocation context (params, query, body, state). */
|
|
23
|
-
invocation?: import('
|
|
23
|
+
invocation?: import('../contract/Invocation.js').InvocationContext;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export type AppNext = () => Promise<unknown>;
|
package/dist/boot/Lifecycle.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Lifecycle.d.ts","sourceRoot":"","sources":["../../src/boot/Lifecycle.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,OAAO,EAAE,YAAY,EAAE,YAAY,IAAI,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACvF,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/boot/Lifecycle.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Lifecycle.js","sourceRoot":"","sources":["../../src/boot/Lifecycle.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,OAAO,EAAE,YAAY,EAAE,YAAY,IAAI,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export declare function setCacheRoot(dir: string): void;
|
|
2
|
-
/**
|
|
3
|
-
* The parse of a file, or the parse again if the file changed — the one gesture, so a
|
|
4
|
-
* caller never assembles the pieces.
|
|
5
|
-
*
|
|
6
|
-
* It handed out `hashFile` + `getCached` + `setCached` and every caller wrote the same
|
|
7
|
-
* five lines in the same order; the scanner wrote them three times, once inline inside a
|
|
8
|
-
* `try`. Three assemblies of one question, each free to forget the half that stores.
|
|
9
|
-
*
|
|
10
|
-
* `key` and not the path alone: one file is parsed several ways (its constructor, its
|
|
11
|
-
* methods, its methods with heritage resolved) and each reading is its own entry.
|
|
12
|
-
*/
|
|
13
|
-
export declare function cachedParse<T>(key: string, filePath: string, parse: () => Promise<T>): Promise<T>;
|
|
14
|
-
/** Write cache to disk if modified. */
|
|
15
|
-
export declare function flushCache(): void;
|
|
16
|
-
//# sourceMappingURL=scan-cache.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scan-cache.d.ts","sourceRoot":"","sources":["../../src/scan/scan-cache.ts"],"names":[],"mappings":"AAqDA,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAG9C;AA2BD;;;;;;;;;;GAUG;AACH,wBAAsB,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAUvG;AAED,uCAAuC;AACvC,wBAAgB,UAAU,IAAI,IAAI,CAOjC"}
|
package/dist/scan/scan-cache.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A parse kept per file, so an unchanged source never loads TypeScript again.
|
|
3
|
-
*
|
|
4
|
-
* The envelope carries PARSER_VERSION, and a mismatch drops the whole file. The hash
|
|
5
|
-
* answers "did this source change?"; it cannot answer "does the parser still read it the
|
|
6
|
-
* same way?". A parser fix therefore left every unchanged handler serving the OLD parse,
|
|
7
|
-
* indefinitely and without a word — that is how `Crud`'s inherited ops stayed silently
|
|
8
|
-
* underived for weeks, until someone deleted `.fougere/` by hand and the tests went red.
|
|
9
|
-
*
|
|
10
|
-
* Bump PARSER_VERSION whenever the parser's OUTPUT for unchanged source can differ.
|
|
11
|
-
*/
|
|
12
|
-
import { createHash } from 'node:crypto';
|
|
13
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
14
|
-
import { join } from 'node:path';
|
|
15
|
-
/**
|
|
16
|
-
* Bump when the parser can read unchanged source differently.
|
|
17
|
-
*
|
|
18
|
-
* 2 — `handler-parser` unwraps a class returned through a call/assertion, so a mixin
|
|
19
|
-
* like `asCrudConstructor(class { … })` yields its methods again.
|
|
20
|
-
* 3 — parsed array types carry `arrayDepth`, used to distinguish a presenter page
|
|
21
|
-
* returning scalar fields from one returning list fields.
|
|
22
|
-
*/
|
|
23
|
-
// 4 — the parse became a pair (methods + unresolvedHeritage); a v3 entry
|
|
24
|
-
// would answer with an array and lose the half that reports what it could not open.
|
|
25
|
-
// 5 — a method carried `readOnly`, read off its body. REMOVED (2026-08-16): it answered
|
|
26
|
-
// "does this touch storage", not "is this a read", so a read that wrote an audit row
|
|
27
|
-
// was published as a mutation. The number stays taken; going back to 4 would revive
|
|
28
|
-
// a v4 cache written before 5, which is the same shape but not the same history.
|
|
29
|
-
// 7 — parsed types carry checked, alias-resolved meaning and distinguish `undefined`
|
|
30
|
-
// from `null`; a v6 entry contains only the syntax that happened to be written.
|
|
31
|
-
const PARSER_VERSION = 7;
|
|
32
|
-
let root = process.cwd();
|
|
33
|
-
let cache = null;
|
|
34
|
-
let dirty = false;
|
|
35
|
-
function cachePath() {
|
|
36
|
-
return join(root, '.fougere', 'scan-cache.json');
|
|
37
|
-
}
|
|
38
|
-
export function setCacheRoot(dir) {
|
|
39
|
-
root = dir;
|
|
40
|
-
cache = null;
|
|
41
|
-
}
|
|
42
|
-
function load() {
|
|
43
|
-
if (cache)
|
|
44
|
-
return cache;
|
|
45
|
-
const path = cachePath();
|
|
46
|
-
if (existsSync(path)) {
|
|
47
|
-
try {
|
|
48
|
-
const file = JSON.parse(readFileSync(path, 'utf-8'));
|
|
49
|
-
// A stamp from another parser is not stale data to refresh entry by entry — every
|
|
50
|
-
// entry in the file was produced by it. Drop the lot; the next scan is cold once.
|
|
51
|
-
if (file.parser === PARSER_VERSION && file.entries) {
|
|
52
|
-
cache = file.entries;
|
|
53
|
-
return cache;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
// Corrupted cache — start fresh
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
cache = {};
|
|
61
|
-
return cache;
|
|
62
|
-
}
|
|
63
|
-
function hashFile(filePath) {
|
|
64
|
-
const content = readFileSync(filePath, 'utf-8');
|
|
65
|
-
return createHash('sha1').update(content).digest('hex').slice(0, 16);
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* The parse of a file, or the parse again if the file changed — the one gesture, so a
|
|
69
|
-
* caller never assembles the pieces.
|
|
70
|
-
*
|
|
71
|
-
* It handed out `hashFile` + `getCached` + `setCached` and every caller wrote the same
|
|
72
|
-
* five lines in the same order; the scanner wrote them three times, once inline inside a
|
|
73
|
-
* `try`. Three assemblies of one question, each free to forget the half that stores.
|
|
74
|
-
*
|
|
75
|
-
* `key` and not the path alone: one file is parsed several ways (its constructor, its
|
|
76
|
-
* methods, its methods with heritage resolved) and each reading is its own entry.
|
|
77
|
-
*/
|
|
78
|
-
export async function cachedParse(key, filePath, parse) {
|
|
79
|
-
const hash = hashFile(filePath);
|
|
80
|
-
const store = load();
|
|
81
|
-
const entry = store[key];
|
|
82
|
-
if (entry && entry.hash === hash)
|
|
83
|
-
return entry.data;
|
|
84
|
-
const data = await parse();
|
|
85
|
-
store[key] = { hash, data };
|
|
86
|
-
dirty = true;
|
|
87
|
-
return data;
|
|
88
|
-
}
|
|
89
|
-
/** Write cache to disk if modified. */
|
|
90
|
-
export function flushCache() {
|
|
91
|
-
if (!dirty || !cache)
|
|
92
|
-
return;
|
|
93
|
-
const path = cachePath();
|
|
94
|
-
mkdirSync(join(root, '.fougere'), { recursive: true });
|
|
95
|
-
const file = { parser: PARSER_VERSION, entries: cache };
|
|
96
|
-
writeFileSync(path, JSON.stringify(file, null, 2));
|
|
97
|
-
dirty = false;
|
|
98
|
-
}
|
|
99
|
-
//# sourceMappingURL=scan-cache.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scan-cache.js","sourceRoot":"","sources":["../../src/scan/scan-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
/** Compatibility entry point. New code imports the invocation value from contract. */
|
|
2
|
-
export { Invocation, EMPTY_INVOCATION, canonicalInvocation, } from '../contract/Invocation.js';
|
|
3
|
-
export type { InvocationContext, InvocationInput, } from '../contract/Invocation.js';
|
|
4
|
-
//# sourceMappingURL=invocation.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"invocation.d.ts","sourceRoot":"","sources":["../../src/wire/invocation.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,iBAAiB,EACjB,eAAe,GAChB,MAAM,2BAA2B,CAAC"}
|
package/dist/wire/invocation.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"invocation.js","sourceRoot":"","sources":["../../src/wire/invocation.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,2BAA2B,CAAC"}
|
package/src/boot/Lifecycle.ts
DELETED
package/src/scan/scan-cache.ts
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A parse kept per file, so an unchanged source never loads TypeScript again.
|
|
3
|
-
*
|
|
4
|
-
* The envelope carries PARSER_VERSION, and a mismatch drops the whole file. The hash
|
|
5
|
-
* answers "did this source change?"; it cannot answer "does the parser still read it the
|
|
6
|
-
* same way?". A parser fix therefore left every unchanged handler serving the OLD parse,
|
|
7
|
-
* indefinitely and without a word — that is how `Crud`'s inherited ops stayed silently
|
|
8
|
-
* underived for weeks, until someone deleted `.fougere/` by hand and the tests went red.
|
|
9
|
-
*
|
|
10
|
-
* Bump PARSER_VERSION whenever the parser's OUTPUT for unchanged source can differ.
|
|
11
|
-
*/
|
|
12
|
-
import { createHash } from 'node:crypto';
|
|
13
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
14
|
-
import { join } from 'node:path';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Bump when the parser can read unchanged source differently.
|
|
18
|
-
*
|
|
19
|
-
* 2 — `handler-parser` unwraps a class returned through a call/assertion, so a mixin
|
|
20
|
-
* like `asCrudConstructor(class { … })` yields its methods again.
|
|
21
|
-
* 3 — parsed array types carry `arrayDepth`, used to distinguish a presenter page
|
|
22
|
-
* returning scalar fields from one returning list fields.
|
|
23
|
-
*/
|
|
24
|
-
// 4 — the parse became a pair (methods + unresolvedHeritage); a v3 entry
|
|
25
|
-
// would answer with an array and lose the half that reports what it could not open.
|
|
26
|
-
// 5 — a method carried `readOnly`, read off its body. REMOVED (2026-08-16): it answered
|
|
27
|
-
// "does this touch storage", not "is this a read", so a read that wrote an audit row
|
|
28
|
-
// was published as a mutation. The number stays taken; going back to 4 would revive
|
|
29
|
-
// a v4 cache written before 5, which is the same shape but not the same history.
|
|
30
|
-
// 7 — parsed types carry checked, alias-resolved meaning and distinguish `undefined`
|
|
31
|
-
// from `null`; a v6 entry contains only the syntax that happened to be written.
|
|
32
|
-
const PARSER_VERSION = 7;
|
|
33
|
-
|
|
34
|
-
interface CacheEntry {
|
|
35
|
-
hash: string;
|
|
36
|
-
data: unknown;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface CacheFile {
|
|
40
|
-
parser: number;
|
|
41
|
-
entries: Record<string, CacheEntry>;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
type CacheData = Record<string, CacheEntry>;
|
|
45
|
-
|
|
46
|
-
let root = process.cwd();
|
|
47
|
-
let cache: CacheData | null = null;
|
|
48
|
-
let dirty = false;
|
|
49
|
-
|
|
50
|
-
function cachePath(): string {
|
|
51
|
-
return join(root, '.fougere', 'scan-cache.json');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function setCacheRoot(dir: string): void {
|
|
55
|
-
root = dir;
|
|
56
|
-
cache = null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function load(): CacheData {
|
|
60
|
-
if (cache) return cache;
|
|
61
|
-
const path = cachePath();
|
|
62
|
-
if (existsSync(path)) {
|
|
63
|
-
try {
|
|
64
|
-
const file = JSON.parse(readFileSync(path, 'utf-8')) as Partial<CacheFile>;
|
|
65
|
-
// A stamp from another parser is not stale data to refresh entry by entry — every
|
|
66
|
-
// entry in the file was produced by it. Drop the lot; the next scan is cold once.
|
|
67
|
-
if (file.parser === PARSER_VERSION && file.entries) {
|
|
68
|
-
cache = file.entries;
|
|
69
|
-
return cache;
|
|
70
|
-
}
|
|
71
|
-
} catch {
|
|
72
|
-
// Corrupted cache — start fresh
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
cache = {};
|
|
76
|
-
return cache;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function hashFile(filePath: string): string {
|
|
80
|
-
const content = readFileSync(filePath, 'utf-8');
|
|
81
|
-
return createHash('sha1').update(content).digest('hex').slice(0, 16);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* The parse of a file, or the parse again if the file changed — the one gesture, so a
|
|
86
|
-
* caller never assembles the pieces.
|
|
87
|
-
*
|
|
88
|
-
* It handed out `hashFile` + `getCached` + `setCached` and every caller wrote the same
|
|
89
|
-
* five lines in the same order; the scanner wrote them three times, once inline inside a
|
|
90
|
-
* `try`. Three assemblies of one question, each free to forget the half that stores.
|
|
91
|
-
*
|
|
92
|
-
* `key` and not the path alone: one file is parsed several ways (its constructor, its
|
|
93
|
-
* methods, its methods with heritage resolved) and each reading is its own entry.
|
|
94
|
-
*/
|
|
95
|
-
export async function cachedParse<T>(key: string, filePath: string, parse: () => Promise<T>): Promise<T> {
|
|
96
|
-
const hash = hashFile(filePath);
|
|
97
|
-
const store = load();
|
|
98
|
-
const entry = store[key];
|
|
99
|
-
if (entry && entry.hash === hash) return entry.data as T;
|
|
100
|
-
|
|
101
|
-
const data = await parse();
|
|
102
|
-
store[key] = { hash, data };
|
|
103
|
-
dirty = true;
|
|
104
|
-
return data;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Write cache to disk if modified. */
|
|
108
|
-
export function flushCache(): void {
|
|
109
|
-
if (!dirty || !cache) return;
|
|
110
|
-
const path = cachePath();
|
|
111
|
-
mkdirSync(join(root, '.fougere'), { recursive: true });
|
|
112
|
-
const file: CacheFile = { parser: PARSER_VERSION, entries: cache };
|
|
113
|
-
writeFileSync(path, JSON.stringify(file, null, 2));
|
|
114
|
-
dirty = false;
|
|
115
|
-
}
|
package/src/wire/invocation.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/** Compatibility entry point. New code imports the invocation value from contract. */
|
|
2
|
-
export {
|
|
3
|
-
Invocation,
|
|
4
|
-
EMPTY_INVOCATION,
|
|
5
|
-
canonicalInvocation,
|
|
6
|
-
} from '../contract/Invocation.js';
|
|
7
|
-
export type {
|
|
8
|
-
InvocationContext,
|
|
9
|
-
InvocationInput,
|
|
10
|
-
} from '../contract/Invocation.js';
|