@hydranium/core 1.0.0-next.6 → 1.0.0-next.61

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 (262) hide show
  1. package/lib/documents/ast-document-manager.d.ts +42 -5
  2. package/lib/documents/ast-document-manager.d.ts.map +1 -1
  3. package/lib/documents/ast-document-manager.js +18 -13
  4. package/lib/documents/ast-document-manager.js.map +1 -1
  5. package/lib/documents/hydranium-text-documents.d.ts +42 -17
  6. package/lib/documents/hydranium-text-documents.d.ts.map +1 -1
  7. package/lib/documents/hydranium-text-documents.js +118 -39
  8. package/lib/documents/hydranium-text-documents.js.map +1 -1
  9. package/lib/documents/language-client-text-shadow.d.ts +41 -9
  10. package/lib/documents/language-client-text-shadow.d.ts.map +1 -1
  11. package/lib/documents/language-client-text-shadow.js +56 -11
  12. package/lib/documents/language-client-text-shadow.js.map +1 -1
  13. package/lib/documents/self-save-registry.d.ts +37 -6
  14. package/lib/documents/self-save-registry.d.ts.map +1 -1
  15. package/lib/documents/self-save-registry.js +28 -15
  16. package/lib/documents/self-save-registry.js.map +1 -1
  17. package/lib/index.d.ts +2 -0
  18. package/lib/index.d.ts.map +1 -1
  19. package/lib/index.js +5 -0
  20. package/lib/index.js.map +1 -1
  21. package/lib/langium/ast-extension/ast-node-builder.d.ts +32 -9
  22. package/lib/langium/ast-extension/ast-node-builder.d.ts.map +1 -1
  23. package/lib/langium/ast-extension/ast-node-builder.js +60 -22
  24. package/lib/langium/ast-extension/ast-node-builder.js.map +1 -1
  25. package/lib/langium/bootstrap.d.ts.map +1 -1
  26. package/lib/langium/bootstrap.js +16 -9
  27. package/lib/langium/bootstrap.js.map +1 -1
  28. package/lib/langium/config/configuration-provider.d.ts +61 -0
  29. package/lib/langium/config/configuration-provider.d.ts.map +1 -0
  30. package/lib/langium/config/configuration-provider.js +89 -0
  31. package/lib/langium/config/configuration-provider.js.map +1 -0
  32. package/lib/langium/config/index.d.ts +1 -0
  33. package/lib/langium/config/index.d.ts.map +1 -1
  34. package/lib/langium/config/index.js +1 -0
  35. package/lib/langium/config/index.js.map +1 -1
  36. package/lib/langium/diagnostics/lsp-logger.d.ts +17 -0
  37. package/lib/langium/diagnostics/lsp-logger.d.ts.map +1 -1
  38. package/lib/langium/diagnostics/lsp-logger.js +40 -6
  39. package/lib/langium/diagnostics/lsp-logger.js.map +1 -1
  40. package/lib/langium/document-builder/build-pipeline-integration.d.ts +13 -1
  41. package/lib/langium/document-builder/build-pipeline-integration.d.ts.map +1 -1
  42. package/lib/langium/document-builder/build-pipeline-integration.js +1 -1
  43. package/lib/langium/document-builder/build-pipeline-integration.js.map +1 -1
  44. package/lib/langium/document-builder/build-session.d.ts +93 -0
  45. package/lib/langium/document-builder/build-session.d.ts.map +1 -0
  46. package/lib/langium/document-builder/build-session.js +72 -0
  47. package/lib/langium/document-builder/build-session.js.map +1 -0
  48. package/lib/langium/document-builder/document-builder.d.ts +150 -7
  49. package/lib/langium/document-builder/document-builder.d.ts.map +1 -1
  50. package/lib/langium/document-builder/document-builder.js +298 -11
  51. package/lib/langium/document-builder/document-builder.js.map +1 -1
  52. package/lib/langium/document-builder/index.d.ts +1 -0
  53. package/lib/langium/document-builder/index.d.ts.map +1 -1
  54. package/lib/langium/document-builder/index.js +1 -0
  55. package/lib/langium/document-builder/index.js.map +1 -1
  56. package/lib/langium/integration-services.d.ts +45 -1
  57. package/lib/langium/integration-services.d.ts.map +1 -1
  58. package/lib/langium/integration-services.js +10 -5
  59. package/lib/langium/integration-services.js.map +1 -1
  60. package/lib/langium/integrity/integrity-service.d.ts +5 -0
  61. package/lib/langium/integrity/integrity-service.d.ts.map +1 -1
  62. package/lib/langium/integrity/integrity-service.js +34 -1
  63. package/lib/langium/integrity/integrity-service.js.map +1 -1
  64. package/lib/langium/keys/containment.d.ts +64 -0
  65. package/lib/langium/keys/containment.d.ts.map +1 -0
  66. package/lib/langium/keys/containment.js +53 -0
  67. package/lib/langium/keys/containment.js.map +1 -0
  68. package/lib/langium/keys/index.d.ts +1 -0
  69. package/lib/langium/keys/index.d.ts.map +1 -1
  70. package/lib/langium/keys/index.js +1 -0
  71. package/lib/langium/keys/index.js.map +1 -1
  72. package/lib/langium/keys/name-based-key-provider.d.ts +4 -0
  73. package/lib/langium/keys/name-based-key-provider.d.ts.map +1 -1
  74. package/lib/langium/keys/name-based-key-provider.js +4 -0
  75. package/lib/langium/keys/name-based-key-provider.js.map +1 -1
  76. package/lib/langium/model-service/model-service.d.ts +103 -7
  77. package/lib/langium/model-service/model-service.d.ts.map +1 -1
  78. package/lib/langium/model-service/model-service.js +70 -89
  79. package/lib/langium/model-service/model-service.js.map +1 -1
  80. package/lib/langium/module.d.ts +82 -13
  81. package/lib/langium/module.d.ts.map +1 -1
  82. package/lib/langium/module.js +26 -12
  83. package/lib/langium/module.js.map +1 -1
  84. package/lib/langium/naming/name-provider.d.ts +10 -0
  85. package/lib/langium/naming/name-provider.d.ts.map +1 -1
  86. package/lib/langium/naming/name-provider.js.map +1 -1
  87. package/lib/langium/naming/name-separator-validation.d.ts +23 -0
  88. package/lib/langium/naming/name-separator-validation.d.ts.map +1 -1
  89. package/lib/langium/naming/name-separator-validation.js +30 -1
  90. package/lib/langium/naming/name-separator-validation.js.map +1 -1
  91. package/lib/langium/residency/cst-residency-service.d.ts +31 -9
  92. package/lib/langium/residency/cst-residency-service.d.ts.map +1 -1
  93. package/lib/langium/residency/cst-residency-service.js +13 -56
  94. package/lib/langium/residency/cst-residency-service.js.map +1 -1
  95. package/lib/langium/scope/hydranium-scope-provider.d.ts +32 -13
  96. package/lib/langium/scope/hydranium-scope-provider.d.ts.map +1 -1
  97. package/lib/langium/scope/hydranium-scope-provider.js +37 -20
  98. package/lib/langium/scope/hydranium-scope-provider.js.map +1 -1
  99. package/lib/langium/shared-services.d.ts +15 -2
  100. package/lib/langium/shared-services.d.ts.map +1 -1
  101. package/lib/langium/shared-services.js.map +1 -1
  102. package/lib/langium/transfer/transfer-encoder.d.ts +33 -2
  103. package/lib/langium/transfer/transfer-encoder.d.ts.map +1 -1
  104. package/lib/langium/transfer/transfer-encoder.js +14 -3
  105. package/lib/langium/transfer/transfer-encoder.js.map +1 -1
  106. package/lib/langium/validation/document-validator.d.ts +145 -6
  107. package/lib/langium/validation/document-validator.d.ts.map +1 -1
  108. package/lib/langium/validation/document-validator.js +211 -4
  109. package/lib/langium/validation/document-validator.js.map +1 -1
  110. package/lib/langium/workspace/document-uri-policy.d.ts +3 -4
  111. package/lib/langium/workspace/document-uri-policy.d.ts.map +1 -1
  112. package/lib/langium/workspace/document-uri-policy.js +3 -4
  113. package/lib/langium/workspace/document-uri-policy.js.map +1 -1
  114. package/lib/langium/workspace/hydranium-langium-document-factory.d.ts +25 -0
  115. package/lib/langium/workspace/hydranium-langium-document-factory.d.ts.map +1 -1
  116. package/lib/langium/workspace/hydranium-langium-document-factory.js +25 -0
  117. package/lib/langium/workspace/hydranium-langium-document-factory.js.map +1 -1
  118. package/lib/langium/workspace/hydranium-workspace-manager.d.ts +21 -1
  119. package/lib/langium/workspace/hydranium-workspace-manager.d.ts.map +1 -1
  120. package/lib/langium/workspace/hydranium-workspace-manager.js +28 -0
  121. package/lib/langium/workspace/hydranium-workspace-manager.js.map +1 -1
  122. package/lib/langium/workspace/in-memory-file-system-provider.d.ts +8 -0
  123. package/lib/langium/workspace/in-memory-file-system-provider.d.ts.map +1 -1
  124. package/lib/langium/workspace/in-memory-file-system-provider.js +11 -2
  125. package/lib/langium/workspace/in-memory-file-system-provider.js.map +1 -1
  126. package/lib/langium/workspace/initialize-workspace.d.ts +22 -2
  127. package/lib/langium/workspace/initialize-workspace.d.ts.map +1 -1
  128. package/lib/langium/workspace/initialize-workspace.js +13 -5
  129. package/lib/langium/workspace/initialize-workspace.js.map +1 -1
  130. package/lib/langium/workspace/langium-documents.d.ts +79 -17
  131. package/lib/langium/workspace/langium-documents.d.ts.map +1 -1
  132. package/lib/langium/workspace/langium-documents.js +83 -28
  133. package/lib/langium/workspace/langium-documents.js.map +1 -1
  134. package/lib/locale/index.d.ts +10 -0
  135. package/lib/locale/index.d.ts.map +1 -0
  136. package/lib/locale/index.js +10 -0
  137. package/lib/locale/index.js.map +1 -0
  138. package/lib/locale/server-locale.d.ts +73 -0
  139. package/lib/locale/server-locale.d.ts.map +1 -0
  140. package/lib/locale/server-locale.js +61 -0
  141. package/lib/locale/server-locale.js.map +1 -0
  142. package/lib/lsp/hydranium-document-update-handler.js +1 -1
  143. package/lib/lsp/hydranium-document-update-handler.js.map +1 -1
  144. package/lib/messages/carriers.d.ts +54 -0
  145. package/lib/messages/carriers.d.ts.map +1 -0
  146. package/lib/messages/carriers.js +62 -0
  147. package/lib/messages/carriers.js.map +1 -0
  148. package/lib/messages/index.d.ts +25 -0
  149. package/lib/messages/index.d.ts.map +1 -0
  150. package/lib/messages/index.js +25 -0
  151. package/lib/messages/index.js.map +1 -0
  152. package/lib/messages/renderer.d.ts +136 -0
  153. package/lib/messages/renderer.d.ts.map +1 -0
  154. package/lib/messages/renderer.js +159 -0
  155. package/lib/messages/renderer.js.map +1 -0
  156. package/lib/node/node-file-system-provider.d.ts.map +1 -1
  157. package/lib/node/node-file-system-provider.js +1 -40
  158. package/lib/node/node-file-system-provider.js.map +1 -1
  159. package/lib/node/profiling-run.js +1 -1
  160. package/lib/node/profiling-run.js.map +1 -1
  161. package/lib/node/rename-over-open-readers.d.ts +52 -0
  162. package/lib/node/rename-over-open-readers.d.ts.map +1 -0
  163. package/lib/node/rename-over-open-readers.js +66 -0
  164. package/lib/node/rename-over-open-readers.js.map +1 -0
  165. package/lib/testing/fake-document.d.ts +14 -3
  166. package/lib/testing/fake-document.d.ts.map +1 -1
  167. package/lib/testing/fake-document.js +18 -3
  168. package/lib/testing/fake-document.js.map +1 -1
  169. package/lib/testing/make-noop-shared-services.d.ts +14 -0
  170. package/lib/testing/make-noop-shared-services.d.ts.map +1 -1
  171. package/lib/testing/make-noop-shared-services.js +28 -2
  172. package/lib/testing/make-noop-shared-services.js.map +1 -1
  173. package/lib/testing/make-test-services.d.ts +31 -4
  174. package/lib/testing/make-test-services.d.ts.map +1 -1
  175. package/lib/testing/make-test-services.js +19 -3
  176. package/lib/testing/make-test-services.js.map +1 -1
  177. package/lib/testing/node/spawned-server.d.ts +14 -0
  178. package/lib/testing/node/spawned-server.d.ts.map +1 -1
  179. package/lib/testing/node/spawned-server.js +33 -1
  180. package/lib/testing/node/spawned-server.js.map +1 -1
  181. package/lib/testing/playwright/flaky-network-proxy.d.ts +76 -0
  182. package/lib/testing/playwright/flaky-network-proxy.d.ts.map +1 -0
  183. package/lib/testing/playwright/flaky-network-proxy.js +164 -0
  184. package/lib/testing/playwright/flaky-network-proxy.js.map +1 -0
  185. package/lib/testing/playwright/index.d.ts +1 -0
  186. package/lib/testing/playwright/index.d.ts.map +1 -1
  187. package/lib/testing/playwright/index.js +1 -0
  188. package/lib/testing/playwright/index.js.map +1 -1
  189. package/lib/testing/playwright/server-log-capture.d.ts +14 -4
  190. package/lib/testing/playwright/server-log-capture.d.ts.map +1 -1
  191. package/lib/testing/playwright/server-log-capture.js +21 -4
  192. package/lib/testing/playwright/server-log-capture.js.map +1 -1
  193. package/lib/testing/playwright/server-log-rename-reporter.d.ts.map +1 -1
  194. package/lib/testing/playwright/server-log-rename-reporter.js +10 -1
  195. package/lib/testing/playwright/server-log-rename-reporter.js.map +1 -1
  196. package/lib/testing/stub-ast-document-manager.d.ts +3 -4
  197. package/lib/testing/stub-ast-document-manager.d.ts.map +1 -1
  198. package/lib/testing/stub-ast-document-manager.js +2 -5
  199. package/lib/testing/stub-ast-document-manager.js.map +1 -1
  200. package/lib/testing/stub-document-builder.d.ts +8 -0
  201. package/lib/testing/stub-document-builder.d.ts.map +1 -1
  202. package/lib/testing/stub-document-builder.js +22 -3
  203. package/lib/testing/stub-document-builder.js.map +1 -1
  204. package/lib/testing/stub-model-service.d.ts +1 -1
  205. package/lib/testing/stub-model-service.d.ts.map +1 -1
  206. package/lib/testing/stub-model-service.js +2 -2
  207. package/lib/testing/stub-model-service.js.map +1 -1
  208. package/package.json +17 -8
  209. package/src/documents/ast-document-manager.ts +63 -15
  210. package/src/documents/hydranium-text-documents.ts +135 -54
  211. package/src/documents/language-client-text-shadow.ts +59 -11
  212. package/src/documents/self-save-registry.ts +52 -13
  213. package/src/index.ts +5 -0
  214. package/src/langium/ast-extension/ast-node-builder.ts +66 -22
  215. package/src/langium/bootstrap.ts +16 -9
  216. package/src/langium/config/configuration-provider.ts +96 -0
  217. package/src/langium/config/index.ts +1 -0
  218. package/src/langium/diagnostics/lsp-logger.ts +41 -6
  219. package/src/langium/document-builder/build-pipeline-integration.ts +14 -1
  220. package/src/langium/document-builder/build-session.ts +87 -0
  221. package/src/langium/document-builder/document-builder.ts +339 -11
  222. package/src/langium/document-builder/index.ts +1 -0
  223. package/src/langium/integration-services.ts +67 -6
  224. package/src/langium/integrity/integrity-service.ts +35 -1
  225. package/src/langium/keys/containment.ts +87 -0
  226. package/src/langium/keys/index.ts +1 -0
  227. package/src/langium/keys/name-based-key-provider.ts +4 -0
  228. package/src/langium/model-service/model-service.ts +145 -22
  229. package/src/langium/module.ts +104 -19
  230. package/src/langium/naming/name-provider.ts +10 -0
  231. package/src/langium/naming/name-separator-validation.ts +34 -5
  232. package/src/langium/residency/cst-residency-service.ts +36 -12
  233. package/src/langium/scope/hydranium-scope-provider.ts +40 -22
  234. package/src/langium/shared-services.ts +19 -2
  235. package/src/langium/transfer/transfer-encoder.ts +45 -3
  236. package/src/langium/validation/document-validator.ts +255 -8
  237. package/src/langium/workspace/document-uri-policy.ts +3 -4
  238. package/src/langium/workspace/hydranium-langium-document-factory.ts +34 -0
  239. package/src/langium/workspace/hydranium-workspace-manager.ts +30 -1
  240. package/src/langium/workspace/in-memory-file-system-provider.ts +13 -3
  241. package/src/langium/workspace/initialize-workspace.ts +34 -5
  242. package/src/langium/workspace/langium-documents.ts +106 -33
  243. package/src/locale/index.ts +10 -0
  244. package/src/locale/server-locale.ts +86 -0
  245. package/src/lsp/hydranium-document-update-handler.ts +1 -1
  246. package/src/messages/carriers.ts +78 -0
  247. package/src/messages/index.ts +27 -0
  248. package/src/messages/renderer.ts +204 -0
  249. package/src/node/node-file-system-provider.ts +1 -41
  250. package/src/node/profiling-run.ts +1 -1
  251. package/src/node/rename-over-open-readers.ts +78 -0
  252. package/src/testing/fake-document.ts +22 -4
  253. package/src/testing/make-noop-shared-services.ts +50 -2
  254. package/src/testing/make-test-services.ts +49 -7
  255. package/src/testing/node/spawned-server.ts +35 -1
  256. package/src/testing/playwright/flaky-network-proxy.ts +240 -0
  257. package/src/testing/playwright/index.ts +1 -0
  258. package/src/testing/playwright/server-log-capture.ts +24 -5
  259. package/src/testing/playwright/server-log-rename-reporter.ts +12 -1
  260. package/src/testing/stub-ast-document-manager.ts +5 -10
  261. package/src/testing/stub-document-builder.ts +32 -3
  262. package/src/testing/stub-model-service.ts +2 -2
