@hydranium/protocol 1.0.0-next.39 → 1.0.0-next.391

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 (311) hide show
  1. package/README.md +46 -83
  2. package/lib/abstract-logger.d.ts +11 -0
  3. package/lib/abstract-logger.d.ts.map +1 -1
  4. package/lib/abstract-logger.js +16 -0
  5. package/lib/abstract-logger.js.map +1 -1
  6. package/lib/client/data-connection.d.ts +303 -0
  7. package/lib/client/data-connection.d.ts.map +1 -0
  8. package/lib/client/data-connection.js +534 -0
  9. package/lib/client/data-connection.js.map +1 -0
  10. package/lib/client/data-events.d.ts +11 -2
  11. package/lib/client/data-events.d.ts.map +1 -1
  12. package/lib/client/data-events.js +13 -1
  13. package/lib/client/data-events.js.map +1 -1
  14. package/lib/client/data-port.d.ts +34 -24
  15. package/lib/client/data-port.d.ts.map +1 -1
  16. package/lib/client/data-session.d.ts +762 -89
  17. package/lib/client/data-session.d.ts.map +1 -1
  18. package/lib/client/data-session.js +1253 -115
  19. package/lib/client/data-session.js.map +1 -1
  20. package/lib/client/index.d.ts +16 -9
  21. package/lib/client/index.d.ts.map +1 -1
  22. package/lib/client/index.js +16 -9
  23. package/lib/client/index.js.map +1 -1
  24. package/lib/client/message-relay.d.ts +6 -4
  25. package/lib/client/message-relay.d.ts.map +1 -1
  26. package/lib/client/message-relay.js +4 -4
  27. package/lib/client/message-relay.js.map +1 -1
  28. package/lib/client/post-message-transport.d.ts +69 -3
  29. package/lib/client/post-message-transport.d.ts.map +1 -1
  30. package/lib/client/post-message-transport.js +175 -1
  31. package/lib/client/post-message-transport.js.map +1 -1
  32. package/lib/client/rpc-connection.d.ts +173 -0
  33. package/lib/client/rpc-connection.d.ts.map +1 -0
  34. package/lib/client/rpc-connection.js +225 -0
  35. package/lib/client/rpc-connection.js.map +1 -0
  36. package/lib/client/worker-handshake.d.ts +94 -0
  37. package/lib/client/worker-handshake.d.ts.map +1 -0
  38. package/lib/client/worker-handshake.js +51 -0
  39. package/lib/client/worker-handshake.js.map +1 -0
  40. package/lib/client/working-copy.d.ts +420 -0
  41. package/lib/client/working-copy.d.ts.map +1 -0
  42. package/lib/client/working-copy.js +958 -0
  43. package/lib/client/working-copy.js.map +1 -0
  44. package/lib/client-ids.d.ts +45 -0
  45. package/lib/client-ids.d.ts.map +1 -0
  46. package/lib/client-ids.js +48 -0
  47. package/lib/client-ids.js.map +1 -0
  48. package/lib/clock.d.ts +50 -10
  49. package/lib/clock.d.ts.map +1 -1
  50. package/lib/clock.js +36 -1
  51. package/lib/clock.js.map +1 -1
  52. package/lib/console-logger.d.ts +23 -0
  53. package/lib/console-logger.d.ts.map +1 -0
  54. package/lib/console-logger.js +39 -0
  55. package/lib/console-logger.js.map +1 -0
  56. package/lib/data/data-protocol-methods.d.ts +4 -4
  57. package/lib/data/data-protocol-methods.d.ts.map +1 -1
  58. package/lib/data/data-protocol-methods.js +7 -0
  59. package/lib/data/data-protocol-methods.js.map +1 -1
  60. package/lib/data/data-server-protocol.d.ts +160 -50
  61. package/lib/data/data-server-protocol.d.ts.map +1 -1
  62. package/lib/data/events.d.ts +75 -31
  63. package/lib/data/events.d.ts.map +1 -1
  64. package/lib/data/requests.d.ts +84 -11
  65. package/lib/data/requests.d.ts.map +1 -1
  66. package/lib/debouncer.d.ts +7 -4
  67. package/lib/debouncer.d.ts.map +1 -1
  68. package/lib/debouncer.js +10 -10
  69. package/lib/debouncer.js.map +1 -1
  70. package/lib/errors.d.ts +241 -48
  71. package/lib/errors.d.ts.map +1 -1
  72. package/lib/errors.js +395 -60
  73. package/lib/errors.js.map +1 -1
  74. package/lib/format-log-arg.d.ts +33 -0
  75. package/lib/format-log-arg.d.ts.map +1 -0
  76. package/lib/format-log-arg.js +158 -0
  77. package/lib/format-log-arg.js.map +1 -0
  78. package/lib/glsp-edits-settled-actions.d.ts +54 -0
  79. package/lib/glsp-edits-settled-actions.d.ts.map +1 -0
  80. package/lib/glsp-edits-settled-actions.js +28 -0
  81. package/lib/glsp-edits-settled-actions.js.map +1 -0
  82. package/lib/glsp-request-model-args.d.ts +16 -0
  83. package/lib/glsp-request-model-args.d.ts.map +1 -0
  84. package/lib/glsp-request-model-args.js +19 -0
  85. package/lib/glsp-request-model-args.js.map +1 -0
  86. package/lib/glsp-save-model-actions.d.ts +55 -0
  87. package/lib/glsp-save-model-actions.d.ts.map +1 -0
  88. package/lib/glsp-save-model-actions.js +28 -0
  89. package/lib/glsp-save-model-actions.js.map +1 -0
  90. package/lib/index.d.ts +11 -0
  91. package/lib/index.d.ts.map +1 -1
  92. package/lib/index.js +11 -0
  93. package/lib/index.js.map +1 -1
  94. package/lib/latency-collector.d.ts +10 -4
  95. package/lib/latency-collector.d.ts.map +1 -1
  96. package/lib/latency-collector.js +2 -0
  97. package/lib/latency-collector.js.map +1 -1
  98. package/lib/logger.d.ts +38 -7
  99. package/lib/logger.d.ts.map +1 -1
  100. package/lib/logger.js +42 -4
  101. package/lib/logger.js.map +1 -1
  102. package/lib/messages/index.d.ts +4 -2
  103. package/lib/messages/index.d.ts.map +1 -1
  104. package/lib/messages/index.js +17 -4
  105. package/lib/messages/index.js.map +1 -1
  106. package/lib/messages/primitives.d.ts +59 -4
  107. package/lib/messages/primitives.d.ts.map +1 -1
  108. package/lib/messages/primitives.js +97 -4
  109. package/lib/messages/primitives.js.map +1 -1
  110. package/lib/model-server.d.ts +58 -11
  111. package/lib/model-server.d.ts.map +1 -1
  112. package/lib/model-server.js +4 -2
  113. package/lib/model-server.js.map +1 -1
  114. package/lib/model-service/base-version.d.ts +67 -0
  115. package/lib/model-service/base-version.d.ts.map +1 -0
  116. package/lib/model-service/base-version.js +44 -0
  117. package/lib/model-service/base-version.js.map +1 -0
  118. package/lib/model-service/index.d.ts +1 -1
  119. package/lib/model-service/index.d.ts.map +1 -1
  120. package/lib/model-service/index.js +4 -5
  121. package/lib/model-service/index.js.map +1 -1
  122. package/lib/model-service/reference-candidate.d.ts +5 -3
  123. package/lib/model-service/reference-candidate.d.ts.map +1 -1
  124. package/lib/node/index.d.ts +10 -0
  125. package/lib/node/index.d.ts.map +1 -0
  126. package/lib/node/index.js +29 -0
  127. package/lib/node/index.js.map +1 -0
  128. package/lib/node/process-memory.d.ts +66 -0
  129. package/lib/node/process-memory.d.ts.map +1 -0
  130. package/lib/node/process-memory.js +291 -0
  131. package/lib/node/process-memory.js.map +1 -0
  132. package/lib/noop-logger.d.ts.map +1 -1
  133. package/lib/noop-logger.js.map +1 -1
  134. package/lib/observable-value.js.map +1 -1
  135. package/lib/patch-merge.d.ts +63 -35
  136. package/lib/patch-merge.d.ts.map +1 -1
  137. package/lib/patch-merge.js +171 -25
  138. package/lib/patch-merge.js.map +1 -1
  139. package/lib/pending-work-tracker.d.ts +33 -0
  140. package/lib/pending-work-tracker.d.ts.map +1 -0
  141. package/lib/pending-work-tracker.js +48 -0
  142. package/lib/pending-work-tracker.js.map +1 -0
  143. package/lib/profile-session.d.ts +24 -8
  144. package/lib/profile-session.d.ts.map +1 -1
  145. package/lib/profile-session.js +13 -4
  146. package/lib/profile-session.js.map +1 -1
  147. package/lib/profiling.d.ts +15 -2
  148. package/lib/profiling.d.ts.map +1 -1
  149. package/lib/profiling.js.map +1 -1
  150. package/lib/project.d.ts +6 -2
  151. package/lib/project.d.ts.map +1 -1
  152. package/lib/project.js.map +1 -1
  153. package/lib/random-uuid.d.ts +14 -0
  154. package/lib/random-uuid.d.ts.map +1 -0
  155. package/lib/random-uuid.js +24 -0
  156. package/lib/random-uuid.js.map +1 -0
  157. package/lib/reconcile-write.d.ts +70 -0
  158. package/lib/reconcile-write.d.ts.map +1 -0
  159. package/lib/reconcile-write.js +69 -0
  160. package/lib/reconcile-write.js.map +1 -0
  161. package/lib/rpc/bind-rpc-methods.d.ts +6 -2
  162. package/lib/rpc/bind-rpc-methods.d.ts.map +1 -1
  163. package/lib/rpc/bind-rpc-methods.js +17 -7
  164. package/lib/rpc/bind-rpc-methods.js.map +1 -1
  165. package/lib/rpc/create-rpc-proxy.d.ts +3 -0
  166. package/lib/rpc/create-rpc-proxy.d.ts.map +1 -1
  167. package/lib/rpc/create-rpc-proxy.js +6 -1
  168. package/lib/rpc/create-rpc-proxy.js.map +1 -1
  169. package/lib/rpc/index.d.ts +1 -0
  170. package/lib/rpc/index.d.ts.map +1 -1
  171. package/lib/rpc/index.js +1 -0
  172. package/lib/rpc/index.js.map +1 -1
  173. package/lib/rpc/send-by-method-name.d.ts +76 -0
  174. package/lib/rpc/send-by-method-name.d.ts.map +1 -0
  175. package/lib/rpc/send-by-method-name.js +120 -0
  176. package/lib/rpc/send-by-method-name.js.map +1 -0
  177. package/lib/rpc/wire-prefix.js.map +1 -1
  178. package/lib/save-text-document.d.ts +40 -0
  179. package/lib/save-text-document.d.ts.map +1 -0
  180. package/lib/save-text-document.js +34 -0
  181. package/lib/save-text-document.js.map +1 -0
  182. package/lib/testing/catalogue-audit.js.map +1 -1
  183. package/lib/testing/data-doubles.d.ts +36 -11
  184. package/lib/testing/data-doubles.d.ts.map +1 -1
  185. package/lib/testing/data-doubles.js +47 -9
  186. package/lib/testing/data-doubles.js.map +1 -1
  187. package/lib/testing/fake-clock.d.ts +17 -2
  188. package/lib/testing/fake-clock.d.ts.map +1 -1
  189. package/lib/testing/fake-clock.js +61 -46
  190. package/lib/testing/fake-clock.js.map +1 -1
  191. package/lib/testing/harness.d.ts +9 -0
  192. package/lib/testing/harness.d.ts.map +1 -1
  193. package/lib/testing/index.d.ts +1 -0
  194. package/lib/testing/index.d.ts.map +1 -1
  195. package/lib/testing/index.js +4 -3
  196. package/lib/testing/index.js.map +1 -1
  197. package/lib/testing/node/duplex-connection.d.ts.map +1 -1
  198. package/lib/testing/node/duplex-connection.js +3 -2
  199. package/lib/testing/node/duplex-connection.js.map +1 -1
  200. package/lib/testing/node/duplex-stream.js.map +1 -1
  201. package/lib/testing/node/index.d.ts +2 -0
  202. package/lib/testing/node/index.d.ts.map +1 -1
  203. package/lib/testing/node/index.js +3 -2
  204. package/lib/testing/node/index.js.map +1 -1
  205. package/lib/testing/node/message-port-pair.d.ts +25 -0
  206. package/lib/testing/node/message-port-pair.d.ts.map +1 -0
  207. package/lib/testing/node/message-port-pair.js +26 -0
  208. package/lib/testing/node/message-port-pair.js.map +1 -0
  209. package/lib/testing/node/unhandled-rejections.d.ts +20 -0
  210. package/lib/testing/node/unhandled-rejections.d.ts.map +1 -0
  211. package/lib/testing/node/unhandled-rejections.js +35 -0
  212. package/lib/testing/node/unhandled-rejections.js.map +1 -0
  213. package/lib/testing/transfer-node-builder.d.ts +81 -0
  214. package/lib/testing/transfer-node-builder.d.ts.map +1 -0
  215. package/lib/testing/transfer-node-builder.js +47 -0
  216. package/lib/testing/transfer-node-builder.js.map +1 -0
  217. package/lib/testing/wait-for.d.ts +9 -3
  218. package/lib/testing/wait-for.d.ts.map +1 -1
  219. package/lib/testing/wait-for.js +57 -11
  220. package/lib/testing/wait-for.js.map +1 -1
  221. package/lib/tracer.d.ts.map +1 -1
  222. package/lib/tracer.js +5 -9
  223. package/lib/tracer.js.map +1 -1
  224. package/lib/transfer-diagnostic.d.ts +11 -0
  225. package/lib/transfer-diagnostic.d.ts.map +1 -1
  226. package/lib/transfer-diagnostic.js +17 -0
  227. package/lib/transfer-diagnostic.js.map +1 -1
  228. package/lib/transfer-document.d.ts +108 -32
  229. package/lib/transfer-document.d.ts.map +1 -1
  230. package/lib/transfer-document.js +17 -9
  231. package/lib/transfer-document.js.map +1 -1
  232. package/lib/transfer-element.d.ts +4 -4
  233. package/lib/types.d.ts +32 -0
  234. package/lib/types.d.ts.map +1 -0
  235. package/lib/{model-service/args.js → types.js} +2 -2
  236. package/lib/types.js.map +1 -0
  237. package/lib/uri.d.ts.map +1 -1
  238. package/lib/uri.js.map +1 -1
  239. package/lib/util.d.ts +8 -2
  240. package/lib/util.d.ts.map +1 -1
  241. package/lib/util.js +32 -4
  242. package/lib/util.js.map +1 -1
  243. package/package.json +24 -40
  244. package/src/abstract-logger.ts +18 -0
  245. package/src/client/data-connection.ts +654 -0
  246. package/src/client/data-events.ts +15 -1
  247. package/src/client/data-port.ts +36 -25
  248. package/src/client/data-session.ts +1606 -137
  249. package/src/client/index.ts +16 -9
  250. package/src/client/message-relay.ts +6 -4
  251. package/src/client/post-message-transport.ts +224 -4
  252. package/src/client/rpc-connection.ts +302 -0
  253. package/src/client/worker-handshake.ts +121 -0
  254. package/src/client/working-copy.ts +1116 -0
  255. package/src/client-ids.ts +49 -0
  256. package/src/clock.ts +68 -10
  257. package/src/console-logger.ts +39 -0
  258. package/src/data/data-protocol-methods.ts +8 -1
  259. package/src/data/data-server-protocol.ts +184 -49
  260. package/src/data/events.ts +77 -31
  261. package/src/data/requests.ts +90 -11
  262. package/src/debouncer.ts +11 -11
  263. package/src/errors.ts +496 -71
  264. package/src/format-log-arg.ts +176 -0
  265. package/src/glsp-edits-settled-actions.ts +62 -0
  266. package/src/glsp-request-model-args.ts +16 -0
  267. package/src/glsp-save-model-actions.ts +64 -0
  268. package/src/index.ts +11 -0
  269. package/src/latency-collector.ts +10 -3
  270. package/src/logger.ts +44 -8
  271. package/src/messages/index.ts +12 -2
  272. package/src/messages/primitives.ts +129 -5
  273. package/src/model-server.ts +62 -17
  274. package/src/model-service/base-version.ts +75 -0
  275. package/src/model-service/index.ts +4 -5
  276. package/src/model-service/reference-candidate.ts +5 -3
  277. package/src/node/index.ts +14 -0
  278. package/src/node/process-memory.ts +299 -0
  279. package/src/patch-merge.ts +230 -45
  280. package/src/pending-work-tracker.ts +48 -0
  281. package/src/profile-session.ts +31 -11
  282. package/src/profiling.ts +15 -2
  283. package/src/project.ts +6 -2
  284. package/src/random-uuid.ts +21 -0
  285. package/src/reconcile-write.ts +131 -0
  286. package/src/rpc/README.md +6 -6
  287. package/src/rpc/bind-rpc-methods.ts +18 -8
  288. package/src/rpc/create-rpc-proxy.ts +6 -1
  289. package/src/rpc/index.ts +1 -0
  290. package/src/rpc/send-by-method-name.ts +140 -0
  291. package/src/save-text-document.ts +39 -0
  292. package/src/testing/data-doubles.ts +119 -19
  293. package/src/testing/fake-clock.ts +76 -48
  294. package/src/testing/harness.ts +10 -0
  295. package/src/testing/index.ts +4 -3
  296. package/src/testing/node/duplex-connection.ts +3 -2
  297. package/src/testing/node/index.ts +3 -2
  298. package/src/testing/node/message-port-pair.ts +40 -0
  299. package/src/testing/node/unhandled-rejections.ts +31 -0
  300. package/src/testing/transfer-node-builder.ts +107 -0
  301. package/src/testing/wait-for.ts +61 -13
  302. package/src/tracer.ts +5 -9
  303. package/src/transfer-diagnostic.ts +17 -0
  304. package/src/transfer-document.ts +130 -34
  305. package/src/transfer-element.ts +4 -4
  306. package/src/types.ts +30 -0
  307. package/src/util.ts +33 -6
  308. package/lib/model-service/args.d.ts +0 -64
  309. package/lib/model-service/args.d.ts.map +0 -1
  310. package/lib/model-service/args.js.map +0 -1
  311. package/src/model-service/args.ts +0 -67
