@hydranium/conformance 1.0.0-next.10

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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +97 -0
  3. package/lib/conformance-suite.d.ts +97 -0
  4. package/lib/conformance-suite.d.ts.map +1 -0
  5. package/lib/conformance-suite.js +97 -0
  6. package/lib/conformance-suite.js.map +1 -0
  7. package/lib/data/index.d.ts +69 -0
  8. package/lib/data/index.d.ts.map +1 -0
  9. package/lib/data/index.js +223 -0
  10. package/lib/data/index.js.map +1 -0
  11. package/lib/glsp/index.d.ts +124 -0
  12. package/lib/glsp/index.d.ts.map +1 -0
  13. package/lib/glsp/index.js +99 -0
  14. package/lib/glsp/index.js.map +1 -0
  15. package/lib/index.d.ts +11 -0
  16. package/lib/index.d.ts.map +1 -0
  17. package/lib/index.js +19 -0
  18. package/lib/index.js.map +1 -0
  19. package/lib/jest/index.d.ts +34 -0
  20. package/lib/jest/index.d.ts.map +1 -0
  21. package/lib/jest/index.js +59 -0
  22. package/lib/jest/index.js.map +1 -0
  23. package/lib/lsp/index.d.ts +88 -0
  24. package/lib/lsp/index.d.ts.map +1 -0
  25. package/lib/lsp/index.js +198 -0
  26. package/lib/lsp/index.js.map +1 -0
  27. package/lib/model.d.ts +111 -0
  28. package/lib/model.d.ts.map +1 -0
  29. package/lib/model.js +28 -0
  30. package/lib/model.js.map +1 -0
  31. package/lib/vitest/index.d.ts +48 -0
  32. package/lib/vitest/index.d.ts.map +1 -0
  33. package/lib/vitest/index.js +45 -0
  34. package/lib/vitest/index.js.map +1 -0
  35. package/package.json +120 -0
  36. package/src/conformance-suite.ts +148 -0
  37. package/src/data/index.ts +297 -0
  38. package/src/glsp/index.ts +217 -0
  39. package/src/index.ts +20 -0
  40. package/src/jest/index.ts +81 -0
  41. package/src/lsp/index.ts +269 -0
  42. package/src/model.ts +122 -0
  43. package/src/vitest/index.ts +81 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CrossBreeze, EclipseSource and others.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # `@hydranium/conformance`
