@hyperscale0/hsx 1.0.0-beta.1 → 1.0.0

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 (255) hide show
  1. package/CHANGELOG.md +58 -9
  2. package/LICENSING.md +3 -3
  3. package/README.md +39 -149
  4. package/TRADEMARKS.md +4 -4
  5. package/bin/hsx.ts +2 -0
  6. package/dist/bin/hsx.js +2 -0
  7. package/dist/bin/hsx.js.map +1 -1
  8. package/dist/src/ast.d.ts +135 -10
  9. package/dist/src/ast.d.ts.map +1 -1
  10. package/dist/src/ast.js +24 -0
  11. package/dist/src/ast.js.map +1 -1
  12. package/dist/src/cli.d.ts +4 -1
  13. package/dist/src/cli.d.ts.map +1 -1
  14. package/dist/src/cli.js +172 -16
  15. package/dist/src/cli.js.map +1 -1
  16. package/dist/src/compile.d.ts +42 -7
  17. package/dist/src/compile.d.ts.map +1 -1
  18. package/dist/src/compile.js +82 -16
  19. package/dist/src/compile.js.map +1 -1
  20. package/dist/src/cost.d.ts +78 -0
  21. package/dist/src/cost.d.ts.map +1 -0
  22. package/dist/src/cost.js +369 -0
  23. package/dist/src/cost.js.map +1 -0
  24. package/dist/src/diagnostics.d.ts +11 -0
  25. package/dist/src/diagnostics.d.ts.map +1 -0
  26. package/dist/src/diagnostics.js +420 -0
  27. package/dist/src/diagnostics.js.map +1 -0
  28. package/dist/src/emit.d.ts +34 -0
  29. package/dist/src/emit.d.ts.map +1 -0
  30. package/dist/src/emit.js +137 -0
  31. package/dist/src/emit.js.map +1 -0
  32. package/dist/src/entry-overrides.d.ts +17 -3
  33. package/dist/src/entry-overrides.d.ts.map +1 -1
  34. package/dist/src/entry-overrides.js +180 -35
  35. package/dist/src/entry-overrides.js.map +1 -1
  36. package/dist/src/format.d.ts +10 -0
  37. package/dist/src/format.d.ts.map +1 -0
  38. package/dist/src/format.js +183 -0
  39. package/dist/src/format.js.map +1 -0
  40. package/dist/src/index.d.ts +11 -4
  41. package/dist/src/index.d.ts.map +1 -1
  42. package/dist/src/index.js +10 -3
  43. package/dist/src/index.js.map +1 -1
  44. package/dist/src/ir.d.ts +66 -0
  45. package/dist/src/ir.d.ts.map +1 -0
  46. package/dist/src/ir.js +15 -0
  47. package/dist/src/ir.js.map +1 -0
  48. package/dist/src/lex.d.ts +9 -1
  49. package/dist/src/lex.d.ts.map +1 -1
  50. package/dist/src/lex.js +50 -4
  51. package/dist/src/lex.js.map +1 -1
  52. package/dist/src/limits.d.ts +2 -3
  53. package/dist/src/limits.d.ts.map +1 -1
  54. package/dist/src/limits.js +2 -3
  55. package/dist/src/limits.js.map +1 -1
  56. package/dist/src/lsp/server.d.ts +32 -0
  57. package/dist/src/lsp/server.d.ts.map +1 -0
  58. package/dist/src/lsp/server.js +391 -0
  59. package/dist/src/lsp/server.js.map +1 -0
  60. package/dist/src/modules.d.ts +38 -0
  61. package/dist/src/modules.d.ts.map +1 -0
  62. package/dist/src/modules.js +368 -0
  63. package/dist/src/modules.js.map +1 -0
  64. package/dist/src/parse.d.ts.map +1 -1
  65. package/dist/src/parse.js +590 -17
  66. package/dist/src/parse.js.map +1 -1
  67. package/dist/src/std-bundle.d.ts +2 -0
  68. package/dist/src/std-bundle.d.ts.map +1 -0
  69. package/dist/src/std-bundle.js +88 -0
  70. package/dist/src/std-bundle.js.map +1 -0
  71. package/dist/src/std-library.d.ts +5 -0
  72. package/dist/src/std-library.d.ts.map +1 -0
  73. package/dist/src/std-library.js +9 -0
  74. package/dist/src/std-library.js.map +1 -0
  75. package/dist/src/typecheck.d.ts +8 -0
  76. package/dist/src/typecheck.d.ts.map +1 -0
  77. package/dist/src/typecheck.js +2958 -0
  78. package/dist/src/typecheck.js.map +1 -0
  79. package/dist/src/version.d.ts +4 -15
  80. package/dist/src/version.d.ts.map +1 -1
  81. package/dist/src/version.js +4 -15
  82. package/dist/src/version.js.map +1 -1
  83. package/docs/README.md +26 -0
  84. package/docs/guide/01-first-program.md +24 -0
  85. package/docs/guide/02-money.md +25 -0
  86. package/docs/guide/03-instruments.md +22 -0
  87. package/docs/guide/04-lifecycles.md +25 -0
  88. package/docs/guide/05-fees-and-splits.md +45 -0
  89. package/docs/guide/06-schedules.md +22 -0
  90. package/docs/guide/07-composition.md +46 -0
  91. package/docs/guide/08-writing-a-module.md +26 -0
  92. package/docs/guide/09-cost.md +24 -0
  93. package/docs/guide/10-diagnostics.md +27 -0
  94. package/docs/llms-full.txt +1739 -0
  95. package/docs/llms.txt +45 -0
  96. package/docs/playground.md +54 -0
  97. package/docs/reference/cli.md +35 -0
  98. package/docs/reference/diagnostics.md +619 -0
  99. package/docs/reference/grammar.md +39 -0
  100. package/docs/reference/std/advance.md +35 -0
  101. package/docs/reference/std/cancellable_booking.md +35 -0
  102. package/docs/reference/std/captured_payment.md +43 -0
  103. package/docs/reference/std/conditional_disbursement.md +33 -0
  104. package/docs/reference/std/credit_facility.md +35 -0
  105. package/docs/reference/std/held_payment.md +64 -0
  106. package/docs/reference/std/instant_transfer.md +30 -0
  107. package/docs/reference/std/metered.md +27 -0
  108. package/docs/reference/std/pooled_split.md +26 -0
  109. package/docs/reference/std/premium_forward.md +39 -0
  110. package/docs/reference/std/reconciled_payout.md +33 -0
  111. package/docs/reference/std/recurring_collection.md +25 -0
  112. package/docs/reference/std/rotating_pool.md +42 -0
  113. package/docs/reference/std/scheduled.md +51 -0
  114. package/docs/reference/std/security_deposit.md +46 -0
  115. package/docs/reference/std/settlement_batch.md +42 -0
  116. package/docs/reference/std/swap.md +46 -0
  117. package/docs/reference/std/threshold_pool.md +47 -0
  118. package/docs/reference/std/weighted_distribution.md +39 -0
  119. package/docs/reference/types.md +20 -0
  120. package/docs/reference/udl-output.md +13 -0
  121. package/docs/sessions/2026-09-02-two-instruments.hsx +21 -0
  122. package/docs/sessions/2026-09-02-two-instruments.md +41 -0
  123. package/examples/01-first-program/README.md +45 -0
  124. package/examples/01-first-program/tip-jar.hsx +17 -0
  125. package/examples/02-imports-and-archetypes/README.md +96 -0
  126. package/examples/02-imports-and-archetypes/photo-booth.hsx +37 -0
  127. package/examples/03-diagnostics/README.md +68 -0
  128. package/examples/03-diagnostics/corner-shop-fixed.hsx +22 -0
  129. package/examples/03-diagnostics/corner-shop.hsx +19 -0
  130. package/examples/04-complete-product/README.md +84 -0
  131. package/examples/04-complete-product/study-hall.hsx +67 -0
  132. package/examples/05-watch-club/README.md +9 -0
  133. package/examples/05-watch-club/watch-club.hsx +124 -0
  134. package/examples/README.md +18 -0
  135. package/examples/advance/README.md +3 -0
  136. package/examples/advance/advance.hsx +13 -0
  137. package/examples/advance/advance.udl +339 -0
  138. package/examples/cancellable_booking/README.md +3 -0
  139. package/examples/cancellable_booking/cancellable_booking.hsx +14 -0
  140. package/examples/cancellable_booking/cancellable_booking.udl +391 -0
  141. package/examples/captured_payment/README.md +3 -0
  142. package/examples/captured_payment/captured_payment.hsx +21 -0
  143. package/examples/captured_payment/captured_payment.udl +677 -0
  144. package/examples/conditional_disbursement/README.md +3 -0
  145. package/examples/conditional_disbursement/conditional_disbursement.hsx +17 -0
  146. package/examples/conditional_disbursement/conditional_disbursement.udl +412 -0
  147. package/examples/cost-table.json +386 -0
  148. package/examples/credit_facility/README.md +3 -0
  149. package/examples/credit_facility/credit_facility.hsx +27 -0
  150. package/examples/credit_facility/credit_facility.udl +1373 -0
  151. package/examples/held_payment/README.md +3 -0
  152. package/examples/held_payment/held_payment.hsx +13 -0
  153. package/examples/held_payment/held_payment.udl +507 -0
  154. package/examples/instant_transfer/README.md +3 -0
  155. package/examples/instant_transfer/instant_transfer.hsx +16 -0
  156. package/examples/instant_transfer/instant_transfer.udl +364 -0
  157. package/examples/metered/README.md +3 -0
  158. package/examples/metered/metered.hsx +13 -0
  159. package/examples/metered/metered.udl +247 -0
  160. package/examples/pooled_split/README.md +3 -0
  161. package/examples/pooled_split/pooled_split.hsx +15 -0
  162. package/examples/pooled_split/pooled_split.udl +425 -0
  163. package/examples/premium_forward/README.md +3 -0
  164. package/examples/premium_forward/premium_forward.hsx +19 -0
  165. package/examples/premium_forward/premium_forward.udl +594 -0
  166. package/examples/reconciled_payout/README.md +3 -0
  167. package/examples/reconciled_payout/reconciled_payout.hsx +13 -0
  168. package/examples/reconciled_payout/reconciled_payout.udl +303 -0
  169. package/examples/recurring_collection/README.md +3 -0
  170. package/examples/recurring_collection/recurring_collection.hsx +21 -0
  171. package/examples/recurring_collection/recurring_collection.udl +1130 -0
  172. package/examples/rotating_pool/README.md +3 -0
  173. package/examples/rotating_pool/rotating_pool.hsx +18 -0
  174. package/examples/rotating_pool/rotating_pool.udl +4352 -0
  175. package/examples/scheduled/README.md +3 -0
  176. package/examples/scheduled/scheduled.hsx +12 -0
  177. package/examples/scheduled/scheduled.udl +335 -0
  178. package/examples/security_deposit/README.md +3 -0
  179. package/examples/security_deposit/security_deposit.hsx +21 -0
  180. package/examples/security_deposit/security_deposit.udl +293 -0
  181. package/examples/settlement_batch/README.md +3 -0
  182. package/examples/settlement_batch/settlement_batch.hsx +19 -0
  183. package/examples/settlement_batch/settlement_batch.udl +753 -0
  184. package/examples/swap/README.md +3 -0
  185. package/examples/swap/swap.hsx +19 -0
  186. package/examples/swap/swap.udl +876 -0
  187. package/examples/threshold_pool/README.md +3 -0
  188. package/examples/threshold_pool/threshold_pool.hsx +16 -0
  189. package/examples/threshold_pool/threshold_pool.udl +667 -0
  190. package/examples/weighted_distribution/README.md +3 -0
  191. package/examples/weighted_distribution/weighted_distribution.hsx +20 -0
  192. package/examples/weighted_distribution/weighted_distribution.udl +337 -0
  193. package/package.json +27 -6
  194. package/skills/hsx/SKILL.md +483 -0
  195. package/src/ast.ts +193 -9
  196. package/src/cli.ts +191 -20
  197. package/src/compile.ts +160 -23
  198. package/src/cost.ts +579 -0
  199. package/src/diagnostics.ts +442 -0
  200. package/src/emit.ts +201 -0
  201. package/src/entry-overrides.ts +263 -48
  202. package/src/format.ts +261 -0
  203. package/src/index.ts +34 -4
  204. package/src/ir.ts +99 -0
  205. package/src/lex.ts +59 -4
  206. package/src/limits.ts +2 -3
  207. package/src/lsp/server.ts +460 -0
  208. package/src/modules.ts +446 -0
  209. package/src/parse.ts +655 -21
  210. package/src/std-bundle.ts +91 -0
  211. package/src/std-library.ts +12 -0
  212. package/src/typecheck.ts +3876 -0
  213. package/src/version.ts +4 -16
  214. package/std/SEMANTICS.md +130 -0
  215. package/std/settlements/advance.hsx +177 -0
  216. package/std/settlements/cancellable_booking.hsx +87 -0
  217. package/std/settlements/captured_payment.hsx +144 -0
  218. package/std/settlements/conditional_disbursement.hsx +185 -0
  219. package/std/settlements/credit_facility.hsx +167 -0
  220. package/std/settlements/held_payment.hsx +578 -0
  221. package/std/settlements/index.hsx +3 -0
  222. package/std/settlements/instant_transfer.hsx +190 -0
  223. package/std/settlements/metered.hsx +67 -0
  224. package/std/settlements/pooled_split.hsx +68 -0
  225. package/std/settlements/premium_forward.hsx +257 -0
  226. package/std/settlements/reconciled_payout.hsx +77 -0
  227. package/std/settlements/recurring_collection.hsx +27 -0
  228. package/std/settlements/rotating_pool.hsx +252 -0
  229. package/std/settlements/scheduled.hsx +641 -0
  230. package/std/settlements/security_deposit.hsx +166 -0
  231. package/std/settlements/settlement_batch.hsx +122 -0
  232. package/std/settlements/swap.hsx +1114 -0
  233. package/std/settlements/threshold_pool.hsx +221 -0
  234. package/std/settlements/weighted_distribution.hsx +141 -0
  235. package/dist/src/archetypes.d.ts +0 -17
  236. package/dist/src/archetypes.d.ts.map +0 -1
  237. package/dist/src/archetypes.js +0 -327
  238. package/dist/src/archetypes.js.map +0 -1
  239. package/dist/src/check.d.ts +0 -12
  240. package/dist/src/check.d.ts.map +0 -1
  241. package/dist/src/check.js +0 -1910
  242. package/dist/src/check.js.map +0 -1
  243. package/dist/src/lower.d.ts +0 -146
  244. package/dist/src/lower.d.ts.map +0 -1
  245. package/dist/src/lower.js +0 -4570
  246. package/dist/src/lower.js.map +0 -1
  247. package/dist/src/model.d.ts +0 -467
  248. package/dist/src/model.d.ts.map +0 -1
  249. package/dist/src/model.js +0 -15
  250. package/dist/src/model.js.map +0 -1
  251. package/spec/hsx-ir.schema.json +0 -920
  252. package/src/archetypes.ts +0 -340
  253. package/src/check.ts +0 -2938
  254. package/src/lower.ts +0 -5416
  255. package/src/model.ts +0 -519
