@hydranium/protocol 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 (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -0
  3. package/lib/abstract-logger.d.ts +66 -0
  4. package/lib/abstract-logger.d.ts.map +1 -0
  5. package/lib/abstract-logger.js +110 -0
  6. package/lib/abstract-logger.js.map +1 -0
  7. package/lib/browser-runtime.d.ts +124 -0
  8. package/lib/browser-runtime.d.ts.map +1 -0
  9. package/lib/browser-runtime.js +11 -0
  10. package/lib/browser-runtime.js.map +1 -0
  11. package/lib/client/data-events.d.ts +49 -0
  12. package/lib/client/data-events.d.ts.map +1 -0
  13. package/lib/client/data-events.js +60 -0
  14. package/lib/client/data-events.js.map +1 -0
  15. package/lib/client/data-port.d.ts +95 -0
  16. package/lib/client/data-port.d.ts.map +1 -0
  17. package/lib/client/data-port.js +11 -0
  18. package/lib/client/data-port.js.map +1 -0
  19. package/lib/client/data-session.d.ts +129 -0
  20. package/lib/client/data-session.d.ts.map +1 -0
  21. package/lib/client/data-session.js +179 -0
  22. package/lib/client/data-session.js.map +1 -0
  23. package/lib/client/index.d.ts +39 -0
  24. package/lib/client/index.d.ts.map +1 -0
  25. package/lib/client/index.js +55 -0
  26. package/lib/client/index.js.map +1 -0
  27. package/lib/client/message-relay.d.ts +111 -0
  28. package/lib/client/message-relay.d.ts.map +1 -0
  29. package/lib/client/message-relay.js +139 -0
  30. package/lib/client/message-relay.js.map +1 -0
  31. package/lib/client/post-message-transport.d.ts +77 -0
  32. package/lib/client/post-message-transport.d.ts.map +1 -0
  33. package/lib/client/post-message-transport.js +95 -0
  34. package/lib/client/post-message-transport.js.map +1 -0
  35. package/lib/clock.d.ts +111 -0
  36. package/lib/clock.d.ts.map +1 -0
  37. package/lib/clock.js +74 -0
  38. package/lib/clock.js.map +1 -0
  39. package/lib/data/data-protocol-methods.d.ts +39 -0
  40. package/lib/data/data-protocol-methods.d.ts.map +1 -0
  41. package/lib/data/data-protocol-methods.js +61 -0
  42. package/lib/data/data-protocol-methods.js.map +1 -0
  43. package/lib/data/data-server-protocol.d.ts +264 -0
  44. package/lib/data/data-server-protocol.d.ts.map +1 -0
  45. package/lib/data/data-server-protocol.js +11 -0
  46. package/lib/data/data-server-protocol.js.map +1 -0
  47. package/lib/data/diagnostics.d.ts +97 -0
  48. package/lib/data/diagnostics.d.ts.map +1 -0
  49. package/lib/data/diagnostics.js +25 -0
  50. package/lib/data/diagnostics.js.map +1 -0
  51. package/lib/data/events.d.ts +107 -0
  52. package/lib/data/events.d.ts.map +1 -0
  53. package/lib/data/events.js +11 -0
  54. package/lib/data/events.js.map +1 -0
  55. package/lib/data/index.d.ts +15 -0
  56. package/lib/data/index.d.ts.map +1 -0
  57. package/lib/data/index.js +38 -0
  58. package/lib/data/index.js.map +1 -0
  59. package/lib/data/methods.d.ts +57 -0
  60. package/lib/data/methods.d.ts.map +1 -0
  61. package/lib/data/methods.js +60 -0
  62. package/lib/data/methods.js.map +1 -0
  63. package/lib/data/requests.d.ts +65 -0
  64. package/lib/data/requests.d.ts.map +1 -0
  65. package/lib/data/requests.js +11 -0
  66. package/lib/data/requests.js.map +1 -0
  67. package/lib/debouncer.d.ts +75 -0
  68. package/lib/debouncer.d.ts.map +1 -0
  69. package/lib/debouncer.js +91 -0
  70. package/lib/debouncer.js.map +1 -0
  71. package/lib/errors.d.ts +84 -0
  72. package/lib/errors.d.ts.map +1 -0
  73. package/lib/errors.js +113 -0
  74. package/lib/errors.js.map +1 -0
  75. package/lib/host-diagnostics.d.ts +58 -0
  76. package/lib/host-diagnostics.d.ts.map +1 -0
  77. package/lib/host-diagnostics.js +18 -0
  78. package/lib/host-diagnostics.js.map +1 -0
  79. package/lib/index.d.ts +34 -0
  80. package/lib/index.d.ts.map +1 -0
  81. package/lib/index.js +56 -0
  82. package/lib/index.js.map +1 -0
  83. package/lib/latency-collector.d.ts +126 -0
  84. package/lib/latency-collector.d.ts.map +1 -0
  85. package/lib/latency-collector.js +137 -0
  86. package/lib/latency-collector.js.map +1 -0
  87. package/lib/logger.d.ts +119 -0
  88. package/lib/logger.d.ts.map +1 -0
  89. package/lib/logger.js +126 -0
  90. package/lib/logger.js.map +1 -0
  91. package/lib/model-server.d.ts +352 -0
  92. package/lib/model-server.d.ts.map +1 -0
  93. package/lib/model-server.js +160 -0
  94. package/lib/model-server.js.map +1 -0
  95. package/lib/model-service/args.d.ts +64 -0
  96. package/lib/model-service/args.d.ts.map +1 -0
  97. package/lib/model-service/args.js +11 -0
  98. package/lib/model-service/args.js.map +1 -0
  99. package/lib/model-service/index.d.ts +11 -0
  100. package/lib/model-service/index.d.ts.map +1 -0
  101. package/lib/model-service/index.js +31 -0
  102. package/lib/model-service/index.js.map +1 -0
  103. package/lib/model-service/reference-candidate.d.ts +56 -0
  104. package/lib/model-service/reference-candidate.d.ts.map +1 -0
  105. package/lib/model-service/reference-candidate.js +11 -0
  106. package/lib/model-service/reference-candidate.js.map +1 -0
  107. package/lib/noop-logger.d.ts +25 -0
  108. package/lib/noop-logger.d.ts.map +1 -0
  109. package/lib/noop-logger.js +36 -0
  110. package/lib/noop-logger.js.map +1 -0
  111. package/lib/observable-value.d.ts +72 -0
  112. package/lib/observable-value.d.ts.map +1 -0
  113. package/lib/observable-value.js +52 -0
  114. package/lib/observable-value.js.map +1 -0
  115. package/lib/patch-merge.d.ts +123 -0
  116. package/lib/patch-merge.d.ts.map +1 -0
  117. package/lib/patch-merge.js +104 -0
  118. package/lib/patch-merge.js.map +1 -0
  119. package/lib/profile-session.d.ts +87 -0
  120. package/lib/profile-session.d.ts.map +1 -0
  121. package/lib/profile-session.js +98 -0
  122. package/lib/profile-session.js.map +1 -0
  123. package/lib/profiling.d.ts +83 -0
  124. package/lib/profiling.d.ts.map +1 -0
  125. package/lib/profiling.js +20 -0
  126. package/lib/profiling.js.map +1 -0
  127. package/lib/project.d.ts +121 -0
  128. package/lib/project.d.ts.map +1 -0
  129. package/lib/project.js +34 -0
  130. package/lib/project.js.map +1 -0
  131. package/lib/rpc/bind-rpc-methods.d.ts +100 -0
  132. package/lib/rpc/bind-rpc-methods.d.ts.map +1 -0
  133. package/lib/rpc/bind-rpc-methods.js +110 -0
  134. package/lib/rpc/bind-rpc-methods.js.map +1 -0
  135. package/lib/rpc/create-rpc-proxy.d.ts +188 -0
  136. package/lib/rpc/create-rpc-proxy.d.ts.map +1 -0
  137. package/lib/rpc/create-rpc-proxy.js +158 -0
  138. package/lib/rpc/create-rpc-proxy.js.map +1 -0
  139. package/lib/rpc/index.d.ts +12 -0
  140. package/lib/rpc/index.d.ts.map +1 -0
  141. package/lib/rpc/index.js +31 -0
  142. package/lib/rpc/index.js.map +1 -0
  143. package/lib/rpc/wire-prefix.d.ts +37 -0
  144. package/lib/rpc/wire-prefix.d.ts.map +1 -0
  145. package/lib/rpc/wire-prefix.js +49 -0
  146. package/lib/rpc/wire-prefix.js.map +1 -0
  147. package/lib/testing/data-doubles.d.ts +121 -0
  148. package/lib/testing/data-doubles.d.ts.map +1 -0
  149. package/lib/testing/data-doubles.js +102 -0
  150. package/lib/testing/data-doubles.js.map +1 -0
  151. package/lib/testing/fake-clock.d.ts +35 -0
  152. package/lib/testing/fake-clock.d.ts.map +1 -0
  153. package/lib/testing/fake-clock.js +86 -0
  154. package/lib/testing/fake-clock.js.map +1 -0
  155. package/lib/testing/harness.d.ts +24 -0
  156. package/lib/testing/harness.d.ts.map +1 -0
  157. package/lib/testing/harness.js +11 -0
  158. package/lib/testing/harness.js.map +1 -0
  159. package/lib/testing/index.d.ts +13 -0
  160. package/lib/testing/index.d.ts.map +1 -0
  161. package/lib/testing/index.js +41 -0
  162. package/lib/testing/index.js.map +1 -0
  163. package/lib/testing/node/duplex-connection.d.ts +25 -0
  164. package/lib/testing/node/duplex-connection.d.ts.map +1 -0
  165. package/lib/testing/node/duplex-connection.js +34 -0
  166. package/lib/testing/node/duplex-connection.js.map +1 -0
  167. package/lib/testing/node/duplex-stream.d.ts +32 -0
  168. package/lib/testing/node/duplex-stream.d.ts.map +1 -0
  169. package/lib/testing/node/duplex-stream.js +35 -0
  170. package/lib/testing/node/duplex-stream.js.map +1 -0
  171. package/lib/testing/node/index.d.ts +11 -0
  172. package/lib/testing/node/index.d.ts.map +1 -0
  173. package/lib/testing/node/index.js +37 -0
  174. package/lib/testing/node/index.js.map +1 -0
  175. package/lib/testing/wait-for.d.ts +69 -0
  176. package/lib/testing/wait-for.d.ts.map +1 -0
  177. package/lib/testing/wait-for.js +67 -0
  178. package/lib/testing/wait-for.js.map +1 -0
  179. package/lib/tracer.d.ts +115 -0
  180. package/lib/tracer.d.ts.map +1 -0
  181. package/lib/tracer.js +205 -0
  182. package/lib/tracer.js.map +1 -0
  183. package/lib/transfer-diagnostic.d.ts +73 -0
  184. package/lib/transfer-diagnostic.d.ts.map +1 -0
  185. package/lib/transfer-diagnostic.js +45 -0
  186. package/lib/transfer-diagnostic.js.map +1 -0
  187. package/lib/transfer-document.d.ts +73 -0
  188. package/lib/transfer-document.d.ts.map +1 -0
  189. package/lib/transfer-document.js +46 -0
  190. package/lib/transfer-document.js.map +1 -0
  191. package/lib/transfer-element.d.ts +39 -0
  192. package/lib/transfer-element.d.ts.map +1 -0
  193. package/lib/transfer-element.js +11 -0
  194. package/lib/transfer-element.js.map +1 -0
  195. package/lib/uri.d.ts +62 -0
  196. package/lib/uri.d.ts.map +1 -0
  197. package/lib/uri.js +28 -0
  198. package/lib/uri.js.map +1 -0
  199. package/lib/util.d.ts +147 -0
  200. package/lib/util.d.ts.map +1 -0
  201. package/lib/util.js +211 -0
  202. package/lib/util.js.map +1 -0
  203. package/package.json +100 -0
  204. package/src/abstract-logger.ts +120 -0
  205. package/src/browser-runtime.ts +135 -0
  206. package/src/client/data-events.ts +71 -0
  207. package/src/client/data-port.ts +99 -0
  208. package/src/client/data-session.ts +209 -0
  209. package/src/client/index.ts +40 -0
  210. package/src/client/message-relay.ts +216 -0
  211. package/src/client/post-message-transport.ts +167 -0
  212. package/src/clock.ts +173 -0
  213. package/src/data/data-protocol-methods.ts +79 -0
  214. package/src/data/data-server-protocol.ts +296 -0
  215. package/src/data/diagnostics.ts +116 -0
  216. package/src/data/events.ts +127 -0
  217. package/src/data/index.ts +23 -0
  218. package/src/data/methods.ts +59 -0
  219. package/src/data/requests.ts +70 -0
  220. package/src/debouncer.ts +118 -0
  221. package/src/errors.ts +127 -0
  222. package/src/host-diagnostics.ts +63 -0
  223. package/src/index.ts +41 -0
  224. package/src/latency-collector.ts +231 -0
  225. package/src/logger.ts +172 -0
  226. package/src/model-server.ts +479 -0
  227. package/src/model-service/args.ts +67 -0
  228. package/src/model-service/index.ts +16 -0
  229. package/src/model-service/reference-candidate.ts +58 -0
  230. package/src/noop-logger.ts +36 -0
  231. package/src/observable-value.ts +92 -0
  232. package/src/patch-merge.ts +165 -0
  233. package/src/profile-session.ts +164 -0
  234. package/src/profiling.ts +113 -0
  235. package/src/project.ts +125 -0
  236. package/src/rpc/README.md +163 -0
  237. package/src/rpc/bind-rpc-methods.ts +180 -0
  238. package/src/rpc/create-rpc-proxy.ts +292 -0
  239. package/src/rpc/index.ts +16 -0
  240. package/src/rpc/wire-prefix.ts +48 -0
  241. package/src/testing/data-doubles.ts +178 -0
  242. package/src/testing/fake-clock.ts +106 -0
  243. package/src/testing/harness.ts +24 -0
  244. package/src/testing/index.ts +26 -0
  245. package/src/testing/node/duplex-connection.ts +50 -0
  246. package/src/testing/node/duplex-stream.ts +51 -0
  247. package/src/testing/node/index.ts +22 -0
  248. package/src/testing/wait-for.ts +94 -0
  249. package/src/tracer.ts +277 -0
  250. package/src/transfer-diagnostic.ts +92 -0
  251. package/src/transfer-document.ts +99 -0
  252. package/src/transfer-element.ts +43 -0
  253. package/src/uri.ts +61 -0
  254. package/src/util.ts +250 -0
@@ -0,0 +1,127 @@
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 type { TransferDiagnostic } from '../transfer-diagnostic';
11
+ import type { TransferElement } from '../transfer-element';
12
+ import type { Project } from '../project';
13
+ import type { TransferDocument } from '../transfer-document';
14
+
15
+ /**
16
+ * Why an update event fired. Subscribers filter on reason for behaviour
17
+ * decisions (e.g. dirty-flag handling, undo-history grouping, telemetry):
18
+ *
19
+ * - `'changed'` — the URI appeared in `DocumentBuilder.onUpdate`'s
20
+ * `changed` list. The framework's underlying primitive is "this URI was
21
+ * passed to `documentBuilder.update(changed, deleted)`", which spans
22
+ * `didChange` text-document events, `notifyDidChangeTextDocument` calls,
23
+ * and any programmatic `documentBuilder.update([uri], [])` invocation.
24
+ * The name matches Langium's own `changed` parameter — it's vague-on-
25
+ * purpose because the underlying primitive is.
26
+ * - `'rebuilt'` — the URI was rebuilt as a cascade from another URI's
27
+ * build (dependency graph re-derivation), without itself being passed to
28
+ * `documentBuilder.update`. The complement of `'changed'`.
29
+ * - `'saved'` — emitted by adopters that synthesise a unified update stream
30
+ * from both `onDocumentUpdated` and `onDocumentSaved`. The framework's own
31
+ * `dispatchPhaseEvent` does NOT emit `'saved'` — saves take the dedicated
32
+ * `DataClientProtocol.onDocumentSaved` channel.
33
+ * - `'deleted'` — the URI appeared in `DocumentBuilder.onUpdate`'s
34
+ * `deleted` list. The backing file was removed.
35
+ */
36
+ export type TransferDocumentUpdateReason = 'changed' | 'rebuilt' | 'saved' | 'deleted';
37
+
38
+ /**
39
+ * Delivered on the data-server when a document's content (or existence)
40
+ * changed. Subscribers receive this via `DocumentServerProtocol.watchModelDocument`.
41
+ *
42
+ * The `document` field carries the latest built state — diagnostics + root
43
+ * are coherent with each other at the time the event fired.
44
+ */
45
+ export interface TransferDocumentUpdatedEvent<
46
+ TTransfer extends TransferElement,
47
+ TDiagnostic extends TransferDiagnostic = TransferDiagnostic
48
+ > {
49
+ document: TransferDocument<TTransfer, TDiagnostic>;
50
+ /** Stable identifier of the client that triggered the update. */
51
+ sourceClientId: string;
52
+ reason: TransferDocumentUpdateReason;
53
+ }
54
+
55
+ /** Callback shape for `DataClientProtocol.onDocumentUpdated`. */
56
+ export type TransferDocumentUpdatedListener<
57
+ TTransfer extends TransferElement,
58
+ TDiagnostic extends TransferDiagnostic = TransferDiagnostic
59
+ > = (event: TransferDocumentUpdatedEvent<TTransfer, TDiagnostic>) => void;
60
+
61
+ /**
62
+ * Delivered on the data-server when a document was persisted to disk via
63
+ * `DataServerProtocol.saveModelDocument`. Distinct from
64
+ * {@link TransferDocumentUpdatedEvent}: subscribers that only care about
65
+ * persistence (external sync, editor "saved" indicators, dirty-flag
66
+ * clear) listen for this event family instead of filtering an update
67
+ * stream for `reason: 'saved'`.
68
+ *
69
+ * The `document` field carries the post-save built state — same shape
70
+ * the synchronous `saveModelDocument` RPC response returns, delivered
71
+ * to subscribers (`DataClientProtocol.onDocumentSaved`) so non-caller
72
+ * clients see the persistence event too.
73
+ */
74
+ export interface TransferDocumentSavedEvent<
75
+ TTransfer extends TransferElement,
76
+ TDiagnostic extends TransferDiagnostic = TransferDiagnostic
77
+ > {
78
+ document: TransferDocument<TTransfer, TDiagnostic>;
79
+ /**
80
+ * The client whose `saveModelDocument` call produced this. Every subscriber
81
+ * receives the event including the originator, which already had the same
82
+ * state as the RPC response — compare against your own id to drop the echo
83
+ * rather than re-rendering from it.
84
+ */
85
+ sourceClientId: string;
86
+ }
87
+
88
+ /** Callback shape for `DataClientProtocol.onDocumentSaved`. */
89
+ export type TransferDocumentSavedListener<
90
+ TTransfer extends TransferElement,
91
+ TDiagnostic extends TransferDiagnostic = TransferDiagnostic
92
+ > = (event: TransferDocumentSavedEvent<TTransfer, TDiagnostic>) => void;
93
+
94
+ /**
95
+ * Why a project-change event fired. `added` — the project was newly
96
+ * registered (descriptor discovered); `updated` — the project's
97
+ * descriptor content changed and its registry entry was refreshed
98
+ * (same id, possibly different version / dependencies / adopter-specific
99
+ * fields); `removed` — the project's descriptor disappeared and the
100
+ * registry entry was dropped.
101
+ */
102
+ export type ProjectChangeReason = 'added' | 'updated' | 'removed';
103
+
104
+ /**
105
+ * Delivered on the data-server when a project's lifecycle state changed
106
+ * (added / updated / removed in the registry). Subscribers receive this
107
+ * via `DataClientProtocol.onProjectsChanged`.
108
+ *
109
+ * Granularity is one event per affected project: an internal registry
110
+ * diff with three additions and one removal fans out to four wire
111
+ * notifications. This matches the typical client-side pattern of "react
112
+ * to one project at a time" (refresh a tree entry, update a tab badge,
113
+ * etc.) without forcing clients to walk arrays.
114
+ *
115
+ * For `reason: 'removed'`, the carried {@link project} is the pre-removal
116
+ * snapshot — by the time the event fires, the registry has already
117
+ * cleared the entry, so clients that need the removed project's metadata
118
+ * (id, version, dependencies, adopter-specific fields) read it here
119
+ * instead of maintaining their own snapshot.
120
+ */
121
+ export interface ProjectsChangedEvent<TProject extends Project = Project> {
122
+ readonly project: TProject;
123
+ readonly reason: ProjectChangeReason;
124
+ }
125
+
126
+ /** Callback shape for `DataClientProtocol.onProjectsChanged`. */
127
+ export type ProjectsChangedListener<TProject extends Project = Project> = (event: ProjectsChangedEvent<TProject>) => void;
@@ -0,0 +1,23 @@
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
+ // Subpath barrel for `@hydranium/protocol/data` — typed bidirectional
11
+ // contract for the data-server protocol head, split into composable
12
+ // `*ServerProtocol` / `*ClientProtocol` fragments. Implementations live in
13
+ // `@hydranium/data-server`; typed proxies are produced by the
14
+ // direction-neutral `createRpcProxy` (in `../rpc`) with the data-server
15
+ // method-name lists + wire prefix exported here from
16
+ // `./data-protocol-methods.ts`.
17
+
18
+ export * from './data-protocol-methods';
19
+ export * from './data-server-protocol';
20
+ export * from './diagnostics';
21
+ export * from './events';
22
+ export * from './methods';
23
+ export * from './requests';
@@ -0,0 +1,59 @@
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
+ /**
11
+ * The shared path identifier under which the data-server exposes its typed
12
+ * RPC methods. Production adopters using vscode-jsonrpc transports use this
13
+ * as the connection-builder path; the data-server side uses it when
14
+ * registering handlers.
15
+ *
16
+ * Distinct from the LSP (`languageId`-routed) and GLSP (`diagram-type`-routed)
17
+ * heads — each protocol head owns its own path namespace.
18
+ */
19
+ export const DATA_SERVER_PATH = '/hydranium/data-server';
20
+
21
+ /**
22
+ * Wire-name prefix used by `createRpcProxy` and `DataServer`'s
23
+ * handler binding. Every property name on the typed `DataServerProtocol`
24
+ * (or `DataClientProtocol`) interface lowers to `<prefix><methodName>` on
25
+ * the wire — so renaming a TS method renames the wire method,
26
+ * intentionally.
27
+ *
28
+ * Adopters writing custom transports or inspecting the wire derive each
29
+ * wire name as `DATA_SERVER_WIRE_PREFIX + methodName`, where the method
30
+ * names live in `DATA_SERVER_PROTOCOL_METHODS` (request methods) and
31
+ * `DATA_CLIENT_PROTOCOL_METHODS` (notification methods), both exported
32
+ * from `./data-protocol-methods`. The interface (`DataServerProtocol`,
33
+ * `DataClientProtocol`) is the source of truth; the method-name arrays
34
+ * are typed against the interface (`as const satisfies …`) so they
35
+ * cannot drift.
36
+ */
37
+ export const DATA_SERVER_WIRE_PREFIX = 'data-server/';
38
+
39
+ /**
40
+ * Default LSP command id under which a data-server publishes its listening
41
+ * TCP port for client discovery. Parallel to {@link DATA_SERVER_PATH} (the
42
+ * service-path routing constant) — both name the data-server head's transport
43
+ * surface so a simple adopter needs no constants of its own.
44
+ *
45
+ * Two ends reference this id:
46
+ * - PUBLISH (server): the launcher registers the port under this command on
47
+ * the LSP connection —
48
+ * `publishPortOnLspConnection(conn, DATA_SERVER_PORT_COMMAND, port)`.
49
+ * `@hydranium/core`'s `publishPortOnLspConnection` stays command-agnostic
50
+ * (it publishes any port under any command); the adopter passes this
51
+ * constant.
52
+ * - EXTRACT (Theia host): `@hydranium/data-client-theia`'s
53
+ * `DataServerConnectionHandler` polls this command to discover the port —
54
+ * its `portCommand` option defaults to this value.
55
+ *
56
+ * Adopters with an established command id (e.g. `'modelserver:port'`)
57
+ * override both ends.
58
+ */
59
+ export const DATA_SERVER_PORT_COMMAND = 'hydranium/data-server/port';
@@ -0,0 +1,70 @@
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 type { TransferSaveArgs, TransferUpdateArgs } from '../model-service/args';
11
+
12
+ /** Get the current state of a single document. The server returns the latest built version. */
13
+ export interface GetModelDocumentArgs {
14
+ /** Document URI. */
15
+ uri: string;
16
+ /**
17
+ * When `true`, the response is settled at the validation phase so its
18
+ * `diagnostics` are populated. When `false`/absent, the response returns at
19
+ * the (faster) integrity-settled phase and `diagnostics` may be absent —
20
+ * they are computed asynchronously and delivered via the subscription
21
+ * channel (and, for an LSP head, `publishDiagnostics`).
22
+ *
23
+ * Set this for one-shot / unsubscribed callers (CLI queries, batch checks)
24
+ * that need diagnostics in the response itself. Mirrors Langium's
25
+ * `BuildOptions.validation: boolean`. Note it does not *strip* diagnostics:
26
+ * a document already validated still carries them; the flag only controls
27
+ * whether the read forces/awaits validation.
28
+ */
29
+ includeDiagnostics?: boolean;
30
+ }
31
+
32
+ /**
33
+ * Look up the project owning the given document URI. Membership semantics
34
+ * are decided by the server's `ProjectManager` (default in
35
+ * `AbstractProjectManager`: closest-ancestor descriptor folder); the data-
36
+ * server forwards the URI without interpretation.
37
+ */
38
+ export interface GetProjectForUriArgs {
39
+ /** Document URI to look up. */
40
+ uri: string;
41
+ }
42
+
43
+ /**
44
+ * Update a document's content. Wire-side projection of the facade's
45
+ * {@link TransferUpdateArgs}; structurally identical so the data-server RPC
46
+ * handler can forward straight to the in-process `ModelService.update`
47
+ * without an args mapping.
48
+ */
49
+ export type TransferUpdateDocumentArgs<TTransfer> = TransferUpdateArgs<TTransfer>;
50
+
51
+ /** Persist a document to disk. Wire-side projection of {@link TransferSaveArgs}. */
52
+ export type TransferSaveDocumentArgs<TTransfer> = TransferSaveArgs<TTransfer>;
53
+
54
+ /**
55
+ * Identifies a per-document watch on the data server. Shared by both
56
+ * `watchModelDocument` and `unwatchModelDocument` — the `(uri, clientId)`
57
+ * pair is the watch key, so unwatching names the same watch that was
58
+ * started. The `clientId` identifies the originator the same way it does
59
+ * on facade-side mutations (`TransferUpdateArgs.clientId`,
60
+ * `TransferSaveArgs.clientId`): it keys the per-`(uri, clientId)` watch
61
+ * bucket so multiple watchers on the same wire stay distinct, AND it lets
62
+ * each watcher recognise its own echo on inbound `onDocumentUpdated` events
63
+ * (the wire shape's `sourceClientId` carries the originating mutation's
64
+ * `clientId`).
65
+ */
66
+ export interface WatchModelDocumentArgs {
67
+ uri: string;
68
+ /** Stable identifier for the watching client. */
69
+ clientId: string;
70
+ }
@@ -0,0 +1,118 @@
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 { type Clock } from './clock';
11
+ import { ObservableValue, type MaybeObservableValue } from './observable-value';
12
+ import { type Disposable } from './util';
13
+
14
+ /** Construction options for {@link Debouncer}. */
15
+ export interface DebouncerOptions {
16
+ /**
17
+ * Trailing-edge delay in milliseconds, read per {@link Debouncer.schedule}
18
+ * so a setting-bound cell reshapes the live window without rebuilding the
19
+ * debouncer. A value `<= 0` still defers to the next tick via
20
+ * {@link Clock.setTimer}; callers wanting a synchronous path handle that
21
+ * themselves before scheduling.
22
+ */
23
+ readonly delayMs: MaybeObservableValue<number>;
24
+ /**
25
+ * Optional starvation ceiling. When calls keep arriving faster than
26
+ * {@link delayMs}, the trailing edge never settles and `fn` would never
27
+ * run; with `maxWaitMs` set, a fire is forced once that many milliseconds
28
+ * have elapsed since the first pending {@link Debouncer.schedule}, so a
29
+ * continuous stream still makes progress. Omitted (the default) = pure
30
+ * trailing edge, which may defer indefinitely under a non-stop stream.
31
+ */
32
+ readonly maxWaitMs?: number;
33
+ }
34
+
35
+ /**
36
+ * Trailing-edge debounce built on the injectable {@link Clock}, so the window
37
+ * is deterministically testable with `makeFakeClock` — the framework's timer
38
+ * seam rather than raw `setTimeout` (which `p-debounce` / `lodash.debounce`
39
+ * hold internally and cannot be driven by a fake clock).
40
+ *
41
+ * Owns ONLY timing. {@link fn} is zero-argument and returns nothing: the caller
42
+ * keeps whatever payload it is coalescing — an accumulator set, a "latest
43
+ * value" field — and reads it inside `fn` when the debouncer fires. That
44
+ * timing/payload split is what lets one primitive serve both an accumulating
45
+ * caller (merge changed/deleted sets) and a replacing one (last value wins).
46
+ *
47
+ * Leading-edge invocation and a result-returning ("await the debounced run")
48
+ * variant are deliberately omitted to keep this a pure timing primitive; both
49
+ * are additive later (a `leading` option; a separate promise helper) rather
50
+ * than a breaking change.
51
+ */
52
+ export class Debouncer implements Disposable {
53
+ protected readonly delay: ObservableValue<number>;
54
+ protected readonly maxWaitMs?: number;
55
+ protected timer?: Disposable;
56
+ /** Virtual time of the first {@link schedule} of the current pending window; drives {@link DebouncerOptions.maxWaitMs}. */
57
+ protected firstScheduledAt?: number;
58
+
59
+ constructor(
60
+ protected readonly clock: Clock,
61
+ protected readonly fn: () => void,
62
+ options: DebouncerOptions
63
+ ) {
64
+ this.delay = ObservableValue.from(options.delayMs);
65
+ this.maxWaitMs = options.maxWaitMs;
66
+ }
67
+
68
+ /** True while a fire is armed (scheduled, not yet fired or cancelled). */
69
+ get pending(): boolean {
70
+ return this.timer !== undefined;
71
+ }
72
+
73
+ /**
74
+ * Arm — or re-arm — the trailing-edge timer. Repeated calls within the
75
+ * window collapse into a single {@link fn} run at the trailing edge; with
76
+ * {@link DebouncerOptions.maxWaitMs} set, the run is forced once that ceiling
77
+ * elapses since the first pending call.
78
+ */
79
+ schedule(): void {
80
+ const now = this.clock.now();
81
+ if (this.firstScheduledAt === undefined) {
82
+ this.firstScheduledAt = now;
83
+ }
84
+ let wait = Math.max(0, this.delay.value);
85
+ if (this.maxWaitMs !== undefined) {
86
+ const untilCeiling = this.firstScheduledAt + this.maxWaitMs - now;
87
+ wait = Math.min(wait, Math.max(0, untilCeiling));
88
+ }
89
+ this.timer?.dispose();
90
+ this.timer = this.clock.setTimer(() => this.fire(), wait);
91
+ }
92
+
93
+ /** If a fire is pending, run {@link fn} now and clear the window; otherwise a no-op. */
94
+ flush(): void {
95
+ if (this.timer !== undefined) {
96
+ this.fire();
97
+ }
98
+ }
99
+
100
+ /** Drop a pending fire without running {@link fn}. */
101
+ cancel(): void {
102
+ this.timer?.dispose();
103
+ this.timer = undefined;
104
+ this.firstScheduledAt = undefined;
105
+ }
106
+
107
+ /** Cancel any pending fire and release. */
108
+ dispose(): void {
109
+ this.cancel();
110
+ }
111
+
112
+ protected fire(): void {
113
+ this.timer?.dispose();
114
+ this.timer = undefined;
115
+ this.firstScheduledAt = undefined;
116
+ this.fn();
117
+ }
118
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,127 @@
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 { ResponseError } from 'vscode-jsonrpc';
11
+
12
+ /**
13
+ * Application-specific JSON-RPC error code for {@link ConflictError}.
14
+ * Outside the reserved range (-32768 .. -32000) per JSON-RPC 2.0.
15
+ *
16
+ * The code is the load-bearing identifier across realm boundaries —
17
+ * `code` is a first-class field on the JSON-RPC error envelope and
18
+ * survives wire reconstruction; the custom `Error` subclass name does
19
+ * not.
20
+ */
21
+ export const CONFLICT_ERROR_CODE = 1001;
22
+
23
+ /**
24
+ * Structured payload carried in {@link ConflictError.data}, and the only place
25
+ * a post-RPC caller can read the version mismatch from.
26
+ */
27
+ export interface ConflictErrorData {
28
+ readonly uri: string;
29
+ /** The based-on version the caller authored against. */
30
+ readonly expected: number;
31
+ /** The server's current text-document version at the time of the throw. */
32
+ readonly actual: number;
33
+ }
34
+
35
+ /**
36
+ * Thrown by `ModelService.update` / `ModelService.save` when the caller-
37
+ * supplied based-on version no longer matches the server's current text-
38
+ * document version for the same URI — i.e. the snapshot the caller
39
+ * authored against has been superseded by an intervening edit.
40
+ *
41
+ * Extends vscode-jsonrpc's {@link ResponseError} so the typed
42
+ * {@link ConflictErrorData} payload rides on the standard JSON-RPC
43
+ * error envelope (`code`, `message`, `data`) — all three fields are
44
+ * preserved by RPC reconstruction. Adopters that catch the error on
45
+ * the receiving side of an RPC call read the version mismatch from
46
+ * `err.data` (the instance is reconstructed as a generic
47
+ * `ResponseError`, so subclass getters / fields do not survive).
48
+ *
49
+ * Detection is opt-in via the optional `baseVersion` field on
50
+ * `TransferUpdateArgs` / `TransferSaveArgs`; callers that omit the field get
51
+ * no gating. This mirrors LSP's `OptionalVersionedTextDocumentIdentifier`
52
+ * posture, so headless / CLI / batch tooling with no meaningful based-on
53
+ * version can opt out explicitly.
54
+ *
55
+ * Three reasonable adopter recovery strategies:
56
+ *
57
+ * | Strategy | Use case |
58
+ * |---|---|
59
+ * | Drop + refetch | Form-widget save; user can re-trigger if they still want the edit. |
60
+ * | Refetch + replay user edit | Specific structural edits (`setField`, drag-position). Adopter responsibility. |
61
+ * | Surface to user | Large edits, multi-step transactions. Adopter UI. |
62
+ *
63
+ * The framework provides the **detection**; adopters provide the **policy**.
64
+ * No auto-retry or auto-merge ships by default.
65
+ */
66
+ export class ConflictError extends ResponseError<ConflictErrorData> {
67
+ constructor(uri: string, expected: number, actual: number) {
68
+ super(CONFLICT_ERROR_CODE, `Stale-based update for ${uri}: expected v${expected}, server is at v${actual}`, {
69
+ uri,
70
+ expected,
71
+ actual
72
+ });
73
+ this.name = 'ConflictError';
74
+ // ResponseError's constructor calls `Object.setPrototypeOf(this,
75
+ // ResponseError.prototype)` to keep its own prototype chain intact across
76
+ // transpilation targets; that resets us to ResponseError, hiding the
77
+ // ConflictError-specific getters. Restore the prototype here so
78
+ // `err.uri` / `.expected` / `.actual` resolve through this class.
79
+ Object.setPrototypeOf(this, ConflictError.prototype);
80
+ }
81
+
82
+ get uri(): string {
83
+ return this.data!.uri;
84
+ }
85
+
86
+ get expected(): number {
87
+ return this.data!.expected;
88
+ }
89
+
90
+ get actual(): number {
91
+ return this.data!.actual;
92
+ }
93
+ }
94
+
95
+ /** Marker substring present in every {@link ConflictError} message, used by
96
+ * {@link isConflictError} as a fallback when a transport re-wraps the error
97
+ * and drops the JSON-RPC code. */
98
+ const CONFLICT_ERROR_MESSAGE_MARKER = 'Stale-based update for ';
99
+
100
+ /**
101
+ * Type guard for {@link ConflictError}. Detection ladder:
102
+ *
103
+ * 1. `error.name === 'ConflictError'` — direct in-process throw, no
104
+ * RPC round-trip.
105
+ * 2. `(error as ResponseError).code === CONFLICT_ERROR_CODE` — the
106
+ * canonical wire-side check; the JSON-RPC `code` field is preserved
107
+ * across reconstruction, so any adopter catching after an RPC call
108
+ * hits this branch.
109
+ * 3. `error.message.includes('Stale-based update for ')` — fallback
110
+ * for transports that re-wrap the message and drop the code (rare).
111
+ *
112
+ * `instanceof ConflictError` alone would silently return `false` on the
113
+ * reconstructed shape, so callers do not use it.
114
+ */
115
+ export function isConflictError(error: unknown): error is ConflictError {
116
+ if (!(error instanceof Error)) {
117
+ return false;
118
+ }
119
+ if (error.name === 'ConflictError') {
120
+ return true;
121
+ }
122
+ const code = (error as Partial<ResponseError<unknown>>).code;
123
+ if (code === CONFLICT_ERROR_CODE) {
124
+ return true;
125
+ }
126
+ return typeof error.message === 'string' && error.message.includes(CONFLICT_ERROR_MESSAGE_MARKER);
127
+ }
@@ -0,0 +1,63 @@
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
+ /**
11
+ * Diagnostics contract for the HOST (parent) process — the Theia backend
12
+ * itself, the process that runs the data-client connection handlers and any
13
+ * in-process services. Distinct from `DataServerDiagnosticsProtocol`,
14
+ * which targets the separate data-server child (the heavy model store): the
15
+ * host process is reached by an ordinary in-process Theia RPC service, NOT over
16
+ * the data-server socket. Universal (`process.memoryUsage()` always works) and
17
+ * cheap, so the framework wires it by default for every host that opts the
18
+ * data-client backend module in.
19
+ *
20
+ * Returns are plain formatted strings — the caller (a command, a log sink, the
21
+ * pod's stdout) decides how to surface them.
22
+ */
23
+
24
+ /** Arguments for {@link HostDiagnosticsProtocol.dumpHostState}. */
25
+ export interface DumpHostStateArgs {
26
+ /** Optional label folded into the snapshot heading (e.g. an ISO timestamp). */
27
+ label?: string;
28
+ }
29
+
30
+ /** Arguments for {@link HostDiagnosticsProtocol.writeHostHeapSnapshot}. */
31
+ export interface WriteHostHeapSnapshotArgs {
32
+ /** Optional label folded into the snapshot filename. */
33
+ label?: string;
34
+ /** Directory to write into; defaults to the OS temp dir when absent or not present on disk. */
35
+ directory?: string;
36
+ }
37
+
38
+ /**
39
+ * Memory diagnostics the running HOST (Theia backend) process exposes. Computed
40
+ * in-process: the snapshots reflect the parent process, the counterpart to the
41
+ * data-server child's `DataServerDiagnosticsProtocol`.
42
+ */
43
+ export interface HostDiagnosticsProtocol {
44
+ /**
45
+ * Capture a host-process memory snapshot — heap, rss, external, V8 limit —
46
+ * and return it formatted. The host hosts no Langium documents, so this is
47
+ * the lighter `formatProcessMemory` view, not `formatServerState`.
48
+ */
49
+ dumpHostState(args: DumpHostStateArgs): Promise<string>;
50
+
51
+ /**
52
+ * Write a V8 heap snapshot of the host process to disk (full GC first;
53
+ * briefly pauses the process) and return the absolute file path.
54
+ */
55
+ writeHostHeapSnapshot(args: WriteHostHeapSnapshotArgs): Promise<string>;
56
+ }
57
+
58
+ /**
59
+ * Theia service path the host-diagnostics RPC service is registered under. An
60
+ * ordinary in-process backend service (`RpcConnectionHandler`), unlike the
61
+ * socket-forwarded `DATA_SERVER_PATH`.
62
+ */
63
+ export const HOST_DIAGNOSTICS_PATH = '/hydranium/host-diagnostics';
package/src/index.ts ADDED
@@ -0,0 +1,41 @@
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
+ // Public API barrel — re-exports every PRODUCTION entry of `@hydranium/protocol`.
11
+ // `./client` and `./data` are re-exported as well as declared standalone, so a
12
+ // consumer wanting the whole production surface needs one specifier while one
13
+ // wanting a narrower graph can still name the subpath. `./testing` is excluded
14
+ // on the opposite constraint: re-exporting it would put the test doubles in
15
+ // every production bundle that imports the root.
16
+
17
+ export * from './abstract-logger';
18
+ export * from './client';
19
+ export * from './clock';
20
+ export * from './data';
21
+ export * from './browser-runtime';
22
+ export * from './debouncer';
23
+ export * from './errors';
24
+ export * from './host-diagnostics';
25
+ export * from './logger';
26
+ export * from './latency-collector';
27
+ export * from './patch-merge';
28
+ export * from './noop-logger';
29
+ export * from './observable-value';
30
+ export * from './profile-session';
31
+ export * from './profiling';
32
+ export * from './tracer';
33
+ export * from './transfer-diagnostic';
34
+ export * from './transfer-element';
35
+ export * from './model-service';
36
+ export * from './transfer-document';
37
+ export * from './model-server';
38
+ export * from './project';
39
+ export * from './rpc';
40
+ export * from './uri';
41
+ export * from './util';