bonescript-compiler 0.5.3 → 0.5.4

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 (194) 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 +543 -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_runtime.d.ts +11 -17
  22. package/dist/emit_runtime.js +688 -29
  23. package/dist/emit_runtime.js.map +1 -1
  24. package/dist/emit_sourcemap.js +66 -66
  25. package/dist/emit_tests.js +12 -47
  26. package/dist/emit_tests.js.map +1 -1
  27. package/dist/emit_websocket.js +3 -0
  28. package/dist/emit_websocket.js.map +1 -1
  29. package/dist/emitter.js +49 -94
  30. package/dist/emitter.js.map +1 -1
  31. package/dist/extension_manager.d.ts +2 -2
  32. package/dist/extension_manager.js +20 -9
  33. package/dist/extension_manager.js.map +1 -1
  34. package/dist/ir.d.ts +0 -4
  35. package/dist/lowering.d.ts +14 -5
  36. package/dist/lowering.js +417 -66
  37. package/dist/lowering.js.map +1 -1
  38. package/dist/module_loader.d.ts +2 -2
  39. package/dist/module_loader.js +23 -20
  40. package/dist/module_loader.js.map +1 -1
  41. package/dist/optimizer.js +3 -6
  42. package/dist/optimizer.js.map +1 -1
  43. package/dist/scaffold.d.ts +2 -2
  44. package/dist/scaffold.js +319 -315
  45. package/dist/scaffold.js.map +1 -1
  46. package/dist/solver.js +1 -1
  47. package/dist/solver.js.map +1 -1
  48. package/dist/source_map.js.map +1 -0
  49. package/dist/test.js.map +1 -0
  50. package/dist/test_typechecker.d.ts +5 -0
  51. package/dist/test_typechecker.js +126 -0
  52. package/dist/test_typechecker.js.map +1 -0
  53. package/dist/typechecker.d.ts +0 -7
  54. package/dist/typechecker.js +16 -103
  55. package/dist/typechecker.js.map +1 -1
  56. package/dist/verifier.d.ts +1 -5
  57. package/dist/verifier.js +38 -142
  58. package/dist/verifier.js.map +1 -1
  59. package/package.json +52 -62
  60. package/src/algorithm_catalog.ts +345 -345
  61. package/src/ast.d.ts +244 -0
  62. package/src/ast.ts +334 -334
  63. package/src/cli.ts +624 -98
  64. package/src/emit_batch.ts +140 -140
  65. package/src/emit_capability.ts +436 -613
  66. package/src/emit_composition.ts +196 -229
  67. package/src/emit_deploy.ts +190 -187
  68. package/src/emit_events.ts +307 -362
  69. package/src/emit_extras.ts +240 -237
  70. package/src/emit_full.ts +309 -472
  71. package/src/emit_maintenance.ts +459 -459
  72. package/src/emit_runtime.ts +730 -17
  73. package/src/emit_sourcemap.ts +140 -140
  74. package/src/emit_tests.ts +205 -243
  75. package/src/emit_websocket.ts +229 -226
  76. package/src/emitter.ts +578 -626
  77. package/src/extension_manager.ts +187 -177
  78. package/src/formatter.ts +297 -297
  79. package/src/index.ts +88 -88
  80. package/src/ir.ts +215 -216
  81. package/src/lexer.d.ts +195 -0
  82. package/src/lexer.ts +630 -630
  83. package/src/lowering.ts +556 -168
  84. package/src/module_loader.ts +114 -112
  85. package/src/optimizer.ts +196 -199
  86. package/src/parse_decls.d.ts +13 -0
  87. package/src/parse_decls.ts +409 -409
  88. package/src/parse_decls2.d.ts +13 -0
  89. package/src/parse_decls2.ts +244 -244
  90. package/src/parse_expr.d.ts +7 -0
  91. package/src/parse_expr.ts +197 -197
  92. package/src/parse_types.d.ts +6 -0
  93. package/src/parse_types.ts +54 -54
  94. package/src/parser.d.ts +10 -0
  95. package/src/parser.ts +1 -1
  96. package/src/parser_base.d.ts +19 -0
  97. package/src/parser_base.ts +57 -57
  98. package/src/parser_recovery.ts +153 -153
  99. package/src/scaffold.ts +375 -371
  100. package/src/solver.ts +330 -330
  101. package/src/typechecker.d.ts +52 -0
  102. package/src/typechecker.ts +591 -700
  103. package/src/types.d.ts +38 -0
  104. package/src/types.ts +122 -122
  105. package/src/verifier.ts +49 -154
  106. package/README.md +0 -382
  107. package/dist/commands/check.d.ts +0 -5
  108. package/dist/commands/check.js +0 -34
  109. package/dist/commands/check.js.map +0 -1
  110. package/dist/commands/compile.d.ts +0 -5
  111. package/dist/commands/compile.js +0 -215
  112. package/dist/commands/compile.js.map +0 -1
  113. package/dist/commands/debug.d.ts +0 -5
  114. package/dist/commands/debug.js +0 -59
  115. package/dist/commands/debug.js.map +0 -1
  116. package/dist/commands/diff.d.ts +0 -5
  117. package/dist/commands/diff.js +0 -123
  118. package/dist/commands/diff.js.map +0 -1
  119. package/dist/commands/fmt.d.ts +0 -5
  120. package/dist/commands/fmt.js +0 -49
  121. package/dist/commands/fmt.js.map +0 -1
  122. package/dist/commands/init.d.ts +0 -5
  123. package/dist/commands/init.js +0 -96
  124. package/dist/commands/init.js.map +0 -1
  125. package/dist/commands/ir.d.ts +0 -5
  126. package/dist/commands/ir.js +0 -27
  127. package/dist/commands/ir.js.map +0 -1
  128. package/dist/commands/lex.d.ts +0 -5
  129. package/dist/commands/lex.js +0 -21
  130. package/dist/commands/lex.js.map +0 -1
  131. package/dist/commands/parse.d.ts +0 -5
  132. package/dist/commands/parse.js +0 -30
  133. package/dist/commands/parse.js.map +0 -1
  134. package/dist/commands/test.d.ts +0 -5
  135. package/dist/commands/test.js +0 -61
  136. package/dist/commands/test.js.map +0 -1
  137. package/dist/commands/verify_determinism.d.ts +0 -5
  138. package/dist/commands/verify_determinism.js +0 -64
  139. package/dist/commands/verify_determinism.js.map +0 -1
  140. package/dist/commands/watch.d.ts +0 -5
  141. package/dist/commands/watch.js +0 -50
  142. package/dist/commands/watch.js.map +0 -1
  143. package/dist/emit_auth.d.ts +0 -18
  144. package/dist/emit_auth.js +0 -507
  145. package/dist/emit_auth.js.map +0 -1
  146. package/dist/emit_database.d.ts +0 -7
  147. package/dist/emit_database.js +0 -72
  148. package/dist/emit_database.js.map +0 -1
  149. package/dist/emit_index.d.ts +0 -6
  150. package/dist/emit_index.js +0 -202
  151. package/dist/emit_index.js.map +0 -1
  152. package/dist/emit_models.d.ts +0 -12
  153. package/dist/emit_models.js +0 -171
  154. package/dist/emit_models.js.map +0 -1
  155. package/dist/emit_openapi.d.ts +0 -9
  156. package/dist/emit_openapi.js +0 -306
  157. package/dist/emit_openapi.js.map +0 -1
  158. package/dist/emit_package.d.ts +0 -7
  159. package/dist/emit_package.js +0 -68
  160. package/dist/emit_package.js.map +0 -1
  161. package/dist/emit_router.d.ts +0 -12
  162. package/dist/emit_router.js +0 -389
  163. package/dist/emit_router.js.map +0 -1
  164. package/dist/lowering_channels.d.ts +0 -11
  165. package/dist/lowering_channels.js +0 -103
  166. package/dist/lowering_channels.js.map +0 -1
  167. package/dist/lowering_entities.d.ts +0 -11
  168. package/dist/lowering_entities.js +0 -232
  169. package/dist/lowering_entities.js.map +0 -1
  170. package/dist/lowering_helpers.d.ts +0 -13
  171. package/dist/lowering_helpers.js +0 -76
  172. package/dist/lowering_helpers.js.map +0 -1
  173. package/src/commands/check.ts +0 -33
  174. package/src/commands/compile.ts +0 -191
  175. package/src/commands/debug.ts +0 -33
  176. package/src/commands/diff.ts +0 -105
  177. package/src/commands/fmt.ts +0 -22
  178. package/src/commands/init.ts +0 -72
  179. package/src/commands/ir.ts +0 -23
  180. package/src/commands/lex.ts +0 -17
  181. package/src/commands/parse.ts +0 -24
  182. package/src/commands/test.ts +0 -36
  183. package/src/commands/verify_determinism.ts +0 -66
  184. package/src/commands/watch.ts +0 -25
  185. package/src/emit_auth.ts +0 -513
  186. package/src/emit_database.ts +0 -72
  187. package/src/emit_index.ts +0 -210
  188. package/src/emit_models.ts +0 -176
  189. package/src/emit_openapi.ts +0 -315
  190. package/src/emit_package.ts +0 -66
  191. package/src/emit_router.ts +0 -408
  192. package/src/lowering_channels.ts +0 -108
  193. package/src/lowering_entities.ts +0 -258
  194. package/src/lowering_helpers.ts +0 -75