@@ -97,7 +97,7 @@ export class ProfilingRun {
97
97
  // `HYDRANIUM_LOG_FILE` env, which every `@hydranium/core` copy reads when
98
98
  // its logger is CONSTRUCTED — so `ProfilingRun.start` must run before the
99
99
  // head's services are created (as `measureModelMemory` does). This is the
100
- // only mechanism that crosses a yalc'd adopter's separate core copy; a
100
+ // only mechanism that crosses an adopter's separate core copy; a
101
101
  // process-local `setLogFilePath` would only reach this module's own copy.
102
102
  this.previousLogFileEnv = process.env[DEFAULT_LOG_FILE_ENV];
103
103
  process.env[DEFAULT_LOG_FILE_ENV] = this.serverLogPath;
@@ -0,0 +1,78 @@
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
+ import * as fsp from 'node:fs/promises';
11
+
12
+ /**
13
+ * Errnos Windows raises when a replacing `rename` meets a handle already on the
14
+ * destination open. POSIX renames are unaffected by open handles, so these
15
+ * cannot fire there and the retry below costs nothing.
16
+ */
17
+ export const RENAME_CONTENTION_CODES = new Set(['EPERM', 'EACCES', 'EBUSY']);
18
+
19
+ /**
20
+ * How long a replacing rename may keep retrying before it gives up.
21
+ *
22
+ * BOUNDED, not open-ended: a genuine permission fault raises the same errno as
23
+ * contention and is distinguishable from it only by never clearing, so an
24
+ * unbounded wait would convert a hard error into a hang.
25
+ *
26
+ * The figure is a judgement, not a measurement, and the honest bound on it is
27
+ * that the previous 2s was observed EXHAUSTED on a loaded Windows CI runner
28
+ * while no upper bound has been established. What would settle it is recording
29
+ * how long a real contended rename takes to clear. graceful-fs tolerates 60s
30
+ * for Windows rename contention, but that is weaker support than it looks: it
31
+ * retries only where the destination has GONE, which is the opposite of
32
+ * replacing one a reader holds open.
33
+ */
34
+ export const RENAME_BUDGET_MS = 10_000;
35
+
36
+ /** Seams for the test, which cannot make a real rename fail on demand. */
37
+ export interface RenameOverOpenReadersOptions {
38
+ readonly rename?: (from: string, to: string) => Promise<void>;
39
+ readonly budgetMs?: number;
40
+ }
41
+
42
+ /**
43
+ * `rename` the staged content over `destination`, retrying while Windows
44
+ * reports the destination as held open.
45
+ *
46
+ * The staging file exists so a reader never sees a half-written file, and on
47
+ * Windows the very presence of that reader is what makes the replacing rename
48
+ * fail — so the indivisible write breaks under exactly the contention it was
49
+ * built for, and it breaks intermittently, which is worse than never working.
50
+ * A reader's handle is released in milliseconds, so waiting turns a spurious
51
+ * failure into a slightly later success.
52
+ *
53
+ * Exhausting the budget rethrows the last failure, leaving the caller's cleanup
54
+ * and error contract unchanged. An errno OUTSIDE the contention set is rethrown
55
+ * without waiting at all: retrying a fault that will not clear only delays it.
56
+ */
57
+ export async function renameOverOpenReaders(
58
+ staging: string,
59
+ destination: string,
60
+ options: RenameOverOpenReadersOptions = {}
61
+ ): Promise<void> {
62
+ const rename = options.rename ?? fsp.rename;
63
+ const deadline = Date.now() + (options.budgetMs ?? RENAME_BUDGET_MS);
64
+ for (let attempt = 0; ; attempt++) {
65
+ try {
66
+ await rename(staging, destination);
67
+ return;
68
+ } catch (err: unknown) {
69
+ const code = err instanceof Error && 'code' in err ? String((err as NodeJS.ErrnoException).code) : undefined;
70
+ if (code === undefined || !RENAME_CONTENTION_CODES.has(code) || Date.now() >= deadline) {
71
+ throw err;
72
+ }
73
+ // Backs off to keep a long contention window from spinning, capped so
74
+ // a late attempt still lands promptly once the handle is released.
75
+ await new Promise(resolve => setTimeout(resolve, Math.min(2 ** attempt, 50)));
76
+ }
77
+ }
78
+ }
@@ -7,7 +7,8 @@
7
7
  * SPDX-License-Identifier: MIT
8
8
  ********************************************************************************/
9
9
 
10
- import { DocumentState, type AstNode, type LangiumDocument, UriUtils, type URI } from '@hydranium/langium';
10
+ import { DocumentState, type AstNode, type AstReflection, type LangiumDocument, UriUtils, type URI } from '@hydranium/langium';
11
+ import { buildAstNode } from '../langium/ast-extension/ast-node-builder.js';
11
12
 
12
13
  /**
13
14
  * Build a typed AST-node fixture for unit tests.
@@ -23,12 +24,29 @@ import { DocumentState, type AstNode, type LangiumDocument, UriUtils, type URI }
23
24
  * framework synthetic node, and marking it would change scope/index behaviour.
24
25
  * A test that wants a genuine synthetic node calls `markSynthetic` explicitly.
25
26
  *
27
+ * **Without a `reflection`, the fixture carries only what the test spells out**,
28
+ * which is a shape production never produces: a parsed or built node always has
29
+ * its grammar-declared containment arrays, so a fixture that omits one lets a
30
+ * test pass against an impossible node, and lets code that walks a list crash
31
+ * only under test. Pass the grammar's reflection whenever the type is a real
32
+ * one and the code under test reads a collection off it. Fixtures typed against
33
+ * ad-hoc names have no reflection to pass and keep the plain form.
34
+ *
26
35
  * @param node the node shape — `$type` is required; every other property is a
27
36
  * value the test cares about (`$container`, cross-reference values, internal
28
- * `_id` fields, …). The object is returned by reference, not copied.
37
+ * `_id` fields, …).
38
+ * @param reflection when given, pre-fills the grammar's declared defaults the
39
+ * same way {@link buildAstNode} does, and the result is a fresh object rather
40
+ * than `node` itself. Omitted, `node` is returned by reference.
29
41
  */
30
- export function makeFakeAstNode<TAst extends AstNode = AstNode>(node: { $type: string } & Record<string, unknown>): TAst {
31
- return node as unknown as TAst;
42
+ export function makeFakeAstNode<TAst extends AstNode = AstNode>(
43
+ node: { $type: string } & Record<string, unknown>,
44
+ reflection?: AstReflection
45
+ ): TAst {
46
+ if (!reflection) {
47
+ return node as unknown as TAst;
48
+ }
49
+ return buildAstNode<TAst>(reflection, node.$type, node);
32
50
  }
33
51
 
34
52
  /**
@@ -9,6 +9,8 @@
9
9
 
10
10
  import { type Clock, DefaultTracer, type Logger, NoopLogger, SystemClock, type Tracer } from '@hydranium/protocol';
11
11
  import type { ServerSharedServicesMinimal } from '../langium/shared-services.js';
12
+ import { DefaultServerLocale, type ServerLocale } from '../locale/server-locale.js';
13
+ import { DefaultMessageRenderer, type MessageRenderer } from '../messages/renderer.js';
12
14
 
13
15
  /**
14
16
  * Overrides for {@link makeNoopSharedServices}. The three observability slots
@@ -29,6 +31,18 @@ export interface NoopSharedServicesOverrides {
29
31
  Logger?: Logger;
30
32
  /** Bound on the top-level `Tracer` slot. Default: a `DefaultTracer` over `Logger` + `Clock`. */
31
33
  Tracer?: Tracer;
34
+ /** Factory for the `ServerLocale` slot. Default: a real one, reporting no locale. */
35
+ ServerLocale?: (services: ServerSharedServicesMinimal) => ServerLocale;
36
+ /**
37
+ * Factory for the `MessageRenderer` slot. Default: the real
38
+ * {@link DefaultMessageRenderer}, whose no-catalogue behaviour is a
39
+ * pass-through — so the default is the framework's own behaviour, not a stub.
40
+ *
41
+ * A factory rather than an instance, because a renderer reads the tree it is
42
+ * bound into. Both slots are resolved lazily, so an override is honoured
43
+ * however late the caller reads them.
44
+ */
45
+ MessageRenderer?: (services: ServerSharedServicesMinimal) => MessageRenderer;
32
46
  /** Per-slot `workspace` overrides. Slots left out resolve to `undefined`. */
33
47
  workspace?: Record<string, unknown>;
34
48
  /** Any other minimal slot (`ServiceRegistry`, `AstReflection`, `additionalDocuments`, …). */
@@ -63,11 +77,21 @@ export interface NoopSharedServicesOverrides {
63
77
  export function makeNoopSharedServices<T extends ServerSharedServicesMinimal = ServerSharedServicesMinimal>(
64
78
  overrides: NoopSharedServicesOverrides = {}
65
79
  ): T {
66
- const { Clock: clock, Logger: logger, Tracer: tracer, workspace, additionalDocuments, ...restTop } = overrides;
80
+ const {
81
+ Clock: clock,
82
+ Logger: logger,
83
+ Tracer: tracer,
84
+ ServerLocale: serverLocale,
85
+ MessageRenderer: messageRenderer,
86
+ workspace,
87
+ additionalDocuments,
88
+ ...restTop
89
+ } = overrides;
67
90
  const resolvedClock = clock ?? new SystemClock();
68
91
  const resolvedLogger = logger ?? new NoopLogger();
69
92
  const resolvedTracer = tracer ?? new DefaultTracer(resolvedLogger, resolvedClock);
70
- return {
93
+ // Assembled in two steps, because both defaults read the tree they belong to.
94
+ const services = {
71
95
  Clock: resolvedClock,
72
96
  Logger: resolvedLogger,
73
97
  Tracer: resolvedTracer,
@@ -75,4 +99,28 @@ export function makeNoopSharedServices<T extends ServerSharedServicesMinimal = S
75
99
  ...restTop,
76
100
  workspace: { ...(workspace ?? {}) }
77
101
  } as unknown as T;
102
+ // Lazy, like Langium's own per-slot construction — and load-bearing here:
103
+ // both services emit an `instantiated` trace, so building them eagerly puts
104
+ // two lines into the capture of every test that passes a capturing logger
105
+ // and asserts on emptiness.
106
+ defineLazySlot(services, 'ServerLocale', () => serverLocale?.(services) ?? new DefaultServerLocale(services));
107
+ defineLazySlot(services, 'MessageRenderer', () => messageRenderer?.(services) ?? new DefaultMessageRenderer(services));
108
+ return services;
109
+ }
110
+
111
+ /** Install `name` as a memoized getter, so nothing is constructed until it is read. */
112
+ function defineLazySlot(target: object, name: string, create: () => unknown): void {
113
+ let resolved: unknown;
114
+ let built = false;
115
+ Object.defineProperty(target, name, {
116
+ configurable: true,
117
+ enumerable: true,
118
+ get: () => {
119
+ if (!built) {
120
+ resolved = create();
121
+ built = true;
122
+ }
123
+ return resolved;
124
+ }
125
+ });
78
126
  }
@@ -18,11 +18,13 @@ import {
18
18
  type TransferDiagnostic,
19
19
  type TransferElement
20
20
  } from '@hydranium/protocol';
21
+ import { DefaultServerLocale, type ServerLocale } from '../locale/server-locale.js';
22
+ import { DefaultMessageRenderer, type MessageRenderer } from '../messages/renderer.js';
21
23
  import type { Harness } from '@hydranium/protocol/testing';
22
24
  import { type AstNode, type AstNodeDescription, type WorkspaceLock } from '@hydranium/langium';
23
25
  import type { ModelService, ModelServiceOptions } from '../langium/model-service/model-service.js';
24
26
  import type { ServerSharedServices } from '../langium/module.js';
25
- import { TransferEncoder } from '../langium/transfer/transfer-encoder.js';
27
+ import { DefaultTransferEncoder, type TransferEncoder } from '../langium/transfer/transfer-encoder.js';
26
28
  import { DefaultDocumentUriPolicy, type DocumentUriPolicy } from '../langium/workspace/document-uri-policy.js';
27
29
  import { HydraniumWorkspaceLock } from '../langium/workspace/hydranium-workspace-lock.js';
28
30
  import type { FakeDocumentOptions } from './fake-document.js';
@@ -90,9 +92,18 @@ export interface TestSharedServices<
90
92
  WorkspaceLock: WorkspaceLock;
91
93
  };
92
94
  readonly model: {
93
- TransferEncoder: TransferEncoder<unknown, TDiagnostic>;
95
+ TransferEncoder: TransferEncoder<TDiagnostic>;
94
96
  ModelService: ModelService<TAst, TDiagnostic, TTransfer>;
95
97
  };
98
+ /**
99
+ * The REAL services, not stubs — the framework renderer's no-catalogue
100
+ * behaviour is a pass-through, so the stub tree agrees with a production one
101
+ * unless a test installs a catalogue. Bound rather than omitted because
102
+ * `HydraniumDocumentBuilder` renders through the renderer on every
103
+ * `Validated` phase, where an omitted slot is a `TypeError`.
104
+ */
105
+ readonly ServerLocale: ServerLocale;
106
+ readonly MessageRenderer: MessageRenderer;
96
107
  }
97
108
 
98
109
  /** Optional configuration for {@link makeTestServices}. */
@@ -112,6 +123,14 @@ export interface MakeTestServicesOptions<
112
123
  * for tests that don't assert on the produced text.
113
124
  */
114
125
  serialize?: (uri: string, root: TTransfer) => string;
126
+ /**
127
+ * Renderer bound on `MessageRenderer`. Default: the framework's own, which
128
+ * passes every sentence through unchanged. Supply one to install a catalogue
129
+ * or to drive the throwing path.
130
+ */
131
+ messageRenderer?: (services: ServerSharedServices<TProject>) => MessageRenderer;
132
+ /** Locale handed to the bundle's {@link ServerLocale}. Default: none, i.e. the framework's English. */
133
+ locale?: string;
115
134
  /**
116
135
  * Languages to register on a {@link StubServiceRegistry} bound on the
117
136
  * `ServiceRegistry` slot. This is how a test gets multi-language routing:
@@ -187,7 +206,7 @@ export interface MakeTestServicesOptions<
187
206
  * Override the {@link TransferEncoder} factory. Default: framework
188
207
  * {@link TransferEncoder} with no overrides.
189
208
  */
190
- transferEncoder?: (services: ServerSharedServices<TProject>) => TransferEncoder<unknown, TDiagnostic>;
209
+ transferEncoder?: (services: ServerSharedServices<TProject>) => TransferEncoder<TDiagnostic>;
191
210
  }
192
211
 
193
212
  /**
@@ -229,10 +248,18 @@ export interface TestServicesBundle<
229
248
  */
230
249
  readonly indexManager: StubIndexManager | undefined;
231
250
  readonly modelService: ModelService<TAst, TDiagnostic, TTransfer>;
232
- readonly transferEncoder: TransferEncoder<unknown, TDiagnostic>;
251
+ readonly transferEncoder: TransferEncoder<TDiagnostic>;
233
252
  readonly logger: Logger;
234
253
  /** The clock bound on the `Clock` slot — a `makeFakeClock()` if one was passed. */
235
254
  readonly clock: Clock;
255
+ /**
256
+ * The service bound on `ServerLocale`, so a test can hand over a locale
257
+ * mid-run — which is also how it verifies the renderer reads the locale per
258
+ * render rather than caching it at construction.
259
+ */
260
+ readonly serverLocale: ServerLocale;
261
+ /** The renderer bound on `MessageRenderer` — the framework's own unless one was supplied. */
262
+ readonly messageRenderer: MessageRenderer;
236
263
  }