@@ -14,17 +14,19 @@
14
14
  * over `TRoot`.
15
15
  */
16
16
 
17
- import type { TransferClientArgs } from './model-service/args';
18
17
  import type { ReferenceCandidate } from './model-service/reference-candidate';
19
18
 
20
19
  // ---------------------------------------------------------------------------
21
20
  // Client / server arguments
22
21
  // ---------------------------------------------------------------------------
23
22
 
24
- // `TransferClientArgs` / `TransferUpdateArgs` / `TransferSaveArgs` live in
25
- // `./model-service/args.ts` and are re-exported by the main barrel via
26
- // `./model-service`. The legacy LSP-style protocol (Open / Close / events)
27
- // defined here only consumes them as a supertype.
23
+ /** Identifies a client-document binding. */
24
+ export interface TransferClientArgs {
25
+ /** Document URI. */
26
+ uri: string;
27
+ /** Stable identifier for the client invoking the operation. */
28
+ clientId: string;
29
+ }
28
30
 
29
31
  /** Open a document on behalf of a client. */
30
32
  export interface OpenModelArgs extends TransferClientArgs {
@@ -47,14 +49,22 @@ export interface OpenModelArgs extends TransferClientArgs {
47
49
  * means read from the filesystem, which is the normal case.
48
50
  *
49
51
  * Honoured only on the first open of a URI: opening an already-open document
50
- * is a no-op, so passing `text` for one silently changes nothing. Write
51
- * through the update path instead.
52
+ * attaches the caller to the existing shared entry and does not replace its
53
+ * content with this seed. Write through the update path instead. The open
54
+ * response reads the current shared content after registration; a textual
55
+ * `didOpen` attach may separately refresh the build.
52
56
  */
53
57
  text?: string;
58
+ /**
59
+ * Kept on the server for this client's open until it closes, for the
60
+ * server's own open path to read. Only a client session's open keeps them,
61
+ * and a repeat open keeps the first open's.
62
+ */
63
+ options?: object;
54
64
  }
55
65
 
56
66
  /** Close a previously-opened document for the client. */
57
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
67
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- close names its own args; it adds no field to the base
58
68
  export interface CloseModelArgs extends TransferClientArgs {}
59
69
 
60
70
  // ---------------------------------------------------------------------------
@@ -63,9 +73,25 @@ export interface CloseModelArgs extends TransferClientArgs {}
63
73
 
64
74
  export interface TransferUpdatedEvent<TDocument> {
65
75
  document: TDocument;
76
+ /**
77
+ * The client whose write this event echoes: the author of the version on a
78
+ * `'changed'`, and the unknown-client id on a `'rebuilt'`, which echoes no
79
+ * write. A recipient compares it against its own id to recognise its echo.
80
+ */
66
81
  sourceClientId: string;
67
82
  /** See `TransferDocumentUpdateReason` in `./data/events` for the canonical reason set + semantics. */
68
83
  reason: 'changed' | 'rebuilt' | 'saved';
84
+ /**
85
+ * The client whose write caused the build that produced this event, or the
86
+ * unknown-client id when no single client's did. On a `'changed'` it is the
87
+ * version's author. A `'rebuilt'` names the writer of another document, so
88
+ * it is a cause, never an echo: its diagnostics and references are new to
89
+ * that writer too.
90
+ *
91
+ * In-process only: the data head does not send it, and an event built
92
+ * elsewhere may leave it out, which a reader takes as "not known".
93
+ */
94
+ causedBy?: string;
69
95
  }
70
96
 
71
97
  export interface TransferSavedEvent<TDocument> {
@@ -147,10 +173,10 @@ export function isElementSource(object: unknown): object is ElementSource {
147
173
  /** An element of a document that does not yet exist on disk — used during element creation flows. */
148
174
  export interface SyntheticSource {
149
175
  /**
150
- * The document the element would belong to. That document must already be
151
- * LOADED — the default resolution takes its parse root as the synthetic
152
- * node's container and answers `undefined` when it is not, so a URI for a
153
- * file that exists on disk but was never opened resolves to nothing.
176
+ * The document the element would belong to. It need not be loaded, or
177
+ * exist — an unloaded URI materialises a transient empty document, so a
178
+ * folder is a valid anchor. A URI that IS loaded contributes that
179
+ * document's parse root as the container, putting its contents in scope.
154
180
  */
155
181
  uri: string;
156
182
  /**
@@ -159,6 +185,16 @@ export interface SyntheticSource {
159
185
  * it is what scoping filters candidates against.
160
186
  */
161
187
  type: string;
188
+ /**
189
+ * The grammar the element will belong to, as a language id.
190
+ *
191
+ * Optional because `uri` and `type` each answer on their own when
192
+ * unambiguous. Set it when neither is: a URI naming no file carries no
193
+ * extension to route on, and a `type` two grammars can produce identifies
194
+ * neither. Unset, such a source reaches the server's own policy, which
195
+ * answers the same way for every caller.
196
+ */
197
+ language?: string;
162
198
  }
163
199
 
164
200
  export function isSyntheticSource(object: unknown): object is SyntheticSource {
@@ -183,8 +219,10 @@ export namespace ReferenceSource {
183
219
  export function element(name: string, type?: string): ElementSource {
184
220
  return { name, type };
185
221
  }
186
- export function synthetic(uri: string, type: string): SyntheticSource {
187
- return { uri, type };
222
+ export function synthetic(uri: string, type: string, language?: string): SyntheticSource {
223
+ // The key is omitted rather than set to `undefined`, so a source built
224
+ // without a language stays deep-equal to the two-argument form.
225
+ return language === undefined ? { uri, type } : { uri, type, language };
188
226
  }
189
227
  }
190
228
 
@@ -457,9 +495,10 @@ export interface FindNextNameArgs {
457
495
  */
458
496
  uri: string;
459
497
  /**
460
- * The AST `$type` of the element being named. Collisions are only looked for
461
- * among elements of that same type, so the returned name may still be taken
462
- * by an element of another type.
498
+ * The AST `$type` of the element being named. Collisions are looked for
499
+ * among elements of that type and of any subtype of it, so a supertype
500
+ * names a uniqueness scope its concrete types share; the returned name may
501
+ * still be taken by an element outside that hierarchy.
463
502
  */
464
503
  type: string;
465
504
  /**
@@ -476,4 +515,10 @@ export interface FindNextNameArgs {
476
515
  * See {@link NameTier} for what each tier covers.
477
516
  */
478
517
  tier?: NameTier;
518
+ /**
519
+ * The grammar to name under, routed as {@link SyntheticSource.language} —
520
+ * this call builds one. A caller that sets it there must set it here, or
521
+ * candidates and the name it proposes come from different grammars.
522
+ */
523
+ language?: string;
479
524
  }
@@ -0,0 +1,75 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 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
+ declare const snapshotMarker: unique symbol;
11
+
12
+ /**
13
+ * The version of the text a model was parsed from: a snapshot of the store's
14
+ * {@link TextVersion}, not a counter of its own. A document envelope carries
15
+ * it, and it cannot move once the envelope exists. A create's answer carries
16
+ * a second snapshot of the same kind, the version the create's text took,
17
+ * which its creator holds because it sent the text.
18
+ *
19
+ * A plain number at runtime, usable anywhere a number is, and it travels the
20
+ * wire as one. The marker exists only so the compiler can tell it apart from a
21
+ * version read off a LIVE document, which is the same number type and is the
22
+ * defect the conflict gate exists to prevent: read at write time it is whatever
23
+ * the server is at now, which is the number the gate is about to compare it
24
+ * against, so the gate passes unconditionally and a concurrent edit is
25
+ * overwritten with nothing logged.
26
+ */
27
+ export type ModelVersion = number & { readonly [snapshotMarker]: true };
28
+
29
+ /**
30
+ * The store's version of a document's text, which moves with every change of it.
31
+ * Unbranded, so a write refuses it as `baseVersion`: it can name text newer than
32
+ * the caller's model, and a write based on it would pass the gate with older content.
33
+ */
34
+ export type TextVersion = number;
35
+
36
+ /**
37
+ * The version a write was authored against, from a snapshot read, or `'any'`: a
38
+ * precondition that always holds, so the write overwrites whatever the server has.
39
+ * `'any'` is a lie for a write authored against a document a read handed over.
40
+ */
41
+ export type BaseVersion = ModelVersion | 'any';
42
+
43
+ /** Whether `baseVersion` names a version, and so arms the gate. */
44
+ export function isModelVersion(baseVersion: BaseVersion): baseVersion is ModelVersion {
45
+ return typeof baseVersion === 'number';
46
+ }
47
+
48
+ /**
49
+ * Mark a version as having come from a snapshot read.
50
+ *
51
+ * **For the envelope constructors, not for callers.** Every door that builds a
52
+ * document envelope applies it on the way in, the create's answer included
53
+ * for the version its text took, so anything a read returns already carries
54
+ * it and a writer never needs this. A caller reaching for it is
55
+ * asserting a provenance the compiler was about to deny — the read-late defect
56
+ * written out where a reviewer can see it.
57
+ */
58
+ export function asModelVersion(version: number): ModelVersion {
59
+ return version as ModelVersion;
60
+ }
61
+
62
+ /**
63
+ * The model version of a root no document factory path built, so nothing
64
+ * records the text it came from. No write matches it, so a write based on it
65
+ * conflicts.
66
+ */
67
+ export const UNRECORDED_VERSION: ModelVersion = asModelVersion(-2);
68
+
69
+ /**
70
+ * The model version of a root parsed from text the store holds under no
71
+ * version: the file, read while the store held the document open with other
72
+ * text. Below every text version, so the model reads as behind and a write
73
+ * based on it conflicts.
74
+ */
75
+ export const STALE_VERSION: ModelVersion = asModelVersion(-1);
@@ -7,10 +7,9 @@
7
7
  * SPDX-License-Identifier: MIT
8
8
  ********************************************************************************/
9
9
 
10
- // Subpath barrel for the in-process `ModelService` facade contract. The
11
- // implementation lives in `@hydranium/core`; this package owns the
12
- // argument types so both the framework facade and the data-server wire
13
- // protocol can structurally agree on the lifecycle shape.
10
+ // Generic payload types the data protocol shares with core: the base
11
+ // version, which the server's model service checks writes against, and the
12
+ // reference candidates, which core's scope and completion code produce.
14
13
 
15
- export * from './args';
14
+ export * from './base-version';
16
15
  export * from './reference-candidate';
@@ -50,9 +50,11 @@ export interface ReferenceCandidate {
50
50
  * node's subtree, NOT the whole document root — callers that want the full
51
51
  * document fetch it via `getModelDocument(uri)`.
52
52
  *
53
- * Generic over the transfer root type so adopters get their typed overlay.
53
+ * `TElement` is the type of the target node, not of a document root. A server
54
+ * answers with the structural base, since a reference can name any node; a
55
+ * caller that knows what the reference targets may narrow it.
54
56
  */
55
- export interface ReferenceTarget<TTransfer extends TransferElement = TransferElement> extends ReferenceCandidate {
57
+ export interface ReferenceTarget<TElement extends TransferElement = TransferElement> extends ReferenceCandidate {
56
58
  /** The resolved target node, encoded as a transfer subtree. */
57
- element: TTransfer;
59
+ element: TElement;
58
60
  }
@@ -0,0 +1,14 @@
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/node` — the diagnostics helpers that
11
+ // need a Node runtime, kept out of the root barrel so the root stays
12
+ // bundleable for a browser.
13
+
14
+ export * from './process-memory';
@@ -0,0 +1,299 @@
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
+ * Node-only process- and pod-level memory introspection plus heap-snapshot
12
+ * writing. Everything here is generic OS-level code (V8 stats, cgroup v2/v1,
13
+ * `/proc`); the only host-specific part is naming the processes, which a head
14
+ * supplies through a `ProcessClassifier`.
15
+ */
16
+
17
+ import * as fs from 'node:fs';
18
+ import * as os from 'node:os';
19
+ import * as path from 'node:path';
20
+ import * as v8 from 'node:v8';
21
+ import { Format } from '../logger';
22
+
23
+ /**
24
+ * Format a one-shot memory snapshot of the CURRENT process (heap, rss, external, V8 limit).
25
+ * Suited to a process that hosts no Langium documents (e.g. a host backend); for the
26
+ * language-server process with document counts, see `formatServerState`.
27
+ */
28
+ export function formatProcessMemory(label: string): string {
29
+ const mem = process.memoryUsage();
30
+ const heap = v8.getHeapStatistics();
31
+ const heapPercent = Math.round((mem.heapUsed / heap.heap_size_limit) * 100);
32
+ return [
33
+ `${label}:`,
34
+ ` heap ${Format.bytes(mem.heapUsed)} used / ${Format.bytes(mem.heapTotal)} total / ` +
35
+ `${Format.bytes(heap.heap_size_limit)} limit (${heapPercent}% of limit)`,
36
+ ` rss ${Format.bytes(mem.rss)}, external ${Format.bytes(mem.external)}, arrayBuffers ${Format.bytes(mem.arrayBuffers)}`,
37
+ ` uptime ${Math.round(process.uptime())}s, pid ${process.pid}`
38
+ ].join('\n');
39
+ }
40
+
41
+ /**
42
+ * Compose an absolute artifact file path of the form `<prefix>[-<label>].<ext>` inside
43
+ * {@link directory} (e.g. the workspace folder, so it lands on the persistent volume in cloud and
44
+ * is visible in the file explorer), falling back to the OS temp dir when the directory is missing
45
+ * or does not exist. The {@link prefix} carries the origin-first stem (e.g. `server-cpu`) and, when
46
+ * a non-empty {@link label} is given (a named capture window), it is sanitised (non-word characters
47
+ * → `_`, truncated to 40 chars) and appended as `-<label>`; an empty label yields the bare
48
+ * `<prefix>.<ext>` so the manifest `kind` equals the filename stem. No timestamp — a fresh session
49
+ * directory per run keeps names stable and collision-free. Shared by the heap-snapshot writer and
50
+ * the sampled-profile capture so heap / CPU / allocation artefacts land identically, differing only
51
+ * by extension.
52
+ */
53
+ export function snapshotFilePath(directory: string | undefined, label: string, prefix: string, ext: string): string {
54
+ const safeLabel = label ? label.replace(/[^\w.-]+/g, '_').slice(0, 40) : '';
55
+ const dir = directory && fs.existsSync(directory) ? directory : os.tmpdir();
56
+ const stem = safeLabel ? `${prefix}-${safeLabel}` : prefix;
57
+ return path.join(dir, `${stem}.${ext}`);
58
+ }
59
+
60
+ /**
61
+ * Write a V8 heap snapshot of the CURRENT process to {@link directory}, falling back to the OS temp
62
+ * dir when it is missing (see {@link snapshotFilePath}). `v8.writeHeapSnapshot` runs a full GC first and
63
+ * briefly pauses the process; it also transiently inflates RSS while serialising. Returns the
64
+ * absolute file path. The {@link prefix} (default `server-heap`, the origin-first stem) and
65
+ * {@link label} are folded into the name.
66
+ */
67
+ export function writeHeapSnapshotToDir(directory: string | undefined, label: string, prefix = 'server-heap'): string {
68
+ const filePath = snapshotFilePath(directory, label, prefix, 'heapsnapshot');
69
+ v8.writeHeapSnapshot(filePath);
70
+ return filePath;
71
+ }
72
+
73
+ const CGROUP_ROOT = '/sys/fs/cgroup';
74
+
75
+ function readNumber(file: string): number | undefined {
76
+ try {
77
+ const text = fs.readFileSync(file, 'utf8').trim();
78
+ if (text === 'max') {
79
+ return Number.POSITIVE_INFINITY;
80
+ }
81
+ const value = Number(text);
82
+ return Number.isFinite(value) ? value : undefined;
83
+ } catch {
84
+ return undefined;
85
+ }
86
+ }
87
+
88
+ /** Parse a `key value` per-line file (cgroup memory.stat) into a map. */
89
+ function readKeyedBytes(file: string): Record<string, number> {
90
+ const out: Record<string, number> = {};
91
+ try {
92
+ for (const line of fs.readFileSync(file, 'utf8').split('\n')) {
93
+ const [key, value] = line.trim().split(/\s+/);
94
+ if (key && value !== undefined) {
95
+ const num = Number(value);
96
+ if (Number.isFinite(num)) {
97
+ out[key] = num;
98
+ }
99
+ }
100
+ }
101
+ } catch {
102
+ // file absent or unreadable; caller handles the empty map.
103
+ }
104
+ return out;
105
+ }
106
+
107
+ interface ProcInfo {
108
+ pid: number;
109
+ rssBytes: number;
110
+ label: string;
111
+ }
112
+
113
+ interface RawProc extends ProcInfo {
114
+ ppid: number;
115
+ }
116
+
117
+ /** A readable role for a process, whether it belongs to the app, and whether exactly one is expected. */
118
+ export interface ProcessRole {
119
+ /** Human-readable role shown in the snapshot. */
120
+ role: string;
121
+ /** Whether this process is part of the application (vs. tooling/debugger), counted in the app total. */
122
+ isApp: boolean;
123
+ /**
124
+ * Set when at most one instance of this role should exist; `formatPodMemory` warns when more than
125
+ * one is found (e.g. a duplicated/ghost language server).
126
+ */
127
+ singletonExpected?: boolean;
128
+ }
129
+
130
+ /** Map a raw command line to a {@link ProcessRole}. A head supplies this to label its own processes. */
131
+ export type ProcessClassifier = (cmdline: string) => ProcessRole;
132
+
133
+ /** Generic fallback: every process is shown under the app heading, labelled by its (truncated) command line. */
134
+ const defaultProcessClassifier: ProcessClassifier = cmdline => ({ role: cmdline ? cmdline.slice(0, 60) : 'process', isApp: true });
135
+
136
+ /** Read /proc for every visible process: pid, ppid, RSS, and a command label. */
137
+ function readAllProcs(): RawProc[] {
138
+ const procs: RawProc[] = [];
139
+ let entries: string[] = [];
140
+ try {
141
+ entries = fs.readdirSync('/proc');
142
+ } catch {
143
+ return procs;
144
+ }
145
+ for (const entry of entries) {
146
+ if (!/^\d+$/.test(entry)) {
147
+ continue;
148
+ }
149
+ let status = '';
150
+ try {
151
+ status = fs.readFileSync(`/proc/${entry}/status`, 'utf8');
152
+ } catch {
153
+ continue;
154
+ }
155
+ const rssMatch = status.match(/^VmRSS:\s+(\d+)\s+kB/m);
156
+ if (!rssMatch) {
157
+ continue;
158
+ }
159
+ const ppid = Number(status.match(/^PPid:\s+(\d+)/m)?.[1] ?? 0);
160
+ let label = '';
161
+ try {
162
+ label = fs.readFileSync(`/proc/${entry}/cmdline`, 'utf8').replace(/\0/g, ' ').trim();
163
+ } catch {
164
+ // fall back to the process name from status below.
165
+ }
166
+ if (!label) {
167
+ label = status.match(/^Name:\s+(.+)$/m)?.[1] ?? `pid ${entry}`;
168
+ }
169
+ procs.push({ pid: Number(entry), ppid, rssBytes: Number(rssMatch[1]) * 1024, label });
170
+ }
171
+ return procs;
172
+ }
173
+
174
+ /**
175
+ * Sum and list per-process RSS. When {@link rootPid} is given, restrict to that process and its
176
+ * descendants (the app's own process tree), which keeps the figure meaningful in local dev where
177
+ * /proc otherwise shows the whole machine. In a container's PID namespace the tree is effectively
178
+ * all the pod's processes anyway.
179
+ */
180
+ function collectProcessRss(rootPid?: number): { total: number; processes: ProcInfo[]; scoped: boolean } {
181
+ const all = readAllProcs();
182
+ let selected: RawProc[] = all;
183
+ let scoped = false;
184
+ if (rootPid !== undefined) {
185
+ const childrenByPpid = new Map<number, RawProc[]>();
186
+ for (const proc of all) {
187
+ (childrenByPpid.get(proc.ppid) ?? childrenByPpid.set(proc.ppid, []).get(proc.ppid)!).push(proc);
188
+ }
189
+ const tree: RawProc[] = [];
190
+ const queue = all.filter(proc => proc.pid === rootPid);
191
+ while (queue.length > 0) {
192
+ const proc = queue.shift()!;
193
+ tree.push(proc);
194
+ queue.push(...(childrenByPpid.get(proc.pid) ?? []));
195
+ }
196
+ if (tree.length > 0) {
197
+ selected = tree;
198
+ scoped = true;
199
+ }
200
+ }
201
+ const total = selected.reduce((sum, proc) => sum + proc.rssBytes, 0);
202
+ selected.sort((a, b) => b.rssBytes - a.rssBytes);
203
+ return { total, processes: selected, scoped };
204
+ }
205
+
206
+ /** Options for {@link formatPodMemory}. */
207
+ export interface PodMemoryOptions {
208
+ /** Map a process command line to a readable role; lets a head label its own processes. */
209
+ classify?: ProcessClassifier;
210
+ /** Heading for the in-app process group (default `Application`). */
211
+ appLabel?: string;
212
+ }
213
+
214
+ /**
215
+ * Format a pod/container-level memory snapshot read from the cgroup this process belongs to
216
+ * (cgroup v2 `memory.current`/`peak`/`max`/`stat`, falling back to cgroup v1). This is the figure
217
+ * the Kubernetes OOM-killer watches — it covers every process in the pod plus page cache and kernel
218
+ * memory, so it is higher than any single `process.memoryUsage().rss`. Also sums per-process RSS so
219
+ * the per-process split is visible. Explains itself when no cgroup memory controller is present
220
+ * (e.g. local dev outside a container).
221
+ */
222
+ export function formatPodMemory(options: PodMemoryOptions = {}): string {
223
+ const classify = options.classify ?? defaultProcessClassifier;
224
+ const appLabel = options.appLabel ?? 'Application';
225
+ const lines: string[] = ['Pod memory snapshot:'];
226
+
227
+ const v2Current = path.join(CGROUP_ROOT, 'memory.current');
228
+ const v1Usage = path.join(CGROUP_ROOT, 'memory', 'memory.usage_in_bytes');
229
+
230
+ if (fs.existsSync(v2Current)) {
231
+ const current = readNumber(v2Current);
232
+ const peak = readNumber(path.join(CGROUP_ROOT, 'memory.peak'));
233
+ const max = readNumber(path.join(CGROUP_ROOT, 'memory.max'));
234
+ const stat = readKeyedBytes(path.join(CGROUP_ROOT, 'memory.stat'));
235
+ lines.push(' cgroup v2');
236
+ lines.push(` current ${current !== undefined ? Format.bytes(current) : 'n/a'}`);
237
+ lines.push(` peak ${peak !== undefined ? Format.bytes(peak) : 'n/a (kernel too old for memory.peak)'}`);
238
+ lines.push(` limit ${max === Number.POSITIVE_INFINITY ? 'unlimited' : max !== undefined ? Format.bytes(max) : 'n/a'}`);
239
+ if (Object.keys(stat).length > 0) {
240
+ lines.push(
241
+ ` breakdown anon ${Format.bytes(stat.anon ?? 0)} (heaps/stacks), file ${Format.bytes(stat.file ?? 0)} (page cache), ` +
242
+ `kernel ${Format.bytes(stat.kernel ?? stat.slab ?? 0)}, sock ${Format.bytes(stat.sock ?? 0)}`
243
+ );
244
+ }
245
+ } else if (fs.existsSync(v1Usage)) {
246
+ const v1 = path.join(CGROUP_ROOT, 'memory');
247
+ const current = readNumber(v1Usage);
248
+ const peak = readNumber(path.join(v1, 'memory.max_usage_in_bytes'));
249
+ const limit = readNumber(path.join(v1, 'memory.limit_in_bytes'));
250
+ const stat = readKeyedBytes(path.join(v1, 'memory.stat'));
251
+ lines.push(' cgroup v1');
252
+ lines.push(` current ${current !== undefined ? Format.bytes(current) : 'n/a'}`);
253
+ lines.push(` peak ${peak !== undefined ? Format.bytes(peak) : 'n/a'}`);
254
+ // v1 reports a huge sentinel for "unlimited"; treat anything >= 2^60 as unlimited.
255
+ lines.push(` limit ${limit !== undefined && limit < 2 ** 60 ? Format.bytes(limit) : 'unlimited'}`);
256
+ if (Object.keys(stat).length > 0) {
257
+ lines.push(
258
+ ` breakdown rss ${Format.bytes(stat.rss ?? 0)}, cache ${Format.bytes(stat.cache ?? 0)} (page cache), ` +
259
+ `kernel ${Format.bytes(stat.kernel ?? 0)}`
260
+ );
261
+ }
262
+ } else {
263
+ lines.push(' cgroup no memory controller under /sys/fs/cgroup (not running in a container?)');
264
+ }
265
+
266
+ // Root at this process so local dev reports only the app's tree, not the whole machine.
267
+ const { processes } = collectProcessRss(process.pid);
268
+ if (processes.length > 0) {
269
+ const classified = processes.map(proc => ({ ...proc, ...classify(proc.label) }));
270
+ const appProcs = classified.filter(proc => proc.isApp);
271
+ const otherProcs = classified.filter(proc => !proc.isApp);
272
+ const appTotal = appProcs.reduce((sum, proc) => sum + proc.rssBytes, 0);
273
+
274
+ lines.push('');
275
+ lines.push(` ${appLabel}: ${Format.bytes(appTotal)} across ${appProcs.length} process(es)`);
276
+ for (const proc of appProcs) {
277
+ lines.push(` ${Format.bytes(proc.rssBytes).padStart(10)} ${proc.role} (pid ${proc.pid})`);
278
+ }
279
+ const singletonCounts = new Map<string, number>();
280
+ for (const proc of appProcs) {
281
+ if (proc.singletonExpected) {
282
+ singletonCounts.set(proc.role, (singletonCounts.get(proc.role) ?? 0) + 1);
283
+ }
284
+ }
285
+ for (const [role, count] of singletonCounts) {
286
+ if (count > 1) {
287
+ lines.push(` WARNING: ${count} instances of "${role}" running - likely ghost process(es); expected 1`);
288
+ }
289
+ }
290
+ if (otherProcs.length > 0) {
291
+ const otherTotal = otherProcs.reduce((sum, proc) => sum + proc.rssBytes, 0);
292
+ lines.push(` Other (tooling/debugger, not counted above): ${Format.bytes(otherTotal)} across ${otherProcs.length} process(es)`);
293
+ }
294
+ lines.push(' Note: in a real pod the cgroup figure above is the OOM ceiling; this per-process');
295
+ lines.push(' split is for attribution. Local dev may include debugger processes (excluded here).');
296
+ }
297
+
298
+ return lines.join('\n');
299
+ }