@kernlang/core 3.4.6 → 3.4.7-canary.100.1.95f0eb11

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 (45) hide show
  1. package/dist/codegen/body-ts.js +146 -3
  2. package/dist/codegen/body-ts.js.map +1 -1
  3. package/dist/codegen/functions.js +9 -1
  4. package/dist/codegen/functions.js.map +1 -1
  5. package/dist/codegen/react-hook-imports.d.ts +72 -0
  6. package/dist/codegen/react-hook-imports.js +162 -0
  7. package/dist/codegen/react-hook-imports.js.map +1 -0
  8. package/dist/codegen-expression.js +10 -3
  9. package/dist/codegen-expression.js.map +1 -1
  10. package/dist/importer.js +25 -0
  11. package/dist/importer.js.map +1 -1
  12. package/dist/index.d.ts +4 -2
  13. package/dist/index.js +3 -2
  14. package/dist/index.js.map +1 -1
  15. package/dist/native-eligibility-ast.d.ts +30 -3
  16. package/dist/native-eligibility-ast.js +338 -35
  17. package/dist/native-eligibility-ast.js.map +1 -1
  18. package/dist/native-eligibility.d.ts +7 -0
  19. package/dist/native-eligibility.js +81 -7
  20. package/dist/native-eligibility.js.map +1 -1
  21. package/dist/parser-core.js +194 -12
  22. package/dist/parser-core.js.map +1 -1
  23. package/dist/parser-diagnostics.js +2 -0
  24. package/dist/parser-diagnostics.js.map +1 -1
  25. package/dist/parser-expression.d.ts +2 -2
  26. package/dist/parser-expression.js +276 -11
  27. package/dist/parser-expression.js.map +1 -1
  28. package/dist/parser-keywords.js +381 -0
  29. package/dist/parser-keywords.js.map +1 -1
  30. package/dist/parser-validate-body-statements.js +16 -0
  31. package/dist/parser-validate-body-statements.js.map +1 -1
  32. package/dist/parser-validate-native-eligible.js +14 -5
  33. package/dist/parser-validate-native-eligible.js.map +1 -1
  34. package/dist/schema.js +120 -12
  35. package/dist/schema.js.map +1 -1
  36. package/dist/semantic-validator.js +16 -10
  37. package/dist/semantic-validator.js.map +1 -1
  38. package/dist/spec.d.ts +2 -2
  39. package/dist/spec.js +3 -1
  40. package/dist/spec.js.map +1 -1
  41. package/dist/types.d.ts +1 -1
  42. package/dist/value-ir.d.ts +7 -0
  43. package/dist/value-ir.js +1 -0
  44. package/dist/value-ir.js.map +1 -1
  45. package/package.json +1 -1
package/dist/schema.js CHANGED
@@ -30,6 +30,14 @@ export const NODE_SCHEMAS = {
30
30
  code: { kind: 'rawBlock' },
31
31
  },
32
32
  },
33
+ comment: {
34
+ description: 'Native body comment preserved during raw-handler migration. `raw=` may keep a host-style single-line comment; `text=` emits a target-native line comment.',
35
+ example: 'comment text="explain the early return"',
36
+ props: {
37
+ raw: { kind: 'string' },
38
+ text: { kind: 'string' },
39
+ },
40
+ },
33
41
  type: {
34
42
  description: 'TypeScript type alias — union of string literals, or alias to another type (including tuple types like [string, number]). Use generics="<T>" for parameterised aliases.',
35
43
  example: 'type name=Status values="active|inactive|banned"',
@@ -211,7 +219,16 @@ export const NODE_SCHEMAS = {
211
219
  // Validated by parser-validate-effects.ts; see docs/language/effects-pure-spec.md.
212
220
  effects: { kind: 'string' },
213
221
  },
214
- allowedChildren: ['handler', 'signal', 'cleanup', 'overload', 'param'],
222
+ allowedChildren: ['decorator', 'handler', 'signal', 'cleanup', 'overload', 'param'],
223
+ },
224
+ decorator: {
225
+ description: 'KERN decorator metadata attached to a declaration',
226
+ example: '@http.get("/users/:id")\nfn getUser(id: string): User',
227
+ props: {
228
+ name: { required: true, kind: 'string' },
229
+ args: { kind: 'string' },
230
+ },
231
+ allowedChildren: [],
215
232
  },