2
+
3
+ The protocol conformance kit — a TCK — for the
4
+ [Hydranium](https://github.com/eclipse-emfcloud/hydranium) framework.
5
+
6
+ A TCK is a test suite you do not write: the framework ships the checks, and **you run them against
7
+ your own server** to prove it actually speaks each head's protocol. You supply two things — a way to
8
+ stand a server up (`connect`) and per-language fixtures (a valid and an invalid model) — and the kit
9
+ emits one test per check in your own test runner, alongside your own suites.
10
+
11
+ Install it if you are building a Hydranium head and want a protocol contract you did not have to
12
+ author, and that keeps checking you as the framework moves.
13
+
14
+ ## What it gives you
15
+
16
+ - **Per-head batteries**, one subpath each: `/data`, `/lsp` and `/glsp`. Importing one never pulls
17
+ another head's protocol types in, so an LSP-only adopter takes no data-head surface.
18
+ - **Driver ports rather than adapters.** Each slice names a minimal port — `DataConformanceDriver`,
19
+ `LspConformanceDriver`, `GlspConformanceDriver<TAction>` — spelled in `@hydranium/protocol` types,
20
+ plain coordinates and tiny structural minima. The framework's own harnesses
21
+ (`DataServerHarness`, `LspHarness`, `GlspHarness`) satisfy them **structurally, with no adapter**,
22
+ and so can a hand-rolled driver.
23
+ - **Runner-agnostic core.** `ConformanceCheck`, `ConformanceRunner` and `emitConformanceSuite` name
24
+ no test runner and assert with `node:assert/strict`; the thin `/vitest` and `/jest` adapters bind
25
+ `describe` / `it` / `it.skip` / `afterAll`.
26
+ - **Skips that are visible, not silent.** A check whose optional fixture input is absent is emitted
27
+ as a named `it.skip`, and every suite prints a ran-vs-skipped summary (`formatSummary`), so a
28
+ half-wired adopter reads as _skipped_ rather than as a green run.
29
+ - **A false-green guard in the fixture type.** `LanguageFixture` requires **both** a `valid` and an
30
+ `invalid` model, so an "invalid" model that in fact parses clean fails the diagnostics checks
31
+ instead of passing vacuously.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ npm install --save-dev @hydranium/conformance
37
+ ```
38
+
39
+ `@hydranium/protocol` is a required peer. The two runners are **optional** peers — `vitest` and
40
+ `@jest/globals` — so you install only the one you use, and the core never loads the other. The
41
+ package has no runtime dependencies of its own.
42
+
43
+ ## Subpaths
44
+
45
+ | Subpath | Contents |
46
+ | ---------- | ----------------------------------------------------------- |
47
+ | `.` | Fixture model + check/runner primitives. No head, no runner. |
48
+ | `./data` | Data-head driver port + `buildDataChecks`. |
49
+ | `./lsp` | LSP-head driver port + `buildLspChecks`. |
50
+ | `./glsp` | GLSP-head driver port + `buildGlspChecks`. |
51
+ | `./vitest` | Vitest adapter: `run{Data,Lsp,Glsp}Conformance`. |
52
+ | `./jest` | Jest adapter: the same three entry points. |
53
+
54
+ Each named subpath also resolves as `@hydranium/conformance/lib/<name>`, so a consumer on classic
55
+ `moduleResolution: "Node"` can reach it. The root barrel deliberately re-exports none of the slices.
56
+
57
+ ## Usage
58
+
59
+ In one test file per head, call the `run*Conformance` function from your runner's adapter subpath —
60
+ `@hydranium/conformance/vitest` or `@hydranium/conformance/jest`. Both adapters also re-export the
61
+ slice types (driver ports, options, fixtures), so a suite needs a single import site.
62
+
63
+ Each takes `connect` plus `languages`:
64
+
65
+ - **`connect`** returns a freshly wired driver and is called **once per check**, so checks cannot
66
+ interfere; the kit disposes the driver afterwards. The data slice wants a server already ready; the
67
+ LSP slice drives the `initialize` handshake itself, so `connect` must **not** pre-initialise; the
68
+ GLSP slice drives `start()`.
69
+ - **`languages`** is an array of `LanguageFixture`: `valid` and `invalid` (both read by every
70
+ slice), plus two opt-ins — `edit` (data slice only: a replacement text and an `expect(root)`
71
+ predicate, because only you know what "the edit landed" means for your grammar) and
72
+ `completionPosition` (LSP slice only). A fixture's `uri` and `text` may be thunks, resolved after
73
+ `connect`, which is how each check gets pristine input in a workspace `connect` just created.
74
+
75
+ The GLSP slice is generic over your action type and takes `GlspFixture` per diagram type — the
76
+ fixture builds the native actions and the kit matches responses by `kind`, so no `@eclipse-glsp/*`
77
+ type enters the kit.
78
+
79
+ Then run your normal test command:
80
+
81
+ ```bash
82
+ npx vitest run test/data-conformance.integration.test.ts
83
+ ```
84
+
85
+ The suite prints, per head, how many checks ran and which were skipped and why. For where this sits
86
+ among the framework's test layers, see [`docs/contributing/testing.md`](../../docs/contributing/testing.md).
87
+
88
+ ## Status
89
+
90
+ Alpha — pre-v0, not yet published. The check batteries are being populated incrementally and the
91
+ driver ports are not yet stable — a new check can turn a passing adopter red by design. See the
92
+ [repository README](../../README.md) for the current status and known limitations.
93
+
94
+ ## License
95
+
96
+ `MIT` — see this package's [`LICENSE`](./LICENSE), and the repository
97
+ [`NOTICE.md`](../../NOTICE.md) for third-party notices.
@@ -0,0 +1,97 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+ /**
10
+ * One conformance check. A check with a {@link body} is RUN (emitted as a
11
+ * runner test); a check WITHOUT a body is SKIPPED — its optional fixture input
12
+ * was absent, so it reports as skipped with a named {@link skipReason} rather
13
+ * than passing vacuously. The false-green guard (require both a valid and an
14
+ * invalid model) lives in the slices; this type is the head-agnostic unit they
15
+ * build.
16
+ */
17
+ export interface ConformanceCheck {
18
+ readonly title: string;
19
+ /** Present ⇒ the check runs. Absent ⇒ the check is skipped with {@link skipReason}. */
20
+ readonly body?: () => void | Promise<void>;
21
+ /** Why the check was skipped — surfaced in the `it.skip` title and the summary. */
22
+ readonly skipReason?: string;
23
+ }
24
+ /** A single skipped check in a {@link ConformanceSummary}. */
25
+ export interface SkippedCheck {
26
+ readonly title: string;
27
+ readonly reason: string;
28
+ }
29
+ /** Ran-vs-skipped tally over a battery of {@link ConformanceCheck}s. */
30
+ export interface ConformanceSummary {
31
+ readonly total: number;
32
+ readonly ran: number;
33
+ readonly skipped: ReadonlyArray<SkippedCheck>;
34
+ }
35
+ /**
36
+ * Tally a battery of checks into ran-vs-skipped counts. A check is RAN when
37
+ * it carries a {@link ConformanceCheck.body}; otherwise it is SKIPPED and its
38
+ * (possibly defaulted) reason is collected.
39
+ */
40
+ export declare function summarizeChecks(checks: ReadonlyArray<ConformanceCheck>): ConformanceSummary;
41
+ /**
42
+ * The `it.skip` title for a skipped check — the check title annotated with
43
+ * its (possibly defaulted) reason, so the runner's report explains the skip
44
+ * inline instead of showing a bare greyed-out line.
45
+ */
46
+ export declare function skippedTitle(check: Pick<ConformanceCheck, 'title' | 'skipReason'>): string;
47
+ /**
48
+ * Render a one-or-more-line human summary of a {@link ConformanceSummary},
49
+ * prefixed by the suite name. Printed once per suite so a half-implemented
50
+ * adopter reads as *skipped*, never as a silent false green.
51
+ */
52
+ export declare function formatSummary(suite: string, summary: ConformanceSummary): string;
53
+ /**
54
+ * Write the ran-vs-skipped summary somewhere a test runner does not swallow.
55
+ *
56
+ * `console.log` is NOT that place: vitest attributes console output to the
57
+ * running task, and an `afterAll` hook has none, so the line is dropped with no
58
+ * warning — measured, not inferred. That silently disables the kit's only
59
+ * false-green guard: a half-implemented adopter is supposed to read as
60
+ * *skipped*, and with the summary gone it reads as a clean pass.
61
+ *
62
+ * `process.stderr` bypasses the interception. A runner with no `process` (a
63
+ * browser runner) falls back to `console.log`, where nothing is intercepting in
64
+ * the first place — the kit's `.` entry is neutrality-gated, so this must not
65
+ * assume Node.
66
+ */
67
+ export declare function writeConformanceSummary(text: string): void;
68
+ /**
69
+ * The minimal test-runner surface {@link emitConformanceSuite} drives, so the
70
+ * kit core names NO concrete runner (`@jest/globals` / `vitest` / `node:test`)
71
+ * and stays runner-agnostic. A thin per-runner adapter subpath
72
+ * (`@hydranium/conformance/jest`, `@hydranium/conformance/vitest`) binds these
73
+ * methods to its runner's `describe` / `it` / `it.skip` / `afterAll`.
74
+ * Structural by design — jest and vitest both satisfy it directly.
75
+ */
76
+ export interface ConformanceRunner {
77
+ /** Group the suite's checks under `name`; `register` queues them (called synchronously). */
78
+ describe(name: string, register: () => void): void;
79
+ /** Register a running check. */
80
+ test(name: string, body: () => void | Promise<void>): void;
81
+ /** Register a skipped check (greyed out, never executed). */
82
+ skip(name: string): void;
83
+ /** Register a once-per-suite teardown — used to print the ran-vs-skipped summary. */
84
+ afterAll(fn: () => void | Promise<void>): void;
85
+ }
86
+ /**
87
+ * Emit a battery of {@link ConformanceCheck}s through an injected
88
+ * {@link ConformanceRunner} — each check with a body becomes a `test`, each
89
+ * without becomes a `skip` carrying its {@link skippedTitle}, and an `afterAll`
90
+ * prints the {@link formatSummary} so the suite always reports ran-vs-skipped.
91
+ * The thin, runner-agnostic translation layer between a slice's planned checks
92
+ * (whose ran/skip decisions are the tested pure logic above) and a runner; a
93
+ * per-runner adapter's `runXxxConformance` builds the check list and hands it
94
+ * here with the runner bound.
95
+ */
96
+ export declare function emitConformanceSuite(runner: ConformanceRunner, suite: string, checks: ReadonlyArray<ConformanceCheck>): void;
97
+ //# sourceMappingURL=conformance-suite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conformance-suite.d.ts","sourceRoot":"","sources":["../src/conformance-suite.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,uFAAuF;IACvF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,mFAAmF;IACnF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,8DAA8D;AAC9D,MAAM,WAAW,YAAY;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CAC1B;AAED,wEAAwE;AACxE,MAAM,WAAW,kBAAkB;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;CAChD;AAKD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,aAAa,CAAC,gBAAgB,CAAC,GAAG,kBAAkB,CAW3F;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,YAAY,CAAC,GAAG,MAAM,CAE1F;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAOhF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAO1D;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAC/B,4FAA4F;IAC5F,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACnD,gCAAgC;IAChC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3D,6DAA6D;IAC7D,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,qFAAqF;IACrF,QAAQ,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACjD;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAa5H"}
@@ -0,0 +1,97 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+ /** Placeholder reason for a skipped check that supplied none. */
10
+ const NO_REASON = 'no reason given';
11
+ /**
12
+ * Tally a battery of checks into ran-vs-skipped counts. A check is RAN when
13
+ * it carries a {@link ConformanceCheck.body}; otherwise it is SKIPPED and its
14
+ * (possibly defaulted) reason is collected.
15
+ */
16
+ export function summarizeChecks(checks) {
17
+ const skipped = [];
18
+ let ran = 0;
19
+ for (const check of checks) {
20
+ if (check.body) {
21
+ ran++;
22
+ }
23
+ else {
24
+ skipped.push({ title: check.title, reason: check.skipReason ?? NO_REASON });
25
+ }
26
+ }
27
+ return { total: checks.length, ran, skipped };
28
+ }
29
+ /**
30
+ * The `it.skip` title for a skipped check — the check title annotated with
31
+ * its (possibly defaulted) reason, so the runner's report explains the skip
32
+ * inline instead of showing a bare greyed-out line.
33
+ */
34
+ export function skippedTitle(check) {
35
+ return `${check.title} [skipped: ${check.skipReason ?? NO_REASON}]`;
36
+ }
37
+ /**
38
+ * Render a one-or-more-line human summary of a {@link ConformanceSummary},
39
+ * prefixed by the suite name. Printed once per suite so a half-implemented
40
+ * adopter reads as *skipped*, never as a silent false green.
41
+ */
42
+ export function formatSummary(suite, summary) {
43
+ const header = `${suite}: ${summary.ran}/${summary.total} ran, ${summary.skipped.length} skipped`;
44
+ if (summary.skipped.length === 0) {
45
+ return header;
46
+ }
47
+ const lines = summary.skipped.map(skip => ` - skipped: ${skip.title} (${skip.reason})`);
48
+ return [header, ...lines].join('\n');
49
+ }
50
+ /**
51
+ * Write the ran-vs-skipped summary somewhere a test runner does not swallow.
52
+ *
53
+ * `console.log` is NOT that place: vitest attributes console output to the
54
+ * running task, and an `afterAll` hook has none, so the line is dropped with no
55
+ * warning — measured, not inferred. That silently disables the kit's only
56
+ * false-green guard: a half-implemented adopter is supposed to read as
57
+ * *skipped*, and with the summary gone it reads as a clean pass.
58
+ *
59
+ * `process.stderr` bypasses the interception. A runner with no `process` (a
60
+ * browser runner) falls back to `console.log`, where nothing is intercepting in
61
+ * the first place — the kit's `.` entry is neutrality-gated, so this must not
62
+ * assume Node.
63
+ */
64
+ export function writeConformanceSummary(text) {
65
+ const stderr = globalThis.process?.stderr;
66
+ if (typeof stderr?.write === 'function') {
67
+ stderr.write(`${text}\n`);
68
+ return;
69
+ }
70
+ console.log(text);
71
+ }
72
+ /**
73
+ * Emit a battery of {@link ConformanceCheck}s through an injected
74
+ * {@link ConformanceRunner} — each check with a body becomes a `test`, each
75
+ * without becomes a `skip` carrying its {@link skippedTitle}, and an `afterAll`
76
+ * prints the {@link formatSummary} so the suite always reports ran-vs-skipped.
77
+ * The thin, runner-agnostic translation layer between a slice's planned checks
78
+ * (whose ran/skip decisions are the tested pure logic above) and a runner; a
79
+ * per-runner adapter's `runXxxConformance` builds the check list and hands it
80
+ * here with the runner bound.
81
+ */
82
+ export function emitConformanceSuite(runner, suite, checks) {
83
+ runner.describe(suite, () => {
84
+ for (const check of checks) {
85
+ if (check.body) {
86
+ runner.test(check.title, check.body);
87
+ }
88
+ else {
89
+ runner.skip(skippedTitle(check));
90
+ }
91
+ }
92
+ runner.afterAll(() => {
93
+ writeConformanceSummary(formatSummary(suite, summarizeChecks(checks)));
94
+ });
95
+ });
96
+ }
97
+ //# sourceMappingURL=conformance-suite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conformance-suite.js","sourceRoot":"","sources":["../src/conformance-suite.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AA+BlF,iEAAiE;AACjE,MAAM,SAAS,GAAG,iBAAiB,CAAC;AAEpC;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,MAAuC;IACpE,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC1B,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;YACd,GAAG,EAAE,CAAC;QACT,CAAC;aAAM,CAAC;YACL,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,UAAU,IAAI,SAAS,EAAE,CAAC,CAAC;QAC/E,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,KAAqD;IAC/E,OAAO,GAAG,KAAK,CAAC,KAAK,cAAc,KAAK,CAAC,UAAU,IAAI,SAAS,GAAG,CAAC;AACvE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,OAA2B;IACrE,MAAM,MAAM,GAAG,GAAG,KAAK,KAAK,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,KAAK,SAAS,OAAO,CAAC,OAAO,CAAC,MAAM,UAAU,CAAC;IAClG,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,MAAM,CAAC;IACjB,CAAC;IACD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,gBAAgB,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;IACzF,OAAO,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAY;IACjD,MAAM,MAAM,GAAI,UAA4E,CAAC,OAAO,EAAE,MAAM,CAAC;IAC7G,IAAI,OAAO,MAAM,EAAE,KAAK,KAAK,UAAU,EAAE,CAAC;QACvC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;QAC1B,OAAO;IACV,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACrB,CAAC;AAqBD;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAyB,EAAE,KAAa,EAAE,MAAuC;IACnH,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE;QACzB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC1B,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;gBACd,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACxC,CAAC;iBAAM,CAAC;gBACL,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;YACpC,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE;YAClB,uBAAuB,CAAC,aAAa,CAAC,KAAK,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;IACN,CAAC,CAAC,CAAC;AACN,CAAC"}
@@ -0,0 +1,69 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+ import { type TransferDiagnostic, type TransferElement } from '@hydranium/protocol';
10
+ import type { DataServerProtocol, TransferDocumentUpdatedEvent } from '@hydranium/protocol/data';
11
+ import { type Harness } from '@hydranium/protocol/testing';
12
+ import type { ConformanceCheck } from '../conformance-suite.js';
13
+ import { type LanguageFixture } from '../model.js';
14
+ /**
15
+ * The data-server driver port — a live, connected, READY data-server exposed
16
+ * through its protocol-native proxy plus the captured client-side update
17
+ * events. The kit names only `@hydranium/protocol` types, so a
18
+ * `DataServerHarness` satisfies the port structurally (it has `proxy` +
19
+ * `events` + `dispose`) with NO adapter. `extends Harness` gives the kit the
20
+ * universal `dispose()` teardown.
21
+ *
22
+ * The kit seeds documents purely through the proxy: `updateModelDocument` is
23
+ * an upsert (it creates a cold URI from the payload, not just modifies an
24
+ * existing one), so the slice needs no services-level open hook. The adopter
25
+ * only has to stand the server up READY in its `connect` — see
26
+ * {@link DataConformanceOptions.connect}.
27
+ */
28
+ export interface DataConformanceDriver<TTransfer extends TransferElement, TDiagnostic extends TransferDiagnostic = TransferDiagnostic> extends Harness {
29
+ readonly proxy: DataServerProtocol<TTransfer, TDiagnostic>;
30
+ /** Captured `onDocumentUpdated` events, append order — the subscription check's observation target. */
31
+ readonly events: ReadonlyArray<TransferDocumentUpdatedEvent<TTransfer, TDiagnostic>>;
32
+ }
33
+ /** Options for `runDataConformance`. */
34
+ export interface DataConformanceOptions<TTransfer extends TransferElement, TDiagnostic extends TransferDiagnostic = TransferDiagnostic> {
35
+ /**
36
+ * Establish a freshly-wired, connected, READY data-server driver. Called
37
+ * once per check for isolation; the kit disposes it after the check. The
38
+ * adopter must initialise the workspace before returning, so `waitForReady`
39
+ * resolves rather than hanging.
40
+ */
41
+ readonly connect: () => DataConformanceDriver<TTransfer, TDiagnostic> | Promise<DataConformanceDriver<TTransfer, TDiagnostic>>;
42
+ /** Per-language fixtures; the grammar-bearing checks run once per language. */
43
+ readonly languages: ReadonlyArray<LanguageFixture>;
44
+ /**
45
+ * Set this when the head under test HAS a project tier. Supplying it IS the
46
+ * claim that `getProjects` answers at least one project, so an empty array
47
+ * becomes a failure rather than a vacuous pass.
48
+ *
49
+ * Left unset the claim is not made and the check reports skipped with a
50
+ * named reason, because `[]` is the documented answer for a head with no
51
+ * project tier (`UNQUALIFIED_PROJECT_REFERENCE` everywhere) — indistinguishable
52
+ * from "never implemented" without the adopter saying which it is.
53
+ */
54
+ readonly expectsProjects?: boolean;
55
+ /** Suite title override. Default `'conformance: data-server'`. */
56
+ readonly suiteTitle?: string;
57
+ }
58
+ /**
59
+ * Build the data-server check battery: server-level checks once, then the
60
+ * grammar-bearing checks per language. Each check connects a fresh driver
61
+ * and disposes it, so checks never interfere. Exported for the kit's own
62
+ * unit tests; adopters call `runDataConformance`.
63
+ *
64
+ * `LanguageFixture.edit` is read HERE and nowhere else in the kit, and is
65
+ * optional: its two checks report skipped when it is absent. See
66
+ * {@link LanguageFixture} for which slice reads which field.
67
+ */
68
+ export declare function buildDataChecks<TTransfer extends TransferElement, TDiagnostic extends TransferDiagnostic = TransferDiagnostic>(options: DataConformanceOptions<TTransfer, TDiagnostic>): ConformanceCheck[];
69
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/data/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAWlF,OAAO,EAAoB,KAAK,kBAAkB,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtG,OAAO,KAAK,EAAE,kBAAkB,EAAE,4BAA4B,EAAE,MAAM,0BAA0B,CAAC;AACjG,OAAO,EAAE,KAAK,OAAO,EAAW,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,KAAK,eAAe,EAAiC,MAAM,aAAa,CAAC;AAElF;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,qBAAqB,CACnC,SAAS,SAAS,eAAe,EACjC,WAAW,SAAS,kBAAkB,GAAG,kBAAkB,CAC5D,SAAQ,OAAO;IACd,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAC3D,uGAAuG;IACvG,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,4BAA4B,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC;CACvF;AAED,wCAAwC;AACxC,MAAM,WAAW,sBAAsB,CAAC,SAAS,SAAS,eAAe,EAAE,WAAW,SAAS,kBAAkB,GAAG,kBAAkB;IACnI;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,qBAAqB,CAAC,SAAS,EAAE,WAAW,CAAC,GAAG,OAAO,CAAC,qBAAqB,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC;IAC/H,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IACnD;;;;;;;;;OASG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC,kEAAkE;IAClE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC/B;AAeD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,SAAS,SAAS,eAAe,EAAE,WAAW,SAAS,kBAAkB,GAAG,kBAAkB,EAC3H,OAAO,EAAE,sBAAsB,CAAC,SAAS,EAAE,WAAW,CAAC,GACvD,gBAAgB,EAAE,CAsMpB"}
@@ -0,0 +1,223 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+ /**
10
+ * The `@hydranium/conformance/data` slice — protocol conformance for the
11
+ * data-server head. The driver port IS the protocol-native
12
+ * {@link DataServerProtocol} proxy (plus the captured `onDocumentUpdated`
13
+ * events), so no upstream wire-lib dep enters the kit and `DataServerHarness`
14
+ * satisfies the port structurally with no adapter.
15
+ */
16
+ import assert from 'node:assert/strict';
17
+ import { TransferDocument } from '@hydranium/protocol';
18
+ import { waitFor } from '@hydranium/protocol/testing';
19
+ import { resolveDeferred, resolveModel } from '../model.js';
20
+ /** Client id the kit seeds/edits documents under (the originating author). */
21
+ const AUTHOR = 'conformance-author';
22
+ /** Client id the kit subscribes under — distinct from {@link AUTHOR} so the echo is recognisable. */
23
+ const SUBSCRIBER = 'conformance-subscriber';
24
+ /**
25
+ * Client id for a write made BEFORE any subscription exists. Distinct from
26
+ * {@link AUTHOR} so an event caused by it is recognisable: a head that fans
27
+ * notifications out regardless of its subscription table is otherwise
28
+ * indistinguishable from one that honours the table, since both deliver
29
+ * something for the post-subscribe write.
30
+ */
31
+ const SEEDER = 'conformance-seeder';
32
+ /**
33
+ * Build the data-server check battery: server-level checks once, then the
34
+ * grammar-bearing checks per language. Each check connects a fresh driver
35
+ * and disposes it, so checks never interfere. Exported for the kit's own
36
+ * unit tests; adopters call `runDataConformance`.
37
+ *
38
+ * `LanguageFixture.edit` is read HERE and nowhere else in the kit, and is
39
+ * optional: its two checks report skipped when it is absent. See
40
+ * {@link LanguageFixture} for which slice reads which field.
41
+ */
42
+ export function buildDataChecks(options) {
43
+ const { connect, expectsProjects } = options;
44
+ const checks = [];
45
+ // Split from `waitForReady` so a failure names which of the two broke.
46
+ //
47
+ // SHAPE ONLY: an empty array is the documented answer for a head with no
48
+ // project tier, so "returns `[]`" passes here and is indistinguishable from
49
+ // "never implemented". The discriminating part is the element-type assertion
50
+ // (it fails a head answering with the wrong element type, which the bare
51
+ // `Array.isArray` it replaced could not) plus the separate emptiness check
52
+ // below, which only an adopter's `expectsProjects` can license.
53
+ checks.push({
54
+ title: 'getProjects answers an array of well-formed projects',
55
+ body: async () => {
56
+ const driver = await connect();
57
+ try {
58
+ const projects = await driver.proxy.getProjects();
59
+ assert.ok(Array.isArray(projects), 'getProjects did not return an array');
60
+ for (const project of projects) {
61
+ assert.ok(typeof project?.id === 'string' && project.id.length > 0, `getProjects returned a project with no id: ${JSON.stringify(project)}`);
62
+ // Type, not emptiness: the empty string is
63
+ // `UNQUALIFIED_PROJECT_REFERENCE`, the documented value for a
64
+ // project that does not qualify names, and the framework's own
65
+ // reference example uses it. Requiring non-empty here failed
66
+ // that example and would have failed every adopter taking the
67
+ // documented default.
68
+ assert.ok(typeof project.referenceName === 'string', `project ${project.id} has no referenceName`);
69
+ }
70
+ const ids = projects.map(project => project.id);
71
+ assert.strictEqual(new Set(ids).size, ids.length, `getProjects returned duplicate project ids: ${ids.join(', ')}`);
72
+ }
73
+ finally {
74
+ driver.dispose();
75
+ }
76
+ }
77
+ });
78
+ // Separate from the shape check so a failure names which claim broke, and
79
+ // gated because only the adopter knows whether their head has a project
80
+ // tier at all. Planned either way — reported as skipped with a reason
81
+ // rather than silently absent, which is what distinguishes an opt-out from
82
+ // lost coverage.
83
+ checks.push({
84
+ title: 'getProjects answers at least one project (projects expected)',
85
+ skipReason: expectsProjects
86
+ ? undefined
87
+ : 'options supplied no `expectsProjects` (an empty project list is the documented answer for a head with no project tier)',
88
+ body: expectsProjects
89
+ ? async () => {
90
+ const driver = await connect();
91
+ try {
92
+ const projects = await driver.proxy.getProjects();
93
+ assert.ok(projects.length > 0, 'getProjects returned an empty array although `expectsProjects` claims the head has a project tier');
94
+ }
95
+ finally {
96
+ driver.dispose();
97
+ }
98
+ }
99
+ : undefined
100
+ });
101
+ checks.push({
102
+ title: 'waitForReady resolves rather than hanging or rejecting',
103
+ body: async () => {
104
+ const driver = await connect();
105
+ try {
106
+ // Resolves `Promise<void>`, and over JSON-RPC a void response comes
107
+ // back as `null` — so the value carries no information and only the
108
+ // fact that it settles at all is assertable here.
109
+ await driver.proxy.waitForReady();
110
+ }
111
+ finally {
112
+ driver.dispose();
113
+ }
114
+ }
115
+ });
116
+ for (const language of options.languages) {
117
+ const { valid, invalid, edit } = language;
118
+ const tag = `[${valid.languageId}]`;
119
+ checks.push({
120
+ title: `getModelDocument(valid) returns a coherent envelope ${tag}`,
121
+ body: async () => {
122
+ const driver = await connect();
123
+ try {
124
+ // Resolved AFTER `connect`, which is the whole point of allowing a
125
+ // thunk: the fixture may name a workspace `connect` just created.
126
+ const model = resolveModel(valid);
127
+ await driver.proxy.updateModelDocument({ uri: model.uri, clientId: AUTHOR, model: model.text });
128
+ // `includeDiagnostics` for the same reason the invalid check
129
+ // passes it: a synchronous read settles at the integrity-settled
130
+ // phase, so an empty array without it can mean "validation has
131
+ // not run" rather than "the document is clean".
132
+ const document = await driver.proxy.getModelDocument({ uri: model.uri, includeDiagnostics: true });
133
+ assert.strictEqual(document.uri, model.uri);
134
+ // The SHAPE, not truthiness: `{}` is truthy, so a head answering
135
+ // a shaped-but-contentless envelope for a document it did parse
136
+ // would pass. `$type` is the one field every TransferElement
137
+ // carries, so it is assertable with no fixture knowledge.
138
+ const { root } = TransferDocument.assertLoaded(document);
139
+ assert.ok(typeof root.$type === 'string' && root.$type.length > 0, `getModelDocument(valid) returned a root with no $type: ${JSON.stringify(root)}`);
140
+ // `version` is the conflict token every later update gates on, so
141
+ // an envelope that omits it is unusable however good the root is.
142
+ assert.ok(Number.isInteger(document.version), `getModelDocument(valid) returned a non-integer version: ${String(document.version)}`);
143
+ assert.deepStrictEqual(document.diagnostics, []);
144
+ }
145
+ finally {
146
+ driver.dispose();
147
+ }
148
+ }
149
+ });
150
+ checks.push({
151
+ title: `getModelDocument(invalid) reports at least one diagnostic ${tag}`,
152
+ body: async () => {
153
+ const driver = await connect();
154
+ try {
155
+ const model = resolveModel(invalid);
156
+ await driver.proxy.updateModelDocument({ uri: model.uri, clientId: AUTHOR, model: model.text });
157
+ // Diagnostics are a validation-phase product; a synchronous read settles at the
158
+ // integrity-settled phase by default, so request validation explicitly here.
159
+ // Safe despite `includeDiagnostics` waiting rather than forcing a build: the
160
+ // update above has already driven this document through validation.
161
+ const document = await driver.proxy.getModelDocument({ uri: model.uri, includeDiagnostics: true });
162
+ assert.ok(document.diagnostics.length >= 1, 'getModelDocument(invalid) reported no diagnostics');
163
+ }
164
+ finally {
165
+ driver.dispose();
166
+ }
167
+ }
168
+ });
169
+ // Opt-in: both remaining checks need a second, observably different model
170
+ // text, which only `edit` supplies.
171
+ const editSkipReason = 'fixture supplies no `edit` (data-slice only; omit it if this language is not driven through the data head)';
172
+ checks.push({
173
+ title: `updateModelDocument applies an edit a follow-up get reflects ${tag}`,
174
+ skipReason: edit ? undefined : editSkipReason,
175
+ body: edit
176
+ ? async () => {
177
+ const driver = await connect();
178
+ try {
179
+ const model = resolveModel(valid);
180
+ await driver.proxy.updateModelDocument({ uri: model.uri, clientId: AUTHOR, model: model.text });
181
+ await driver.proxy.updateModelDocument({ uri: model.uri, clientId: AUTHOR, model: resolveDeferred(edit.to) });
182
+ const document = await driver.proxy.getModelDocument({ uri: model.uri });
183
+ assert.ok(edit.expect(document.root), 'edit.expect(root) was false — the edit was not reflected by a follow-up get');
184
+ }
185
+ finally {
186
+ driver.dispose();
187
+ }
188
+ }
189
+ : undefined
190
+ });
191
+ checks.push({
192
+ title: `subscribe + update delivers an onDocumentUpdated event with the originating clientId ${tag}`,
193
+ skipReason: edit ? undefined : editSkipReason,
194
+ body: edit
195
+ ? async () => {
196
+ const driver = await connect();
197
+ try {
198
+ const model = resolveModel(valid);
199
+ await driver.proxy.updateModelDocument({ uri: model.uri, clientId: SEEDER, model: model.text });
200
+ await driver.proxy.watchModelDocument({ uri: model.uri, clientId: SUBSCRIBER });
201
+ await driver.proxy.updateModelDocument({ uri: model.uri, clientId: AUTHOR, model: resolveDeferred(edit.to) });
202
+ await waitFor(() => driver.events.some(event => event.sourceClientId === AUTHOR), {
203
+ message: `no onDocumentUpdated event for ${model.uri} after the post-subscription update`
204
+ });
205
+ const last = driver.events[driver.events.length - 1];
206
+ assert.strictEqual(last.document.uri, model.uri);
207
+ assert.strictEqual(last.sourceClientId, AUTHOR);
208
+ // Nothing from before the subscription. Waiting for the
209
+ // post-subscribe event first is what makes this provable: the
210
+ // two notifications share one ordered connection, so a seeding
211
+ // event that was ever going to arrive has arrived by now.
212
+ assert.ok(!driver.events.some(event => event.sourceClientId === SEEDER), 'an onDocumentUpdated event arrived for the update made BEFORE watchModelDocument');
213
+ }
214
+ finally {
215
+ driver.dispose();
216
+ }
217
+ }
218
+ : undefined
219
+ });
220
+ }
221
+ return checks;
222
+ }
223
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/data/index.ts"],"names":[],"mappings":"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"}