bonescript-compiler 0.5.3 → 0.5.5

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 (201) hide show
  1. package/LICENSE +21 -21
  2. package/dist/algorithm_catalog.js +166 -166
  3. package/dist/cli.d.ts +1 -2
  4. package/dist/cli.js +617 -75
  5. package/dist/cli.js.map +1 -1
  6. package/dist/emit_capability.d.ts +0 -13
  7. package/dist/emit_capability.js +134 -296
  8. package/dist/emit_capability.js.map +1 -1
  9. package/dist/emit_composition.js +3 -37
  10. package/dist/emit_composition.js.map +1 -1
  11. package/dist/emit_deploy.js +167 -165
  12. package/dist/emit_deploy.js.map +1 -1
  13. package/dist/emit_events.d.ts +0 -1
  14. package/dist/emit_events.js +275 -325
  15. package/dist/emit_events.js.map +1 -1
  16. package/dist/emit_extras.js +5 -3
  17. package/dist/emit_extras.js.map +1 -1
  18. package/dist/emit_full.js +112 -272
  19. package/dist/emit_full.js.map +1 -1
  20. package/dist/emit_maintenance.js +249 -249
  21. package/dist/emit_nakama.d.ts +23 -0
  22. package/dist/emit_nakama.js +510 -0
  23. package/dist/emit_nakama.js.map +1 -0
  24. package/dist/emit_runtime.d.ts +11 -17
  25. package/dist/emit_runtime.js +688 -29
  26. package/dist/emit_runtime.js.map +1 -1
  27. package/dist/emit_sourcemap.js +66 -66
  28. package/dist/emit_tests.js +12 -47
  29. package/dist/emit_tests.js.map +1 -1
  30. package/dist/emit_websocket.js +3 -0
  31. package/dist/emit_websocket.js.map +1 -1
  32. package/dist/emitter.js +49 -94
  33. package/dist/emitter.js.map +1 -1
  34. package/dist/extension_manager.d.ts +2 -2
  35. package/dist/extension_manager.js +20 -9
  36. package/dist/extension_manager.js.map +1 -1
  37. package/dist/index.d.ts +2 -0
  38. package/dist/index.js +3 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/ir.d.ts +0 -4
  41. package/dist/lowering.d.ts +14 -5
  42. package/dist/lowering.js +417 -66
  43. package/dist/lowering.js.map +1 -1
  44. package/dist/module_loader.d.ts +2 -2
  45. package/dist/module_loader.js +23 -20
  46. package/dist/module_loader.js.map +1 -1
  47. package/dist/optimizer.js +3 -6
  48. package/dist/optimizer.js.map +1 -1
  49. package/dist/scaffold.d.ts +2 -2
  50. package/dist/scaffold.js +319 -315
  51. package/dist/scaffold.js.map +1 -1
  52. package/dist/solver.js +1 -1
  53. package/dist/solver.js.map +1 -1
  54. package/dist/source_map.js.map +1 -0
  55. package/dist/test.js.map +1 -0
  56. package/dist/test_typechecker.d.ts +5 -0
  57. package/dist/test_typechecker.js +126 -0
  58. package/dist/test_typechecker.js.map +1 -0
  59. package/dist/typechecker.d.ts +0 -7
  60. package/dist/typechecker.js +16 -103
  61. package/dist/typechecker.js.map +1 -1
  62. package/dist/verifier.d.ts +1 -5
  63. package/dist/verifier.js +38 -142
  64. package/dist/verifier.js.map +1 -1
  65. package/package.json +53 -62
  66. package/src/algorithm_catalog.ts +345 -345
  67. package/src/ast.d.ts +244 -0
  68. package/src/ast.ts +334 -334
  69. package/src/cli.ts +704 -98
  70. package/src/emit_batch.ts +140 -140
  71. package/src/emit_capability.ts +436 -613
  72. package/src/emit_composition.ts +196 -229
  73. package/src/emit_deploy.ts +190 -187
  74. package/src/emit_events.ts +307 -362
  75. package/src/emit_extras.ts +240 -237
  76. package/src/emit_full.ts +309 -472
  77. package/src/emit_maintenance.ts +459 -459
  78. package/src/emit_nakama.ts +576 -0
  79. package/src/emit_runtime.ts +730 -17
  80. package/src/emit_sourcemap.ts +140 -140
  81. package/src/emit_tests.ts +205 -243
  82. package/src/emit_websocket.ts +229 -226
  83. package/src/emitter.ts +578 -626
  84. package/src/extension_manager.ts +187 -177
  85. package/src/formatter.ts +297 -297
  86. package/src/index.ts +90 -88
  87. package/src/ir.ts +215 -216
  88. package/src/lexer.d.ts +195 -0
  89. package/src/lexer.ts +630 -630
  90. package/src/lowering.ts +556 -168
  91. package/src/module_loader.ts +114 -112
  92. package/src/optimizer.ts +196 -199
  93. package/src/parse_decls.d.ts +13 -0
  94. package/src/parse_decls.ts +409 -409
  95. package/src/parse_decls2.d.ts +13 -0
  96. package/src/parse_decls2.ts +244 -244
  97. package/src/parse_expr.d.ts +7 -0
  98. package/src/parse_expr.ts +197 -197
  99. package/src/parse_types.d.ts +6 -0
  100. package/src/parse_types.ts +54 -54
  101. package/src/parser.d.ts +10 -0
  102. package/src/parser.ts +1 -1
  103. package/src/parser_base.d.ts +19 -0
  104. package/src/parser_base.ts +57 -57
  105. package/src/parser_recovery.ts +153 -153
  106. package/src/scaffold.ts +375 -371
  107. package/src/solver.ts +330 -330
  108. package/src/typechecker.d.ts +52 -0
  109. package/src/typechecker.ts +591 -700
  110. package/src/types.d.ts +38 -0
  111. package/src/types.ts +122 -122
  112. package/src/verifier.ts +49 -154
  113. package/README.md +0 -382
  114. package/dist/commands/check.d.ts +0 -5
  115. package/dist/commands/check.js +0 -34
  116. package/dist/commands/check.js.map +0 -1
  117. package/dist/commands/compile.d.ts +0 -5
  118. package/dist/commands/compile.js +0 -215
  119. package/dist/commands/compile.js.map +0 -1
  120. package/dist/commands/debug.d.ts +0 -5
  121. package/dist/commands/debug.js +0 -59
  122. package/dist/commands/debug.js.map +0 -1
  123. package/dist/commands/diff.d.ts +0 -5
  124. package/dist/commands/diff.js +0 -123
  125. package/dist/commands/diff.js.map +0 -1
  126. package/dist/commands/fmt.d.ts +0 -5
  127. package/dist/commands/fmt.js +0 -49
  128. package/dist/commands/fmt.js.map +0 -1
  129. package/dist/commands/init.d.ts +0 -5
  130. package/dist/commands/init.js +0 -96
  131. package/dist/commands/init.js.map +0 -1
  132. package/dist/commands/ir.d.ts +0 -5
  133. package/dist/commands/ir.js +0 -27
  134. package/dist/commands/ir.js.map +0 -1
  135. package/dist/commands/lex.d.ts +0 -5
  136. package/dist/commands/lex.js +0 -21
  137. package/dist/commands/lex.js.map +0 -1
  138. package/dist/commands/parse.d.ts +0 -5
  139. package/dist/commands/parse.js +0 -30
  140. package/dist/commands/parse.js.map +0 -1
  141. package/dist/commands/test.d.ts +0 -5
  142. package/dist/commands/test.js +0 -61
  143. package/dist/commands/test.js.map +0 -1
  144. package/dist/commands/verify_determinism.d.ts +0 -5
  145. package/dist/commands/verify_determinism.js +0 -64
  146. package/dist/commands/verify_determinism.js.map +0 -1
  147. package/dist/commands/watch.d.ts +0 -5
  148. package/dist/commands/watch.js +0 -50
  149. package/dist/commands/watch.js.map +0 -1
  150. package/dist/emit_auth.d.ts +0 -18
  151. package/dist/emit_auth.js +0 -507
  152. package/dist/emit_auth.js.map +0 -1
  153. package/dist/emit_database.d.ts +0 -7
  154. package/dist/emit_database.js +0 -72
  155. package/dist/emit_database.js.map +0 -1
  156. package/dist/emit_index.d.ts +0 -6
  157. package/dist/emit_index.js +0 -202
  158. package/dist/emit_index.js.map +0 -1
  159. package/dist/emit_models.d.ts +0 -12
  160. package/dist/emit_models.js +0 -171
  161. package/dist/emit_models.js.map +0 -1
  162. package/dist/emit_openapi.d.ts +0 -9
  163. package/dist/emit_openapi.js +0 -306
  164. package/dist/emit_openapi.js.map +0 -1
  165. package/dist/emit_package.d.ts +0 -7
  166. package/dist/emit_package.js +0 -68
  167. package/dist/emit_package.js.map +0 -1
  168. package/dist/emit_router.d.ts +0 -12
  169. package/dist/emit_router.js +0 -389
  170. package/dist/emit_router.js.map +0 -1
  171. package/dist/lowering_channels.d.ts +0 -11
  172. package/dist/lowering_channels.js +0 -103
  173. package/dist/lowering_channels.js.map +0 -1
  174. package/dist/lowering_entities.d.ts +0 -11
  175. package/dist/lowering_entities.js +0 -232
  176. package/dist/lowering_entities.js.map +0 -1
  177. package/dist/lowering_helpers.d.ts +0 -13
  178. package/dist/lowering_helpers.js +0 -76
  179. package/dist/lowering_helpers.js.map +0 -1
  180. package/src/commands/check.ts +0 -33
  181. package/src/commands/compile.ts +0 -191
  182. package/src/commands/debug.ts +0 -33
  183. package/src/commands/diff.ts +0 -105
  184. package/src/commands/fmt.ts +0 -22
  185. package/src/commands/init.ts +0 -72
  186. package/src/commands/ir.ts +0 -23
  187. package/src/commands/lex.ts +0 -17
  188. package/src/commands/parse.ts +0 -24
  189. package/src/commands/test.ts +0 -36
  190. package/src/commands/verify_determinism.ts +0 -66
  191. package/src/commands/watch.ts +0 -25
  192. package/src/emit_auth.ts +0 -513
  193. package/src/emit_database.ts +0 -72
  194. package/src/emit_index.ts +0 -210
  195. package/src/emit_models.ts +0 -176
  196. package/src/emit_openapi.ts +0 -315
  197. package/src/emit_package.ts +0 -66
  198. package/src/emit_router.ts +0 -408
  199. package/src/lowering_channels.ts +0 -108
  200. package/src/lowering_entities.ts +0 -258
  201. package/src/lowering_helpers.ts +0 -75
