bonescript-compiler 0.2.1 → 0.4.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 (167) hide show
  1. package/LICENSE +21 -21
  2. package/dist/algorithm_catalog.js +166 -166
  3. package/dist/cli.d.ts +2 -1
  4. package/dist/cli.js +75 -543
  5. package/dist/cli.js.map +1 -1
  6. package/dist/commands/check.d.ts +5 -0
  7. package/dist/commands/check.js +34 -0
  8. package/dist/commands/check.js.map +1 -0
  9. package/dist/commands/compile.d.ts +5 -0
  10. package/dist/commands/compile.js +215 -0
  11. package/dist/commands/compile.js.map +1 -0
  12. package/dist/commands/debug.d.ts +5 -0
  13. package/dist/commands/debug.js +59 -0
  14. package/dist/commands/debug.js.map +1 -0
  15. package/dist/commands/diff.d.ts +5 -0
  16. package/dist/commands/diff.js +125 -0
  17. package/dist/commands/diff.js.map +1 -0
  18. package/dist/commands/fmt.d.ts +5 -0
  19. package/dist/commands/fmt.js +49 -0
  20. package/dist/commands/fmt.js.map +1 -0
  21. package/dist/commands/init.d.ts +5 -0
  22. package/dist/commands/init.js +96 -0
  23. package/dist/commands/init.js.map +1 -0
  24. package/dist/commands/ir.d.ts +5 -0
  25. package/dist/commands/ir.js +27 -0
  26. package/dist/commands/ir.js.map +1 -0
  27. package/dist/commands/lex.d.ts +5 -0
  28. package/dist/commands/lex.js +21 -0
  29. package/dist/commands/lex.js.map +1 -0
  30. package/dist/commands/parse.d.ts +5 -0
  31. package/dist/commands/parse.js +30 -0
  32. package/dist/commands/parse.js.map +1 -0
  33. package/dist/commands/test.d.ts +5 -0
  34. package/dist/commands/test.js +61 -0
  35. package/dist/commands/test.js.map +1 -0
  36. package/dist/commands/verify_determinism.d.ts +5 -0
  37. package/dist/commands/verify_determinism.js +64 -0
  38. package/dist/commands/verify_determinism.js.map +1 -0
  39. package/dist/commands/watch.d.ts +5 -0
  40. package/dist/commands/watch.js +50 -0
  41. package/dist/commands/watch.js.map +1 -0
  42. package/dist/emit_auth.d.ts +6 -0
  43. package/dist/emit_auth.js +69 -0
  44. package/dist/emit_auth.js.map +1 -0
  45. package/dist/emit_capability.d.ts +13 -0
  46. package/dist/emit_capability.js +292 -128
  47. package/dist/emit_capability.js.map +1 -1
  48. package/dist/emit_composition.js +37 -3
  49. package/dist/emit_composition.js.map +1 -1
  50. package/dist/emit_database.d.ts +7 -0
  51. package/dist/emit_database.js +74 -0
  52. package/dist/emit_database.js.map +1 -0
  53. package/dist/emit_deploy.js +162 -162
  54. package/dist/emit_events.d.ts +1 -0
  55. package/dist/emit_events.js +342 -275
  56. package/dist/emit_events.js.map +1 -1
  57. package/dist/emit_full.js +135 -95
  58. package/dist/emit_full.js.map +1 -1
  59. package/dist/emit_index.d.ts +6 -0
  60. package/dist/emit_index.js +157 -0
  61. package/dist/emit_index.js.map +1 -0
  62. package/dist/emit_maintenance.js +249 -249
  63. package/dist/emit_models.d.ts +12 -0
  64. package/dist/emit_models.js +171 -0
  65. package/dist/emit_models.js.map +1 -0
  66. package/dist/emit_openapi.d.ts +9 -0
  67. package/dist/emit_openapi.js +308 -0
  68. package/dist/emit_openapi.js.map +1 -0
  69. package/dist/emit_package.d.ts +7 -0
  70. package/dist/emit_package.js +70 -0
  71. package/dist/emit_package.js.map +1 -0
  72. package/dist/emit_router.d.ts +12 -0
  73. package/dist/emit_router.js +390 -0
  74. package/dist/emit_router.js.map +1 -0
  75. package/dist/emit_runtime.d.ts +17 -11
  76. package/dist/emit_runtime.js +29 -686
  77. package/dist/emit_runtime.js.map +1 -1
  78. package/dist/emit_sourcemap.js +66 -66
  79. package/dist/emit_tests.js +37 -0
  80. package/dist/emit_tests.js.map +1 -1
  81. package/dist/emitter.js +34 -5
  82. package/dist/emitter.js.map +1 -1
  83. package/dist/extension_manager.d.ts +2 -2
  84. package/dist/extension_manager.js +6 -3
  85. package/dist/extension_manager.js.map +1 -1
  86. package/dist/lowering.d.ts +5 -14
  87. package/dist/lowering.js +47 -417
  88. package/dist/lowering.js.map +1 -1
  89. package/dist/lowering_channels.d.ts +11 -0
  90. package/dist/lowering_channels.js +102 -0
  91. package/dist/lowering_channels.js.map +1 -0
  92. package/dist/lowering_entities.d.ts +11 -0
  93. package/dist/lowering_entities.js +222 -0
  94. package/dist/lowering_entities.js.map +1 -0
  95. package/dist/lowering_helpers.d.ts +13 -0
  96. package/dist/lowering_helpers.js +76 -0
  97. package/dist/lowering_helpers.js.map +1 -0
  98. package/dist/module_loader.d.ts +2 -2
  99. package/dist/module_loader.js +20 -23
  100. package/dist/module_loader.js.map +1 -1
  101. package/dist/scaffold.d.ts +2 -2
  102. package/dist/scaffold.js +316 -319
  103. package/dist/scaffold.js.map +1 -1
  104. package/dist/typechecker.js +32 -13
  105. package/dist/typechecker.js.map +1 -1
  106. package/dist/verifier.d.ts +5 -0
  107. package/dist/verifier.js +140 -2
  108. package/dist/verifier.js.map +1 -1
  109. package/package.json +62 -52
  110. package/src/algorithm_catalog.ts +345 -345
  111. package/src/ast.ts +334 -334
  112. package/src/cli.ts +98 -624
  113. package/src/commands/check.ts +33 -0
  114. package/src/commands/compile.ts +191 -0
  115. package/src/commands/debug.ts +33 -0
  116. package/src/commands/diff.ts +108 -0
  117. package/src/commands/fmt.ts +22 -0
  118. package/src/commands/init.ts +72 -0
  119. package/src/commands/ir.ts +23 -0
  120. package/src/commands/lex.ts +17 -0
  121. package/src/commands/parse.ts +24 -0
  122. package/src/commands/test.ts +36 -0
  123. package/src/commands/verify_determinism.ts +66 -0
  124. package/src/commands/watch.ts +25 -0
  125. package/src/emit_auth.ts +67 -0
  126. package/src/emit_batch.ts +140 -140
  127. package/src/emit_capability.ts +617 -436
  128. package/src/emit_composition.ts +229 -196
  129. package/src/emit_database.ts +75 -0
  130. package/src/emit_deploy.ts +190 -190
  131. package/src/emit_events.ts +377 -307
  132. package/src/emit_extras.ts +240 -240
  133. package/src/emit_full.ts +351 -309
  134. package/src/emit_index.ts +161 -0
  135. package/src/emit_maintenance.ts +459 -459
  136. package/src/emit_models.ts +176 -0
  137. package/src/emit_openapi.ts +318 -0
  138. package/src/emit_package.ts +69 -0
  139. package/src/emit_router.ts +409 -0
  140. package/src/emit_runtime.ts +17 -728
  141. package/src/emit_sourcemap.ts +140 -140
  142. package/src/emit_tests.ts +246 -205
  143. package/src/emit_websocket.ts +229 -229
  144. package/src/emitter.ts +31 -5
  145. package/src/extension_manager.ts +189 -187
  146. package/src/formatter.ts +297 -297
  147. package/src/index.ts +88 -88
  148. package/src/ir.ts +215 -215
  149. package/src/lexer.ts +630 -630
  150. package/src/lowering.ts +142 -556
  151. package/src/lowering_channels.ts +107 -0
  152. package/src/lowering_entities.ts +248 -0
  153. package/src/lowering_helpers.ts +75 -0
  154. package/src/module_loader.ts +112 -114
  155. package/src/optimizer.ts +196 -196
  156. package/src/parse_decls.ts +409 -409
  157. package/src/parse_decls2.ts +244 -244
  158. package/src/parse_expr.ts +197 -197
  159. package/src/parse_types.ts +54 -54
  160. package/src/parser.ts +1 -1
  161. package/src/parser_base.ts +57 -57
  162. package/src/parser_recovery.ts +153 -153
  163. package/src/scaffold.ts +372 -375
  164. package/src/solver.ts +330 -330
  165. package/src/typechecker.ts +30 -15
  166. package/src/types.ts +122 -122
  167. package/src/verifier.ts +151 -4
