@fougere/testing 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/remotes.d.ts CHANGED
@@ -1,49 +1,8 @@
1
- import { type Change } from '@fougere/schema';
2
- import type { IdentityCard } from '@fougere/core';
3
- /** What separates the copy a consumer holds from what the producer actually serves. */
4
- export interface CardDrift {
5
- frond: string;
6
- /** A door the consumer calls that the producer no longer serves. */
7
- missingDoors: string[];
8
- /** An operation the consumer calls that the door no longer has. */
9
- missingOps: {
10
- door: string;
11
- ops: string[];
12
- }[];
13
- /** A shape that moved under a door the consumer still calls. */
14
- shapes: {
15
- door: string;
16
- changes: Change[];
17
- }[];
18
- /** A fact the consumer subscribes to whose shape moved, or that is gone. */
19
- facts: {
20
- fact: string;
21
- changes: Change[] | 'gone';
22
- }[];
23
- }
24
1
  /**
25
- * What a consumer's synced copy no longer matches in what the producer serves.
2
+ * Contract drift moved to `@fougere/core`, beside the card it compares.
26
3
  *
27
- * The gap TypeScript cannot see, and the only place the gradient genuinely lies: the code
28
- * is identical in-process and split, but one side may have aged. `fougere sync` wrote the
29
- * consumer's copy three weeks ago, the producer moved on, and it still compiles —
30
- * production is where that is found today. This is what Pact sells; the material was
31
- * already here, in `rpc.discover` and in `Card.diff`.
32
- *
33
- * Read in ONE direction on purpose: what the consumer holds, checked against what is
34
- * served. A producer serving MORE than the consumer knows is not drift — it is a producer
35
- * that moved forward without breaking anyone, which is the whole point of the order the
36
- * repo already states (re-sync the readers, then deploy the sender).
37
- */
38
- export declare function driftOf(mine: IdentityCard, theirs: IdentityCard, frond: string): CardDrift;
39
- /** Whether anything at all separates the two cards. */
40
- export declare function agrees(drift: CardDrift): boolean;
41
- /**
42
- * The drift, in the words a deploy needs.
43
- *
44
- * A fact says the order out loud, because the repo already states it as a rule and
45
- * nothing enforced it: a fact is judged strictly, so a reader that has not been re-synced
46
- * refuses what the sender now announces.
4
+ * Republished here because a test states its subject by where it sits, and `agrees(driftOf(…))`
5
+ * is a testing gesture the function is core's, the assertion is this package's.
47
6
  */
48
- export declare function explain(drift: CardDrift): string[];
7
+ export { driftOf, agrees, explain, type CardDrift } from '@fougere/core';
49
8
  //# sourceMappingURL=remotes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"remotes.d.ts","sourceRoot":"","sources":["../src/remotes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,MAAM,EAAyB,MAAM,iBAAiB,CAAC;AAC3E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,uFAAuF;AACvF,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,mEAAmE;IACnE,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;IAC9C,gEAAgE;IAChE,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;IAC9C,4EAA4E;IAC5E,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAAA;KAAE,EAAE,CAAC;CACvD;AAuBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAgC1F;AAED,uDAAuD;AACvD,wBAAgB,MAAM,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAKhD;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,EAAE,CAYlD"}
