@intentius/chant 0.20.0 → 0.22.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.
Files changed (55) hide show
  1. package/dist/build.d.ts +7 -0
  2. package/dist/build.d.ts.map +1 -1
  3. package/dist/cli/commands/build.d.ts.map +1 -1
  4. package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
  5. package/dist/cli/commands/check-lexicon-intrinsics.d.ts +17 -0
  6. package/dist/cli/commands/check-lexicon-intrinsics.d.ts.map +1 -1
  7. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  8. package/dist/codegen/docs-types.d.ts +2 -0
  9. package/dist/codegen/docs-types.d.ts.map +1 -1
  10. package/dist/declarable.d.ts +16 -0
  11. package/dist/declarable.d.ts.map +1 -1
  12. package/dist/discovery/entity-wire-codec.d.ts.map +1 -1
  13. package/dist/discovery/fold-import.d.ts +64 -3
  14. package/dist/discovery/fold-import.d.ts.map +1 -1
  15. package/dist/discovery/index.d.ts +12 -0
  16. package/dist/discovery/index.d.ts.map +1 -1
  17. package/dist/fold/fold.d.ts +89 -16
  18. package/dist/fold/fold.d.ts.map +1 -1
  19. package/dist/fold/foldable-helpers.d.ts +121 -0
  20. package/dist/fold/foldable-helpers.d.ts.map +1 -0
  21. package/dist/fold/subset.d.ts +58 -3
  22. package/dist/fold/subset.d.ts.map +1 -1
  23. package/dist/lexicon-schema.d.ts +2 -0
  24. package/dist/lexicon-schema.d.ts.map +1 -1
  25. package/dist/lexicon.d.ts +73 -23
  26. package/dist/lexicon.d.ts.map +1 -1
  27. package/dist/runtime.d.ts +10 -1
  28. package/dist/runtime.d.ts.map +1 -1
  29. package/dist/serializer-walker.d.ts.map +1 -1
  30. package/package.json +1 -1
  31. package/src/build.ts +9 -0
  32. package/src/cli/commands/build.ts +9 -0
  33. package/src/cli/commands/check-lexicon-examples.ts +16 -1
  34. package/src/cli/commands/check-lexicon-intrinsics.test.ts +35 -1
  35. package/src/cli/commands/check-lexicon-intrinsics.ts +38 -2
  36. package/src/cli/commands/check-lexicon.ts +18 -0
  37. package/src/codegen/docs-sections.test.ts +7 -1
  38. package/src/codegen/docs-sections.ts +1 -1
  39. package/src/codegen/docs-types.ts +2 -0
  40. package/src/declarable.ts +20 -0
  41. package/src/discovery/entity-wire-codec.ts +9 -7
  42. package/src/discovery/fold-import.test.ts +900 -1
  43. package/src/discovery/fold-import.ts +441 -47
  44. package/src/discovery/index.ts +25 -1
  45. package/src/fold/fold.test.ts +277 -0
  46. package/src/fold/fold.ts +219 -58
  47. package/src/fold/foldable-helpers.ts +171 -0
  48. package/src/fold/subset-doc-parity.test.ts +27 -0
  49. package/src/fold/subset.test.ts +177 -0
  50. package/src/fold/subset.ts +139 -31
  51. package/src/lexicon-schema.test.ts +43 -0
  52. package/src/lexicon-schema.ts +5 -0
  53. package/src/lexicon.ts +74 -24
  54. package/src/runtime.ts +11 -2
  55. package/src/serializer-walker.ts +14 -0
@@ -32,11 +32,16 @@ describe("generateIntrinsics — Folds? column", () => {
32
32
  expect(page).toContain("| `Sub` | — | `Sub` | Yes | Yes |");
33
33
  });
34
34
 