216
233
  machine: {
217
234
  description: 'State machine with states and guarded transitions — 12 lines of KERN generates 140+ lines of TypeScript',
@@ -234,6 +251,15 @@ export const NODE_SCHEMAS = {
234
251
  debounce: { kind: 'number' },
235
252
  },
236
253
  },
254
+ cell: {
255
+ description: 'Body-statement reactive state cell — KERN-canonical state primitive inside a `handler lang="kern"` body. Read the cell via its `name`; update via `set name=X to=...` or `assign target=X value=...`. Target lowering: TS+React → `const [X, setX] = useState<T>(initial);` (writes auto-rewrite to setter calls). Python+FastAPI → plain mutable `X = initial` (FastAPI per-request handlers don\'t need reactivity; each request resets). Must be a direct child of `handler lang="kern"` — placing it inside `if`/`for`/`while`/`try` violates React\'s Rules of Hooks.',
256
+ example: 'fn name=Counter\n handler lang="kern"\n cell name=count initial=0 type=number\n set name=count to="count + 1"',
257
+ props: {
258
+ name: { required: true, kind: 'identifier' },
259
+ initial: { kind: 'expression' },
260
+ type: { kind: 'typeAnnotation' },
261
+ },
262
+ },
237
263
  animation: {
238
264
  description: 'Interval-driven state update — generates useEffect with setInterval and auto-cleanup',
239
265
  example: 'animation name=frame interval=100 update="(prev) => (prev + 1) % 4"',
@@ -509,10 +535,12 @@ export const NODE_SCHEMAS = {
509
535
  'handler',
510
536
  'catch',
511
537
  'finally',
538
+ 'comment',
512
539
  'let',
513
540
  'assign',
514
541
  'destructure',
515
542
  'do',
543
+ 'fmt',
516
544
  'return',
517
545
  'if',
518
546
  'else',
@@ -543,10 +571,12 @@ export const NODE_SCHEMAS = {
543
571
  },
544
572
  allowedChildren: [
545
573
  'handler',
574
+ 'comment',
546
575
  'let',
547
576
  'assign',
548
577
  'destructure',
549
578
  'do',
579
+ 'fmt',
550
580
  'return',
551
581
  'if',
552
582
  'else',
@@ -565,9 +595,11 @@ export const NODE_SCHEMAS = {
565
595
  example: 'try\n let name=conn value="acquire()"\n do value="conn.use()"\nfinally\n do value="conn.release()"',
566
596
  props: {},
567
597
  allowedChildren: [
598
+ 'comment',
568
599
  'let',
569
600
  'assign',
570
601
  'do',
602
+ 'fmt',
571
603
  'return',
572
604
  'if',
573
605
  'else',
@@ -1159,14 +1191,14 @@ export const NODE_SCHEMAS = {
1159
1191
  },
1160
1192
  },
1161
1193
  each: {
1162
- description: 'Iteration — renders children for each item in a collection. Inside a render block emits `items.map(...)` with auto-key; elsewhere emits `for...of`. `let` children become iteration-scoped `const` bindings inside the callback (hook-safe, unlike `derive`). Three forms in body-statement position: (1) `each name=x in=xs` → `for (const x of xs)`; (2) `each name=x index=i in=xs` → `for (const [i, x] of xs.entries())`; (3) `each pairKey=k pairValue=v in=map` → `for (const [k, v] of map)` (TS) / `for k, v in map.items():` (Python). Add `type=` to preserve a TS item binding annotation in body-statement form (`each name=x type=User in=users` → `for (const x: User of users)`). Add `await=true` for async iterables (`for await` / `async for`); it cannot be combined with `index=` and is rejected inside render JSX. Async pair-mode expects an async iterable of pairs, not a mapping (`async for k, v in stream`). In pair-mode `name` is optional. `key=` (render-only) is the React render key, distinct from `pairKey=`.',
1194
+ description: 'Iteration — renders children for each item in a collection. Inside a render block emits `items.map(...)` with auto-key; elsewhere emits `for...of`. `let` children become iteration-scoped `const` bindings inside the callback (hook-safe, unlike `derive`). Body-statement forms: (1) `each name=x in=xs` → `for (const x of xs)`; (2) `each name=x index=i in=xs` → `for (const [i, x] of xs.entries())`; (3) `each pairKey=k pairValue=v in=map` → `for (const [k, v] of map)` (TS) / `for k, v in map.items():` (Python). Use `entries=true` with keyed-entry modes for object/dict entries: `pairKey`/`pairValue` lowers to `Object.entries(obj)` / `obj.items()`, `entryKey` lowers to `Object.entries(obj)` / `obj.keys()`, and `entryValue` lowers to `Object.entries(obj)` / `obj.values()`. Add `type=` to preserve a TS item binding annotation in body-statement form (`each name=x type=User in=users` → `for (const x: User of users)`). Add `await=true` for async iterables (`for await` / `async for`); it cannot be combined with `index=` and is rejected inside render JSX. Async pair-mode expects an async iterable of pairs, not a mapping (`async for k, v in stream`). In keyed-entry modes `name` is optional. `key=` (render-only) is the React render key, distinct from `pairKey=` / `entryKey=`.',
1163
1195
  example: 'each name=f in=files index=i key="f.path"\n let name=isSel expr="focused && i === selIdx"\n handler <<<\n <Text bold={isSel}>{f.path}</Text>\n >>>',
1164
1196
  props: {
1165
1197
  // `name` is required schema-side for the array-iteration forms; the
1166
- // pair-mode (`pairKey` + `pairValue`) form relaxes this via a
1198
+ // keyed-entry (`pairKey` + `pairValue`, `entryKey`, `entryValue`) forms relax this via a
1167
1199
  // conditional-required exemption inside `checkRequiredProps` in this
1168
- // same file. (The `each-pair-mode-body-stmt-only` semantic rule in
1169
- // semantic-validator.ts is unrelated — it just blocks pair-mode in
1200
+ // same file. (The `each-keyed-mode-body-stmt-only` semantic rule in
1201
+ // semantic-validator.ts is unrelated — it just blocks keyed modes in
1170
1202
  // render/group ancestor scope.)
1171
1203
  name: { required: true, kind: 'identifier' },
1172
1204
  in: { required: true, kind: 'rawExpr' },
@@ -1175,6 +1207,9 @@ export const NODE_SCHEMAS = {
1175
1207
  type: { kind: 'typeAnnotation' },
1176
1208
  pairKey: { kind: 'identifier' },
1177
1209
  pairValue: { kind: 'identifier' },
1210
+ entryKey: { kind: 'identifier' },
1211
+ entryValue: { kind: 'identifier' },
1212
+ entries: { kind: 'boolean' },
1178
1213
  await: { kind: 'boolean' },
1179
1214
  },
1180
1215
  // Intentionally unrestricted — statement-form `each` composes with `derive`,
@@ -1435,16 +1470,22 @@ export const NODE_SCHEMAS = {
1435
1470
  props: {
1436
1471
  code: { kind: 'rawBlock' },
1437
1472
  lang: { kind: 'string' },
1473
+ reason: { kind: 'string' },
1474
+ review: { kind: 'identifier' },
1438
1475
  },
1439
1476
  // Body-statement children apply when `lang="kern"`. Outside that opt-in,
1440
1477
  // body statements are rejected by validateBodyStatements (the schema list
1441
1478
  // is intentionally permissive so the validator can produce a clearer
1442
1479
  // context-aware error).
1443
1480
  allowedChildren: [
1481
+ 'cell',
1482
+ 'set',
1483
+ 'comment',
1444
1484
  'let',
1445
1485
  'assign',
1446
1486
  'destructure',
1447
1487
  'do',
1488
+ 'fmt',
1448
1489
  'return',
1449
1490
  'if',
1450
1491
  'else',
@@ -1514,10 +1555,12 @@ export const NODE_SCHEMAS = {
1514
1555
  cond: { required: true, kind: 'expression' },
1515
1556
  },
1516
1557
  allowedChildren: [
1558
+ 'comment',
1517
1559
  'let',
1518
1560
  'assign',
1519
1561
  'destructure',
1520
1562
  'do',
1563
+ 'fmt',
1521
1564
  'return',
1522
1565
  'if',
1523
1566
  'else',
@@ -1542,10 +1585,12 @@ export const NODE_SCHEMAS = {
1542
1585
  step: { kind: 'expression' },
1543
1586
  },
1544
1587
  allowedChildren: [
1588
+ 'comment',
1545
1589
  'let',
1546
1590
  'assign',
1547
1591
  'destructure',
1548
1592
  'do',
1593
+ 'fmt',
1549
1594
  'return',
1550
1595
  'if',
1551
1596
  'else',
@@ -2626,8 +2671,9 @@ function checkRequiredProps(node, schema, violations, parent) {
2626
2671
  if (node.type === 'import' && propName === 'from' && parent?.type === 'extern') {
2627
2672
  continue;
2628
2673
  }
2629
- // each-pair-mode (2026-05-06): `name` becomes optional when both
2630
- // `pairKey` and `pairValue` are present (Map / iterable-of-pairs form).
2674
+ // keyed-entry modes (2026-05-12): `name` becomes optional when either
2675
+ // pair-mode (`pairKey` + `pairValue`) or one-binding entry-mode
2676
+ // (`entryKey` / `entryValue`) is present.
2631
2677
  // The schema can't express conditional-required, so suppress the
2632
2678
  // `name`-required violation here under that exact shape. Other props
2633
2679
  // (notably `in=`) still error if missing.
@@ -2643,6 +2689,12 @@ function checkRequiredProps(node, schema, violations, parent) {
2643
2689
  props.pairValue.length > 0) {
2644
2690
  continue;
2645
2691
  }
2692
+ if (node.type === 'each' &&
2693
+ propName === 'name' &&
2694
+ ((typeof props.entryKey === 'string' && props.entryKey.length > 0) ||
2695
+ (typeof props.entryValue === 'string' && props.entryValue.length > 0))) {
2696
+ continue;
2697
+ }
2646
2698
  violations.push({
2647
2699
  nodeType: node.type,
2648
2700
  message: `'${node.type}' requires prop '${propName}'`,
@@ -2684,6 +2736,17 @@ function checkCrossProps(node, violations, parent) {
2684
2736
  });
2685
2737
  }
2686
2738
  }
2739
+ if (node.type === 'handler' && ('reason' in props || 'review' in props)) {
2740
+ const lang = typeof props.lang === 'string' ? props.lang.trim().toLowerCase() : '';
2741
+ if (lang === '' || lang === 'kern') {
2742
+ violations.push({
2743
+ nodeType: 'handler',
2744
+ message: '`handler reason=`/`review=` metadata requires an explicit non-kern `lang=` foreign boundary',
2745
+ line: node.loc?.line,
2746
+ col: node.loc?.col,
2747
+ });
2748
+ }
2749
+ }
2687
2750
  if (node.type === 'import' && parent?.type === 'extern') {
2688
2751
  for (const boundaryProp of ['package', 'registry', 'target']) {
2689
2752
  if (boundaryProp in props) {
@@ -3007,7 +3070,7 @@ function checkCrossProps(node, violations, parent) {
3007
3070
  }
3008
3071
  }
3009
3072
  if (node.type === 'each') {
3010
- // each-pair-mode (2026-05-06): the Map / iterable-of-pairs form uses
3073
+ // keyed-entry modes (2026-05-12): the Map / iterable-of-pairs form uses
3011
3074
  // `pairKey=` + `pairValue=` for `for (const [k, v] of m)`. Three rules:
3012
3075
  // 1. pairKey and pairValue come as a pair — neither alone is meaningful.
3013
3076
  // 2. pair-mode is incompatible with `index=` (entries-with-index form).
@@ -3024,9 +3087,14 @@ function checkCrossProps(node, violations, parent) {
3024
3087
  const isPairProp = (raw) => typeof raw === 'string' && raw.length > 0;
3025
3088
  const hasPairKey = isPairProp(props.pairKey);
3026
3089
  const hasPairValue = isPairProp(props.pairValue);
3090
+ const hasEntryKey = isPairProp(props.entryKey);
3091
+ const hasEntryValue = isPairProp(props.entryValue);
3092
+ const hasFullPair = hasPairKey && hasPairValue;
3093
+ const entryBindingCount = Number(hasFullPair) + Number(hasEntryKey) + Number(hasEntryValue);
3027
3094
  const hasIndex = isPairProp(props.index);
3028
3095
  const hasType = isPairProp(props.type);
3029
3096
  const hasAwait = props.await === true || props.await === 'true';
3097
+ const hasEntries = props.entries === true || props.entries === 'true';
3030
3098
  if (hasPairKey !== hasPairValue) {
3031
3099
  violations.push({
3032
3100
  nodeType: 'each',
@@ -3035,18 +3103,26 @@ function checkCrossProps(node, violations, parent) {
3035
3103
  col: node.loc?.col,
3036
3104
  });
3037
3105
  }
3038
- if (hasPairKey && hasPairValue && hasIndex) {
3106
+ if (entryBindingCount > 1) {
3039
3107
  violations.push({
3040
3108
  nodeType: 'each',
3041
- message: "'each' pair-mode ('pairKey'+'pairValue') is mutually exclusive with 'index='",
3109
+ message: "'each' keyed-entry modes are mutually exclusive: use pairKey+pairValue, entryKey, or entryValue",
3042
3110
  line: node.loc?.line,
3043
3111
  col: node.loc?.col,
3044
3112
  });
3045
3113
  }
3046
- if (hasPairKey && hasPairValue && hasType) {
3114
+ if ((hasFullPair || hasEntryKey || hasEntryValue) && hasIndex) {
3047
3115
  violations.push({
3048
3116
  nodeType: 'each',
3049
- message: "'each' pair-mode ('pairKey'+'pairValue') is mutually exclusive with 'type='",
3117
+ message: "'each' keyed-entry modes are mutually exclusive with 'index='",
3118
+ line: node.loc?.line,
3119
+ col: node.loc?.col,
3120
+ });
3121
+ }
3122
+ if ((hasFullPair || hasEntryKey || hasEntryValue) && hasType) {
3123
+ violations.push({
3124
+ nodeType: 'each',
3125
+ message: "'each' keyed-entry modes are mutually exclusive with 'type='",
3050
3126
  line: node.loc?.line,
3051
3127
  col: node.loc?.col,
3052
3128
  });
@@ -3059,6 +3135,38 @@ function checkCrossProps(node, violations, parent) {
3059
3135
  col: node.loc?.col,
3060
3136
  });
3061
3137
  }
3138
+ if (hasAwait && (hasEntryKey || hasEntryValue)) {
3139
+ violations.push({
3140
+ nodeType: 'each',
3141
+ message: "'each await=true' is mutually exclusive with one-binding entry modes ('entryKey='/'entryValue=')",
3142
+ line: node.loc?.line,
3143
+ col: node.loc?.col,
3144
+ });
3145
+ }
3146
+ if (hasEntries && !hasFullPair && !hasEntryKey && !hasEntryValue) {
3147
+ violations.push({
3148
+ nodeType: 'each',
3149
+ message: "'each entries=true' requires a keyed-entry mode ('pairKey'+'pairValue', 'entryKey', or 'entryValue')",
3150
+ line: node.loc?.line,
3151
+ col: node.loc?.col,
3152
+ });
3153
+ }
3154
+ if (!hasEntries && (hasEntryKey || hasEntryValue)) {
3155
+ violations.push({
3156
+ nodeType: 'each',
3157
+ message: "'each entryKey='/entryValue=' requires entries=true for object/dict entry semantics",
3158
+ line: node.loc?.line,
3159
+ col: node.loc?.col,
3160
+ });
3161
+ }
3162
+ if (hasEntries && hasAwait) {
3163
+ violations.push({
3164
+ nodeType: 'each',
3165
+ message: "'each entries=true' is mutually exclusive with 'await=true'",
3166
+ line: node.loc?.line,
3167
+ col: node.loc?.col,
3168
+ });
3169
+ }
3062
3170
  // Pair-mode relaxes `name` requirement — handled by checkRequiredProps
3063
3171
  // (suppresses the `name`-required violation when pairKey+pairValue are
3064
3172
  // both present).