@@ -1,229 +1,229 @@
1
- /**
2
- * BoneScript WebSocket Runtime Emitter
3
- * Generates runnable WebSocket servers for `channel` declarations.
4
- */
5
-
6
- import * as IR from "./ir";
7
-
8
- function toSnakeCase(s: string): string {
9
- return s.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
10
- }
11
-
12
- function toCamelCase(s: string): string {
13
- return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
14
- }
15
-
16
- // ─── WebSocket Server ────────────────────────────────────────────────────────
17
-
18
- export function emitWebSocketServer(system: IR.IRSystem): string {
19
- const channels = system.modules.filter(m => m.kind === "realtime_service");
20
- if (channels.length === 0) return "";
21
-
22
- const lines: string[] = [];
23
- lines.push(`// Generated by BoneScript compiler. DO NOT EDIT.`);
24
- lines.push(`// WebSocket server for realtime channels`);
25
- lines.push(``);
26
- lines.push(`import { WebSocketServer, WebSocket } from "ws";`);
27
- lines.push(`import { IncomingMessage, Server } from "http";`);
28
- lines.push(`import jwt from "jsonwebtoken";`);
29
- lines.push(`import { eventBus } from "./events";`);
30
- lines.push(`import { logger } from "./logger";`);
31
- lines.push(``);
32
- lines.push(`const JWT_SECRET = process.env.JWT_SECRET || "bonescript-dev-secret-change-in-production";`);
33
- lines.push(``);
34
- // Redis pub/sub for multi-instance support
35
- lines.push(`// Redis pub/sub for multi-instance WebSocket broadcasting`);
36
- lines.push(`let redisSub: any = null;`);
37
- lines.push(`let redisPub: any = null;`);
38
- lines.push(`if (process.env.REDIS_URL) {`);
39
- lines.push(` try {`);
40
- lines.push(` const { createClient } = require("redis");`);
41
- lines.push(` redisSub = createClient({ url: process.env.REDIS_URL });`);
42
- lines.push(` redisPub = createClient({ url: process.env.REDIS_URL });`);
43
- lines.push(` Promise.all([redisSub.connect(), redisPub.connect()]).then(() => {`);
44
- lines.push(` logger.info("redis_connected", { event: "startup" });`);
45
- lines.push(` }).catch((e: any) => {`);
46
- lines.push(` logger.error("redis_connect_failed", { event: "startup", metadata: { error: e.message } });`);
47
- lines.push(` redisSub = null; redisPub = null;`);
48
- lines.push(` });`);
49
- lines.push(` } catch { redisSub = null; redisPub = null; }`);
50
- lines.push(`}`);
51
-
52
- // Per-channel client registry
53
- lines.push(`interface Client {`);
54
- lines.push(` socket: WebSocket;`);
55
- lines.push(` user_id: string;`);
56
- lines.push(` channel: string;`);
57
- lines.push(` topics: Set<string>;`);
58
- lines.push(`}`);
59
- lines.push(``);
60
- lines.push(`const clients: Map<string, Set<Client>> = new Map();`);
61
- lines.push(``);
62
-
63
- // Channel configs
64
- lines.push(`const CHANNELS: Record<string, { ordering: string; persistence: string; max_size: number }> = {`);
65
- for (const ch of channels) {
66
- lines.push(` "${ch.name}": {`);
67
- lines.push(` ordering: "${ch.config["ordering"] || "fifo"}",`);
68
- lines.push(` persistence: "${ch.config["persistence"] || "none"}",`);
69
- lines.push(` max_size: ${ch.config["max_size"] || 10000},`);
70
- lines.push(` },`);
71
- }
72
- lines.push(`};`);
73
- lines.push(``);
74
-
75
- // Message buffer for persistence
76
- lines.push(`const messageBuffers: Map<string, any[]> = new Map();`);
77
- lines.push(``);
78
- lines.push(`function getBuffer(channel: string): any[] {`);
79
- lines.push(` const buf = messageBuffers.get(channel);`);
80
- lines.push(` if (buf) return buf;`);
81
- lines.push(` const fresh: any[] = [];`);
82
- lines.push(` messageBuffers.set(channel, fresh);`);
83
- lines.push(` return fresh;`);
84
- lines.push(`}`);
85
- lines.push(``);
86
- lines.push(`function persistMessage(channel: string, msg: any) {`);
87
- lines.push(` const cfg = CHANNELS[channel];`);
88
- lines.push(` if (!cfg || cfg.persistence === "none") return;`);
89
- lines.push(` const buf = getBuffer(channel);`);
90
- lines.push(` buf.push(msg);`);
91
- lines.push(``);
92
- lines.push(` // Honor persistence config (last_N, full)`);
93
- lines.push(` const match = cfg.persistence.match(/^last_(\\d+)$/);`);
94
- lines.push(` if (match) {`);
95
- lines.push(` const limit = parseInt(match[1], 10);`);
96
- lines.push(` while (buf.length > limit) buf.shift();`);
97
- lines.push(` } else if (buf.length > cfg.max_size) {`);
98
- lines.push(` buf.shift();`);
99
- lines.push(` }`);
100
- lines.push(`}`);
101
- lines.push(``);
102
-
103
- // Setup function
104
- lines.push(`export function setupWebSocketServer(httpServer: Server): WebSocketServer {`);
105
- lines.push(` const wss = new WebSocketServer({ server: httpServer, path: "/ws" });`);
106
- lines.push(``);
107
- lines.push(` wss.on("connection", (socket: WebSocket, req: IncomingMessage) => {`);
108
- lines.push(` const url = new URL(req.url || "/", \`http://\${req.headers.host}\`);`);
109
- lines.push(` const channel = url.searchParams.get("channel") || "";`);
110
- lines.push(` const token = url.searchParams.get("token") || "";`);
111
- lines.push(``);
112
- lines.push(` if (!CHANNELS[channel]) {`);
113
- lines.push(` socket.send(JSON.stringify({ type: "error", message: "Unknown channel: " + channel }));`);
114
- lines.push(` socket.close();`);
115
- lines.push(` return;`);
116
- lines.push(` }`);
117
- lines.push(``);
118
- lines.push(` let userId: string;`);
119
- lines.push(` try {`);
120
- lines.push(` const decoded = jwt.verify(token, JWT_SECRET) as { sub: string };`);
121
- lines.push(` userId = decoded.sub;`);
122
- lines.push(` } catch {`);
123
- lines.push(` socket.send(JSON.stringify({ type: "error", message: "Authentication failed" }));`);
124
- lines.push(` socket.close();`);
125
- lines.push(` return;`);
126
- lines.push(` }`);
127
- lines.push(``);
128
- lines.push(` const client: Client = { socket, user_id: userId, channel, topics: new Set() };`);
129
- lines.push(` const set = clients.get(channel) || new Set();`);
130
- lines.push(` set.add(client);`);
131
- lines.push(` clients.set(channel, set);`);
132
- lines.push(``);
133
- lines.push(` console.log(\`[ws] User \${userId} connected to \${channel} (\${set.size} active)\`);`);
134
- lines.push(``);
135
- lines.push(` // Send buffered history`);
136
- lines.push(` const history = messageBuffers.get(channel) || [];`);
137
- lines.push(` for (const msg of history) {`);
138
- lines.push(` socket.send(JSON.stringify(msg));`);
139
- lines.push(` }`);
140
- lines.push(``);
141
- lines.push(` socket.send(JSON.stringify({ type: "connected", channel, history_size: history.length }));`);
142
- lines.push(``);
143
- lines.push(` socket.on("message", (data) => {`);
144
- lines.push(` try {`);
145
- lines.push(` const msg = JSON.parse(data.toString());`);
146
- lines.push(``);
147
- lines.push(` if (msg.type === "subscribe" && msg.topic) {`);
148
- lines.push(` client.topics.add(msg.topic);`);
149
- lines.push(` return;`);
150
- lines.push(` }`);
151
- lines.push(` if (msg.type === "unsubscribe" && msg.topic) {`);
152
- lines.push(` client.topics.delete(msg.topic);`);
153
- lines.push(` return;`);
154
- lines.push(` }`);
155
- lines.push(``);
156
- lines.push(` // Broadcast`);
157
- lines.push(` const broadcast = {`);
158
- lines.push(` type: msg.type || "message",`);
159
- lines.push(` payload: msg.payload || msg,`);
160
- lines.push(` from: userId,`);
161
- lines.push(` timestamp: new Date().toISOString(),`);
162
- lines.push(` };`);
163
- lines.push(``);
164
- lines.push(` persistMessage(channel, broadcast);`);
165
- lines.push(` broadcastToChannel(channel, broadcast, client);`);
166
- lines.push(` } catch (e: any) {`);
167
- lines.push(` socket.send(JSON.stringify({ type: "error", message: e.message }));`);
168
- lines.push(` }`);
169
- lines.push(` });`);
170
- lines.push(``);
171
- lines.push(` socket.on("close", () => {`);
172
- lines.push(` const set = clients.get(channel);`);
173
- lines.push(` if (set) set.delete(client);`);
174
- lines.push(` console.log(\`[ws] User \${userId} disconnected from \${channel}\`);`);
175
- lines.push(` });`);
176
- lines.push(``);
177
- lines.push(` // Heartbeat`);
178
- lines.push(` const heartbeat = setInterval(() => {`);
179
- lines.push(` if (socket.readyState === WebSocket.OPEN) {`);
180
- lines.push(` socket.ping();`);
181
- lines.push(` } else {`);
182
- lines.push(` clearInterval(heartbeat);`);
183
- lines.push(` }`);
184
- lines.push(` }, 30000);`);
185
- lines.push(` });`);
186
- lines.push(``);
187
- lines.push(` // Bridge: forward eventBus events to WebSocket clients`);
188
- for (const ch of channels) {
189
- lines.push(` // Channel '${ch.name}' bridge`);
190
- }
191
- lines.push(``);
192
- // Redis subscription for cross-instance delivery
193
- lines.push(` // Subscribe to Redis channels for cross-instance delivery`);
194
- lines.push(` if (redisSub) {`);
195
- for (const ch of channels) {
196
- lines.push(` redisSub.subscribe("ws:${ch.name}", (message: string) => {`);
197
- lines.push(` const set = clients.get("${ch.name}");`);
198
- lines.push(` if (!set) return;`);
199
- lines.push(` for (const client of set) {`);
200
- lines.push(` if (client.socket.readyState === WebSocket.OPEN) client.socket.send(message);`);
201
- lines.push(` }`);
202
- lines.push(` }).catch(() => {});`);
203
- }
204
- lines.push(` }`);
205
- lines.push(` return wss;`);
206
- lines.push(`}`);
207
- lines.push(``);
208
- lines.push(`function broadcastToChannel(channel: string, message: any, exclude?: Client) {`);
209
- lines.push(` const set = clients.get(channel);`);
210
- lines.push(` const data = JSON.stringify(message);`);
211
- lines.push(` // Broadcast to local clients`);
212
- lines.push(` if (set) {`);
213
- lines.push(` for (const client of set) {`);
214
- lines.push(` if (client === exclude) continue;`);
215
- lines.push(` if (client.socket.readyState === WebSocket.OPEN) {`);
216
- lines.push(` client.socket.send(data);`);
217
- lines.push(` }`);
218
- lines.push(` }`);
219
- lines.push(` }`);
220
- lines.push(` // Publish to Redis for cross-instance delivery`);
221
- lines.push(` if (redisPub) {`);
222
- lines.push(` redisPub.publish(\`ws:\${channel}\`, data).catch(() => {});`);
223
- lines.push(` }`);
224
- lines.push(`}`);
225
- lines.push(``);
226
- lines.push(`export { broadcastToChannel };`);
227
-
228
- return lines.join("\n");
229
- }
1
+ /**
2
+ * BoneScript WebSocket Runtime Emitter
3
+ * Generates runnable WebSocket servers for `channel` declarations.
4
+ */
5
+
6
+ import * as IR from "./ir";
7
+
8
+ function toSnakeCase(s: string): string {
9
+ return s.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
10
+ }
11
+
12
+ function toCamelCase(s: string): string {
13
+ return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
14
+ }
15
+
16
+ // ─── WebSocket Server ────────────────────────────────────────────────────────
17
+
18
+ export function emitWebSocketServer(system: IR.IRSystem): string {
19
+ const channels = system.modules.filter(m => m.kind === "realtime_service");
20
+ if (channels.length === 0) return "";
21
+
22
+ const lines: string[] = [];
23
+ lines.push(`// Generated by BoneScript compiler. DO NOT EDIT.`);
24
+ lines.push(`// WebSocket server for realtime channels`);
25
+ lines.push(``);
26
+ lines.push(`import { WebSocketServer, WebSocket } from "ws";`);
27
+ lines.push(`import { IncomingMessage, Server } from "http";`);
28
+ lines.push(`import jwt from "jsonwebtoken";`);
29
+ lines.push(`import { eventBus } from "./events";`);
30
+ lines.push(`import { logger } from "./logger";`);
31
+ lines.push(``);
32
+ lines.push(`const JWT_SECRET = process.env.JWT_SECRET || "bonescript-dev-secret-change-in-production";`);
33
+ lines.push(``);
34
+ // Redis pub/sub for multi-instance support
35
+ lines.push(`// Redis pub/sub for multi-instance WebSocket broadcasting`);
36
+ lines.push(`let redisSub: any = null;`);
37
+ lines.push(`let redisPub: any = null;`);
38
+ lines.push(`if (process.env.REDIS_URL) {`);
39
+ lines.push(` try {`);
40
+ lines.push(` const { createClient } = require("redis");`);
41
+ lines.push(` redisSub = createClient({ url: process.env.REDIS_URL });`);
42
+ lines.push(` redisPub = createClient({ url: process.env.REDIS_URL });`);
43
+ lines.push(` Promise.all([redisSub.connect(), redisPub.connect()]).then(() => {`);
44
+ lines.push(` logger.info("redis_connected", { event: "startup" });`);
45
+ lines.push(` }).catch((e: any) => {`);
46
+ lines.push(` logger.error("redis_connect_failed", { event: "startup", metadata: { error: e.message } });`);
47
+ lines.push(` redisSub = null; redisPub = null;`);
48
+ lines.push(` });`);
49
+ lines.push(` } catch { redisSub = null; redisPub = null; }`);
50
+ lines.push(`}`);
51
+
52
+ // Per-channel client registry
53
+ lines.push(`interface Client {`);
54
+ lines.push(` socket: WebSocket;`);
55
+ lines.push(` user_id: string;`);
56
+ lines.push(` channel: string;`);
57
+ lines.push(` topics: Set<string>;`);
58
+ lines.push(`}`);
59
+ lines.push(``);
60
+ lines.push(`const clients: Map<string, Set<Client>> = new Map();`);
61
+ lines.push(``);
62
+
63
+ // Channel configs
64
+ lines.push(`const CHANNELS: Record<string, { ordering: string; persistence: string; max_size: number }> = {`);
65
+ for (const ch of channels) {
66
+ lines.push(` "${ch.name}": {`);
67
+ lines.push(` ordering: "${ch.config["ordering"] || "fifo"}",`);
68
+ lines.push(` persistence: "${ch.config["persistence"] || "none"}",`);
69
+ lines.push(` max_size: ${ch.config["max_size"] || 10000},`);
70
+ lines.push(` },`);
71
+ }
72
+ lines.push(`};`);
73
+ lines.push(``);
74
+
75
+ // Message buffer for persistence
76
+ lines.push(`const messageBuffers: Map<string, any[]> = new Map();`);
77
+ lines.push(``);
78
+ lines.push(`function getBuffer(channel: string): any[] {`);
79
+ lines.push(` const buf = messageBuffers.get(channel);`);
80
+ lines.push(` if (buf) return buf;`);
81
+ lines.push(` const fresh: any[] = [];`);
82
+ lines.push(` messageBuffers.set(channel, fresh);`);
83
+ lines.push(` return fresh;`);
84
+ lines.push(`}`);
85
+ lines.push(``);
86
+ lines.push(`function persistMessage(channel: string, msg: any) {`);
87
+ lines.push(` const cfg = CHANNELS[channel];`);
88
+ lines.push(` if (!cfg || cfg.persistence === "none") return;`);
89
+ lines.push(` const buf = getBuffer(channel);`);
90
+ lines.push(` buf.push(msg);`);
91
+ lines.push(``);
92
+ lines.push(` // Honor persistence config (last_N, full)`);
93
+ lines.push(` const match = cfg.persistence.match(/^last_(\\d+)$/);`);
94
+ lines.push(` if (match) {`);
95
+ lines.push(` const limit = parseInt(match[1], 10);`);
96
+ lines.push(` while (buf.length > limit) buf.shift();`);
97
+ lines.push(` } else if (buf.length > cfg.max_size) {`);
98
+ lines.push(` buf.shift();`);
99
+ lines.push(` }`);
100
+ lines.push(`}`);
101
+ lines.push(``);
102
+
103
+ // Setup function
104
+ lines.push(`export function setupWebSocketServer(httpServer: Server): WebSocketServer {`);
105
+ lines.push(` const wss = new WebSocketServer({ server: httpServer, path: "/ws" });`);
106
+ lines.push(``);
107
+ lines.push(` wss.on("connection", (socket: WebSocket, req: IncomingMessage) => {`);
108
+ lines.push(` const url = new URL(req.url || "/", \`http://\${req.headers.host}\`);`);
109
+ lines.push(` const channel = url.searchParams.get("channel") || "";`);
110
+ lines.push(` const token = url.searchParams.get("token") || "";`);
111
+ lines.push(``);
112
+ lines.push(` if (!CHANNELS[channel]) {`);
113
+ lines.push(` socket.send(JSON.stringify({ type: "error", message: "Unknown channel: " + channel }));`);
114
+ lines.push(` socket.close();`);
115
+ lines.push(` return;`);
116
+ lines.push(` }`);
117
+ lines.push(``);
118
+ lines.push(` let userId: string;`);
119
+ lines.push(` try {`);
120
+ lines.push(` const decoded = jwt.verify(token, JWT_SECRET) as { sub: string };`);
121
+ lines.push(` userId = decoded.sub;`);
122
+ lines.push(` } catch {`);
123
+ lines.push(` socket.send(JSON.stringify({ type: "error", message: "Authentication failed" }));`);
124
+ lines.push(` socket.close();`);
125
+ lines.push(` return;`);
126
+ lines.push(` }`);
127
+ lines.push(``);
128
+ lines.push(` const client: Client = { socket, user_id: userId, channel, topics: new Set() };`);
129
+ lines.push(` const set = clients.get(channel) || new Set();`);
130
+ lines.push(` set.add(client);`);
131
+ lines.push(` clients.set(channel, set);`);
132
+ lines.push(``);
133
+ lines.push(` console.log(\`[ws] User \${userId} connected to \${channel} (\${set.size} active)\`);`);
134
+ lines.push(``);
135
+ lines.push(` // Send buffered history`);
136
+ lines.push(` const history = messageBuffers.get(channel) || [];`);
137
+ lines.push(` for (const msg of history) {`);
138
+ lines.push(` socket.send(JSON.stringify(msg));`);
139
+ lines.push(` }`);
140
+ lines.push(``);
141
+ lines.push(` socket.send(JSON.stringify({ type: "connected", channel, history_size: history.length }));`);
142
+ lines.push(``);
143
+ lines.push(` socket.on("message", (data) => {`);
144
+ lines.push(` try {`);
145
+ lines.push(` const msg = JSON.parse(data.toString());`);
146
+ lines.push(``);
147
+ lines.push(` if (msg.type === "subscribe" && msg.topic) {`);
148
+ lines.push(` client.topics.add(msg.topic);`);
149
+ lines.push(` return;`);
150
+ lines.push(` }`);
151
+ lines.push(` if (msg.type === "unsubscribe" && msg.topic) {`);
152
+ lines.push(` client.topics.delete(msg.topic);`);
153
+ lines.push(` return;`);
154
+ lines.push(` }`);
155
+ lines.push(``);
156
+ lines.push(` // Broadcast`);
157
+ lines.push(` const broadcast = {`);
158
+ lines.push(` type: msg.type || "message",`);
159
+ lines.push(` payload: msg.payload || msg,`);
160
+ lines.push(` from: userId,`);
161
+ lines.push(` timestamp: new Date().toISOString(),`);
162
+ lines.push(` };`);
163
+ lines.push(``);
164
+ lines.push(` persistMessage(channel, broadcast);`);
165
+ lines.push(` broadcastToChannel(channel, broadcast, client);`);
166
+ lines.push(` } catch (e: any) {`);
167
+ lines.push(` socket.send(JSON.stringify({ type: "error", message: e.message }));`);
168
+ lines.push(` }`);
169
+ lines.push(` });`);
170
+ lines.push(``);
171
+ lines.push(` socket.on("close", () => {`);
172
+ lines.push(` const set = clients.get(channel);`);
173
+ lines.push(` if (set) set.delete(client);`);
174
+ lines.push(` console.log(\`[ws] User \${userId} disconnected from \${channel}\`);`);
175
+ lines.push(` });`);
176
+ lines.push(``);
177
+ lines.push(` // Heartbeat`);
178
+ lines.push(` const heartbeat = setInterval(() => {`);
179
+ lines.push(` if (socket.readyState === WebSocket.OPEN) {`);
180
+ lines.push(` socket.ping();`);
181
+ lines.push(` } else {`);
182
+ lines.push(` clearInterval(heartbeat);`);
183
+ lines.push(` }`);
184
+ lines.push(` }, 30000);`);
185
+ lines.push(` });`);
186
+ lines.push(``);
187
+ lines.push(` // Bridge: forward eventBus events to WebSocket clients`);
188
+ for (const ch of channels) {
189
+ lines.push(` // Channel '${ch.name}' bridge`);
190
+ }
191
+ lines.push(``);
192
+ // Redis subscription for cross-instance delivery
193
+ lines.push(` // Subscribe to Redis channels for cross-instance delivery`);
194
+ lines.push(` if (redisSub) {`);
195
+ for (const ch of channels) {
196
+ lines.push(` redisSub.subscribe("ws:${ch.name}", (message: string) => {`);
197
+ lines.push(` const set = clients.get("${ch.name}");`);
198
+ lines.push(` if (!set) return;`);
199
+ lines.push(` for (const client of set) {`);
200
+ lines.push(` if (client.socket.readyState === WebSocket.OPEN) client.socket.send(message);`);
201
+ lines.push(` }`);
202
+ lines.push(` }).catch(() => {});`);
203
+ }
204
+ lines.push(` }`);
205
+ lines.push(` return wss;`);
206
+ lines.push(`}`);
207
+ lines.push(``);
208
+ lines.push(`function broadcastToChannel(channel: string, message: any, exclude?: Client) {`);
209
+ lines.push(` const set = clients.get(channel);`);
210
+ lines.push(` const data = JSON.stringify(message);`);
211
+ lines.push(` // Broadcast to local clients`);
212
+ lines.push(` if (set) {`);
213
+ lines.push(` for (const client of set) {`);
214
+ lines.push(` if (client === exclude) continue;`);
215
+ lines.push(` if (client.socket.readyState === WebSocket.OPEN) {`);
216
+ lines.push(` client.socket.send(data);`);
217
+ lines.push(` }`);
218
+ lines.push(` }`);
219
+ lines.push(` }`);
220
+ lines.push(` // Publish to Redis for cross-instance delivery`);
221
+ lines.push(` if (redisPub) {`);
222
+ lines.push(` redisPub.publish(\`ws:\${channel}\`, data).catch(() => {});`);
223
+ lines.push(` }`);
224
+ lines.push(`}`);
225
+ lines.push(``);
226
+ lines.push(`export { broadcastToChannel };`);
227
+
228
+ return lines.join("\n");
229
+ }
package/src/emitter.ts CHANGED
@@ -62,6 +62,12 @@ function toSqlType(irType: string): string {
62
62
  return "JSONB";
63
63
  }