35
- test("a plain-call intrinsic does not fold today", () => {
35
+ test("a plain-call intrinsic its lexicon never opted in does not fold", () => {
36
36
  const page = generateIntrinsics(config, manifestWith([{ name: "Ref", isTag: false }]));
37
37
  expect(page).toContain("| `Ref` | — | `Ref` | No | No |");
38
38
  });
39
39
 
40
+ test("a plain-call intrinsic opted into call-form folding does fold (chant #1044)", () => {
41
+ const page = generateIntrinsics(config, manifestWith([{ name: "Ref", isTag: false, foldsAsCall: true }]));
42
+ expect(page).toContain("| `Ref` | — | `Ref` | No | Yes |");
43
+ });
44
+
40
45
  test("an intrinsic with isTag omitted does not fold (same as isTag: false)", () => {
41
46
  const page = generateIntrinsics(config, manifestWith([{ name: "reference", outputKey: "!reference" }]));
42
47
  expect(page).toContain("| `reference` | — | `!reference` | No | No |");
@@ -47,6 +52,7 @@ describe("generateIntrinsics — Folds? column", () => {
47
52
  { name: "Sub", isTag: true },
48
53
  { name: "Ref" },
49
54
  { name: "GetAtt", isTag: false },
55
+ { name: "Join", isTag: false, foldsAsCall: true },
50
56
  ];
51
57
  const page = generateIntrinsics(config, manifestWith(intrinsics));
52
58
  for (const fn of intrinsics) {
@@ -95,7 +95,7 @@ export function generateIntrinsics(
95
95
  "",
96
96
  `The ${config.displayName} lexicon provides **${intrinsics.length}** intrinsic functions.`,
97
97
  "",
98
- `**Tag?** shows how an intrinsic is authored: a genuine tagged template (\`Sub\\\`...\\\`\`) or a plain function call (\`Ref(...)\`). **Folds?** shows whether [\`chant build --fold\`](/chant/concepts/typescript-as-data/#folded-vs-run) can reduce a call to this intrinsic today, without running the file — generated directly from this lexicon's registration, not restated by hand. Right now folding only reaches tagged templates, so most rows read \`No\`; that reflects the current state of the folder, not something specific to these intrinsics, and it changes as folding gains coverage.`,
98
+ `**Tag?** shows how an intrinsic is authored: a genuine tagged template (\`Sub\\\`...\\\`\`) or a plain function call (\`Ref(...)\`). **Folds?** shows whether [\`chant build --fold\`](/chant/concepts/typescript-as-data/#folded-vs-run) can reduce a use of this intrinsic today, without running the file — generated directly from this lexicon's registration, not restated by hand. A tagged template folds once it is registered. A plain call folds only where this lexicon has opted that intrinsic in one at a time (\`foldsAsCall\`), so a \`No\` in this column means this intrinsic has not been opted in, not that calls in general cannot fold.`,
99
99
  "",
100
100
  "| Function | Description | Output Key | Tag? | Folds? |",
101
101
  "|----------|-------------|------------|------|--------|",
@@ -55,6 +55,8 @@ export interface ManifestJSON {
55
55
  description?: string;
56
56
  outputKey?: string;
57
57
  isTag?: boolean;
58
+ /** chant #1044 — the call-form fold opt-in, as published in the lexicon's manifest. Absent in any manifest built before #1044, which reads the same as "not opted in". */
59
+ foldsAsCall?: boolean;
58
60
  }>;
59
61
  pseudoParameters?: Record<string, string>;
60
62
  }
package/src/declarable.ts CHANGED
@@ -13,6 +13,26 @@ export interface Declarable {
13
13
  readonly [DECLARABLE_MARKER]: true;
14
14
  }
15
15
 
16
+ /**
17
+ * A `Declarable` that carries a resource payload — the `props`/`attributes`
18
+ * fields that most lexicon serializers read to produce output. Not every
19
+ * `Declarable` has one (outputs and parameters genuinely don't), so this is
20
+ * kept as a sub-interface rather than widening the base type. See chant #1049.
21
+ */
22
+ export interface ResourceDeclarable extends Declarable {
23
+ readonly props: unknown;
24
+ readonly attributes?: unknown;
25
+ }
26
+
27
+ /**
28
+ * Type guard for `ResourceDeclarable` — replaces the ad-hoc `"props" in x`
29
+ * checks that were previously repeated (with an `as unknown as` cast) at every
30
+ * call site that reads `props`/`attributes` off a `Declarable`.
31
+ */
32
+ export function isResourceDeclarable(value: Declarable): value is ResourceDeclarable {
33
+ return "props" in value;
34
+ }
35
+
16
36
  /**
17
37
  * Core parameter type for lexicon-agnostic parameters
18
38
  */
@@ -54,7 +54,7 @@
54
54
  * today.
55
55
  */
56
56
 
57
- import { DECLARABLE_MARKER, type Declarable } from "../declarable";
57
+ import { DECLARABLE_MARKER, isResourceDeclarable, type Declarable } from "../declarable";
58
58
  import { AttrRef } from "../attrref";
59
59
  import { INTRINSIC_MARKER, type Intrinsic } from "../intrinsic";
60
60
  import { isAttrRefLike } from "../utils";
@@ -256,7 +256,7 @@ function encodeValue(value: unknown, entityNames: Map<unknown, string>): WireVal
256
256
  // directly rather than walking `resource` as a Declarable reference).
257
257
  // Inline exactly that, discarding the unresolvable (and, in every corpus
258
258
  // entry today, unread) identity layer.
259
- const props = "props" in decl ? (decl as unknown as { props?: unknown }).props : undefined;
259
+ const props = isResourceDeclarable(decl) ? decl.props : undefined;
260
260
  return { __property: { lexicon: decl.lexicon, entityType: decl.entityType, props: props !== undefined ? encodeValue(props, entityNames) : undefined } };
261
261
  }
262
262
 
@@ -280,11 +280,13 @@ function encodeDeclarable(entity: Declarable, entityNames: Map<unknown, string>)
280
280
  const wire: Omit<WireDeclarableEntity, "form" | "name"> = { lexicon: entity.lexicon, entityType: entity.entityType, markers: [] };
281
281
  if (entity.kind !== undefined) wire.kind = entity.kind;
282
282
 
283
- if ("props" in entity && (entity as unknown as { props?: unknown }).props !== undefined) {
284
- wire.props = encodeValue((entity as unknown as { props: unknown }).props, entityNames);
285
- }
286
- if ("attributes" in entity && (entity as unknown as { attributes?: unknown }).attributes !== undefined) {
287
- wire.attributes = encodeValue((entity as unknown as { attributes: unknown }).attributes, entityNames);
283
+ if (isResourceDeclarable(entity)) {
284
+ if (entity.props !== undefined) {
285
+ wire.props = encodeValue(entity.props, entityNames);
286
+ }
287
+ if (entity.attributes !== undefined) {
288
+ wire.attributes = encodeValue(entity.attributes, entityNames);
289
+ }
288
290
  }
289
291
 
290
292
  const markers: string[] = [];