@@ -11,41 +11,40 @@
11
11
  * exactly_once — deduplicated via event_id table
12
12
  */
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.emitTypedEventPublishers = exports.emitDurableEventBus = exports.emitOutboxSchema = void 0;
15
- const emit_router_1 = require("./emit_router");
14
+ exports.emitDurableEventBus = exports.emitOutboxSchema = void 0;
16
15
  // ─── Outbox SQL Schema ────────────────────────────────────────────────────────
17
16
  function emitOutboxSchema() {
18
- return `-- BoneScript: Transactional Outbox Schema
19
- -- Generated by BoneScript compiler. DO NOT EDIT.
20
-
21
- CREATE TABLE IF NOT EXISTS event_outbox (
22
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
23
- event_type VARCHAR NOT NULL,
24
- payload JSONB NOT NULL,
25
- source VARCHAR NOT NULL,
26
- correlation_id UUID,
27
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
28
- scheduled_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
29
- delivered_at TIMESTAMPTZ,
30
- attempts INT NOT NULL DEFAULT 0,
31
- last_error TEXT,
32
- status VARCHAR NOT NULL DEFAULT 'pending'
33
- CHECK (status IN ('pending', 'delivered', 'failed', 'dead_letter'))
34
- );
35
-
36
- CREATE INDEX IF NOT EXISTS idx_event_outbox_status ON event_outbox (status, scheduled_at)
37
- WHERE status = 'pending';
38
-
39
- CREATE INDEX IF NOT EXISTS idx_event_outbox_created ON event_outbox (created_at);
40
-
41
- -- Deduplication table for exactly_once delivery
42
- CREATE TABLE IF NOT EXISTS event_processed (
43
- event_id UUID PRIMARY KEY,
44
- event_type VARCHAR NOT NULL,
45
- processed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
46
- );
47
-
48
- CREATE INDEX IF NOT EXISTS idx_event_processed_type ON event_processed (event_type, processed_at);
17
+ return `-- BoneScript: Transactional Outbox Schema
18
+ -- Generated by BoneScript compiler. DO NOT EDIT.
19
+
20
+ CREATE TABLE IF NOT EXISTS event_outbox (
21
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
22
+ event_type VARCHAR NOT NULL,
23
+ payload JSONB NOT NULL,
24
+ source VARCHAR NOT NULL,
25
+ correlation_id UUID,
26
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
27
+ scheduled_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
28
+ delivered_at TIMESTAMPTZ,
29
+ attempts INT NOT NULL DEFAULT 0,
30
+ last_error TEXT,
31
+ status VARCHAR NOT NULL DEFAULT 'pending'
32
+ CHECK (status IN ('pending', 'delivered', 'failed', 'dead_letter'))
33
+ );
34
+
35
+ CREATE INDEX IF NOT EXISTS idx_event_outbox_status ON event_outbox (status, scheduled_at)
36
+ WHERE status = 'pending';
37
+
38
+ CREATE INDEX IF NOT EXISTS idx_event_outbox_created ON event_outbox (created_at);
39
+
40
+ -- Deduplication table for exactly_once delivery
41
+ CREATE TABLE IF NOT EXISTS event_processed (
42
+ event_id UUID PRIMARY KEY,
43
+ event_type VARCHAR NOT NULL,
44
+ processed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
45
+ );
46
+
47
+ CREATE INDEX IF NOT EXISTS idx_event_processed_type ON event_processed (event_type, processed_at);
49
48
  `;
50
49
  }
51
50
  exports.emitOutboxSchema = emitOutboxSchema;
@@ -57,299 +56,250 @@ function emitDurableEventBus(system) {
57
56
  const atLeastOnceEvents = system.events
58
57
  .filter(e => e.delivery === "at_least_once")
59
58
  .map(e => `"${e.name}"`);
60
- return `// Generated by BoneScript compiler. DO NOT EDIT.
61
- // Durable event bus with transactional outbox pattern.
62
- // Set EVENT_MODE=durable in .env to enable.
63
-
64
- import { Pool, PoolClient } from "pg";
65
- import { v4 as uuid } from "uuid";
66
- import { pool } from "./db";
67
- import { logger } from "./logger";
68
- import { counter } from "./metrics";
69
-
70
- export type EventDeliveryMode = "in_process" | "durable";
71
-
72
- const MODE: EventDeliveryMode =
73
- (process.env.EVENT_MODE as EventDeliveryMode) || "in_process";
74
-
75
- // Events requiring exactly_once delivery (deduplicated)
76
- const EXACTLY_ONCE_EVENTS = new Set([${exactlyOnceEvents.join(", ")}]);
77
-
78
- // Events requiring at_least_once delivery (retried until ack)
79
- const AT_LEAST_ONCE_EVENTS = new Set([${atLeastOnceEvents.join(", ")}]);
80
-
81
- export interface EventMetadata {
82
- source: string;
83
- timestamp: Date;
84
- correlation_id: string;
85
- causation_id: string;
86
- }
87
-
88
- export interface SystemEvent {
89
- type: string;
90
- payload: Record<string, unknown>;
91
- metadata: EventMetadata;
92
- }
93
-
94
- type Handler = (event: SystemEvent) => Promise<void>;
95
-
96
- // ─── In-Process Bus ──────────────────────────────────────────────────────────
97
-
98
- class InProcessBus {
99
- private handlers: Map<string, Handler[]> = new Map();
100
-
101
- subscribe(type: string, handler: Handler): void {
102
- const existing = this.handlers.get(type) || [];
103
- existing.push(handler);
104
- this.handlers.set(type, existing);
105
- }
106
-
107
- async publish(type: string, payload: Record<string, unknown>, source: string, correlationId?: string): Promise<void> {
108
- const event: SystemEvent = {
109
- type,
110
- payload,
111
- metadata: {
112
- source,
113
- timestamp: new Date(),
114
- correlation_id: correlationId || uuid(),
115
- causation_id: uuid(),
116
- },
117
- };
118
- counter("event.published", { type, mode: "in_process" });
119
- const handlers = this.handlers.get(type) || [];
120
- for (const handler of handlers) {
121
- try {
122
- await handler(event);
123
- counter("event.delivered", { type, mode: "in_process" });
124
- } catch (e: any) {
125
- counter("event.delivery_failed", { type, mode: "in_process" });
126
- logger.error("event_handler_failed", { event: type, metadata: { error: e.message } });
127
- }
128
- }
129
- }
130
- }
131
-
132
- // ─── Durable Bus (Transactional Outbox) ──────────────────────────────────────
133
-
134
- class DurableBus {
135
- private handlers: Map<string, Handler[]> = new Map();
136
-
137
- subscribe(type: string, handler: Handler): void {
138
- const existing = this.handlers.get(type) || [];
139
- existing.push(handler);
140
- this.handlers.set(type, existing);
141
- }
142
-
143
- // Write event to outbox within the current transaction (or a new one)
144
- async publish(
145
- type: string,
146
- payload: Record<string, unknown>,
147
- source: string,
148
- correlationId?: string,
149
- client?: PoolClient
150
- ): Promise<void> {
151
- const eventId = uuid();
152
- const corrId = correlationId || uuid();
153
- const sql = \`
154
- INSERT INTO event_outbox (id, event_type, payload, source, correlation_id)
155
- VALUES ($1, $2, $3, $4, $5)
156
- \`;
157
- const params = [eventId, type, JSON.stringify({ ...payload, _event_id: eventId }), source, corrId];
158
-
159
- if (client) {
160
- // Write within caller's transaction — atomicity guaranteed
161
- await client.query(sql, params);
162
- } else {
163
- await pool.query(sql, params);
164
- }
165
- counter("event.outboxed", { type });
166
- }
167
-
168
- // Called by the background worker
169
- async flush(): Promise<void> {
170
- const client = await pool.connect();
171
- try {
172
- await client.query("BEGIN");
173
-
174
- // Fetch pending events (lock rows to prevent concurrent processing)
175
- const { rows } = await client.query(\`
176
- SELECT id, event_type, payload, source, correlation_id, attempts
177
- FROM event_outbox
178
- WHERE status = 'pending' AND scheduled_at <= NOW()
179
- ORDER BY scheduled_at ASC
180
- LIMIT 50
181
- FOR UPDATE SKIP LOCKED
182
- \`);
183
-
184
- for (const row of rows) {
185
- try {
186
- // exactly_once: check deduplication table
187
- if (EXACTLY_ONCE_EVENTS.has(row.event_type)) {
188
- const { rows: dup } = await client.query(
189
- "SELECT 1 FROM event_processed WHERE event_id = $1",
190
- [row.payload._event_id || row.id]
191
- );
192
- if (dup.length > 0) {
193
- await client.query(
194
- "UPDATE event_outbox SET status = 'delivered', delivered_at = NOW() WHERE id = $1",
195
- [row.id]
196
- );
197
- continue;
198
- }
199
- }
200
-
201
- const event: SystemEvent = {
202
- type: row.event_type,
203
- payload: row.payload,
204
- metadata: {
205
- source: row.source,
206
- timestamp: new Date(),
207
- correlation_id: row.correlation_id,
208
- causation_id: uuid(),
209
- },
210
- };
211
-
212
- const handlers = this.handlers.get(row.event_type) || [];
213
- for (const handler of handlers) {
214
- await handler(event);
215
- }
216
-
217
- // Mark delivered
218
- await client.query(
219
- "UPDATE event_outbox SET status = 'delivered', delivered_at = NOW(), attempts = attempts + 1 WHERE id = $1",
220
- [row.id]
221
- );
222
-
223
- // Record for exactly_once deduplication
224
- if (EXACTLY_ONCE_EVENTS.has(row.event_type)) {
225
- await client.query(
226
- "INSERT INTO event_processed (event_id, event_type) VALUES ($1, $2) ON CONFLICT DO NOTHING",
227
- [row.payload._event_id || row.id, row.event_type]
228
- );
229
- }
230
-
231
- counter("event.delivered", { type: row.event_type, mode: "durable" });
232
- } catch (e: any) {
233
- const maxAttempts = AT_LEAST_ONCE_EVENTS.has(row.event_type) ? 10 : 3;
234
- const newAttempts = row.attempts + 1;
235
- const status = newAttempts >= maxAttempts ? "dead_letter" : "pending";
236
- const backoffMs = Math.min(1000 * Math.pow(2, newAttempts), 300000);
237
- await client.query(
238
- \`UPDATE event_outbox
239
- SET attempts = $1, last_error = $2, status = $3,
240
- scheduled_at = NOW() + ($4 || ' milliseconds')::interval
241
- WHERE id = $5\`,
242
- [newAttempts, e.message, status, backoffMs, row.id]
243
- );
244
- counter("event.delivery_failed", { type: row.event_type, mode: "durable" });
245
- logger.error("event_delivery_failed", { event: row.event_type, metadata: { error: e.message, attempts: newAttempts } });
246
- }
247
- }
248
-
249
- await client.query("COMMIT");
250
- } catch (e) {
251
- await client.query("ROLLBACK");
252
- throw e;
253
- } finally {
254
- client.release();
255
- }
256
- }
257
-
258
- // Start background worker
259
- startWorker(intervalMs: number = 1000): NodeJS.Timeout {
260
- logger.info("event_worker_started", { event: "startup", metadata: { interval_ms: intervalMs } });
261
- return setInterval(async () => {
262
- try {
263
- await this.flush();
264
- } catch (e: any) {
265
- logger.error("event_worker_error", { event: "flush_failed", metadata: { error: e.message } });
266
- }
267
- }, intervalMs);
268
- }
269
- }
270
-
271
- // ─── Unified Interface ────────────────────────────────────────────────────────
272
-
273
- const inProcess = new InProcessBus();
274
- const durable = new DurableBus();
275
-
276
- export const eventBus = {
277
- subscribe(type: string, handler: Handler): void {
278
- inProcess.subscribe(type, handler);
279
- durable.subscribe(type, handler);
280
- },
281
-
282
- async publish(
283
- type: string,
284
- payload: Record<string, unknown>,
285
- source: string,
286
- correlationId?: string,
287
- client?: PoolClient
288
- ): Promise<void> {
289
- if (MODE === "durable") {
290
- await durable.publish(type, payload, source, correlationId, client);
291
- } else {
292
- await inProcess.publish(type, payload, source, correlationId);
293
- }
294
- },
295
-
296
- startWorker(intervalMs?: number): NodeJS.Timeout | null {
297
- if (MODE === "durable") {
298
- return durable.startWorker(intervalMs);
299
- }
300
- return null;
301
- },
302
- };
59
+ return `// Generated by BoneScript compiler. DO NOT EDIT.
60
+ // Durable event bus with transactional outbox pattern.
61
+ // Set EVENT_MODE=durable in .env to enable.
62
+
63
+ import { Pool, PoolClient } from "pg";
64
+ import { v4 as uuid } from "uuid";
65
+ import { pool } from "./db";
66
+ import { logger } from "./logger";
67
+ import { counter } from "./metrics";
68
+
69
+ export type EventDeliveryMode = "in_process" | "durable";
70
+
71
+ const MODE: EventDeliveryMode =
72
+ (process.env.EVENT_MODE as EventDeliveryMode) || "in_process";
73
+
74
+ // Events requiring exactly_once delivery (deduplicated)
75
+ const EXACTLY_ONCE_EVENTS = new Set<string>([${exactlyOnceEvents.join(", ")}]);
76
+
77
+ // Events requiring at_least_once delivery (retried until ack)
78
+ const AT_LEAST_ONCE_EVENTS = new Set<string>([${atLeastOnceEvents.join(", ")}]);
79
+
80
+ export interface EventMetadata {
81
+ source: string;
82
+ timestamp: Date;
83
+ correlation_id: string;
84
+ causation_id: string;
85
+ }
86
+
87
+ export interface SystemEvent {
88
+ type: string;
89
+ payload: Record<string, unknown>;
90
+ metadata: EventMetadata;
91
+ }
92
+
93
+ type Handler = (event: SystemEvent) => Promise<void>;
94
+
95
+ // ─── In-Process Bus ──────────────────────────────────────────────────────────
96
+
97
+ class InProcessBus {
98
+ private handlers: Map<string, Handler[]> = new Map();
99
+
100
+ subscribe(type: string, handler: Handler): void {
101
+ const existing = this.handlers.get(type) || [];
102
+ existing.push(handler);
103
+ this.handlers.set(type, existing);
104
+ }
105
+
106
+ async publish(type: string, payload: Record<string, unknown>, source: string, correlationId?: string): Promise<void> {
107
+ const event: SystemEvent = {
108
+ type,
109
+ payload,
110
+ metadata: {
111
+ source,
112
+ timestamp: new Date(),
113
+ correlation_id: correlationId || uuid(),
114
+ causation_id: uuid(),
115
+ },
116
+ };
117
+ counter("event.published", { type, mode: "in_process" });
118
+ const handlers = this.handlers.get(type) || [];
119
+ for (const handler of handlers) {
120
+ try {
121
+ await handler(event);
122
+ counter("event.delivered", { type, mode: "in_process" });
123
+ } catch (e: any) {
124
+ counter("event.delivery_failed", { type, mode: "in_process" });
125
+ logger.error("event_handler_failed", { event: type, metadata: { error: e.message } });
126
+ }
127
+ }
128
+ }
129
+ }
130
+
131
+ // ─── Durable Bus (Transactional Outbox) ──────────────────────────────────────
132
+
133
+ class DurableBus {
134
+ private handlers: Map<string, Handler[]> = new Map();
135
+
136
+ subscribe(type: string, handler: Handler): void {
137
+ const existing = this.handlers.get(type) || [];
138
+ existing.push(handler);
139
+ this.handlers.set(type, existing);
140
+ }
141
+
142
+ // Write event to outbox within the current transaction (or a new one)
143
+ async publish(
144
+ type: string,
145
+ payload: Record<string, unknown>,
146
+ source: string,
147
+ correlationId?: string,
148
+ client?: PoolClient
149
+ ): Promise<void> {
150
+ const eventId = uuid();
151
+ const corrId = correlationId || uuid();
152
+ const sql = \`
153
+ INSERT INTO event_outbox (id, event_type, payload, source, correlation_id)
154
+ VALUES ($1, $2, $3, $4, $5)
155
+ \`;
156
+ const params = [eventId, type, JSON.stringify({ ...payload, _event_id: eventId }), source, corrId];
157
+
158
+ if (client) {
159
+ // Write within caller's transaction — atomicity guaranteed
160
+ await client.query(sql, params);
161
+ } else {
162
+ await pool.query(sql, params);
163
+ }
164
+ counter("event.outboxed", { type });
165
+ }
166
+
167
+ // Called by the background worker
168
+ async flush(): Promise<void> {
169
+ const client = await pool.connect();
170
+ try {
171
+ await client.query("BEGIN");
172
+
173
+ // Fetch pending events (lock rows to prevent concurrent processing)
174
+ const { rows } = await client.query(\`
175
+ SELECT id, event_type, payload, source, correlation_id, attempts
176
+ FROM event_outbox
177
+ WHERE status = 'pending' AND scheduled_at <= NOW()
178
+ ORDER BY scheduled_at ASC
179
+ LIMIT 50
180
+ FOR UPDATE SKIP LOCKED
181
+ \`);
182
+
183
+ for (const row of rows) {
184
+ try {
185
+ // exactly_once: check deduplication table
186
+ if (EXACTLY_ONCE_EVENTS.has(row.event_type)) {
187
+ const { rows: dup } = await client.query(
188
+ "SELECT 1 FROM event_processed WHERE event_id = $1",
189
+ [row.payload._event_id || row.id]
190
+ );
191
+ if (dup.length > 0) {
192
+ await client.query(
193
+ "UPDATE event_outbox SET status = 'delivered', delivered_at = NOW() WHERE id = $1",
194
+ [row.id]
195
+ );
196
+ continue;
197
+ }
198
+ }
199
+
200
+ const event: SystemEvent = {
201
+ type: row.event_type,
202
+ payload: row.payload,
203
+ metadata: {
204
+ source: row.source,
205
+ timestamp: new Date(),
206
+ correlation_id: row.correlation_id,
207
+ causation_id: uuid(),
208
+ },
209
+ };
210
+
211
+ const handlers = this.handlers.get(row.event_type) || [];
212
+ for (const handler of handlers) {
213
+ await handler(event);
214
+ }
215
+
216
+ // Mark delivered
217
+ await client.query(
218
+ "UPDATE event_outbox SET status = 'delivered', delivered_at = NOW(), attempts = attempts + 1 WHERE id = $1",
219
+ [row.id]
220
+ );
221
+
222
+ // Record for exactly_once deduplication
223
+ if (EXACTLY_ONCE_EVENTS.has(row.event_type)) {
224
+ await client.query(
225
+ "INSERT INTO event_processed (event_id, event_type) VALUES ($1, $2) ON CONFLICT DO NOTHING",
226
+ [row.payload._event_id || row.id, row.event_type]
227
+ );
228
+ }
229
+
230
+ counter("event.delivered", { type: row.event_type, mode: "durable" });
231
+ } catch (e: any) {
232
+ const maxAttempts = AT_LEAST_ONCE_EVENTS.has(row.event_type) ? 10 : 3;
233
+ const newAttempts = row.attempts + 1;
234
+ const status = newAttempts >= maxAttempts ? "dead_letter" : "pending";
235
+ const backoffMs = Math.min(1000 * Math.pow(2, newAttempts), 300000);
236
+ await client.query(
237
+ \`UPDATE event_outbox
238
+ SET attempts = $1, last_error = $2, status = $3,
239
+ scheduled_at = NOW() + ($4 || ' milliseconds')::interval
240
+ WHERE id = $5\`,
241
+ [newAttempts, e.message, status, backoffMs, row.id]
242
+ );
243
+ counter("event.delivery_failed", { type: row.event_type, mode: "durable" });
244
+ logger.error("event_delivery_failed", { event: row.event_type, metadata: { error: e.message, attempts: newAttempts } });
245
+ }
246
+ }
247
+
248
+ await client.query("COMMIT");
249
+ } catch (e) {
250
+ await client.query("ROLLBACK");
251
+ throw e;
252
+ } finally {
253
+ client.release();
254
+ }
255
+ }
256
+
257
+ // Start background worker
258
+ startWorker(intervalMs: number = 1000): NodeJS.Timeout {
259
+ logger.info("event_worker_started", { event: "startup", metadata: { interval_ms: intervalMs } });
260
+ return setInterval(async () => {
261
+ try {
262
+ await this.flush();
263
+ } catch (e: any) {
264
+ logger.error("event_worker_error", { event: "flush_failed", metadata: { error: e.message } });
265
+ }
266
+ }, intervalMs);
267
+ }
268
+ }
269
+
270
+ // ─── Unified Interface ────────────────────────────────────────────────────────
271
+
272
+ const inProcess = new InProcessBus();
273
+ const durable = new DurableBus();
274
+
275
+ export const eventBus = {
276
+ subscribe(type: string, handler: Handler): void {
277
+ inProcess.subscribe(type, handler);
278
+ durable.subscribe(type, handler);
279
+ },
280
+
281
+ async publish(
282
+ type: string,
283
+ payload: Record<string, unknown>,
284
+ source: string,
285
+ correlationId?: string,
286
+ client?: PoolClient
287
+ ): Promise<void> {
288
+ if (MODE === "durable") {
289
+ await durable.publish(type, payload, source, correlationId, client);
290
+ } else {
291
+ await inProcess.publish(type, payload, source, correlationId);
292
+ }
293
+ },
294
+
295
+ startWorker(intervalMs?: number): NodeJS.Timeout | null {
296
+ if (MODE === "durable") {
297
+ return durable.startWorker(intervalMs);
298
+ }
299
+ return null;
300
+ },
301
+ };
303
302
  `;
304
303
  }
305
304
  exports.emitDurableEventBus = emitDurableEventBus;
306
- // ─── Typed Event Publishers ───────────────────────────────────────────────────
307
- // Generates per-event emitXxx() typed publisher functions as specified in
308
- // spec/09_CODEGEN.md §5.4. These wrap eventBus.publish with a typed payload
309
- // interface so callers get compile-time safety instead of raw Record<string,unknown>.
310
- function emitTypedEventPublishers(system) {
311
- if (system.events.length === 0)
312
- return "";
313
- const lines = [];
314
- lines.push(`// Generated by BoneScript compiler. DO NOT EDIT.`);
315
- lines.push(`// Typed event publisher functions — one per declared event.`);
316
- lines.push(`// Import these instead of calling eventBus.publish() directly.`);
317
- lines.push(``);
318
- lines.push(`import { eventBus } from "./events";`);
319
- lines.push(`import type { PoolClient } from "pg";`);
320
- lines.push(``);
321
- for (const ev of system.events) {
322
- const ifaceName = `${ev.name}Payload`;
323
- // Payload interface
324
- lines.push(`export interface ${ifaceName} {`);
325
- for (const field of ev.payload) {
326
- const nullable = field.nullable ? " | null" : "";
327
- lines.push(` ${field.name}: ${(0, emit_router_1.toTsType)(field.type)}${nullable};`);
328
- }
329
- lines.push(`}`);
330
- lines.push(``);
331
- // Publisher function
332
- const sourceId = ev.source && ev.source !== "unknown" ? ev.source : system.name;
333
- lines.push(`/**`);
334
- lines.push(` * Publish a ${ev.name} event.`);
335
- lines.push(` * Delivery: ${ev.delivery}${ev.ttl_ms ? ` | TTL: ${ev.ttl_ms}ms` : ""}`);
336
- lines.push(` */`);
337
- lines.push(`export async function emit${ev.name}(`);
338
- lines.push(` payload: ${ifaceName},`);
339
- lines.push(` correlationId?: string,`);
340
- lines.push(` client?: PoolClient,`);
341
- lines.push(`): Promise<void> {`);
342
- lines.push(` await eventBus.publish(`);
343
- lines.push(` "${ev.name}",`);
344
- lines.push(` payload as unknown as Record<string, unknown>,`);
345
- lines.push(` "${sourceId}",`);
346
- lines.push(` correlationId,`);
347
- lines.push(` client,`);
348
- lines.push(` );`);
349
- lines.push(`}`);
350
- lines.push(``);
351
- }
352
- return lines.join("\n");
353
- }
354
- exports.emitTypedEventPublishers = emitTypedEventPublishers;
355
305
  //# sourceMappingURL=emit_events.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"emit_events.js","sourceRoot":"","sources":["../src/emit_events.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"emit_events.js","sourceRoot":"","sources":["../src/emit_events.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;;AAIH,iFAAiF;AAEjF,SAAgB,gBAAgB;IAC9B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BR,CAAC;AACF,CAAC;AAjCD,4CAiCC;AAED,iFAAiF;AAEjF,SAAgB,mBAAmB,CAAC,MAAmB;IACrD,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM;SACpC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,cAAc,CAAC;SAC1C,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;IAE3B,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM;SACpC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,eAAe,CAAC;SAC3C,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;IAE3B,OAAO;;;;;;;;;;;;;;;;+CAgBsC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC;;;gDAG3B,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgO3E,CAAC;AACF,CAAC;AA7PD,kDA6PC"}
@@ -8,7 +8,9 @@
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.emitFlowRuntime = exports.emitChannelFilters = exports.emitDerivedFields = void 0;
11
- const lowering_helpers_1 = require("./lowering_helpers");
11
+ function toSnakeCase(s) {
12
+ return s.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
13
+ }
12
14
  // ─── Derived Field Emission ──────────────────────────────────────────────────
13
15
  // Derived fields become PostgreSQL generated columns (when expression supports it)
14
16
  // or TypeScript getters in the model class.
@@ -196,7 +198,7 @@ function emitFlowRuntime(system) {
196
198
  lines.push(` name: "${step.name}",`);
197
199
  lines.push(` action: async (ctx) => {`);
198
200
  lines.push(` // Calls capability: ${step.action}`);
199
- lines.push(` const response = await fetch(\`\${process.env.SERVICE_BASE_URL || "http://localhost:3000"}/${(0, lowering_helpers_1.toSnakeCase)(actionFn).replace(/_/g, "-")}\`, {`);
201
+ lines.push(` const response = await fetch(\`\${process.env.SERVICE_BASE_URL || "http://localhost:3000"}/${toSnakeCase(actionFn).replace(/_/g, "-")}\`, {`);
200
202
  lines.push(` method: "POST",`);
201
203
  lines.push(` headers: { "Content-Type": "application/json", "Authorization": ctx.req.headers.authorization || "" },`);
202
204
  lines.push(` body: JSON.stringify(ctx.req.body),`);
@@ -209,7 +211,7 @@ function emitFlowRuntime(system) {
209
211
  const compFn = compMatch ? compMatch[1] : step.compensation;
210
212
  lines.push(` compensation: async (ctx) => {`);
211
213
  lines.push(` // Compensates: ${step.compensation}`);
212
- lines.push(` await fetch(\`\${process.env.SERVICE_BASE_URL || "http://localhost:3000"}/${(0, lowering_helpers_1.toSnakeCase)(compFn).replace(/_/g, "-")}\`, {`);
214
+ lines.push(` await fetch(\`\${process.env.SERVICE_BASE_URL || "http://localhost:3000"}/${toSnakeCase(compFn).replace(/_/g, "-")}\`, {`);
213
215
  lines.push(` method: "POST",`);
214
216
  lines.push(` headers: { "Content-Type": "application/json", "Authorization": ctx.req.headers.authorization || "" },`);
215
217
  lines.push(` body: JSON.stringify({ ...ctx.req.body, _compensating: true }),`);