@kumikijs/compiler 0.3.0 → 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.
package/dist/index.d.ts CHANGED
@@ -469,6 +469,12 @@ declare function parseCapabilityManifest(raw: unknown): ManifestResult;
469
469
  type CodegenOptions = {
470
470
  runtimeSpecifier: string; /** Emit the in-language `test` definitions (`__kumikiTests`). Off for production builds. */
471
471
  includeTests?: boolean;
472
+ /**
473
+ * Emit `export default App;` instead of auto-mounting to `#root`. Use when the
474
+ * module is imported (e.g. the Vite plugin / Web Component embedding) rather
475
+ * than run as a standalone page bundle.
476
+ */
477
+ exportApp?: boolean;
472
478
  };
473
479
  declare function codegen(program: Program, opts: CodegenOptions): string;
474
480
  /**
@@ -539,6 +545,14 @@ type ExtendedCodegenOptions = CodegenOptions & {
539
545
  declare function inlineRuntime(generatedJs: string, runtimeBundleJs: string): string;
540
546
  declare function compile(source: string, opts: ExtendedCodegenOptions): CompileResult;
541
547
  //#endregion
548
+ //#region src/dts.d.ts
549
+ /**
550
+ * Generate a TypeScript declaration for a Kumiki program's slot/effect/type
551
+ * surface. Returns module source (importing nothing) suitable for writing next
552
+ * to the `.kumiki` file and importing for typed provider authoring.
553
+ */
554
+ declare function generateDts(program: Program): string;
555
+ //#endregion
542
556
  //#region src/lexer.d.ts
543
557
  declare function lex(source: string): Token[];
544
558
  //#endregion
@@ -549,4 +563,4 @@ declare class ParseError extends Error {
549
563
  }
550
564
  declare function parse(tokens: Token[]): Program;
551
565
  //#endregion