237
264
 
238
265
  /**
@@ -311,20 +338,33 @@ export function makeTestServices<
311
338
  ...(indexManager ? { IndexManager: indexManager } : {}),
312
339
  WorkspaceLock: new HydraniumWorkspaceLock()
313
340
  },
314
- model: {} as TestSharedServices<TAst, TDiagnostic, TTransfer, TProject>['model']
341
+ model: {} as TestSharedServices<TAst, TDiagnostic, TTransfer, TProject>['model'],
342
+ ServerLocale: {} as ServerLocale,
343
+ MessageRenderer: {} as DefaultMessageRenderer
315
344
  };
316
345
  const sharedServices = services as unknown as ServerSharedServices<TProject>;
317
346
 
347
+ // Patched in after the literal, like `model` below: both read the tree they
348
+ // belong to, and the renderer reads the locale service.
349
+ const mutableMessages = services as { ServerLocale: ServerLocale; MessageRenderer: MessageRenderer };
350
+ const serverLocale = new DefaultServerLocale(sharedServices);
351
+ if (options.locale) {
352
+ serverLocale.accept(options.locale);
353
+ }
354
+ mutableMessages.ServerLocale = serverLocale;
355
+ const messageRenderer = options.messageRenderer?.(sharedServices) ?? new DefaultMessageRenderer(sharedServices);
356
+ mutableMessages.MessageRenderer = messageRenderer;
357
+
318
358
  const serialize = options.serialize ?? ((_uri: string, root: TTransfer) => JSON.stringify(root));
319
359
  const transferEncoder = options.transferEncoder
320
360
  ? options.transferEncoder(sharedServices)
321
- : new TransferEncoder<unknown, TDiagnostic>(sharedServices);
361
+ : new DefaultTransferEncoder<unknown, TDiagnostic>(sharedServices);
322
362
  const modelService = options.modelService
323
363
  ? options.modelService(sharedServices)
324
364
  : makeStubModelService<TAst, TDiagnostic, TTransfer>(sharedServices, serialize, options.modelServiceOptions);
325
365
 
326
366
  const mutableModel = services.model as {
327
- TransferEncoder: TransferEncoder<unknown, TDiagnostic>;
367
+ TransferEncoder: TransferEncoder<TDiagnostic>;
328
368
  ModelService: ModelService<TAst, TDiagnostic, TTransfer>;
329
369
  };
330
370
  mutableModel.TransferEncoder = transferEncoder;
@@ -346,6 +386,8 @@ export function makeTestServices<
346
386
  transferEncoder,
347
387
  logger,
348
388
  clock,
389
+ serverLocale,
390
+ messageRenderer,
349
391
  dispose: () => {
350
392
  // No-op: the bundled stubs hold only in-memory state (maps, arrays),
351
393
  // released with the bundle when it goes out of scope. The hook exists
@@ -203,6 +203,37 @@ export interface SpawnedServer extends Harness {
203
203
  dispose(): Promise<number | null>;
204
204
  }
205
205
 
206
+ /**
207
+ * Every server started and not yet disposed, so {@link disposeSpawnedServers}
208
+ * can reach one a test forgot.
209
+ *
210
+ * Holds the server — and through it the `ChildProcess` — rather than a pid. A
211
+ * pid identifies a process only while that process is alive: once it exits the
212
+ * OS may reissue the number, and Windows does so within seconds. A teardown
213
+ * that signalled a recorded pid would therefore reach whatever holds it now,
214
+ * which on a concurrent build is another task's compiler or test runner.
215
+ */
216
+ const liveServers = new Set<SpawnedServer>();
217
+
218
+ /**
219
+ * Terminate every server this module started that has not been disposed, and
220
+ * forget them.
221
+ *
222
+ * Safe to call when none are outstanding, and safe to call twice. Nothing here
223
+ * can reach a process this module did not spawn, because every kill goes
224
+ * through the `ChildProcess` handle libuv holds and libuv will not signal a
225
+ * child it has already reaped.
226
+ *
227
+ * A suite that disposes each server as it finishes needs this only as a net; a
228
+ * suite that does not needs it, because an orphan holding a port or a pipe
229
+ * makes the NEXT run's verdict meaningless.
230
+ */
231
+ export async function disposeSpawnedServers(): Promise<void> {
232
+ const outstanding = [...liveServers];
233
+ liveServers.clear();
234
+ await Promise.all(outstanding.map(server => server.dispose()));
235
+ }
236
+
206
237
  /** The captured streams, quoted into a boot-failure message. */