64
64
 
65
+ /** Returns an inline SQL CHECK constraint for types that need one, or empty string. */
66
+ function sqlCheckConstraint(irType: string): string {
67
+ if (irType === "uint") return " CHECK (VALUE >= 0)";
68
+ return "";
69
+ }
70
+
65
71
  function toSnakeCase(s: string): string {
66
72
  return s.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
67
73
  }
@@ -126,7 +132,7 @@ export class Emitter {
126
132
 
127
133
  const fieldLines: string[] = [];
128
134
  for (const field of model.fields) {
129
- let line = ` ${field.name} ${toSqlType(field.type)}`;
135
+ let line = ` ${field.name} ${toSqlType(field.type)}${sqlCheckConstraint(field.type)}`;
130
136
  if (!field.nullable) line += " NOT NULL";
131
137
  if (field.default_value) {
132
138
  if (field.default_value === "gen_random_uuid()") line += " DEFAULT gen_random_uuid()";
@@ -345,7 +351,7 @@ export class Emitter {
345
351
  // Implementation class
346
352
  lines.push(`export class ${mod.name} implements ${iface.name} {`);
347
353
  for (const method of iface.methods) {
348
- lines.push(this.emitMethod(method));
354
+ lines.push(this.emitMethod(method, mod, system));
349
355
  }
350
356
  lines.push(`}`);
351
357
  lines.push(``);
@@ -359,7 +365,7 @@ export class Emitter {
359
365
  };
360
366
  }
361
367
 
362
- private emitMethod(method: IR.IRMethod): string {
368
+ private emitMethod(method: IR.IRMethod, mod: IR.IRModule, system: IR.IRSystem): string {
363
369
  const lines: string[] = [];
364
370
  const params = method.input.map(f => `${f.name}: ${toTsType(f.type)}`).join(", ");
365
371
  const ctxParam = method.authenticated ? "ctx: RequestContext" : "";
@@ -404,8 +410,28 @@ export class Emitter {
404
410
  lines.push(``);
405
411
  }
406
412
 
407
- lines.push(` // TODO: Implementation`);
408
- lines.push(` throw new Error("Not implemented: ${method.name}");`);
413
+ // Real implementation — delegate to emitCapabilityBody for capabilities,
414
+ // or generate CRUD SQL for standard methods
415
+ const { emitCapabilityBody } = require("./emit_capability");
416
+ const { emitPipelineBody, emitAlgorithmBody } = require("./emit_composition");
417
+
418
+ if (method.pipeline) {
419
+ lines.push(emitPipelineBody(method, " "));
420
+ } else if (method.algorithm) {
421
+ lines.push(emitAlgorithmBody(method, " "));
422
+ } else if (method.effects.length > 0 || method.preconditions.length > 0) {
423
+ // Capability with effects/preconditions — use the full capability body emitter
424
+ try {
425
+ lines.push(emitCapabilityBody(method, mod, system, " "));
426
+ } catch {
427
+ // Fallback: emit a descriptive stub if body generation fails
428
+ lines.push(` // Effects: ${method.effects.map((e: any) => e.target + " " + e.op + " " + e.value).join("; ")}`);
429
+ lines.push(` return { ok: false, error: { code: "NOT_IMPLEMENTED", message: "${method.name} not yet implemented" } };`);
430
+ }
431
+ } else {
432
+ // CRUD or simple method — emit a typed not-implemented stub
433
+ lines.push(` return { ok: false, error: { code: "NOT_IMPLEMENTED", message: "${method.name} not yet implemented" } };`);
434
+ }
409
435
  lines.push(` }`);
410
436
  lines.push(``);
411
437