@@ -0,0 +1,460 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { dirname, isAbsolute, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { lineIndex, type LineIndex } from "../ast.ts";
5
+ import { format } from "../format.ts";
6
+ import type { HsxCompilerHost, ModuleSource } from "../modules.ts";
7
+ import { resolveProgramModules } from "../modules.ts";
8
+ import { parseProgram } from "../parse.ts";
9
+ import { checkGeneralProgram } from "../typecheck.ts";
10
+
11
+ export interface LspPosition {
12
+ readonly character: number;
13
+ readonly line: number;
14
+ }
15
+
16
+ export interface LspRange {
17
+ readonly end: LspPosition;
18
+ readonly start: LspPosition;
19
+ }
20
+
21
+ export interface LspDiagnostic {
22
+ readonly code?: string;
23
+ readonly message: string;
24
+ readonly range: LspRange;
25
+ readonly severity: 1 | 2;
26
+ readonly source: "hsx";
27
+ }
28
+
29
+ export interface LspServerOptions {
30
+ readonly host?: HsxCompilerHost;
31
+ readonly onExit?: (code: number) => void;
32
+ }
33
+
34
+ export interface LspServer {
35
+ readonly stop: () => void;
36
+ }
37
+
38
+ export function offsetToPosition(
39
+ index: LineIndex,
40
+ offset: number,
41
+ ): LspPosition {
42
+ const clamped = Math.max(0, Math.min(offset, index.length));
43
+ let low = 0;
44
+ let high = index.starts.length - 1;
45
+ while (low < high) {
46
+ const middle = Math.floor((low + high + 1) / 2);
47
+ if ((index.starts[middle] as number) <= clamped) low = middle;
48
+ else high = middle - 1;
49
+ }
50
+ const lineStart = index.starts[low] as number;
51
+ return {
52
+ character: clamped - lineStart,
53
+ line: low,
54
+ };
55
+ }
56
+
57
+ export function spanToRange(
58
+ index: LineIndex,
59
+ span: { readonly end: number; readonly start: number },
60
+ ): LspRange {
61
+ return {
62
+ end: offsetToPosition(index, Math.max(span.start, span.end)),
63
+ start: offsetToPosition(index, span.start),
64
+ };
65
+ }
66
+
67
+ export function createHostForUri(
68
+ uri: string,
69
+ fallbackHost?: HsxCompilerHost,
70
+ ): HsxCompilerHost {
71
+ let docDir: string | undefined;
72
+ try {
73
+ if (uri.startsWith("file://")) {
74
+ docDir = dirname(fileURLToPath(uri));
75
+ }
76
+ } catch {
77
+ docDir = undefined;
78
+ }
79
+
80
+ return {
81
+ resolveModule(specifier: string, from: string): ModuleSource | undefined {
82
+ if (fallbackHost?.resolveModule) {
83
+ const custom = fallbackHost.resolveModule(specifier, from);
84
+ if (custom) return custom;
85
+ }
86
+ if (!docDir) return undefined;
87
+ const baseDir = from
88
+ ? isAbsolute(from)
89
+ ? dirname(from)
90
+ : resolve(docDir, dirname(from))
91
+ : docDir;
92
+ const candidates = [
93
+ resolve(baseDir, specifier),
94
+ resolve(baseDir, `${specifier}.hsx`),
95
+ resolve(docDir, specifier),
96
+ resolve(docDir, `${specifier}.hsx`),
97
+ ];
98
+ for (const candidate of candidates) {
99
+ if (existsSync(candidate)) {
100
+ try {
101
+ const stat = statSync(candidate);
102
+ if (stat.isFile()) {
103
+ return {
104
+ name: candidate,
105
+ source: readFileSync(candidate, "utf8"),
106
+ };
107
+ }
108
+ } catch {
109
+ // Ignore filesystem errors and check subsequent candidates.
110
+ }
111
+ }
112
+ }
113
+ return undefined;
114
+ },
115
+ };
116
+ }
117
+
118
+ interface JsonRpcMessage {
119
+ readonly id?: number | string;
120
+ readonly jsonrpc: "2.0";
121
+ readonly method?: string;
122
+ readonly params?: any;
123
+ }
124
+
125
+ export function startLspServer(
126
+ input: NodeJS.ReadableStream,
127
+ output: NodeJS.WritableStream,
128
+ options: LspServerOptions = {},
129
+ ): LspServer {
130
+ const documents = new Map<string, string>();
131
+ let isInitialized = false;
132
+ let isShutdown = false;
133
+ let buffer = Buffer.alloc(0);
134
+
135
+ const send = (message: unknown): void => {
136
+ const json = JSON.stringify(message);
137
+ const bytes = Buffer.from(json, "utf8");
138
+ output.write(`Content-Length: ${bytes.length}\r\n\r\n${json}`);
139
+ };
140
+
141
+ const validateAndPublish = (uri: string, source: string): void => {
142
+ const lineIdx = lineIndex(source);
143
+ const diagnostics: LspDiagnostic[] = [];
144
+
145
+ const parsed = parseProgram(source);
146
+ for (const diag of parsed.diagnostics) {
147
+ diagnostics.push({
148
+ ...(diag.code ? { code: diag.code } : {}),
149
+ message: diag.message,
150
+ range: spanToRange(lineIdx, diag.span),
151
+ severity: 1,
152
+ source: "hsx",
153
+ });
154
+ }
155
+
156
+ if (parsed.diagnostics.length === 0) {
157
+ const host = createHostForUri(uri, options.host);
158
+ const resolved = resolveProgramModules(parsed.program, host);
159
+ if (!resolved.ok) {
160
+ for (const issue of resolved.issues) {
161
+ diagnostics.push({
162
+ code: issue.code,
163
+ message: issue.message,
164
+ range: spanToRange(lineIdx, issue.span),
165
+ severity: 1,
166
+ source: "hsx",
167
+ });
168
+ }
169
+ } else {
170
+ const checked = checkGeneralProgram(resolved.program);
171
+ for (const diag of checked.diagnostics) {
172
+ if (!resolved.origins.has(diag.span)) {
173
+ diagnostics.push({
174
+ code: diag.code,
175
+ message: diag.message,
176
+ range: spanToRange(lineIdx, diag.span),
177
+ severity: diag.severity === "warning" ? 2 : 1,
178
+ source: "hsx",
179
+ });
180
+ }
181
+ }
182
+ }
183
+ }
184
+
185
+ send({
186
+ jsonrpc: "2.0",
187
+ method: "textDocument/publishDiagnostics",
188
+ params: {
189
+ diagnostics,
190
+ uri,
191
+ },
192
+ });
193
+ };
194
+
195
+ const handleMessage = (msg: JsonRpcMessage): void => {
196
+ const { id, method, params } = msg;
197
+
198
+ // Requests (id is present)
199
+ if (id !== undefined) {
200
+ if (isShutdown) {
201
+ send({
202
+ error: {
203
+ code: -32600,
204
+ message: "Invalid request: server is shutting down",
205
+ },
206
+ id,
207
+ jsonrpc: "2.0",
208
+ });
209
+ return;
210
+ }
211
+
212
+ if (!isInitialized) {
213
+ if (method !== "initialize") {
214
+ send({
215
+ error: {
216
+ code: -32002,
217
+ message: "Server not initialized",
218
+ },
219
+ id,
220
+ jsonrpc: "2.0",
221
+ });
222
+ return;
223
+ }
224
+
225
+ isInitialized = true;
226
+ send({
227
+ id,
228
+ jsonrpc: "2.0",
229
+ result: {
230
+ capabilities: {
231
+ documentFormattingProvider: true,
232
+ textDocumentSync: 1,
233
+ },
234
+ serverInfo: {
235
+ name: "hsx-lsp",
236
+ version: "1.0.0",
237
+ },
238
+ },
239
+ });
240
+ return;
241
+ }
242
+
243
+ if (method === "initialize") {
244
+ send({
245
+ id,
246
+ jsonrpc: "2.0",
247
+ result: {
248
+ capabilities: {
249
+ documentFormattingProvider: true,
250
+ textDocumentSync: 1,
251
+ },
252
+ serverInfo: {
253
+ name: "hsx-lsp",
254
+ version: "1.0.0",
255
+ },
256
+ },
257
+ });
258
+ return;
259
+ }
260
+
261
+ if (method === "shutdown") {
262
+ isShutdown = true;
263
+ send({
264
+ id,
265
+ jsonrpc: "2.0",
266
+ result: null,
267
+ });
268
+ return;
269
+ }
270
+
271
+ if (method === "textDocument/formatting") {
272
+ const uri = params?.textDocument?.uri;
273
+ const source = uri ? documents.get(uri) : undefined;
274
+ if (!source) {
275
+ send({ id, jsonrpc: "2.0", result: null });
276
+ return;
277
+ }
278
+ const formatted = format(source);
279
+ if (!formatted.ok) {
280
+ send({ id, jsonrpc: "2.0", result: null });
281
+ return;
282
+ }
283
+ if (formatted.formatted === source) {
284
+ send({ id, jsonrpc: "2.0", result: [] });
285
+ return;
286
+ }
287
+ const lines = lineIndex(source);
288
+ send({
289
+ id,
290
+ jsonrpc: "2.0",
291
+ result: [
292
+ {
293
+ newText: formatted.formatted,
294
+ range: {
295
+ end: offsetToPosition(lines, source.length),
296
+ start: { character: 0, line: 0 },
297
+ },
298
+ },
299
+ ],
300
+ });
301
+ return;
302
+ }
303
+
304
+ send({
305
+ error: {
306
+ code: -32601,
307
+ message: `Method not found: ${method ?? "unknown"}`,
308
+ },
309
+ id,
310
+ jsonrpc: "2.0",
311
+ });
312
+ return;
313
+ }
314
+
315
+ // Notifications (id is undefined)
316
+ if (method === "exit") {
317
+ const code = isShutdown ? 0 : 1;
318
+ if (options.onExit) {
319
+ options.onExit(code);
320
+ } else {
321
+ process.exit(code);
322
+ }
323
+ return;
324
+ }
325
+
326
+ if (isShutdown || !isInitialized) {
327
+ return;
328
+ }
329
+
330
+ if (method === "initialized") {
331
+ return;
332
+ }
333
+
334
+ if (method === "textDocument/didOpen") {
335
+ const doc = params?.textDocument;
336
+ if (doc?.uri && typeof doc.text === "string") {
337
+ documents.set(doc.uri, doc.text);
338
+ validateAndPublish(doc.uri, doc.text);
339
+ }
340
+ return;
341
+ }
342
+
343
+ if (method === "textDocument/didChange") {
344
+ const uri = params?.textDocument?.uri;
345
+ const changes = params?.contentChanges;
346
+ if (!uri || !Array.isArray(changes) || changes.length === 0) {
347
+ // Empty contentChanges keeps the document and republishes nothing.
348
+ return;
349
+ }
350
+ // Full sync only; an incremental entry would replace the document with a fragment.
351
+ const hasIncremental = changes.some(
352
+ (change: any) =>
353
+ change && typeof change === "object" && "range" in change,
354
+ );
355
+ if (hasIncremental) {
356
+ return;
357
+ }
358
+ const lastChange = changes[changes.length - 1];
359
+ if (lastChange && typeof lastChange.text === "string") {
360
+ documents.set(uri, lastChange.text);
361
+ validateAndPublish(uri, lastChange.text);
362
+ }
363
+ return;
364
+ }
365
+
366
+ if (method === "textDocument/didClose") {
367
+ const uri = params?.textDocument?.uri;
368
+ if (uri) {
369
+ documents.delete(uri);
370
+ send({
371
+ jsonrpc: "2.0",
372
+ method: "textDocument/publishDiagnostics",
373
+ params: {
374
+ diagnostics: [],
375
+ uri,
376
+ },
377
+ });
378
+ }
379
+ return;
380
+ }
381
+ };
382
+
383
+ const processBuffer = (): void => {
384
+ while (true) {
385
+ const headerEnd = buffer.indexOf("\r\n\r\n");
386
+ if (headerEnd === -1) break;
387
+ const headerText = buffer.subarray(0, headerEnd).toString("ascii");
388
+ const match = /content-length:\s*(\d+)/i.exec(headerText);
389
+ if (!match) {
390
+ buffer = buffer.subarray(headerEnd + 4);
391
+ continue;
392
+ }
393
+ const contentLength = parseInt(match[1] as string, 10);
394
+ if (Number.isNaN(contentLength) || contentLength < 0) {
395
+ buffer = buffer.subarray(headerEnd + 4);
396
+ continue;
397
+ }
398
+ const totalLength = headerEnd + 4 + contentLength;
399
+ if (buffer.length < totalLength) {
400
+ break;
401
+ }
402
+ const bodyBytes = buffer.subarray(headerEnd + 4, totalLength);
403
+ buffer = buffer.subarray(totalLength);
404
+ const bodyStr = bodyBytes.toString("utf8");
405
+ try {
406
+ const msg = JSON.parse(bodyStr) as JsonRpcMessage;
407
+ handleMessage(msg);
408
+ } catch {
409
+ // Ignore unparseable JSON payload.
410
+ }
411
+ }
412
+ };
413
+
414
+ const flushPending = (): void => {
415
+ const headerEnd = buffer.indexOf("\r\n\r\n");
416
+ if (headerEnd !== -1) {
417
+ const bodyStr = buffer
418
+ .subarray(headerEnd + 4)
419
+ .toString("utf8")
420
+ .trim();
421
+ if (bodyStr.length > 0) {
422
+ try {
423
+ const msg = JSON.parse(bodyStr) as JsonRpcMessage;
424
+ handleMessage(msg);
425
+ } catch {
426
+ // Ignore unparseable body.
427
+ }
428
+ }
429
+ }
430
+ };
431
+
432
+ const onData = (chunk: Buffer | string): void => {
433
+ const incoming =
434
+ typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
435
+ buffer = Buffer.concat([buffer, incoming]);
436
+ processBuffer();
437
+ };
438
+
439
+ const onEnd = (): void => {
440
+ processBuffer();
441
+ if (buffer.length > 0) {
442
+ flushPending();
443
+ }
444
+ if (options.onExit) {
445
+ options.onExit(0);
446
+ } else {
447
+ process.exit(0);
448
+ }
449
+ };
450
+
451
+ input.on("data", onData);
452
+ input.on("end", onEnd);
453
+
454
+ return {
455
+ stop(): void {
456
+ input.removeListener("data", onData);
457
+ input.removeListener("end", onEnd);
458
+ },
459
+ };
460
+ }