207
238
  function bootDiagnosis(stdoutPrefix: string, stderr: string): string {
208
239
  return (
@@ -390,7 +421,7 @@ export async function startSpawnedServer(options: SpawnedServerOptions): Promise
390
421
  return exitCode;
391
422
  }
392
423
 
393
- return {
424
+ const server: SpawnedServer = {
394
425
  connection,
395
426
  pid: child.pid,
396
427
  initializeResult,
@@ -440,8 +471,11 @@ export async function startSpawnedServer(options: SpawnedServerOptions): Promise
440
471
  });
441
472
  },
442
473
  dispose(): Promise<number | null> {
474
+ liveServers.delete(server);
443
475
  disposal ??= terminate();
444
476
  return disposal;
445
477
  }
446
478
  };
479
+ liveServers.add(server);
480
+ return server;
447
481
  }
@@ -0,0 +1,240 @@
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
+ import { createServer as createHttpServer, type Server as HttpServer } from 'node:http';
11
+ import { connect, createServer as createTcpServer, type Server as TcpServer, type Socket } from 'node:net';
12
+
13
+ /**
14
+ * A TCP proxy that can half-close a connection, so an e2e suite can reproduce a
15
+ * flaky network against a real browser session.
16
+ *
17
+ * Toggling the browser offline is not enough. On localhost the server sees the
18
+ * close immediately and reaps the socket before the browser reconnects, so the
19
+ * window where both sides disagree about the session — the one where reconnect
20
+ * defects live — never opens. Reaching it needs the browser's socket to die
21
+ * while the server still believes it is alive, and nothing inside the app or in
22
+ * Playwright's offline emulation can produce that asymmetry: both close cleanly
23
+ * at both ends.
24
+ *
25
+ * The proxy holds both halves of every connection and lets a test kill them
26
+ * independently:
27
+ *
28
+ * `break` destroy the BROWSER-side sockets, keep the server-side ones open.
29
+ * The browser reconnects through a fresh pair while the server
30
+ * still holds the old one.
31
+ * `release` destroy the orphaned server-side sockets `break` left. The
32
+ * server's disconnect handler fires at once, rather than whenever
33
+ * its next heartbeat write would have failed.
34
+ */
35
+
36
+ /** Where the proxy listens, and the application it fronts. */
37
+ export interface FlakyNetworkProxyOptions {
38
+ /** Port the browser connects to. */
39
+ readonly listenPort: number;
40
+ /** Port of the application being proxied. */
41
+ readonly targetPort: number;
42
+ /** Host of the application being proxied. Defaults to loopback. */
43
+ readonly targetHost?: string;
44
+ /** Port the HTTP control channel listens on. Defaults to `listenPort + 1000`. */
45
+ readonly controlPort?: number;
46
+ }
47
+
48
+ /** The commands the control channel accepts, and the proxy's own vocabulary. */
49
+ export type FlakyNetworkCommand = 'break' | 'release' | 'status';
50
+
51
+ /** Drives a running proxy. Implemented both in-process and over HTTP. */
52
+ export interface FlakyNetworkControl {
53
+ /** Kill the browser side of every live connection; leave the server side up. */
54
+ break(): Promise<string>;
55
+ /** Kill the server side of every connection `break` orphaned. */
56
+ release(): Promise<string>;
57
+ /** How many connections are live and how many are orphaned. */
58
+ status(): Promise<string>;
59
+ }
60
+
61
+ export interface FlakyNetworkProxy extends FlakyNetworkControl {
62
+ /** Base URL a browser should be pointed at, i.e. Playwright's `baseURL`. */
63
+ readonly url: string;
64
+ /** Control channel, for workers that cannot reach this object directly. */
65
+ readonly controlUrl: string;
66
+ close(): Promise<void>;
67
+ }
68
+
69
+ interface ConnectionPair {
70
+ readonly id: number;
71
+ readonly browserSocket: Socket;
72
+ readonly targetSocket: Socket;
73
+ orphaned: boolean;
74
+ }
75
+
76
+ /**
77
+ * Starts the proxy in the calling process.
78
+ *
79
+ * Call it from a Playwright config, which runs in the main process before any
80
+ * worker forks. Workers are separate processes and cannot reach the returned
81
+ * object, which is what {@link controlUrl} and {@link flakyNetworkControlClient}
82
+ * are for.
83
+ */
84
+ export async function startFlakyNetworkProxy(options: FlakyNetworkProxyOptions): Promise<FlakyNetworkProxy> {
85
+ const targetHost = options.targetHost ?? '127.0.0.1';
86
+ const controlPort = options.controlPort ?? options.listenPort + 1000;
87
+ const pairs = new Set<ConnectionPair>();
88
+ let sequence = 0;
89
+ let transcript: string[] = [];
90
+
91
+ const log = (message: string): void => {
92
+ transcript.push(message);
93
+ console.log(`[flaky-network] ${message}`);
94
+ };
95
+
96
+ const proxyServer: TcpServer = createTcpServer(browserSocket => {
97
+ const id = ++sequence;
98
+ const targetSocket = connect(options.targetPort, targetHost);
99
+ const pair: ConnectionPair = { id, browserSocket, targetSocket, orphaned: false };
100
+ pairs.add(pair);
101
+
102
+ // Forwarded by hand rather than with `pipe()`, so killing one side does not
103
+ // tear the other down — which is the entire point of the proxy.
104
+ browserSocket.on('data', chunk => {
105
+ if (!targetSocket.destroyed) {
106
+ targetSocket.write(chunk);
107
+ }
108
+ });
109
+ targetSocket.on('data', chunk => {
110
+ if (!pair.orphaned && !browserSocket.destroyed) {
111
+ browserSocket.write(chunk);
112
+ }
113
+ });
114
+
115
+ const teardown = (origin: 'browser' | 'target'): void => {
116
+ if (pair.orphaned && origin === 'browser') {
117
+ // Expected: `break` killed this side deliberately. The server side
118
+ // stays up until `release`.
119
+ return;
120
+ }
121
+ pairs.delete(pair);
122
+ browserSocket.destroy();
123
+ targetSocket.destroy();
124
+ };
125
+
126
+ browserSocket.on('close', () => teardown('browser'));
127
+ targetSocket.on('close', () => teardown('target'));
128
+ // A half-killed pair reliably raises ECONNRESET on the surviving side; an
129
+ // unhandled 'error' would take the whole runner down with it.
130
+ browserSocket.on('error', () => undefined);
131
+ targetSocket.on('error', () => undefined);
132
+ });
133
+
134
+ const commands: Record<FlakyNetworkCommand, () => void> = {
135
+ break: () => {
136
+ const live = [...pairs].filter(pair => !pair.orphaned);
137
+ if (live.length === 0) {
138
+ log('nothing to break');
139
+ return;
140
+ }
141
+ for (const pair of live) {
142
+ pair.orphaned = true;
143
+ pair.browserSocket.destroy();
144
+ }
145
+ log(`${live.length} connection(s) broken; the browser should reconnect shortly`);
146
+ },
147
+ release: () => {
148
+ const orphans = [...pairs].filter(pair => pair.orphaned);
149
+ if (orphans.length === 0) {
150
+ log('no orphaned connections; run `break` first');
151
+ return;
152
+ }
153
+ for (const pair of orphans) {
154
+ pairs.delete(pair);
155
+ pair.targetSocket.destroy();
156
+ }
157
+ log(`${orphans.length} orphaned connection(s) destroyed; their late disconnect fires now`);
158
+ },
159
+ status: () => {
160
+ const live = [...pairs].filter(pair => !pair.orphaned).length;
161
+ const orphaned = pairs.size - live;
162
+ log(`${live} live, ${orphaned} orphaned`);
163
+ }
164
+ };
165
+
166
+ const run = (name: string): string => {
167
+ transcript = [];
168
+ const command = commands[name as FlakyNetworkCommand];
169
+ if (command) {
170
+ command();
171
+ } else {
172
+ log(`unknown command '${name}'; use: ${Object.keys(commands).join(' | ')}`);
173
+ }
174
+ return transcript.join('\n');
175
+ };
176
+
177
+ const controlServer: HttpServer = createHttpServer((request, response) => {
178
+ const output = run((request.url ?? '').replace(/^\//, '').split('?')[0]);
179
+ response.writeHead(200, { 'content-type': 'text/plain' });
180
+ response.end(`${output}\n`);
181
+ });
182
+
183
+ await Promise.all([listen(proxyServer, options.listenPort), listen(controlServer, controlPort)]);
184
+ // Neither server keeps the process alive, so a runner that starts one from a
185
+ // config does not need a teardown hook to exit — and a caller that wants a
186
+ // deterministic shutdown still has `close()`.
187
+ proxyServer.unref();
188
+ controlServer.unref();
189
+
190
+ return {
191
+ url: `http://localhost:${options.listenPort}`,
192
+ controlUrl: `http://localhost:${controlPort}`,
193
+ break: async () => run('break'),
194
+ release: async () => run('release'),
195
+ status: async () => run('status'),
196
+ close: async () => {
197
+ for (const pair of pairs) {
198
+ pair.browserSocket.destroy();
199
+ pair.targetSocket.destroy();
200
+ }
201
+ pairs.clear();
202
+ await Promise.all([close(proxyServer), close(controlServer)]);
203
+ }
204
+ };
205
+ }
206
+
207
+ /**
208
+ * Drives a proxy running in another process, over its control channel.
209
+ *
210
+ * This is what a spec uses: Playwright workers are separate processes from the
211
+ * config that started the proxy.
212
+ */
213
+ export function flakyNetworkControlClient(controlUrl: string): FlakyNetworkControl {
214
+ const send = async (command: FlakyNetworkCommand): Promise<string> => {
215
+ const response = await fetch(`${controlUrl}/${command}`);
216
+ if (!response.ok) {
217
+ throw new Error(`flaky-network proxy rejected '${command}': ${response.status}`);
218
+ }
219
+ return (await response.text()).trim();
220
+ };
221
+ return {
222
+ break: () => send('break'),
223
+ release: () => send('release'),
224
+ status: () => send('status')
225
+ };
226
+ }
227
+
228
+ function listen(server: TcpServer | HttpServer, port: number): Promise<void> {
229
+ return new Promise((resolve, reject) => {
230
+ server.once('error', reject);
231
+ server.listen(port, () => {
232
+ server.off('error', reject);
233
+ resolve();
234
+ });
235
+ });
236
+ }
237
+
238
+ function close(server: TcpServer | HttpServer): Promise<void> {
239
+ return new Promise(resolve => server.close(() => resolve()));
240
+ }
@@ -11,6 +11,7 @@
11
11
  // `@hydranium/core/testing` barrel) so importing the framework's unit-test
12
12
  // helpers never pulls in `@playwright/test` — which is an OPTIONAL peer here.
13
13
  export * from './e2e-profiling.js';
14
+ export * from './flaky-network-proxy.js';
14
15
  export * from './browser-capture-bridge.js';
15
16
  export * from './server-log-capture.js';
16
17
  export * from './server-log-fixture.js';