1
+ {"version":3,"file":"remotes.d.ts","sourceRoot":"","sources":["../src/remotes.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC"}
package/dist/remotes.js CHANGED
@@ -1,121 +1,8 @@
1
- import { Card } from '@fougere/schema';
2
- /** Every door of a card, by name. */
3
- function doorsOf(card, frond) {
4
- const found = new Map();
5
- for (const one of card.fronds) {
6
- if (one.name !== frond)
7
- continue;
8
- for (const door of one.doors) {
9
- found.set(door.name, { ops: new Set(door.ops.map((op) => op.name)), schema: door.schema });
10
- }
11
- }
12
- return found;
13
- }
14
- function factsOf(card, frond) {
15
- const found = new Map();
16
- for (const one of card.fronds) {
17
- if (one.name !== frond)
18
- continue;
19
- for (const fact of one.facts ?? [])
20
- found.set(fact.name, fact.schema);
21
- }
22
- return found;
23
- }
24
1
  /**
25
- * What a consumer's synced copy no longer matches in what the producer serves.
2
+ * Contract drift moved to `@fougere/core`, beside the card it compares.
26
3
  *
27
- * The gap TypeScript cannot see, and the only place the gradient genuinely lies: the code
28
- * is identical in-process and split, but one side may have aged. `fougere sync` wrote the
29
- * consumer's copy three weeks ago, the producer moved on, and it still compiles —
30
- * production is where that is found today. This is what Pact sells; the material was
31
- * already here, in `rpc.discover` and in `Card.diff`.
32
- *
33
- * Read in ONE direction on purpose: what the consumer holds, checked against what is
34
- * served. A producer serving MORE than the consumer knows is not drift — it is a producer
35
- * that moved forward without breaking anyone, which is the whole point of the order the
36
- * repo already states (re-sync the readers, then deploy the sender).
37
- */
38
- export function driftOf(mine, theirs, frond) {
39
- const held = doorsOf(mine, frond);
40
- const served = doorsOf(theirs, frond);
41
- const drift = { frond, missingDoors: [], missingOps: [], shapes: [], facts: [] };
42
- for (const [name, door] of held) {
43
- const there = served.get(name);
44
- if (!there) {
45
- drift.missingDoors.push(name);
46
- continue;
47
- }
48
- const missing = [...door.ops].filter((op) => !there.ops.has(op));
49
- if (missing.length > 0)
50
- drift.missingOps.push({ door: name, ops: missing.sort() });
51
- if (door.schema && there.schema) {
52
- // `Card.diff` never guesses a rename — a field gone plus a field appeared lands in
53
- // `ambiguous`, and only a declaration settles it. Here nobody can declare one, so
54
- // the pair is reported as it is and a human reads it.
55
- const moved = Card.fromDescriptor(door.schema).diff(Card.fromDescriptor(there.schema));
56
- if (moved.changes.length > 0)
57
- drift.shapes.push({ door: name, changes: moved.changes });
58
- }
59
- }
60
- const heldFacts = factsOf(mine, frond);
61
- const servedFacts = factsOf(theirs, frond);
62
- for (const [name, shape] of heldFacts) {
63
- if (!servedFacts.has(name)) {
64
- drift.facts.push({ fact: name, changes: 'gone' });
65
- continue;
66
- }
67
- const there = servedFacts.get(name);
68
- if (!shape || !there)
69
- continue;
70
- const moved = Card.fromDescriptor(shape).diff(Card.fromDescriptor(there));
71
- if (moved.changes.length > 0)
72
- drift.facts.push({ fact: name, changes: moved.changes });
73
- }
74
- return drift;
75
- }
76
- /** Whether anything at all separates the two cards. */
77
- export function agrees(drift) {
78
- return drift.missingDoors.length === 0
79
- && drift.missingOps.length === 0
80
- && drift.shapes.length === 0
81
- && drift.facts.length === 0;
82
- }
83
- /**
84
- * The drift, in the words a deploy needs.
85
- *
86
- * A fact says the order out loud, because the repo already states it as a rule and
87
- * nothing enforced it: a fact is judged strictly, so a reader that has not been re-synced
88
- * refuses what the sender now announces.
4
+ * Republished here because a test states its subject by where it sits, and `agrees(driftOf(…))`
5
+ * is a testing gesture the function is core's, the assertion is this package's.
89
6
  */
90
- export function explain(drift) {
91
- const lines = [];
92
- for (const door of drift.missingDoors)
93
- lines.push(`${drift.frond}.${door} — you call it, it is not served`);
94
- for (const { door, ops } of drift.missingOps)
95
- lines.push(`${drift.frond}.${door} — gone: ${ops.join(', ')}`);
96
- for (const { door, changes } of drift.shapes) {
97
- for (const change of changes)
98
- lines.push(`${drift.frond}.${door} — ${describe(change)}`);
99
- }
100
- for (const { fact, changes } of drift.facts) {
101
- if (changes === 'gone') {
102
- lines.push(`${fact} — you subscribe to it, it is no longer announced`);
103
- continue;
104
- }
105
- for (const change of changes)
106
- lines.push(`${fact} — ${describe(change)} → re-sync and deploy the readers, THEN the sender`);
107
- }
108
- return lines;
109
- }
110
- function describe(change) {
111
- switch (change.kind) {
112
- case 'added': return `+ ${change.field}${change.required ? ' (required)' : ''}`;
113
- case 'removed': return `- ${change.field}`;
114
- case 'renamed': return `${change.from} → ${change.to}`;
115
- case 'retyped': return `${change.field}: ${[...change.from].join('|')} → ${[...change.to].join('|')}`;
116
- case 'reshaped': return `${change.field}: its bounds moved`;
117
- case 'required': return `${change.field}: ${change.from ? 'no longer' : 'now'} required`;
118
- case 'restated': return `${change.field}: its ${change.axis} moved`;
119
- }
120
- }
7
+ export { driftOf, agrees, explain } from '@fougere/core';
121
8
  //# sourceMappingURL=remotes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"remotes.js","sourceRoot":"","sources":["../src/remotes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAsC,MAAM,iBAAiB,CAAC;AAgB3E,qCAAqC;AACrC,SAAS,OAAO,CAAC,IAAkB,EAAE,KAAa;IAChD,MAAM,KAAK,GAAG,IAAI,GAAG,EAA2D,CAAC;IACjF,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAC9B,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK;YAAE,SAAS;QACjC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YAC7B,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAC7F,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,OAAO,CAAC,IAAkB,EAAE,KAAa;IAChD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAwC,CAAC;IAC9D,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAC9B,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK;YAAE,SAAS;QACjC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,IAAI,EAAE;YAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,MAAsC,CAAC,CAAC;IACxG,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,OAAO,CAAC,IAAkB,EAAE,MAAoB,EAAE,KAAa;IAC7E,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACtC,MAAM,KAAK,GAAc,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAE5F,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;QAChC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;YAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAAC,SAAS;QAAC,CAAC;QAExD,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QACjE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAEnF,IAAI,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YAChC,mFAAmF;YACnF,kFAAkF;YAClF,sDAAsD;YACtD,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;YACvF,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;gBAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1F,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACvC,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC3C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,SAAS,EAAE,CAAC;QACtC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YAAC,SAAS;QAAC,CAAC;QAC5F,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK;YAAE,SAAS;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1E,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IACzF,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,MAAM,CAAC,KAAgB;IACrC,OAAO,KAAK,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC;WACjC,KAAK,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC;WAC7B,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;WACzB,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC;AAChC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,KAAgB;IACtC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,YAAY;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,kCAAkC,CAAC,CAAC;IAC5G,KAAK,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,KAAK,CAAC,UAAU;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,YAAY,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7G,KAAK,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QAC7C,KAAK,MAAM,MAAM,IAAI,OAAO;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3F,CAAC;IACD,KAAK,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC5C,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;YAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,mDAAmD,CAAC,CAAC;YAAC,SAAS;QAAC,CAAC;QAC7G,KAAK,MAAM,MAAM,IAAI,OAAO;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,MAAM,QAAQ,CAAC,MAAM,CAAC,oDAAoD,CAAC,CAAC;IAC9H,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,QAAQ,CAAC,MAAc;IAC9B,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAChF,KAAK,SAAS,EAAE,OAAO,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC;QAC3C,KAAK,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC,IAAI,MAAM,MAAM,CAAC,EAAE,EAAE,CAAC;QACvD,KAAK,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC,KAAK,KAAK,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACtG,KAAK,UAAU,EAAE,OAAO,GAAG,MAAM,CAAC,KAAK,oBAAoB,CAAC;QAC5D,KAAK,UAAU,EAAE,OAAO,GAAG,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,WAAW,CAAC;QACzF,KAAK,UAAU,EAAE,OAAO,GAAG,MAAM,CAAC,KAAK,SAAS,MAAM,CAAC,IAAI,QAAQ,CAAC;IACtE,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"remotes.js","sourceRoot":"","sources":["../src/remotes.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAkB,MAAM,eAAe,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fougere/testing",
3
- "version": "0.4.0-alpha.0",
3
+ "version": "0.5.0-alpha.1",
4
4
  "description": "Tests derived from the declaration — cases, stubs and a gradient of realities.",
5
5
  "keywords": [
6
6
  "fougere",
@@ -34,19 +34,19 @@
34
34
  ],
35
35
  "dependencies": {
36
36
  "json-schema-faker": "^0.6.3",
37
- "@fougere/core": "0.4.0-alpha.0",
38
- "@fougere/container": "0.4.0-alpha.0",
39
- "@fougere/schema": "0.4.0-alpha.0",
40
- "@fougere/defaults": "0.4.0-alpha.0",
41
- "@fougere/transport-http": "0.4.0-alpha.0",
42
- "@fougere/app": "0.4.0-alpha.0"
37
+ "@fougere/core": "0.5.0-alpha.1",
38
+ "@fougere/container": "0.5.0-alpha.1",
39
+ "@fougere/defaults": "0.5.0-alpha.1",
40
+ "@fougere/app": "0.5.0-alpha.1",
41
+ "@fougere/transport-http": "0.5.0-alpha.1",
42
+ "@fougere/schema": "0.5.0-alpha.1"
43
43
  },
44
44
  "publishConfig": {
45
45
  "access": "public"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "vitest": ">=2",
49
- "@fougere/adapter-graphql": "0.4.0-alpha.0"
49
+ "@fougere/adapter-graphql": "0.5.0-alpha.1"
50
50
  },
51
51
  "peerDependenciesMeta": {
52
52
  "vitest": {
@@ -58,7 +58,7 @@
58
58
  },
59
59
  "devDependencies": {
60
60
  "vitest": "^4.1.0",
61
- "@fougere/adapter-graphql": "0.4.0-alpha.0"
61
+ "@fougere/adapter-graphql": "0.5.0-alpha.1"
62
62
  },
63
63
  "scripts": {
64
64
  "build": "rm -rf dist && tsc",
package/src/remotes.ts CHANGED
@@ -1,125 +1,7 @@
1
- import { Card, type Change, type SchemaDescriptor } from '@fougere/schema';
2
- import type { IdentityCard } from '@fougere/core';
3
-
4
- /** What separates the copy a consumer holds from what the producer actually serves. */
5
- export interface CardDrift {
6
- frond: string;
7
- /** A door the consumer calls that the producer no longer serves. */
8
- missingDoors: string[];
9
- /** An operation the consumer calls that the door no longer has. */
10
- missingOps: { door: string; ops: string[] }[];
11
- /** A shape that moved under a door the consumer still calls. */
12
- shapes: { door: string; changes: Change[] }[];
13
- /** A fact the consumer subscribes to whose shape moved, or that is gone. */
14
- facts: { fact: string; changes: Change[] | 'gone' }[];
15
- }
16
-
17
- /** Every door of a card, by name. */
18
- function doorsOf(card: IdentityCard, frond: string): Map<string, { ops: Set<string>; schema?: SchemaDescriptor }> {
19
- const found = new Map<string, { ops: Set<string>; schema?: SchemaDescriptor }>();
20
- for (const one of card.fronds) {
21
- if (one.name !== frond) continue;
22
- for (const door of one.doors) {
23
- found.set(door.name, { ops: new Set(door.ops.map((op) => op.name)), schema: door.schema });
24
- }
25
- }
26
- return found;
27
- }
28
-
29
- function factsOf(card: IdentityCard, frond: string): Map<string, SchemaDescriptor | undefined> {
30
- const found = new Map<string, SchemaDescriptor | undefined>();
31
- for (const one of card.fronds) {
32
- if (one.name !== frond) continue;
33
- for (const fact of one.facts ?? []) found.set(fact.name, fact.schema as SchemaDescriptor | undefined);
34
- }
35
- return found;
36
- }
37
-
38
1
  /**
39
- * What a consumer's synced copy no longer matches in what the producer serves.
2
+ * Contract drift moved to `@fougere/core`, beside the card it compares.
40
3
  *
41
- * The gap TypeScript cannot see, and the only place the gradient genuinely lies: the code
42
- * is identical in-process and split, but one side may have aged. `fougere sync` wrote the
43
- * consumer's copy three weeks ago, the producer moved on, and it still compiles —
44
- * production is where that is found today. This is what Pact sells; the material was
45
- * already here, in `rpc.discover` and in `Card.diff`.
46
- *
47
- * Read in ONE direction on purpose: what the consumer holds, checked against what is
48
- * served. A producer serving MORE than the consumer knows is not drift — it is a producer
49
- * that moved forward without breaking anyone, which is the whole point of the order the
50
- * repo already states (re-sync the readers, then deploy the sender).
51
- */
52
- export function driftOf(mine: IdentityCard, theirs: IdentityCard, frond: string): CardDrift {
53
- const held = doorsOf(mine, frond);
54
- const served = doorsOf(theirs, frond);
55
- const drift: CardDrift = { frond, missingDoors: [], missingOps: [], shapes: [], facts: [] };
56
-
57
- for (const [name, door] of held) {
58
- const there = served.get(name);
59
- if (!there) { drift.missingDoors.push(name); continue; }
60
-
61
- const missing = [...door.ops].filter((op) => !there.ops.has(op));
62
- if (missing.length > 0) drift.missingOps.push({ door: name, ops: missing.sort() });
63
-
64
- if (door.schema && there.schema) {
65
- // `Card.diff` never guesses a rename — a field gone plus a field appeared lands in
66
- // `ambiguous`, and only a declaration settles it. Here nobody can declare one, so
67
- // the pair is reported as it is and a human reads it.
68
- const moved = Card.fromDescriptor(door.schema).diff(Card.fromDescriptor(there.schema));
69
- if (moved.changes.length > 0) drift.shapes.push({ door: name, changes: moved.changes });
70
- }
71
- }
72
-
73
- const heldFacts = factsOf(mine, frond);
74
- const servedFacts = factsOf(theirs, frond);
75
- for (const [name, shape] of heldFacts) {
76
- if (!servedFacts.has(name)) { drift.facts.push({ fact: name, changes: 'gone' }); continue; }
77
- const there = servedFacts.get(name);
78
- if (!shape || !there) continue;
79
- const moved = Card.fromDescriptor(shape).diff(Card.fromDescriptor(there));
80
- if (moved.changes.length > 0) drift.facts.push({ fact: name, changes: moved.changes });
81
- }
82
-
83
- return drift;
84
- }
85
-
86
- /** Whether anything at all separates the two cards. */
87
- export function agrees(drift: CardDrift): boolean {
88
- return drift.missingDoors.length === 0
89
- && drift.missingOps.length === 0
90
- && drift.shapes.length === 0
91
- && drift.facts.length === 0;
92
- }
93
-
94
- /**
95
- * The drift, in the words a deploy needs.
96
- *
97
- * A fact says the order out loud, because the repo already states it as a rule and
98
- * nothing enforced it: a fact is judged strictly, so a reader that has not been re-synced
99
- * refuses what the sender now announces.
4
+ * Republished here because a test states its subject by where it sits, and `agrees(driftOf(…))`
5
+ * is a testing gesture the function is core's, the assertion is this package's.
100
6
  */
101
- export function explain(drift: CardDrift): string[] {
102
- const lines: string[] = [];
103
- for (const door of drift.missingDoors) lines.push(`${drift.frond}.${door} — you call it, it is not served`);
104
- for (const { door, ops } of drift.missingOps) lines.push(`${drift.frond}.${door} — gone: ${ops.join(', ')}`);
105
- for (const { door, changes } of drift.shapes) {
106
- for (const change of changes) lines.push(`${drift.frond}.${door} — ${describe(change)}`);
107
- }
108
- for (const { fact, changes } of drift.facts) {
109
- if (changes === 'gone') { lines.push(`${fact} — you subscribe to it, it is no longer announced`); continue; }
110
- for (const change of changes) lines.push(`${fact} — ${describe(change)} → re-sync and deploy the readers, THEN the sender`);
111
- }
112
- return lines;
113
- }
114
-
115
- function describe(change: Change): string {
116
- switch (change.kind) {
117
- case 'added': return `+ ${change.field}${change.required ? ' (required)' : ''}`;
118
- case 'removed': return `- ${change.field}`;
119
- case 'renamed': return `${change.from} → ${change.to}`;
120
- case 'retyped': return `${change.field}: ${[...change.from].join('|')} → ${[...change.to].join('|')}`;
121
- case 'reshaped': return `${change.field}: its bounds moved`;
122
- case 'required': return `${change.field}: ${change.from ? 'no longer' : 'now'} required`;
123
- case 'restated': return `${change.field}: its ${change.axis} moved`;
124
- }
125
- }
7
+ export { driftOf, agrees, explain, type CardDrift } from '@fougere/core';