552
- export { type AppDef, type BinOp, type CapabilityManifest, type CodegenOptions, type CompileFail, type CompileOk, type CompileResult, type Def, type EffectDef, type EventPattern, type Expr, type ExtendedCodegenOptions, FIELD_ACCESS_SHORTCUTS, type FnDef, KNOWN_MEMBERS, KNOWN_METHODS, type KumikiError, type Lvalue, type ManifestResult, type MatchArm, type MotionDef, ParseError, type Pattern, type PolicyExpr, type Pos, type Program, RUNTIME_HELPERS, type ReducerDef, type Refinement, type RetryExpr, STANDARD_CAPABILITIES, type SlotDef, type Statement, type TestDef, type ThemeDef, type ThemeValue, type TileArg, type TileDef, type TileExpr, type TileMatchArm, type TileProp, type Token, type TypeDef, type TypeExpr, type UiEventKind, check, codegen, compile, inlineRuntime, lex, parse, parseCapabilityManifest };
566
+ export { type AppDef, type BinOp, type CapabilityManifest, type CodegenOptions, type CompileFail, type CompileOk, type CompileResult, type Def, type EffectDef, type EventPattern, type Expr, type ExtendedCodegenOptions, FIELD_ACCESS_SHORTCUTS, type FnDef, KNOWN_MEMBERS, KNOWN_METHODS, type KumikiError, type Lvalue, type ManifestResult, type MatchArm, type MotionDef, ParseError, type Pattern, type PolicyExpr, type Pos, type Program, RUNTIME_HELPERS, type ReducerDef, type Refinement, type RetryExpr, STANDARD_CAPABILITIES, type SlotDef, type Statement, type TestDef, type ThemeDef, type ThemeValue, type TileArg, type TileDef, type TileExpr, type TileMatchArm, type TileProp, type Token, type TypeDef, type TypeExpr, type UiEventKind, check, codegen, compile, generateDts, inlineRuntime, lex, parse, parseCapabilityManifest };
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ function codegen(program, opts) {
26
26
  lines.push("");
27
27
  lines.push("const _s = _stdlib;");
28
28
  lines.push("");
29
+ lines.push("function createApp() {");
29
30
  for (const fn of fns) lines.push(genFn(fn, ctx));
30
31
  lines.push("const _effects = {");
31
32
  for (const eff of effects) lines.push(` ${JSON.stringify(eff.name)}: ${genEffect(eff, ctx)},`);
@@ -78,21 +79,29 @@ function codegen(program, opts) {
78
79
  lines.push(` themeName: ${themeRef},`);
79
80
  lines.push(" motions: _motions,");
80
81
  lines.push("};");
81
- lines.push("");
82
- lines.push("globalThis.__kumikiApp = App;");
83
82
  if (opts.includeTests && tests.length > 0) {
84
- lines.push("");
85
83
  lines.push("const _tilesById = {");
86
84
  for (const tile of tiles) lines.push(` ${JSON.stringify(tile.name)}: (${jsName("$1")}) => ${genTile(tile, ctx)},`);
87
85
  lines.push("};");
88
- lines.push("void _tilesById;");
86
+ lines.push("App._tilesById = _tilesById;");
87
+ }
88
+ lines.push(" return App;");
89
+ lines.push("}");
90
+ lines.push("");
91
+ lines.push("const App = createApp();");
92
+ lines.push("globalThis.__kumikiApp = App;");
93
+ if (opts.includeTests && tests.length > 0) {
94
+ lines.push("");
89
95
  lines.push("const __kumikiTests = [");
90
96
  for (const t of tests) lines.push(genTest(t, ctx));
91
97
  lines.push("];");
92
98
  lines.push("globalThis.__kumikiTests = __kumikiTests;");
93
99
  }
94
100
  lines.push("");
95
- lines.push(`mount(App, document.getElementById("root"));`);
101
+ if (opts.exportApp) {
102
+ lines.push("export default App;");
103
+ lines.push("export { createApp };");
104
+ } else lines.push(`mount(App, document.getElementById("root"), { providers: globalThis.__kumikiProviders });`);
96
105
  return lines.join("\n");
97
106
  }
98
107
  function recordField(e, name) {
@@ -119,14 +128,14 @@ function genTest(t, gen) {
119
128
  name: ${nameJs},
120
129
  kind: "reducer-test",
121
130
  run: () => {
122
- _s.resetLive(_live, _slots, ${slotsJs});
131
+ _s.resetLive(App.live, App.slots, ${slotsJs});
123
132
  const _el = ${elJs};
124
- const _r = _reducers.find((r) => r.name === ${JSON.stringify(t.target)});
133
+ const _r = App.reducers.find((r) => r.name === ${JSON.stringify(t.target)});
125
134
  if (!_r) throw new Error("reducer ${t.target} not found");
126
135
  let _res = null, _panic = null;
127
- try { _res = _r.apply(_live, { $el: _el, $event: _el }); }
136
+ try { _res = _r.apply(App.live, { $el: _el, $event: _el }); }
128
137
  catch (e) { _panic = (e && e.message) ? e.message : String(e); }
129
- return _s.runReducerTest({ name: ${nameJs}, givenSlots: { ..._live }, result: _res, panic: _panic, expect: ${expectJs} });
138
+ return _s.runReducerTest({ name: ${nameJs}, givenSlots: { ...App.live }, result: _res, panic: _panic, expect: ${expectJs} });
130
139
  },
131
140
  },`;
132
141
  }
@@ -139,8 +148,8 @@ function genTest(t, gen) {
139
148
  name: ${nameJs},
140
149
  kind: "tile-test",
141
150
  run: () => {
142
- _s.resetLive(_live, _slots, ${slotsJs});
143
- const _actual = _tilesById[${JSON.stringify(t.target)}](${inJs});
151
+ _s.resetLive(App.live, App.slots, ${slotsJs});
152
+ const _actual = App._tilesById[${JSON.stringify(t.target)}](${inJs});
144
153
  const _expected = ${expectedJs};
145
154
  return _s.runTileTest({ name: ${nameJs}, actual: _actual, expected: _expected });
146
155
  },
@@ -196,23 +205,29 @@ function genFn(fn, gen) {
196
205
  ]));
197
206
  return `function ${jsName(fn.name)}(${params}) { return ${jsOfExpr(fn.body, ctx)}; }`;
198
207
  }
208
+ /**
209
+ * The built-in implementation call for a standard capability, given the request
210
+ * variable name. Returns null for custom capabilities (no built-in — a host
211
+ * provider is required).
212
+ */
213
+ function builtinEffectCall(eff, reqVar) {
214
+ if (eff.cap === "storage.read") return `builtinEffects.storageRead(${eff.mapRequest ? `{ key: ${reqVar}.key }` : reqVar})`;
215
+ if (eff.cap === "storage.write") return `builtinEffects.storageWrite(${eff.mapRequest ? `{ key: ${reqVar}.key, value: ${reqVar}.value }` : reqVar})`;
216
+ if (eff.cap.startsWith("http.")) {
217
+ const method = eff.cap.slice(5).toUpperCase();
218
+ return `builtinEffects.httpFetch(${JSON.stringify(method)}, ${reqVar}, "")`;
219
+ }
220
+ return null;
221
+ }
199
222
  function genEffect(eff, gen) {
223
+ const capJs = JSON.stringify(eff.cap);
224
+ const reqVar = eff.mapRequest ? "req" : "input";
225
+ const tail = `const p = caps.provider(${capJs}); if (p) return p(${reqVar}, caps); return ${builtinEffectCall(eff, reqVar) ?? `{ kind: "err", value: { message: ${JSON.stringify(`Capability ${eff.cap} has no provider`)} } }`};`;
200
226
  let invokeBody;
201
- if (eff.cap === "storage.read") if (eff.mapRequest) {
227
+ if (eff.mapRequest) {
202
228
  const mapJs = jsOfExpr(eff.mapRequest, makeEvalCtx(gen, new Set(["$1"])));
203
- invokeBody = `async (${jsName("$1")}, _caps) => { const req = ${mapJs}; return builtinEffects.storageRead({ key: req.key }); }`;
204
- } else invokeBody = `async (input) => builtinEffects.storageRead(input)`;
205
- else if (eff.cap === "storage.write") if (eff.mapRequest) {
206
- const mapJs = jsOfExpr(eff.mapRequest, makeEvalCtx(gen, new Set(["$1"])));
207
- invokeBody = `async (${jsName("$1")}, _caps) => { const req = ${mapJs}; return builtinEffects.storageWrite({ key: req.key, value: req.value }); }`;
208
- } else invokeBody = `async (input) => builtinEffects.storageWrite(input)`;
209
- else if (eff.cap.startsWith("http.")) {
210
- const method = eff.cap.slice(5).toUpperCase();
211
- if (eff.mapRequest) {
212
- const mapJs = jsOfExpr(eff.mapRequest, makeEvalCtx(gen, new Set(["$1"])));
213
- invokeBody = `async (${jsName("$1")}, _caps) => { const req = ${mapJs}; return builtinEffects.httpFetch(${JSON.stringify(method)}, req, ""); }`;
214
- } else invokeBody = `async (input) => builtinEffects.httpFetch(${JSON.stringify(method)}, input, "")`;
215
- } else invokeBody = `async () => ({ kind: "err", value: { message: "Capability ${eff.cap} not implemented" } })`;
229
+ invokeBody = `async (${jsName("$1")}, caps) => { const req = ${mapJs}; ${tail} }`;
230
+ } else invokeBody = `async (input, caps) => { ${tail} }`;
216
231
  return `{
217
232
  name: ${JSON.stringify(eff.name)},
218
233
  cap: ${JSON.stringify(eff.cap)},
@@ -2783,6 +2798,7 @@ var Parser = class {
2783
2798
  }
2784
2799
  const tok0 = this.peek();
2785
2800
  if (tok0.kind === "ident") {
2801
+ if (parentTakesValueArg) return this.parseExpr();
2786
2802
  const name = tok0.value;
2787
2803
  const p1 = this.peek(1);
2788
2804
  const looksLikeTileCall = p1.kind === "op" && (p1.value === "(" || p1.value === "{");
@@ -4037,4 +4053,100 @@ function compile(source, opts) {
4037
4053
  };
4038
4054
  }
4039
4055
  //#endregion
4040
- export { FIELD_ACCESS_SHORTCUTS, KNOWN_MEMBERS, KNOWN_METHODS, ParseError, RUNTIME_HELPERS, STANDARD_CAPABILITIES, check, codegen, compile, inlineRuntime, lex, parse, parseCapabilityManifest };
4056
+ //#region src/dts.ts
4057
+ const PRIM_TS = {
4058
+ Int: "number",
4059
+ Float: "number",
4060
+ Time: "number",
4061
+ Text: "string",
4062
+ Bool: "boolean",
4063
+ Unit: "null",
4064
+ Bytes: "Uint8Array"
4065
+ };
4066
+ const KNOWN_SCALAR = {
4067
+ Url: "string",
4068
+ Email: "string",
4069
+ Uuid: "string",
4070
+ HttpStatus: "number",
4071
+ Duration: "number"
4072
+ };
4073
+ function tsOfType(t, ctx) {
4074
+ switch (t.kind) {
4075
+ case "TypePrim": return PRIM_TS[t.name] ?? "unknown";
4076
+ case "TypeRef":
4077
+ if (ctx.userTypes.has(t.name)) return t.name;
4078
+ return KNOWN_SCALAR[t.name] ?? "unknown";
4079
+ case "TypeApp": {
4080
+ const arg = (i) => t.args[i] ? tsOfType(t.args[i], ctx) : "unknown";
4081
+ switch (t.name) {
4082
+ case "List": return `${wrap(arg(0))}[]`;
4083
+ case "Map": return `Record<string, ${arg(1)}>`;
4084
+ case "Set": return "Record<string, true>";
4085
+ case "Option": return `{ _tag: "Some"; _0: ${arg(0)} } | { _tag: "None" }`;
4086
+ case "Result": return `{ _tag: "Ok"; _0: ${arg(0)} } | { _tag: "Err"; _0: ${arg(1)} }`;
4087
+ case "Tuple": return `[${t.args.map((a) => tsOfType(a, ctx)).join(", ")}]`;
4088
+ default: return "unknown";
4089
+ }
4090
+ }
4091
+ case "TypeRecord": return `{ ${t.fields.map((f) => `${f.name}: ${tsOfType(f.type, ctx)}`).join("; ")} }`;
4092
+ case "TypeUnion": return t.variants.map((v) => variantTs(v.name, v.payloads, ctx)).join(" | ");
4093
+ case "TypeNominal":
4094
+ case "TypeRefinement": return tsOfType(t.inner, ctx);
4095
+ default: return "unknown";
4096
+ }
4097
+ }
4098
+ /** One tagged variant: `{ _tag: "Name" }` or `{ _tag: "Name"; _0: P0; … }`. */
4099
+ function variantTs(name, payloads, ctx) {
4100
+ if (payloads.length === 0) return `{ _tag: ${JSON.stringify(name)} }`;
4101
+ const fields = payloads.map((p, i) => `_${i}: ${tsOfType(p, ctx)}`).join("; ");
4102
+ return `{ _tag: ${JSON.stringify(name)}; ${fields} }`;
4103
+ }
4104
+ /** Parenthesize a union so `T[]` binds correctly (`(A | null)[]`). */
4105
+ function wrap(ts) {
4106
+ return ts.includes("|") ? `(${ts})` : ts;
4107
+ }
4108
+ /**
4109
+ * Generate a TypeScript declaration for a Kumiki program's slot/effect/type
4110
+ * surface. Returns module source (importing nothing) suitable for writing next
4111
+ * to the `.kumiki` file and importing for typed provider authoring.
4112
+ */
4113
+ function generateDts(program) {
4114
+ const types = program.defs.filter((d) => d.kind === "TypeDef");
4115
+ const slots = program.defs.filter((d) => d.kind === "SlotDef");
4116
+ const effects = program.defs.filter((d) => d.kind === "EffectDef");
4117
+ const ctx = { userTypes: new Set(types.map((t) => t.name)) };
4118
+ const out = [];
4119
+ out.push("// Auto-generated by @kumikijs/compiler from the .kumiki source. Do not edit.");
4120
+ out.push("");
4121
+ out.push("/** A typed host implementation for one custom capability. */");
4122
+ out.push("export type Provider<In, Out> = (");
4123
+ out.push(" input: In,");
4124
+ out.push(" caps: { has(c: string): boolean; provider(c: string): unknown },");
4125
+ out.push(") =>");
4126
+ out.push(" | { kind: 'ok'; value: Out }");
4127
+ out.push(" | { kind: 'err'; value: unknown }");
4128
+ out.push(" | Promise<{ kind: 'ok'; value: Out } | { kind: 'err'; value: unknown }>;");
4129
+ out.push("");
4130
+ for (const t of types) {
4131
+ const params = t.params.length > 0 ? `<${t.params.join(", ")}>` : "";
4132
+ out.push(`export type ${t.name}${params} = ${tsOfType(t.body, ctx)};`);
4133
+ }
4134
+ if (types.length > 0) out.push("");
4135
+ out.push("export interface Slots {");
4136
+ for (const s of slots) out.push(` ${s.name}: ${tsOfType(s.type, ctx)};`);
4137
+ out.push("}");
4138
+ out.push("");
4139
+ const custom = effects.filter((e) => !STANDARD_CAPABILITIES.has(e.cap));
4140
+ out.push("/** Host implementations for this app's custom capabilities. */");
4141
+ out.push("export interface Providers {");
4142
+ for (const e of custom) {
4143
+ const input = tsOfType(e.inType, ctx);
4144
+ const output = tsOfType(e.outType, ctx);
4145
+ out.push(` ${JSON.stringify(e.cap)}?: Provider<${input}, ${output}>;`);
4146
+ }
4147
+ out.push("}");
4148
+ out.push("");
4149
+ return out.join("\n");
4150
+ }
4151
+ //#endregion
4152
+ export { FIELD_ACCESS_SHORTCUTS, KNOWN_MEMBERS, KNOWN_METHODS, ParseError, RUNTIME_HELPERS, STANDARD_CAPABILITIES, check, codegen, compile, generateDts, inlineRuntime, lex, parse, parseCapabilityManifest };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kumikijs/compiler",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "Kumiki compiler — lexer, parser, typechecker, and code generator.",
6
6
  "license": "Apache-2.0",
@@ -41,7 +41,7 @@
41
41
  "provenance": true
42
42
  },
43
43
  "dependencies": {
44
- "@kumikijs/runtime": "0.3.0"
44
+ "@kumikijs/runtime": "0.4.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/node": "^25.9.1",