package/src/emit_full.ts CHANGED
@@ -1,472 +1,309 @@
1
- /**
2
- * BoneScript Full Emitter — Produces a complete, runnable project.
3
- * Combines schema generation with runtime service code.
4
- */
5
-
6
- import * as IR from "./ir";
7
- import { Emitter, EmittedFile } from "./emitter";
8
- import {
9
- emitPackageJson,
10
- emitTsConfig,
11
- emitDbClient,
12
- emitAuthMiddleware,
13
- emitEntityRouter,
14
- emitStateMachineRuntime,
15
- emitIndex,
16
- emitMigration,
17
- } from "./emit_runtime";
18
- import { emitWebSocketServer } from "./emit_websocket";
19
- import {
20
- emitLogger,
21
- emitMetrics,
22
- emitHealthChecks,
23
- emitFailureRules,
24
- emitMigrationDiff,
25
- } from "./emit_maintenance";
26
- import { emitFlowRuntime, emitChannelFilters } from "./emit_extras";
27
- import { emitAlgorithmsFile, collectUsedAlgorithms } from "./emit_composition";
28
- import { emitExtensionPointStub } from "./extension_manager";
29
- import * as AST from "./ast";
30
- import { emitDurableEventBus, emitOutboxSchema, emitTypedEventPublishers } from "./emit_events";
31
- import { emitBatchExecutor } from "./emit_batch";
32
- import { emitSourceMapFile, emitDebugHandler } from "./emit_sourcemap";
33
- import { emitTestSuite } from "./emit_tests";
34
- import { emitDockerfile, emitDockerignore, emitK8sDeployment, emitGithubActions } from "./emit_deploy";
35
- import { emitModelFile, emitModelsIndex } from "./emit_models";
36
- import { emitOpenApiSchema } from "./emit_openapi";
37
- import { toSnakeCase } from "./lowering_helpers";
38
-
39
- /** Resolve the auth method for the system from the resolution map or module configs. */
40
- function resolveSystemAuthMethod(system: IR.IRSystem): "jwt" | "oauth2" | "apikey" {
41
- const direct = system.resolution["implied.auth_method"] || system.resolution["system.auth_method"];
42
- if (direct === "oauth2" || direct === "apikey" || direct === "jwt") return direct as "jwt" | "oauth2" | "apikey";
43
- for (const [key, val] of Object.entries(system.resolution)) {
44
- if (key.endsWith(".auth_method") && (val === "oauth2" || val === "apikey" || val === "jwt")) {
45
- return val as "jwt" | "oauth2" | "apikey";
46
- }
47
- }
48
- for (const mod of system.modules) {
49
- const m = mod.config["auth_method"] as string | undefined;
50
- if (m === "oauth2" || m === "apikey" || m === "jwt") return m;
51
- }
52
- return "jwt";
53
- }
54
-
55
- export class FullEmitter {
56
- private schemaEmitter = new Emitter();
57
-
58
- emit(system: IR.IRSystem): EmittedFile[] {
59
- const files: EmittedFile[] = [];
60
-
61
- // 1. Package files
62
- files.push({ path: "package.json", content: emitPackageJson(system), language: "json", source_module: "root" });
63
- files.push({ path: "tsconfig.json", content: emitTsConfig(), language: "json", source_module: "root" });
64
- files.push({ path: ".env.example", content: this.emitEnvExample(system), language: "yaml", source_module: "root" });
65
-
66
- // 2. Source: infrastructure
67
- files.push({ path: "src/db.ts", content: emitDbClient(system), language: "typescript", source_module: "infra" });
68
- // Durable event bus replaces the old in-process stub
69
- files.push({ path: "src/events.ts", content: emitDurableEventBus(system), language: "typescript", source_module: "infra" });
70
- // Outbox SQL schema
71
- files.push({ path: "migrations/event_outbox.sql", content: emitOutboxSchema(), language: "sql", source_module: "infra" });
72
-
73
- // API key table migration (only when auth_method = apikey)
74
- const authMethod = resolveSystemAuthMethod(system);
75
- if (authMethod === "apikey") {
76
- files.push({
77
- path: "migrations/api_keys.sql",
78
- content: [
79
- "-- Generated by BoneScript compiler. DO NOT EDIT.",
80
- "-- API key table for apikey auth strategy.",
81
- "",
82
- "CREATE TABLE IF NOT EXISTS api_keys (",
83
- " id UUID PRIMARY KEY DEFAULT gen_random_uuid(),",
84
- " actor_id UUID NOT NULL,",
85
- " key_hash VARCHAR(64) NOT NULL UNIQUE,",
86
- " key_prefix VARCHAR(16) NOT NULL,",
87
- " name VARCHAR(255) NOT NULL DEFAULT 'default',",
88
- " created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),",
89
- " expires_at TIMESTAMPTZ NOT NULL,",
90
- " revoked BOOLEAN NOT NULL DEFAULT false",
91
- ");",
92
- "CREATE INDEX IF NOT EXISTS idx_api_keys_actor ON api_keys (actor_id);",
93
- "CREATE INDEX IF NOT EXISTS idx_api_keys_hash ON api_keys (key_hash);",
94
- ].join("\n"),
95
- language: "sql",
96
- source_module: "infra",
97
- });
98
- }
99
- // Typed event publisher functions (one per declared event)
100
- if (system.events.length > 0) {
101
- files.push({ path: "src/publishers.ts", content: emitTypedEventPublishers(system), language: "typescript", source_module: "infra" });
102
- }
103
- files.push({ path: "src/auth.ts", content: emitAuthMiddleware(system), language: "typescript", source_module: "infra" });
104
- files.push({ path: "src/logger.ts", content: emitLogger(system), language: "typescript", source_module: "infra" });
105
- files.push({ path: "src/metrics.ts", content: emitMetrics(), language: "typescript", source_module: "infra" });
106
- files.push({ path: "src/health.ts", content: emitHealthChecks(system), language: "typescript", source_module: "infra" });
107
- files.push({ path: "src/failure_rules.ts", content: emitFailureRules(system), language: "typescript", source_module: "infra" });
108
-
109
- // 2a. WebSocket server (only if there are realtime channels)
110
- const wsContent = emitWebSocketServer(system);
111
- if (wsContent) {
112
- files.push({ path: "src/websocket.ts", content: wsContent, language: "typescript", source_module: "infra" });
113
- }
114
-
115
- // 2b. Flow saga runtime (only if there are flows)
116
- const flowContent = emitFlowRuntime(system);
117
- if (flowContent) {
118
- files.push({ path: "src/flows.ts", content: flowContent, language: "typescript", source_module: "infra" });
119
- }
120
-
121
- // 2b2. Batch executor (only if there are batch capabilities)
122
- const batchContent = emitBatchExecutor(system);
123
- if (batchContent) {
124
- files.push({ path: "src/batch.ts", content: batchContent, language: "typescript", source_module: "infra" });
125
- }
126
-
127
- // 2c. Migration diff utility (always emitted)
128
- files.push({ path: "src/migration_diff.ts", content: emitMigrationDiff(), language: "typescript", source_module: "infra" });
129
-
130
- // 2d. Algorithm implementations (only what's used)
131
- const usedAlgorithms = collectUsedAlgorithms(system);
132
- if (usedAlgorithms.size > 0) {
133
- const algoContent = emitAlgorithmsFile(usedAlgorithms);
134
- files.push({ path: "src/algorithms.ts", content: algoContent, language: "typescript", source_module: "algorithms" });
135
- } else {
136
- files.push({
137
- path: "src/algorithms.ts",
138
- content: "// No algorithms used in this system.\nexport {};\n",
139
- language: "typescript",
140
- source_module: "algorithms",
141
- });
142
- }
143
-
144
- // 2e. Extension points (escape hatches — preserved across recompilation)
145
- if (system.extension_points && system.extension_points.length > 0) {
146
- const extLines: string[] = [
147
- "// Generated by BoneScript compiler.",
148
- "// Extension points: implement the functions below.",
149
- "// Code between sentinel comments is preserved on recompile.",
150
- "// DO NOT remove the sentinel comments.",
151
- "",
152
- ];
153
- for (const ep of system.extension_points) {
154
- // Use the shared emitExtensionPointStub so the format is consistent
155
- // with what extension_manager.ts expects when merging on recompile.
156
- const stub = emitExtensionPointStub({
157
- kind: "ExtensionPointDecl",
158
- loc: { line: 0, column: 0, offset: 0 },
159
- name: ep.name,
160
- params: ep.params.map(p => ({
161
- kind: "Param" as const,
162
- loc: { line: 0, column: 0, offset: 0 },
163
- name: p.name,
164
- type: { kind: "PrimitiveType" as const, loc: { line: 0, column: 0, offset: 0 }, name: p.type },
165
- })),
166
- returns: ep.returns
167
- ? { kind: "PrimitiveType" as const, loc: { line: 0, column: 0, offset: 0 }, name: ep.returns }
168
- : null,
169
- stable: ep.stable,
170
- });
171
- extLines.push(stub);
172
- extLines.push("");
173
- }
174
- files.push({
175
- path: "src/extensions.ts",
176
- content: extLines.join("\n"),
177
- language: "typescript",
178
- source_module: "extensions",
179
- });
180
- }
181
-
182
- // 3. Source: state machines
183
- for (const mod of system.modules) {
184
- for (const sm of mod.state_machines) {
185
- files.push({
186
- path: `src/state_machines/${toSnakeCase(sm.entity)}.ts`,
187
- content: emitStateMachineRuntime(sm),
188
- language: "typescript",
189
- source_module: mod.id,
190
- });
191
- }
192
- }
193
-
194
- // 3a. Derived field helpers (one file per entity that has derived fields)
195
- for (const mod of system.modules) {
196
- for (const model of mod.models) {
197
- const derivedFields = model.fields.filter(f =>
198
- f.default_value && f.default_value.startsWith("GENERATED ALWAYS AS")
199
- );
200
- if (derivedFields.length === 0) continue;
201
- const lines: string[] = [
202
- `// Generated by BoneScript compiler. DO NOT EDIT.`,
203
- `// Derived field helpers for ${model.name}.`,
204
- `// These mirror the GENERATED ALWAYS AS columns in the SQL migration.`,
205
- `// Use them when you need the computed value in application code before a DB round-trip.`,
206
- ``,
207
- `export type ${model.name}Derived = {`,
208
- ];
209
- for (const f of derivedFields) {
210
- lines.push(` ${f.name}: unknown;`);
211
- }
212
- lines.push(`};`);
213
- lines.push(``);
214
- lines.push(`export const ${model.name.toUpperCase()}_DERIVED_FIELDS = [${derivedFields.map(f => `"${f.name}"`).join(", ")}] as const;`);
215
- lines.push(``);
216
- lines.push(`/** Returns true if the field name is a derived (computed) field on ${model.name}. */`);
217
- lines.push(`export function is${model.name}DerivedField(field: string): boolean {`);
218
- lines.push(` return (${model.name.toUpperCase()}_DERIVED_FIELDS as readonly string[]).includes(field);`);
219
- lines.push(`}`);
220
- files.push({
221
- path: `src/derived/${toSnakeCase(model.name)}.ts`,
222
- content: lines.join("\n"),
223
- language: "typescript",
224
- source_module: mod.id,
225
- });
226
- }
227
- }
228
-
229
- // 3b. Channel filter predicates (only if any channel has a filter expression)
230
- const realtimeMods = system.modules.filter(m => m.kind === "realtime_service" && m.config["filter"]);
231
- if (realtimeMods.length > 0) {
232
- const filterLines: string[] = [
233
- `// Generated by BoneScript compiler. DO NOT EDIT.`,
234
- `// Channel filter predicates — applied before delivering messages to participants.`,
235
- ``,
236
- `export const CHANNEL_FILTERS: Record<string, (event: any, participant: any) => boolean> = {`,
237
- ];
238
- for (const mod of realtimeMods) {
239
- const filterExpr = String(mod.config["filter"] || "true");
240
- // Translate bone field refs to JS: event.field and participant.field pass through,
241
- // bare identifiers are assumed to be event properties.
242
- const jsFilter = filterExpr
243
- .replace(/\band\b/g, "&&")
244
- .replace(/\bor\b/g, "||")
245
- .replace(/\bnot\b/g, "!")
246
- .replace(/\b==\b/g, "===")
247
- .replace(/\b!=\b/g, "!==")
248
- .replace(/\bcontains\b/g, "?.includes");
249
- filterLines.push(` "${mod.name}": (event, participant) => {`);
250
- filterLines.push(` try { return Boolean(${jsFilter}); } catch { return true; }`);
251
- filterLines.push(` },`);
252
- }
253
- filterLines.push(`};`);
254
- filterLines.push(``);
255
- filterLines.push(`export function shouldDeliver(channel: string, event: any, participant: any): boolean {`);
256
- filterLines.push(` const filter = CHANNEL_FILTERS[channel];`);
257
- filterLines.push(` return filter ? filter(event, participant) : true;`);
258
- filterLines.push(`}`);
259
- files.push({
260
- path: "src/channel_filters.ts",
261
- content: filterLines.join("\n"),
262
- language: "typescript",
263
- source_module: "infra",
264
- });
265
- }
266
-
267
- // 4. Source: route files (CRUD + capabilities)
268
- for (const mod of system.modules) {
269
- if (mod.kind === "api_service" && mod.models.length > 0) {
270
- const content = emitEntityRouter(mod, system);
271
- if (content) {
272
- files.push({
273
- path: `src/routes/${toSnakeCase(mod.models[0].name)}.ts`,
274
- content,
275
- language: "typescript",
276
- source_module: mod.id,
277
- });
278
- }
279
- }
280
- }
281
-
282
- // 5. Source: main entry point
283
- files.push({ path: "src/index.ts", content: emitIndex(system), language: "typescript", source_module: "root" });
284
-
285
- // 5a. Model interfaces, schemas, and validators (one file per model)
286
- const modelFiles: string[] = [];
287
- for (const mod of system.modules) {
288
- for (const model of mod.models) {
289
- const modelPath = `src/models/${toSnakeCase(model.name)}.ts`;
290
- files.push({
291
- path: modelPath,
292
- content: emitModelFile(model, mod, system),
293
- language: "typescript",
294
- source_module: mod.id,
295
- });
296
- modelFiles.push(modelPath);
297
- }
298
- }
299
- if (modelFiles.length > 0) {
300
- files.push({
301
- path: "src/models/index.ts",
302
- content: emitModelsIndex(system),
303
- language: "typescript",
304
- source_module: "shared",
305
- });
306
- }
307
-
308
- // 6. SQL migrations — run schema emitter ONCE, then match by model name
309
- // Includes both api_service entities AND data_store schemas.
310
- const schemas: string[] = [];
311
- const allSchemaFiles = this.schemaEmitter.emit(system);
312
- for (const mod of system.modules) {
313
- if (mod.kind === "data_store" || mod.kind === "api_service") {
314
- for (const model of mod.models) {
315
- const schemaFile = allSchemaFiles.find(f => f.path.includes(toSnakeCase(model.name)) && f.language === "sql");
316
- if (schemaFile) {
317
- files.push({ ...schemaFile, path: `migrations/${schemaFile.path.replace("schema/", "")}` });
318
- schemas.push(schemaFile.content);
319
- }
320
- }
321
- }
322
- }
323
-
324
- // 7. Migration runner
325
- files.push({ path: "src/migrate.ts", content: emitMigration(system, schemas), language: "typescript", source_module: "infra" });
326
-
327
- // 8. Docker compose for local dev
328
- files.push({ path: "docker-compose.yaml", content: this.emitDockerCompose(system), language: "yaml", source_module: "infra" });
329
-
330
- // 9. README
331
- files.push({ path: "README.md", content: this.emitReadme(system), language: "yaml", source_module: "root" });
332
-
333
- // 9a. OpenAPI schema (spec/09_CODEGEN.md §2 — ApiService secondary target)
334
- const openApiContent = emitOpenApiSchema(system);
335
- if (openApiContent) {
336
- files.push({ path: "openapi.json", content: openApiContent, language: "json", source_module: "root" });
337
- }
338
-
339
- // 10. Source map + debug handler
340
- files.push({ path: `${system.name}.bone.map`, content: emitSourceMapFile(system, `${system.name}.bone`), language: "json", source_module: "root" });
341
- files.push({ path: "src/debug.ts", content: emitDebugHandler(system), language: "typescript", source_module: "infra" });
342
- files.push({ path: "src/tests.ts", content: emitTestSuite(system), language: "typescript", source_module: "tests" });
343
-
344
- // 11. Deploy targets
345
- files.push({ path: "Dockerfile", content: emitDockerfile(system), language: "yaml", source_module: "deploy" });
346
- files.push({ path: ".dockerignore", content: emitDockerignore(), language: "yaml", source_module: "deploy" });
347
- files.push({ path: "k8s/deployment.yaml", content: emitK8sDeployment(system), language: "yaml", source_module: "deploy" });
348
- files.push({ path: ".github/workflows/ci.yaml", content: emitGithubActions(system), language: "yaml", source_module: "deploy" });
349
-
350
- return files;
351
- }
352
-
353
- private emitEnvExample(system: IR.IRSystem): string {
354
- return `# ${system.name} Environment Variables
355
- # Copy this file to .env and fill in real values. Never commit .env to source control.
356
-
357
- # --- Required in production ---
358
- # Generate with: node -e "console.log(require('crypto').randomBytes(48).toString('hex'))"
359
- JWT_SECRET=
360
-
361
- # --- Database ---
362
- DATABASE_URL=postgresql://postgres:postgres@localhost:5432/${toSnakeCase(system.name)}
363
-
364
- # --- Redis (optional, used by some domain templates) ---
365
- REDIS_URL=redis://localhost:6379
366
-
367
- # --- Server ---
368
- PORT=3000
369
- NODE_ENV=development
370
-
371
- # --- CORS ---
372
- # Comma-separated list of allowed origins. Leave empty to disallow all cross-origin requests.
373
- # Example: ALLOWED_ORIGINS=https://app.example.com,https://admin.example.com
374
- ALLOWED_ORIGINS=
375
-
376
- # --- Rate limiting ---
377
- # Global limit: requests per window per IP (default 300/min)
378
- RATE_LIMIT_MAX=300
379
- RATE_LIMIT_WINDOW_MS=60000
380
- # Auth endpoints (login/register) limit per IP (default 20/min — brute-force protection)
381
- AUTH_RATE_LIMIT_MAX=20
382
-
383
- # --- Event delivery mode ---
384
- # in_process: in-memory, fast, no durability guarantees (default for development)
385
- # durable: Postgres-backed transactional outbox (recommended for production)
386
- EVENT_MODE=in_process
387
- EVENT_WORKER_INTERVAL_MS=1000
388
-
389
- # --- Request timeout ---
390
- REQUEST_TIMEOUT_MS=30000
391
- `;
392
- }
393
-
394
- private emitDockerCompose(system: IR.IRSystem): string {
395
- return `# Generated by BoneScript compiler.
396
- version: "3.8"
397
-
398
- services:
399
- postgres:
400
- image: postgres:16-alpine
401
- environment:
402
- POSTGRES_DB: ${toSnakeCase(system.name)}
403
- POSTGRES_USER: postgres
404
- POSTGRES_PASSWORD: postgres
405
- ports:
406
- - "5432:5432"
407
- volumes:
408
- - pgdata:/var/lib/postgresql/data
409
-
410
- redis:
411
- image: redis:7-alpine
412
- ports:
413
- - "6379:6379"
414
-
415
- volumes:
416
- pgdata:
417
- `;
418
- }
419
-
420
- private emitReadme(system: IR.IRSystem): string {
421
- const apiModules = system.modules.filter(m => m.kind === "api_service");
422
- const routes = apiModules
423
- .filter(m => m.models.length > 0)
424
- .map(m => `- \`/${toSnakeCase(m.models[0].name)}s\` — ${m.name}`)
425
- .join("\n");
426
-
427
- return `# ${system.name}
428
-
429
- Generated by BoneScript compiler. Source hash: ${system.source_hash}
430
-
431
- ## Quick Start
432
-
433
- \`\`\`bash
434
- # Start dependencies
435
- docker compose up -d
436
-
437
- # Install
438
- npm install
439
-
440
- # Run migrations
441
- npm run migrate
442
-
443
- # Start server
444
- npm run dev
445
- \`\`\`
446
-
447
- ## API Routes
448
-
449
- ${routes}
450
-
451
- Each route supports:
452
- - \`GET /\` — List (paginated)
453
- - \`GET /:id\` — Read
454
- - \`POST /\` — Create
455
- - \`PUT /:id\` — Update
456
- - \`DELETE /:id\` — Delete
457
-
458
- Plus capability-specific endpoints.
459
-
460
- ## Auth
461
-
462
- Send a Bearer token in the Authorization header:
463
- \`\`\`
464
- Authorization: Bearer <jwt-token>
465
- \`\`\`
466
-
467
- ## Environment
468
-
469
- Copy \`.env.example\` to \`.env\` and configure.
470
- `;
471
- }
472
- }
1
+ /**
2
+ * BoneScript Full Emitter — Produces a complete, runnable project.
3
+ * Combines schema generation with runtime service code.
4
+ */
5
+
6
+ import * as IR from "./ir";
7
+ import { Emitter, EmittedFile } from "./emitter";
8
+ import {
9
+ emitPackageJson,
10
+ emitTsConfig,
11
+ emitDbClient,
12
+ emitAuthMiddleware,
13
+ emitEntityRouter,
14
+ emitStateMachineRuntime,
15
+ emitIndex,
16
+ emitMigration,
17
+ } from "./emit_runtime";
18
+ import { emitWebSocketServer } from "./emit_websocket";
19
+ import {
20
+ emitLogger,
21
+ emitMetrics,
22
+ emitHealthChecks,
23
+ emitFailureRules,
24
+ emitMigrationDiff,
25
+ } from "./emit_maintenance";
26
+ import { emitFlowRuntime } from "./emit_extras";
27
+ import { emitAlgorithmsFile, collectUsedAlgorithms } from "./emit_composition";
28
+ import { emitExtensionPointStub } from "./extension_manager";
29
+ import * as AST from "./ast";
30
+ import { emitDurableEventBus, emitOutboxSchema } from "./emit_events";
31
+ import { emitBatchExecutor } from "./emit_batch";
32
+ import { emitSourceMapFile, emitDebugHandler } from "./emit_sourcemap";
33
+ import { emitTestSuite } from "./emit_tests";
34
+ import { emitDockerfile, emitDockerignore, emitK8sDeployment, emitGithubActions } from "./emit_deploy";
35
+
36
+ function toSnakeCase(s: string): string {
37
+ return s.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
38
+ }
39
+
40
+ export class FullEmitter {
41
+ private schemaEmitter = new Emitter();
42
+
43
+ emit(system: IR.IRSystem): EmittedFile[] {
44
+ const files: EmittedFile[] = [];
45
+
46
+ // 1. Package files
47
+ files.push({ path: "package.json", content: emitPackageJson(system), language: "json", source_module: "root" });
48
+ files.push({ path: "tsconfig.json", content: emitTsConfig(), language: "json", source_module: "root" });
49
+ files.push({ path: ".env.example", content: this.emitEnvExample(system), language: "yaml", source_module: "root" });
50
+
51
+ // 2. Source: infrastructure
52
+ files.push({ path: "src/db.ts", content: emitDbClient(system), language: "typescript", source_module: "infra" });
53
+ // Durable event bus replaces the old in-process stub
54
+ files.push({ path: "src/events.ts", content: emitDurableEventBus(system), language: "typescript", source_module: "infra" });
55
+ // Outbox SQL schema
56
+ files.push({ path: "migrations/event_outbox.sql", content: emitOutboxSchema(), language: "sql", source_module: "infra" });
57
+ files.push({ path: "src/auth.ts", content: emitAuthMiddleware(system), language: "typescript", source_module: "infra" });
58
+ files.push({ path: "src/logger.ts", content: emitLogger(system), language: "typescript", source_module: "infra" });
59
+ files.push({ path: "src/metrics.ts", content: emitMetrics(), language: "typescript", source_module: "infra" });
60
+ files.push({ path: "src/health.ts", content: emitHealthChecks(system), language: "typescript", source_module: "infra" });
61
+ files.push({ path: "src/failure_rules.ts", content: emitFailureRules(system), language: "typescript", source_module: "infra" });
62
+
63
+ // 2a. WebSocket server (only if there are realtime channels)
64
+ const wsContent = emitWebSocketServer(system);
65
+ if (wsContent) {
66
+ files.push({ path: "src/websocket.ts", content: wsContent, language: "typescript", source_module: "infra" });
67
+ }
68
+
69
+ // 2b. Flow saga runtime (only if there are flows)
70
+ const flowContent = emitFlowRuntime(system);
71
+ if (flowContent) {
72
+ files.push({ path: "src/flows.ts", content: flowContent, language: "typescript", source_module: "infra" });
73
+ }
74
+
75
+ // 2b2. Batch executor (only if there are batch capabilities)
76
+ const batchContent = emitBatchExecutor(system);
77
+ if (batchContent) {
78
+ files.push({ path: "src/batch.ts", content: batchContent, language: "typescript", source_module: "infra" });
79
+ }
80
+
81
+ // 2c. Migration diff utility (always emitted)
82
+ files.push({ path: "src/migration_diff.ts", content: emitMigrationDiff(), language: "typescript", source_module: "infra" });
83
+
84
+ // 2d. Algorithm implementations (only what's used)
85
+ const usedAlgorithms = collectUsedAlgorithms(system);
86
+ if (usedAlgorithms.size > 0) {
87
+ const algoContent = emitAlgorithmsFile(usedAlgorithms);
88
+ files.push({ path: "src/algorithms.ts", content: algoContent, language: "typescript", source_module: "algorithms" });
89
+ } else {
90
+ files.push({
91
+ path: "src/algorithms.ts",
92
+ content: "// No algorithms used in this system.\nexport {};\n",
93
+ language: "typescript",
94
+ source_module: "algorithms",
95
+ });
96
+ }
97
+
98
+ // 2e. Extension points (escape hatches — preserved across recompilation)
99
+ if (system.extension_points && system.extension_points.length > 0) {
100
+ const extLines: string[] = [
101
+ "// Generated by BoneScript compiler.",
102
+ "// Extension points: implement the functions below.",
103
+ "// Code between sentinel comments is preserved on recompile.",
104
+ "// DO NOT remove the sentinel comments.",
105
+ "",
106
+ ];
107
+ for (const ep of system.extension_points) {
108
+ const params = ep.params.map((p: { name: string; type: string }) => `${p.name}: ${p.type}`).join(", ");
109
+ const returnType = ep.returns || "void";
110
+ extLines.push(`/**`);
111
+ extLines.push(` * Extension point: ${ep.name}`);
112
+ extLines.push(` * ${ep.stable ? "STABLE: implementation required." : "Optional."}`);
113
+ extLines.push(` */`);
114
+ extLines.push(`export function ${ep.name}(${params}): ${returnType} {`);
115
+ extLines.push(` // <bonescript:ext:${ep.name}:begin>`);
116
+ extLines.push(` throw new Error("Not implemented: ${ep.name}");`);
117
+ extLines.push(` // <bonescript:ext:${ep.name}:end>`);
118
+ extLines.push(`}`);
119
+ extLines.push("");
120
+ }
121
+ files.push({
122
+ path: "src/extensions.ts",
123
+ content: extLines.join("\n"),
124
+ language: "typescript",
125
+ source_module: "extensions",
126
+ });
127
+ }
128
+
129
+ // 3. Source: state machines
130
+ for (const mod of system.modules) {
131
+ for (const sm of mod.state_machines) {
132
+ files.push({
133
+ path: `src/state_machines/${toSnakeCase(sm.entity)}.ts`,
134
+ content: emitStateMachineRuntime(sm),
135
+ language: "typescript",
136
+ source_module: mod.id,
137
+ });
138
+ }
139
+ }
140
+
141
+ // 4. Source: route files (CRUD + capabilities)
142
+ for (const mod of system.modules) {
143
+ if (mod.kind === "api_service" && mod.models.length > 0) {
144
+ const content = emitEntityRouter(mod, system);
145
+ if (content) {
146
+ files.push({
147
+ path: `src/routes/${toSnakeCase(mod.models[0].name)}.ts`,
148
+ content,
149
+ language: "typescript",
150
+ source_module: mod.id,
151
+ });
152
+ }
153
+ }
154
+ }
155
+
156
+ // 5. Source: main entry point
157
+ files.push({ path: "src/index.ts", content: emitIndex(system), language: "typescript", source_module: "root" });
158
+
159
+ // 6. SQL migrations — run schema emitter ONCE, then match by model name
160
+ const schemas: string[] = [];
161
+ const allSchemaFiles = this.schemaEmitter.emit(system);
162
+ for (const mod of system.modules) {
163
+ if (mod.kind === "data_store" || mod.kind === "api_service") {
164
+ for (const model of mod.models) {
165
+ const schemaFile = allSchemaFiles.find(f => f.path.includes(toSnakeCase(model.name)) && f.language === "sql");
166
+ if (schemaFile) {
167
+ files.push({ ...schemaFile, path: `migrations/${schemaFile.path.replace("schema/", "")}` });
168
+ schemas.push(schemaFile.content);
169
+ }
170
+ }
171
+ }
172
+ }
173
+
174
+ // 7. Migration runner
175
+ files.push({ path: "src/migrate.ts", content: emitMigration(system, schemas), language: "typescript", source_module: "infra" });
176
+
177
+ // 8. Docker compose for local dev
178
+ files.push({ path: "docker-compose.yaml", content: this.emitDockerCompose(system), language: "yaml", source_module: "infra" });
179
+
180
+ // 9. README
181
+ files.push({ path: "README.md", content: this.emitReadme(system), language: "yaml", source_module: "root" });
182
+
183
+ // 10. Source map + debug handler
184
+ files.push({ path: `${system.name}.bone.map`, content: emitSourceMapFile(system, `${system.name}.bone`), language: "json", source_module: "root" });
185
+ files.push({ path: "src/debug.ts", content: emitDebugHandler(system), language: "typescript", source_module: "infra" });
186
+ files.push({ path: "src/tests.ts", content: emitTestSuite(system), language: "typescript", source_module: "tests" });
187
+
188
+ // 11. Deploy targets
189
+ files.push({ path: "Dockerfile", content: emitDockerfile(system), language: "yaml", source_module: "deploy" });
190
+ files.push({ path: ".dockerignore", content: emitDockerignore(), language: "yaml", source_module: "deploy" });
191
+ files.push({ path: "k8s/deployment.yaml", content: emitK8sDeployment(system), language: "yaml", source_module: "deploy" });
192
+ files.push({ path: ".github/workflows/ci.yaml", content: emitGithubActions(system), language: "yaml", source_module: "deploy" });
193
+
194
+ return files;
195
+ }
196
+
197
+ private emitEnvExample(system: IR.IRSystem): string {
198
+ return `# ${system.name} Environment Variables
199
+ # Copy this file to .env and fill in real values. Never commit .env to source control.
200
+
201
+ # --- Required in production ---
202
+ # Generate with: node -e "console.log(require('crypto').randomBytes(48).toString('hex'))"
203
+ JWT_SECRET=
204
+
205
+ # --- Database ---
206
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5432/${toSnakeCase(system.name)}
207
+
208
+ # --- Redis (optional, used by some domain templates) ---
209
+ REDIS_URL=redis://localhost:6379
210
+
211
+ # --- Server ---
212
+ PORT=3000
213
+ NODE_ENV=development
214
+
215
+ # --- CORS ---
216
+ # Comma-separated list of allowed origins. Leave empty to disallow all cross-origin requests.
217
+ # Example: ALLOWED_ORIGINS=https://app.example.com,https://admin.example.com
218
+ ALLOWED_ORIGINS=
219
+
220
+ # --- Event delivery mode ---
221
+ # in_process: in-memory, fast, no durability guarantees (default for development)
222
+ # durable: Postgres-backed transactional outbox (recommended for production)
223
+ EVENT_MODE=in_process
224
+ EVENT_WORKER_INTERVAL_MS=1000
225
+
226
+ # --- Request timeout ---
227
+ REQUEST_TIMEOUT_MS=30000
228
+ `;
229
+ }
230
+
231
+ private emitDockerCompose(system: IR.IRSystem): string {
232
+ return `# Generated by BoneScript compiler.
233
+ version: "3.8"
234
+
235
+ services:
236
+ postgres:
237
+ image: postgres:16-alpine
238
+ environment:
239
+ POSTGRES_DB: ${toSnakeCase(system.name)}
240
+ POSTGRES_USER: postgres
241
+ POSTGRES_PASSWORD: postgres
242
+ ports:
243
+ - "5432:5432"
244
+ volumes:
245
+ - pgdata:/var/lib/postgresql/data
246
+
247
+ redis:
248
+ image: redis:7-alpine
249
+ ports:
250
+ - "6379:6379"
251
+
252
+ volumes:
253
+ pgdata:
254
+ `;
255
+ }
256
+
257
+ private emitReadme(system: IR.IRSystem): string {
258
+ const apiModules = system.modules.filter(m => m.kind === "api_service");
259
+ const routes = apiModules
260
+ .filter(m => m.models.length > 0)
261
+ .map(m => `- \`/${toSnakeCase(m.models[0].name)}s\` — ${m.name}`)
262
+ .join("\n");
263
+
264
+ return `# ${system.name}
265
+
266
+ Generated by BoneScript compiler. Source hash: ${system.source_hash}
267
+
268
+ ## Quick Start
269
+
270
+ \`\`\`bash
271
+ # Start dependencies
272
+ docker compose up -d
273
+
274
+ # Install
275
+ npm install
276
+
277
+ # Run migrations
278
+ npm run migrate
279
+
280
+ # Start server
281
+ npm run dev
282
+ \`\`\`
283
+
284
+ ## API Routes
285
+
286
+ ${routes}
287
+
288
+ Each route supports:
289
+ - \`GET /\` — List (paginated)
290
+ - \`GET /:id\` — Read
291
+ - \`POST /\` — Create
292
+ - \`PUT /:id\` — Update
293
+ - \`DELETE /:id\` — Delete
294
+
295
+ Plus capability-specific endpoints.
296
+
297
+ ## Auth
298
+
299
+ Send a Bearer token in the Authorization header:
300
+ \`\`\`
301
+ Authorization: Bearer <jwt-token>
302
+ \`\`\`
303
+
304
+ ## Environment
305
+
306
+ Copy \`.env.example\` to \`.env\` and configure.
307
+ `;
308
+ }
309
+ }