@kernlang/core 3.3.4 → 3.3.6
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/codegen/events.js +48 -5
- package/dist/codegen/events.js.map +1 -1
- package/dist/codegen/ground-layer.d.ts +44 -0
- package/dist/codegen/ground-layer.js +971 -0
- package/dist/codegen/ground-layer.js.map +1 -1
- package/dist/codegen/machines.js +35 -3
- package/dist/codegen/machines.js.map +1 -1
- package/dist/codegen/screens.js +285 -1
- package/dist/codegen/screens.js.map +1 -1
- package/dist/codegen-core.d.ts +1 -1
- package/dist/codegen-core.js +165 -5
- package/dist/codegen-core.js.map +1 -1
- package/dist/concepts.d.ts +69 -1
- package/dist/concepts.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/decompiler.js +56 -0
- package/dist/decompiler.js.map +1 -1
- package/dist/importer.js +35 -11
- package/dist/importer.js.map +1 -1
- package/dist/node-props.d.ts +245 -0
- package/dist/node-props.js.map +1 -1
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/schema.js +569 -12
- package/dist/schema.js.map +1 -1
- package/dist/semantic-validator.d.ts +6 -0
- package/dist/semantic-validator.js +93 -3
- package/dist/semantic-validator.js.map +1 -1
- package/dist/spec.d.ts +2 -2
- package/dist/spec.js +50 -1
- package/dist/spec.js.map +1 -1
- package/package.json +1 -1
package/dist/schema.js
CHANGED
|
@@ -184,12 +184,14 @@ export const NODE_SCHEMAS = {
|
|
|
184
184
|
},
|
|
185
185
|
},
|
|
186
186
|
transition: {
|
|
187
|
-
description: 'A guarded transition between machine states, with optional handler',
|
|
188
|
-
example: 'transition name=
|
|
187
|
+
description: 'A guarded transition between machine states, with optional typed payload and/or guard predicate. `params` uses the same comma-separated typed list shape as `fn` (e.g. "prompt:string,chatId:string") — those parameters enter the emitted transition function signature and are in scope inside the handler and the guard. `guard` is a raw JS boolean expression evaluated AFTER the from-state check; when falsy the transition throws `<Machine>GuardError(\'<transition>\', entity.state)`.',
|
|
188
|
+
example: 'transition name=submit from=idle to=running params="prompt:string,chatId:string" guard="entity.turnsLeft > 0"\n handler <<<\n await notifyUser(prompt)\n >>>',
|
|
189
189
|
props: {
|
|
190
190
|
name: { required: true, kind: 'identifier' },
|
|
191
191
|
from: { required: true, kind: 'string' },
|
|
192
192
|
to: { required: true, kind: 'identifier' },
|
|
193
|
+
params: { kind: 'string' },
|
|
194
|
+
guard: { kind: 'rawExpr' },
|
|
193
195
|
},
|
|
194
196
|
allowedChildren: ['handler'],
|
|
195
197
|
},
|
|
@@ -271,7 +273,7 @@ export const NODE_SCHEMAS = {
|
|
|
271
273
|
key: { kind: 'string' },
|
|
272
274
|
async: { kind: 'boolean' },
|
|
273
275
|
},
|
|
274
|
-
allowedChildren: ['handler'],
|
|
276
|
+
allowedChildren: ['handler', 'set'],
|
|
275
277
|
},
|
|
276
278
|
websocket: {
|
|
277
279
|
description: 'WebSocket server endpoint with event handlers',
|
|
@@ -293,6 +295,518 @@ export const NODE_SCHEMAS = {
|
|
|
293
295
|
export: { kind: 'boolean' },
|
|
294
296
|
},
|
|
295
297
|
},
|
|
298
|
+
fmt: {
|
|
299
|
+
description: 'Formatted string binding — declarative template literal. The `template` body is emitted verbatim between backticks, so `${expr}` placeholders interpolate normally. Use this instead of dropping into a handler just to build an interpolated string.',
|
|
300
|
+
example: 'fmt name=label template="${count} files over ${totalMb.toFixed(1)} MB"',
|
|
301
|
+
props: {
|
|
302
|
+
name: { required: true, kind: 'identifier' },
|
|
303
|
+
template: { required: true, kind: 'string' },
|
|
304
|
+
type: { kind: 'typeAnnotation' },
|
|
305
|
+
export: { kind: 'boolean' },
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
set: {
|
|
309
|
+
description: 'Declarative state update — inside an `on` event block, `set name=count to="count + 1"` lowers to `setCount(count + 1);`. The setter name follows React useState convention (`set` + capitalized state name). Lets authors skip a handler block when all they need is to mutate a piece of state.',
|
|
310
|
+
example: 'on event=click\n set name=count to="count + 1"',
|
|
311
|
+
props: {
|
|
312
|
+
name: { required: true, kind: 'identifier' },
|
|
313
|
+
to: { required: true, kind: 'rawExpr' },
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
async: {
|
|
317
|
+
description: 'Declarative async block — a named async unit that runs its `handler` body once, optionally wrapped by a `recover` child that delegates to the existing `recover`/`strategy` machinery. Reuses `generateRecover` verbatim, so fallback/retry semantics match the rest of the ground layer. The emitted code is a statement (IIFE when no recover, wrapped call when recover is present) so it can be spliced inside any statement context.',
|
|
318
|
+
example: 'async name=loadUser\n handler <<<\n const res = await fetch(`/api/users/${id}`);\n setUser(await res.json());\n >>>\n recover\n strategy name=fallback\n handler <<<\n setUser(null);\n >>>',
|
|
319
|
+
props: {
|
|
320
|
+
name: { kind: 'identifier' },
|
|
321
|
+
},
|
|
322
|
+
allowedChildren: ['handler', 'recover'],
|
|
323
|
+
},
|
|
324
|
+
filter: {
|
|
325
|
+
description: 'Declarative `.filter` binding — `filter name=active in=items where="item.active"` lowers to `const active = items.filter(item => item.active);`. Use `item=x` to rename the per-item binding.',
|
|
326
|
+
example: 'filter name=active in=items where="item.active"',
|
|
327
|
+
props: {
|
|
328
|
+
name: { required: true, kind: 'identifier' },
|
|
329
|
+
in: { required: true, kind: 'rawExpr' },
|
|
330
|
+
item: { kind: 'identifier' },
|
|
331
|
+
where: { required: true, kind: 'rawExpr' },
|
|
332
|
+
type: { kind: 'typeAnnotation' },
|
|
333
|
+
export: { kind: 'boolean' },
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
find: {
|
|
337
|
+
description: "Declarative `.find` binding — `find name=admin in=users where=\"item.role === 'admin'\"` lowers to `const admin = users.find(item => item.role === 'admin');`. Use `item=x` to rename the per-item binding.",
|
|
338
|
+
example: 'find name=admin in=users item=u where="u.role === \'admin\'"',
|
|
339
|
+
props: {
|
|
340
|
+
name: { required: true, kind: 'identifier' },
|
|
341
|
+
in: { required: true, kind: 'rawExpr' },
|
|
342
|
+
item: { kind: 'identifier' },
|
|
343
|
+
where: { required: true, kind: 'rawExpr' },
|
|
344
|
+
type: { kind: 'typeAnnotation' },
|
|
345
|
+
export: { kind: 'boolean' },
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
some: {
|
|
349
|
+
description: 'Declarative `.some` binding — `some name=hasError in=results where="!item.ok"` lowers to `const hasError = results.some(item => !item.ok);`.',
|
|
350
|
+
example: 'some name=hasError in=results where="!item.ok"',
|
|
351
|
+
props: {
|
|
352
|
+
name: { required: true, kind: 'identifier' },
|
|
353
|
+
in: { required: true, kind: 'rawExpr' },
|
|
354
|
+
item: { kind: 'identifier' },
|
|
355
|
+
where: { required: true, kind: 'rawExpr' },
|
|
356
|
+
type: { kind: 'typeAnnotation' },
|
|
357
|
+
export: { kind: 'boolean' },
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
every: {
|
|
361
|
+
description: 'Declarative `.every` binding — `every name=allDone in=tasks where="item.done"` lowers to `const allDone = tasks.every(item => item.done);`.',
|
|
362
|
+
example: 'every name=allDone in=tasks where="item.done"',
|
|
363
|
+
props: {
|
|
364
|
+
name: { required: true, kind: 'identifier' },
|
|
365
|
+
in: { required: true, kind: 'rawExpr' },
|
|
366
|
+
item: { kind: 'identifier' },
|
|
367
|
+
where: { required: true, kind: 'rawExpr' },
|
|
368
|
+
type: { kind: 'typeAnnotation' },
|
|
369
|
+
export: { kind: 'boolean' },
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
reduce: {
|
|
373
|
+
description: 'Declarative `.reduce` binding — two bound names (accumulator + item). `reduce name=total in=items initial="0" expr="acc + item.value"` lowers to `const total = items.reduce((acc, item) => acc + item.value, 0);`. Override the binding names with `acc=` and `item=`.',
|
|
374
|
+
example: 'reduce name=total in=items initial="0" expr="acc + item.value"',
|
|
375
|
+
props: {
|
|
376
|
+
name: { required: true, kind: 'identifier' },
|
|
377
|
+
in: { required: true, kind: 'rawExpr' },
|
|
378
|
+
acc: { kind: 'identifier' },
|
|
379
|
+
item: { kind: 'identifier' },
|
|
380
|
+
initial: { required: true, kind: 'rawExpr' },
|
|
381
|
+
expr: { required: true, kind: 'rawExpr' },
|
|
382
|
+
type: { kind: 'typeAnnotation' },
|
|
383
|
+
export: { kind: 'boolean' },
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
flatMap: {
|
|
387
|
+
description: 'Declarative `.flatMap` binding — `flatMap name=tags in=posts expr="item.tags"` lowers to `const tags = posts.flatMap(item => item.tags);`. Use `item=` to rename the per-item binding. `expr` is the arrow body (an array or iterable), not a predicate.',
|
|
388
|
+
example: 'flatMap name=tags in=posts expr="item.tags"',
|
|
389
|
+
props: {
|
|
390
|
+
name: { required: true, kind: 'identifier' },
|
|
391
|
+
in: { required: true, kind: 'rawExpr' },
|
|
392
|
+
item: { kind: 'identifier' },
|
|
393
|
+
expr: { required: true, kind: 'rawExpr' },
|
|
394
|
+
type: { kind: 'typeAnnotation' },
|
|
395
|
+
export: { kind: 'boolean' },
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
slice: {
|
|
399
|
+
description: 'Declarative `.slice` binding — `slice name=first5 in=items start=0 end=5` lowers to `const first5 = items.slice(0, 5);`. `start` and `end` default to undefined (JS semantics: a bare `.slice()` copies the whole array).',
|
|
400
|
+
example: 'slice name=first5 in=items start=0 end=5',
|
|
401
|
+
props: {
|
|
402
|
+
name: { required: true, kind: 'identifier' },
|
|
403
|
+
in: { required: true, kind: 'rawExpr' },
|
|
404
|
+
start: { kind: 'rawExpr' },
|
|
405
|
+
end: { kind: 'rawExpr' },
|
|
406
|
+
type: { kind: 'typeAnnotation' },
|
|
407
|
+
export: { kind: 'boolean' },
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
map: {
|
|
411
|
+
description: 'Declarative `.map` binding — `map name=names in=users expr="item.name"` lowers to `const names = users.map(item => item.name);`. Sibling to `each` (JSX iteration form); use `map` for data-transformation bindings. `expr` is the arrow body.',
|
|
412
|
+
example: 'map name=names in=users expr="item.name"',
|
|
413
|
+
props: {
|
|
414
|
+
name: { required: true, kind: 'identifier' },
|
|
415
|
+
in: { required: true, kind: 'rawExpr' },
|
|
416
|
+
item: { kind: 'identifier' },
|
|
417
|
+
expr: { required: true, kind: 'rawExpr' },
|
|
418
|
+
type: { kind: 'typeAnnotation' },
|
|
419
|
+
export: { kind: 'boolean' },
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
findIndex: {
|
|
423
|
+
description: 'Declarative `.findIndex` binding — `findIndex name=pos in=users where="item.active"` lowers to `const pos = users.findIndex(item => item.active);`. Returns a number; add `type=number` when the binding is exported.',
|
|
424
|
+
example: 'findIndex name=pos in=users where="item.active"',
|
|
425
|
+
props: {
|
|
426
|
+
name: { required: true, kind: 'identifier' },
|
|
427
|
+
in: { required: true, kind: 'rawExpr' },
|
|
428
|
+
item: { kind: 'identifier' },
|
|
429
|
+
where: { required: true, kind: 'rawExpr' },
|
|
430
|
+
type: { kind: 'typeAnnotation' },
|
|
431
|
+
export: { kind: 'boolean' },
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
flat: {
|
|
435
|
+
description: 'Declarative `.flat` binding — `flat name=flattened in=nested depth=2` lowers to `const flattened = nested.flat(2);`. Omit `depth` for the default depth of 1.',
|
|
436
|
+
example: 'flat name=flattened in=nested depth=2',
|
|
437
|
+
props: {
|
|
438
|
+
name: { required: true, kind: 'identifier' },
|
|
439
|
+
in: { required: true, kind: 'rawExpr' },
|
|
440
|
+
depth: { kind: 'rawExpr' },
|
|
441
|
+
type: { kind: 'typeAnnotation' },
|
|
442
|
+
export: { kind: 'boolean' },
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
at: {
|
|
446
|
+
description: 'Declarative `.at` binding — `at name=last in=items index=-1` lowers to `const last = items.at(-1);`. Supports negative indices for tail access.',
|
|
447
|
+
example: 'at name=last in=items index=-1',
|
|
448
|
+
props: {
|
|
449
|
+
name: { required: true, kind: 'identifier' },
|
|
450
|
+
in: { required: true, kind: 'rawExpr' },
|
|
451
|
+
index: { required: true, kind: 'rawExpr' },
|
|
452
|
+
type: { kind: 'typeAnnotation' },
|
|
453
|
+
export: { kind: 'boolean' },
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
sort: {
|
|
457
|
+
description: 'Declarative immutable sort — `sort name=sorted in=items compare="a.age - b.age"` lowers to `const sorted = [...items].sort((a, b) => a.age - b.age);`. Source collection is never mutated. Omit `compare` for lexicographic sort. Rename bindings with `a=` / `b=`.',
|
|
458
|
+
example: 'sort name=sorted in=items compare="a.age - b.age"',
|
|
459
|
+
props: {
|
|
460
|
+
name: { required: true, kind: 'identifier' },
|
|
461
|
+
in: { required: true, kind: 'rawExpr' },
|
|
462
|
+
a: { kind: 'identifier' },
|
|
463
|
+
b: { kind: 'identifier' },
|
|
464
|
+
compare: { kind: 'rawExpr' },
|
|
465
|
+
type: { kind: 'typeAnnotation' },
|
|
466
|
+
export: { kind: 'boolean' },
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
reverse: {
|
|
470
|
+
description: 'Declarative immutable reverse — `reverse name=reversed in=items` lowers to `const reversed = [...items].reverse();`. Source collection is never mutated.',
|
|
471
|
+
example: 'reverse name=reversed in=items',
|
|
472
|
+
props: {
|
|
473
|
+
name: { required: true, kind: 'identifier' },
|
|
474
|
+
in: { required: true, kind: 'rawExpr' },
|
|
475
|
+
type: { kind: 'typeAnnotation' },
|
|
476
|
+
export: { kind: 'boolean' },
|
|
477
|
+
},
|
|
478
|
+
},
|
|
479
|
+
join: {
|
|
480
|
+
description: 'Declarative `.join` binding — `join name=csv in=fields separator=","` lowers to `const csv = fields.join(\',\');`. Omit `separator` for the default (`,`). The separator is emitted as a quoted string literal unless wrapped as `{{ expr }}`.',
|
|
481
|
+
example: 'join name=csv in=fields separator=","',
|
|
482
|
+
props: {
|
|
483
|
+
name: { required: true, kind: 'identifier' },
|
|
484
|
+
in: { required: true, kind: 'rawExpr' },
|
|
485
|
+
separator: { kind: 'rawExpr' },
|
|
486
|
+
type: { kind: 'typeAnnotation' },
|
|
487
|
+
export: { kind: 'boolean' },
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
includes: {
|
|
491
|
+
description: "Declarative `.includes` binding — `includes name=hasError in=errors value=\"'fatal'\"` lowers to `const hasError = errors.includes('fatal');`. `value` is a raw expression — quote string literals inside it.",
|
|
492
|
+
example: 'includes name=hasError in=errors value="\'fatal\'"',
|
|
493
|
+
props: {
|
|
494
|
+
name: { required: true, kind: 'identifier' },
|
|
495
|
+
in: { required: true, kind: 'rawExpr' },
|
|
496
|
+
value: { required: true, kind: 'rawExpr' },
|
|
497
|
+
from: { kind: 'rawExpr' },
|
|
498
|
+
type: { kind: 'typeAnnotation' },
|
|
499
|
+
export: { kind: 'boolean' },
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
indexOf: {
|
|
503
|
+
description: 'Declarative `.indexOf` binding — `indexOf name=pos in=items value=target` lowers to `const pos = items.indexOf(target);`. `value` is a raw expression.',
|
|
504
|
+
example: 'indexOf name=pos in=items value=target',
|
|
505
|
+
props: {
|
|
506
|
+
name: { required: true, kind: 'identifier' },
|
|
507
|
+
in: { required: true, kind: 'rawExpr' },
|
|
508
|
+
value: { required: true, kind: 'rawExpr' },
|
|
509
|
+
from: { kind: 'rawExpr' },
|
|
510
|
+
type: { kind: 'typeAnnotation' },
|
|
511
|
+
export: { kind: 'boolean' },
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
lastIndexOf: {
|
|
515
|
+
description: 'Declarative `.lastIndexOf` binding — `lastIndexOf name=pos in=items value=target` lowers to `const pos = items.lastIndexOf(target);`. `value` is a raw expression.',
|
|
516
|
+
example: 'lastIndexOf name=pos in=items value=target',
|
|
517
|
+
props: {
|
|
518
|
+
name: { required: true, kind: 'identifier' },
|
|
519
|
+
in: { required: true, kind: 'rawExpr' },
|
|
520
|
+
value: { required: true, kind: 'rawExpr' },
|
|
521
|
+
from: { kind: 'rawExpr' },
|
|
522
|
+
type: { kind: 'typeAnnotation' },
|
|
523
|
+
export: { kind: 'boolean' },
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
concat: {
|
|
527
|
+
description: 'Declarative `.concat` binding — `concat name=all in=items with="a, b"` lowers to `const all = items.concat(a, b);`. `with` is a raw expression injected directly — supports a single arg or comma-separated spread.',
|
|
528
|
+
example: 'concat name=all in=items with=other',
|
|
529
|
+
props: {
|
|
530
|
+
name: { required: true, kind: 'identifier' },
|
|
531
|
+
in: { required: true, kind: 'rawExpr' },
|
|
532
|
+
with: { required: true, kind: 'rawExpr' },
|
|
533
|
+
type: { kind: 'typeAnnotation' },
|
|
534
|
+
export: { kind: 'boolean' },
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
forEach: {
|
|
538
|
+
description: 'Declarative `.forEach` statement — `forEach in=items` with a `handler <<<>>>` child lowers to `items.forEach((item) => { handler-body });`. No binding (no `name`, no `const`). Distinct from `each` (JSX composition) and `map` (value binding). Use `item=` / `index=` to rename the parameters.',
|
|
539
|
+
example: 'forEach in=items\n handler <<<\n doSomething(item);\n >>>',
|
|
540
|
+
props: {
|
|
541
|
+
in: { required: true, kind: 'rawExpr' },
|
|
542
|
+
item: { kind: 'identifier' },
|
|
543
|
+
index: { kind: 'identifier' },
|
|
544
|
+
},
|
|
545
|
+
allowedChildren: ['handler'],
|
|
546
|
+
},
|
|
547
|
+
compact: {
|
|
548
|
+
description: 'Declarative `.filter(Boolean)` binding — `compact name=truthy in=items` lowers to `const truthy = items.filter(Boolean);`. Named primitive for the common "drop falsy values" pattern (36 sites in agon).',
|
|
549
|
+
example: 'compact name=truthy in=items',
|
|
550
|
+
props: {
|
|
551
|
+
name: { required: true, kind: 'identifier' },
|
|
552
|
+
in: { required: true, kind: 'rawExpr' },
|
|
553
|
+
type: { kind: 'typeAnnotation' },
|
|
554
|
+
export: { kind: 'boolean' },
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
pluck: {
|
|
558
|
+
description: 'Declarative property-extraction map — `pluck name=names in=users prop=name` lowers to `const names = users.map(item => item.name);`. `prop=` is a raw identifier path (e.g. `prop=user.profile.name` emits `item.user.profile.name`). Use `map` when the projection is not a property access.',
|
|
559
|
+
example: 'pluck name=names in=users prop=name',
|
|
560
|
+
props: {
|
|
561
|
+
name: { required: true, kind: 'identifier' },
|
|
562
|
+
in: { required: true, kind: 'rawExpr' },
|
|
563
|
+
item: { kind: 'identifier' },
|
|
564
|
+
prop: { required: true, kind: 'rawExpr' },
|
|
565
|
+
type: { kind: 'typeAnnotation' },
|
|
566
|
+
export: { kind: 'boolean' },
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
unique: {
|
|
570
|
+
description: 'Declarative dedupe — `unique name=distinct in=items` lowers to `const distinct = [...new Set(items)];`. Uses JS `Set` identity (triple-equals on primitives, reference equality on objects). For key-based dedup of object arrays, use `uniqueBy`.',
|
|
571
|
+
example: 'unique name=distinct in=items',
|
|
572
|
+
props: {
|
|
573
|
+
name: { required: true, kind: 'identifier' },
|
|
574
|
+
in: { required: true, kind: 'rawExpr' },
|
|
575
|
+
type: { kind: 'typeAnnotation' },
|
|
576
|
+
export: { kind: 'boolean' },
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
uniqueBy: {
|
|
580
|
+
description: 'Key-based dedup (first-wins, matches Lodash uniqBy) — `uniqueBy name=distinct in=users by="item.id"` emits a Set+filter form.',
|
|
581
|
+
example: 'uniqueBy name=distinct in=users by="item.id"',
|
|
582
|
+
props: {
|
|
583
|
+
name: { required: true, kind: 'identifier' },
|
|
584
|
+
in: { required: true, kind: 'rawExpr' },
|
|
585
|
+
item: { kind: 'identifier' },
|
|
586
|
+
by: { required: true, kind: 'rawExpr' },
|
|
587
|
+
type: { kind: 'typeAnnotation' },
|
|
588
|
+
export: { kind: 'boolean' },
|
|
589
|
+
},
|
|
590
|
+
},
|
|
591
|
+
groupBy: {
|
|
592
|
+
description: 'Partition an array into buckets by a key selector. Emits a reduce-based form (compatible with ES2022) — does not depend on `Object.groupBy` (ES2024).',
|
|
593
|
+
example: 'groupBy name=byType in=items by="item.type"',
|
|
594
|
+
props: {
|
|
595
|
+
name: { required: true, kind: 'identifier' },
|
|
596
|
+
in: { required: true, kind: 'rawExpr' },
|
|
597
|
+
item: { kind: 'identifier' },
|
|
598
|
+
by: { required: true, kind: 'rawExpr' },
|
|
599
|
+
type: { kind: 'typeAnnotation' },
|
|
600
|
+
export: { kind: 'boolean' },
|
|
601
|
+
},
|
|
602
|
+
},
|
|
603
|
+
partition: {
|
|
604
|
+
description: 'Split an array into two by a predicate — single-pass reduce. Emits `const [pass, fail] = ...`. Both `pass` and `fail` prop names are required.',
|
|
605
|
+
example: 'partition pass=active fail=inactive in=users where="item.active"',
|
|
606
|
+
props: {
|
|
607
|
+
name: { kind: 'identifier' },
|
|
608
|
+
pass: { required: true, kind: 'identifier' },
|
|
609
|
+
fail: { required: true, kind: 'identifier' },
|
|
610
|
+
in: { required: true, kind: 'rawExpr' },
|
|
611
|
+
item: { kind: 'identifier' },
|
|
612
|
+
where: { required: true, kind: 'rawExpr' },
|
|
613
|
+
type: { kind: 'typeAnnotation' },
|
|
614
|
+
export: { kind: 'boolean' },
|
|
615
|
+
},
|
|
616
|
+
},
|
|
617
|
+
indexBy: {
|
|
618
|
+
description: 'Array → keyed record via selector. `indexBy name=byId in=users by="item.id"` lowers to `Object.fromEntries(users.map(...))`. Collisions are last-write-wins.',
|
|
619
|
+
example: 'indexBy name=byId in=users by="item.id"',
|
|
620
|
+
props: {
|
|
621
|
+
name: { required: true, kind: 'identifier' },
|
|
622
|
+
in: { required: true, kind: 'rawExpr' },
|
|
623
|
+
item: { kind: 'identifier' },
|
|
624
|
+
by: { required: true, kind: 'rawExpr' },
|
|
625
|
+
type: { kind: 'typeAnnotation' },
|
|
626
|
+
export: { kind: 'boolean' },
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
countBy: {
|
|
630
|
+
description: 'Count occurrences by key. `countBy name=counts in=items by="item.type"` lowers to a reduce with `Object.create(null)` accumulator (prototype-pollution safe).',
|
|
631
|
+
example: 'countBy name=counts in=items by="item.type"',
|
|
632
|
+
props: {
|
|
633
|
+
name: { required: true, kind: 'identifier' },
|
|
634
|
+
in: { required: true, kind: 'rawExpr' },
|
|
635
|
+
item: { kind: 'identifier' },
|
|
636
|
+
by: { required: true, kind: 'rawExpr' },
|
|
637
|
+
type: { kind: 'typeAnnotation' },
|
|
638
|
+
export: { kind: 'boolean' },
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
chunk: {
|
|
642
|
+
description: 'Split into fixed-size chunks. `chunk name=batches in=items size=10`.',
|
|
643
|
+
example: 'chunk name=batches in=items size=10',
|
|
644
|
+
props: {
|
|
645
|
+
name: { required: true, kind: 'identifier' },
|
|
646
|
+
in: { required: true, kind: 'rawExpr' },
|
|
647
|
+
size: { required: true, kind: 'rawExpr' },
|
|
648
|
+
type: { kind: 'typeAnnotation' },
|
|
649
|
+
export: { kind: 'boolean' },
|
|
650
|
+
},
|
|
651
|
+
},
|
|
652
|
+
zip: {
|
|
653
|
+
description: 'Pair two arrays element-wise. `zip name=pairs in=items with=other`. Short-side wins — extra right-hand elements are dropped.',
|
|
654
|
+
example: 'zip name=pairs in=items with=other',
|
|
655
|
+
props: {
|
|
656
|
+
name: { required: true, kind: 'identifier' },
|
|
657
|
+
in: { required: true, kind: 'rawExpr' },
|
|
658
|
+
with: { required: true, kind: 'rawExpr' },
|
|
659
|
+
item: { kind: 'identifier' },
|
|
660
|
+
index: { kind: 'identifier' },
|
|
661
|
+
type: { kind: 'typeAnnotation' },
|
|
662
|
+
export: { kind: 'boolean' },
|
|
663
|
+
},
|
|
664
|
+
},
|
|
665
|
+
range: {
|
|
666
|
+
description: 'Generate a numeric range. `range name=nums end=10` → `[0..9]`. `range name=nums start=5 end=10` → `[5..9]`. No `step` in v1.',
|
|
667
|
+
example: 'range name=nums end=10',
|
|
668
|
+
props: {
|
|
669
|
+
name: { required: true, kind: 'identifier' },
|
|
670
|
+
start: { kind: 'rawExpr' },
|
|
671
|
+
end: { required: true, kind: 'rawExpr' },
|
|
672
|
+
type: { kind: 'typeAnnotation' },
|
|
673
|
+
export: { kind: 'boolean' },
|
|
674
|
+
},
|
|
675
|
+
},
|
|
676
|
+
take: {
|
|
677
|
+
description: 'First N elements. Alias for `slice(0, n)` but named for intent clarity.',
|
|
678
|
+
example: 'take name=first5 in=items n=5',
|
|
679
|
+
props: {
|
|
680
|
+
name: { required: true, kind: 'identifier' },
|
|
681
|
+
in: { required: true, kind: 'rawExpr' },
|
|
682
|
+
n: { required: true, kind: 'rawExpr' },
|
|
683
|
+
type: { kind: 'typeAnnotation' },
|
|
684
|
+
export: { kind: 'boolean' },
|
|
685
|
+
},
|
|
686
|
+
},
|
|
687
|
+
drop: {
|
|
688
|
+
description: 'Drop first N elements. Alias for `slice(n)` but named for intent clarity.',
|
|
689
|
+
example: 'drop name=tail in=items n=5',
|
|
690
|
+
props: {
|
|
691
|
+
name: { required: true, kind: 'identifier' },
|
|
692
|
+
in: { required: true, kind: 'rawExpr' },
|
|
693
|
+
n: { required: true, kind: 'rawExpr' },
|
|
694
|
+
type: { kind: 'typeAnnotation' },
|
|
695
|
+
export: { kind: 'boolean' },
|
|
696
|
+
},
|
|
697
|
+
},
|
|
698
|
+
min: {
|
|
699
|
+
description: 'Scalar min on a number array. Returns `undefined` on empty. Reduce-based — no stack-overflow risk on huge arrays.',
|
|
700
|
+
example: 'min name=lowest in=values',
|
|
701
|
+
props: {
|
|
702
|
+
name: { required: true, kind: 'identifier' },
|
|
703
|
+
in: { required: true, kind: 'rawExpr' },
|
|
704
|
+
type: { kind: 'typeAnnotation' },
|
|
705
|
+
export: { kind: 'boolean' },
|
|
706
|
+
},
|
|
707
|
+
},
|
|
708
|
+
max: {
|
|
709
|
+
description: 'Scalar max on a number array. Returns `undefined` on empty. Reduce-based — no stack-overflow risk on huge arrays.',
|
|
710
|
+
example: 'max name=highest in=values',
|
|
711
|
+
props: {
|
|
712
|
+
name: { required: true, kind: 'identifier' },
|
|
713
|
+
in: { required: true, kind: 'rawExpr' },
|
|
714
|
+
type: { kind: 'typeAnnotation' },
|
|
715
|
+
export: { kind: 'boolean' },
|
|
716
|
+
},
|
|
717
|
+
},
|
|
718
|
+
minBy: {
|
|
719
|
+
description: 'Find the element with the minimum key. `minBy name=youngest in=users by="item.age"`. Returns `undefined` on empty.',
|
|
720
|
+
example: 'minBy name=youngest in=users by="item.age"',
|
|
721
|
+
props: {
|
|
722
|
+
name: { required: true, kind: 'identifier' },
|
|
723
|
+
in: { required: true, kind: 'rawExpr' },
|
|
724
|
+
item: { kind: 'identifier' },
|
|
725
|
+
by: { required: true, kind: 'rawExpr' },
|
|
726
|
+
type: { kind: 'typeAnnotation' },
|
|
727
|
+
export: { kind: 'boolean' },
|
|
728
|
+
},
|
|
729
|
+
},
|
|
730
|
+
maxBy: {
|
|
731
|
+
description: 'Find the element with the maximum key. `maxBy name=oldest in=users by="item.age"`. Returns `undefined` on empty.',
|
|
732
|
+
example: 'maxBy name=oldest in=users by="item.age"',
|
|
733
|
+
props: {
|
|
734
|
+
name: { required: true, kind: 'identifier' },
|
|
735
|
+
in: { required: true, kind: 'rawExpr' },
|
|
736
|
+
item: { kind: 'identifier' },
|
|
737
|
+
by: { required: true, kind: 'rawExpr' },
|
|
738
|
+
type: { kind: 'typeAnnotation' },
|
|
739
|
+
export: { kind: 'boolean' },
|
|
740
|
+
},
|
|
741
|
+
},
|
|
742
|
+
sum: {
|
|
743
|
+
description: 'Sum of a number array. Returns `0` on empty (additive identity).',
|
|
744
|
+
example: 'sum name=total in=prices',
|
|
745
|
+
props: {
|
|
746
|
+
name: { required: true, kind: 'identifier' },
|
|
747
|
+
in: { required: true, kind: 'rawExpr' },
|
|
748
|
+
type: { kind: 'typeAnnotation' },
|
|
749
|
+
export: { kind: 'boolean' },
|
|
750
|
+
},
|
|
751
|
+
},
|
|
752
|
+
sumBy: {
|
|
753
|
+
description: 'Sum via key selector. `sumBy name=totalCost in=items by="item.price * item.qty"`.',
|
|
754
|
+
example: 'sumBy name=totalCost in=items by="item.price * item.qty"',
|
|
755
|
+
props: {
|
|
756
|
+
name: { required: true, kind: 'identifier' },
|
|
757
|
+
in: { required: true, kind: 'rawExpr' },
|
|
758
|
+
item: { kind: 'identifier' },
|
|
759
|
+
by: { required: true, kind: 'rawExpr' },
|
|
760
|
+
type: { kind: 'typeAnnotation' },
|
|
761
|
+
export: { kind: 'boolean' },
|
|
762
|
+
},
|
|
763
|
+
},
|
|
764
|
+
avg: {
|
|
765
|
+
description: 'Mean of a number array. Returns `NaN` on empty (Lodash parity — preserves the "no data" signal).',
|
|
766
|
+
example: 'avg name=mean in=prices',
|
|
767
|
+
props: {
|
|
768
|
+
name: { required: true, kind: 'identifier' },
|
|
769
|
+
in: { required: true, kind: 'rawExpr' },
|
|
770
|
+
type: { kind: 'typeAnnotation' },
|
|
771
|
+
export: { kind: 'boolean' },
|
|
772
|
+
},
|
|
773
|
+
},
|
|
774
|
+
intersect: {
|
|
775
|
+
description: 'Set intersection of two arrays. `intersect name=shared in=a with=b`. O(N+M) via a Set.',
|
|
776
|
+
example: 'intersect name=shared in=a with=b',
|
|
777
|
+
props: {
|
|
778
|
+
name: { required: true, kind: 'identifier' },
|
|
779
|
+
in: { required: true, kind: 'rawExpr' },
|
|
780
|
+
with: { required: true, kind: 'rawExpr' },
|
|
781
|
+
item: { kind: 'identifier' },
|
|
782
|
+
type: { kind: 'typeAnnotation' },
|
|
783
|
+
export: { kind: 'boolean' },
|
|
784
|
+
},
|
|
785
|
+
},
|
|
786
|
+
findLast: {
|
|
787
|
+
description: 'ES2023 counterpart to `find` — iterate from the end. `findLast name=lastActive in=users where="item.active"`.',
|
|
788
|
+
example: 'findLast name=lastActive in=users where="item.active"',
|
|
789
|
+
props: {
|
|
790
|
+
name: { required: true, kind: 'identifier' },
|
|
791
|
+
in: { required: true, kind: 'rawExpr' },
|
|
792
|
+
item: { kind: 'identifier' },
|
|
793
|
+
where: { required: true, kind: 'rawExpr' },
|
|
794
|
+
type: { kind: 'typeAnnotation' },
|
|
795
|
+
export: { kind: 'boolean' },
|
|
796
|
+
},
|
|
797
|
+
},
|
|
798
|
+
findLastIndex: {
|
|
799
|
+
description: 'ES2023 counterpart to `findIndex` — iterate from the end. `findLastIndex name=pos in=users where="item.active"`.',
|
|
800
|
+
example: 'findLastIndex name=pos in=users where="item.active"',
|
|
801
|
+
props: {
|
|
802
|
+
name: { required: true, kind: 'identifier' },
|
|
803
|
+
in: { required: true, kind: 'rawExpr' },
|
|
804
|
+
item: { kind: 'identifier' },
|
|
805
|
+
where: { required: true, kind: 'rawExpr' },
|
|
806
|
+
type: { kind: 'typeAnnotation' },
|
|
807
|
+
export: { kind: 'boolean' },
|
|
808
|
+
},
|
|
809
|
+
},
|
|
296
810
|
transform: {
|
|
297
811
|
description: 'Data transformation pipeline — maps target through a via function or handler',
|
|
298
812
|
example: 'transform name=normalized target=rawData via=normalize type=NormalizedData',
|
|
@@ -383,12 +897,34 @@ export const NODE_SCHEMAS = {
|
|
|
383
897
|
},
|
|
384
898
|
},
|
|
385
899
|
each: {
|
|
386
|
-
description: 'Iteration — renders children for each item in a collection (
|
|
387
|
-
example: 'each name=
|
|
900
|
+
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`).',
|
|
901
|
+
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 >>>',
|
|
388
902
|
props: {
|
|
389
903
|
name: { required: true, kind: 'identifier' },
|
|
390
904
|
in: { required: true, kind: 'rawExpr' },
|
|
391
905
|
index: { kind: 'identifier' },
|
|
906
|
+
key: { kind: 'rawExpr' },
|
|
907
|
+
},
|
|
908
|
+
// Intentionally unrestricted — statement-form `each` composes with `derive`,
|
|
909
|
+
// `transform`, etc. in fn/handler contexts. The `let` node is constrained
|
|
910
|
+
// separately via the `let-must-be-inside-each` semantic rule.
|
|
911
|
+
},
|
|
912
|
+
let: {
|
|
913
|
+
description: 'Iteration-scoped binding — emits a plain `const` inside the containing `each` callback. Use for values that depend on the iteration variable or index. Unlike `derive` (which compiles to `useMemo` and violates Rules of Hooks inside `.map`), `let` is hook-safe by construction.',
|
|
914
|
+
example: 'let name=idx expr="start + i"',
|
|
915
|
+
props: {
|
|
916
|
+
name: { required: true, kind: 'identifier' },
|
|
917
|
+
expr: { required: true, kind: 'rawExpr' },
|
|
918
|
+
type: { kind: 'typeAnnotation' },
|
|
919
|
+
},
|
|
920
|
+
},
|
|
921
|
+
local: {
|
|
922
|
+
description: 'Render-scope binding — emits `const name = expr;` at the top of the enclosing screen function, before its JSX return. Use for shared pre-compute that multiple sibling `each`/`conditional`/`handler` nodes inside the same `render` block read. Expression-only (no handler body) — drop to an explicit `derive` / `memo` above the render if a hook or imperative body is needed. Direct child of `render` only.',
|
|
923
|
+
example: 'render wrapper="<Box paddingX={1}>"\n local name=visible expr="items.slice(start, start + pageSize)"\n each name=item in=visible\n handler <<< <Text>{item.label}</Text> >>>',
|
|
924
|
+
props: {
|
|
925
|
+
name: { required: true, kind: 'identifier' },
|
|
926
|
+
expr: { required: true, kind: 'rawExpr' },
|
|
927
|
+
type: { kind: 'typeAnnotation' },
|
|
392
928
|
},
|
|
393
929
|
},
|
|
394
930
|
collect: {
|
|
@@ -626,11 +1162,26 @@ export const NODE_SCHEMAS = {
|
|
|
626
1162
|
},
|
|
627
1163
|
},
|
|
628
1164
|
conditional: {
|
|
629
|
-
description: 'Conditional rendering — shows
|
|
630
|
-
example: 'conditional if="
|
|
1165
|
+
description: 'Conditional rendering — shows the `then` branch when if-expression is truthy. Inside a `render` block the branch JSX goes in a `handler <<<>>>` child; optional `elseif` / `else` children provide alternative branches.',
|
|
1166
|
+
example: 'conditional if="loading"\n handler <<<\n <Spinner />\n >>>\n elseif expr="error"\n handler <<<\n <Error msg={error} />\n >>>\n else\n handler <<<\n <Content />\n >>>',
|
|
631
1167
|
props: {
|
|
632
1168
|
if: { required: true, kind: 'rawExpr' },
|
|
633
1169
|
},
|
|
1170
|
+
// No allowedChildren: conditional must remain permissive because
|
|
1171
|
+
// `generateConditional` also wraps arbitrary core nodes when used outside
|
|
1172
|
+
// a render block (e.g. `conditional if=isAdmin` with `type`/`config` kids).
|
|
1173
|
+
},
|
|
1174
|
+
elseif: {
|
|
1175
|
+
description: 'Alternative branch inside a `conditional` — matched when the preceding branches are falsy and `expr` is truthy.',
|
|
1176
|
+
example: 'elseif expr="error"\n handler <<<\n <Error msg={error} />\n >>>',
|
|
1177
|
+
props: {
|
|
1178
|
+
expr: { required: true, kind: 'rawExpr' },
|
|
1179
|
+
},
|
|
1180
|
+
},
|
|
1181
|
+
else: {
|
|
1182
|
+
description: 'Fallback branch inside a `conditional` — rendered when no preceding branch matched.',
|
|
1183
|
+
example: 'else\n handler <<<\n <Content />\n >>>',
|
|
1184
|
+
props: {},
|
|
634
1185
|
},
|
|
635
1186
|
// ── Express / Backend nodes ───────────────────────────────────────────
|
|
636
1187
|
server: {
|
|
@@ -660,6 +1211,7 @@ export const NODE_SCHEMAS = {
|
|
|
660
1211
|
'error',
|
|
661
1212
|
'guard',
|
|
662
1213
|
'derive',
|
|
1214
|
+
'fmt',
|
|
663
1215
|
'branch',
|
|
664
1216
|
'each',
|
|
665
1217
|
'collect',
|
|
@@ -939,9 +1491,12 @@ export const NODE_SCHEMAS = {
|
|
|
939
1491
|
example: 'fetch name=posts url="/api/posts" options="{ next: { revalidate: 60 } }"',
|
|
940
1492
|
props: {
|
|
941
1493
|
name: { required: true, kind: 'identifier' },
|
|
942
|
-
|
|
1494
|
+
// When a `handler <<<>>>` child is provided the handler body is the loader,
|
|
1495
|
+
// so `url` becomes irrelevant and is no longer required.
|
|
1496
|
+
url: { kind: 'rawExpr' },
|
|
943
1497
|
options: { kind: 'rawExpr' },
|
|
944
1498
|
},
|
|
1499
|
+
allowedChildren: ['handler'],
|
|
945
1500
|
},
|
|
946
1501
|
generateMetadata: {
|
|
947
1502
|
description: 'Next.js generateMetadata export — async function for dynamic page metadata',
|
|
@@ -1065,10 +1620,12 @@ export const NODE_SCHEMAS = {
|
|
|
1065
1620
|
},
|
|
1066
1621
|
},
|
|
1067
1622
|
render: {
|
|
1068
|
-
description: 'Render function — JSX output block for a component or hook',
|
|
1069
|
-
example: 'render\n
|
|
1070
|
-
props: {
|
|
1071
|
-
|
|
1623
|
+
description: 'Render function — JSX output block for a component or hook. Accepts a raw `handler` block OR declarative KERN children (`each`, `conditional`, `local`) that compose into a JSX tree. Optional `wrapper="<Tag attrs>"` prop emits that tag as the outer element around the composed children (replaces the default `<>...</>` Fragment). `local` children emit `const name = expr;` bindings at the enclosing screen-function scope before the return — use them for shared pre-compute that multiple sibling `each`/`conditional`/`handler` nodes read.',
|
|
1624
|
+
example: 'render wrapper="<Box paddingX={1}>"\n local name=visible expr="items.slice(start, start + pageSize)"\n each name=item in=visible\n handler <<< <Text>{item.label}</Text> >>>',
|
|
1625
|
+
props: {
|
|
1626
|
+
wrapper: { kind: 'string' },
|
|
1627
|
+
},
|
|
1628
|
+
allowedChildren: ['handler', 'each', 'conditional', 'local'],
|
|
1072
1629
|
},
|
|
1073
1630
|
template: {
|
|
1074
1631
|
description: 'Reusable template with named slots — defines a composable layout pattern',
|