@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.
- package/dist/build.d.ts +7 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-intrinsics.d.ts +17 -0
- package/dist/cli/commands/check-lexicon-intrinsics.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/codegen/docs-types.d.ts +2 -0
- package/dist/codegen/docs-types.d.ts.map +1 -1
- package/dist/declarable.d.ts +16 -0
- package/dist/declarable.d.ts.map +1 -1
- package/dist/discovery/entity-wire-codec.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +64 -3
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/index.d.ts +12 -0
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/fold/fold.d.ts +89 -16
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/foldable-helpers.d.ts +121 -0
- package/dist/fold/foldable-helpers.d.ts.map +1 -0
- package/dist/fold/subset.d.ts +58 -3
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +2 -0
- package/dist/lexicon-schema.d.ts.map +1 -1
- package/dist/lexicon.d.ts +73 -23
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/runtime.d.ts +10 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/serializer-walker.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/build.ts +9 -0
- package/src/cli/commands/build.ts +9 -0
- package/src/cli/commands/check-lexicon-examples.ts +16 -1
- package/src/cli/commands/check-lexicon-intrinsics.test.ts +35 -1
- package/src/cli/commands/check-lexicon-intrinsics.ts +38 -2
- package/src/cli/commands/check-lexicon.ts +18 -0
- package/src/codegen/docs-sections.test.ts +7 -1
- package/src/codegen/docs-sections.ts +1 -1
- package/src/codegen/docs-types.ts +2 -0
- package/src/declarable.ts +20 -0
- package/src/discovery/entity-wire-codec.ts +9 -7
- package/src/discovery/fold-import.test.ts +900 -1
- package/src/discovery/fold-import.ts +441 -47
- package/src/discovery/index.ts +25 -1
- package/src/fold/fold.test.ts +277 -0
- package/src/fold/fold.ts +219 -58
- package/src/fold/foldable-helpers.ts +171 -0
- package/src/fold/subset-doc-parity.test.ts +27 -0
- package/src/fold/subset.test.ts +177 -0
- package/src/fold/subset.ts +139 -31
- package/src/lexicon-schema.test.ts +43 -0
- package/src/lexicon-schema.ts +5 -0
- package/src/lexicon.ts +74 -24
- package/src/runtime.ts +11 -2
- package/src/serializer-walker.ts +14 -0
package/src/lexicon.ts
CHANGED
|
@@ -153,39 +153,89 @@ export interface IntrinsicDef {
|
|
|
153
153
|
* `../cli/commands/check-lexicon-intrinsics.ts`.
|
|
154
154
|
*/
|
|
155
155
|
readonly isTag: boolean;
|
|
156
|
+
/**
|
|
157
|
+
* chant #1044 — opt this intrinsic's PLAIN-CALL form into folding
|
|
158
|
+
* (`Ref(bucket)`, `Concat(a, b)` reduce to their intrinsic node instead of
|
|
159
|
+
* falling the whole file back to the run path).
|
|
160
|
+
*
|
|
161
|
+
* Optional, and OFF unless a lexicon writes `true`. That default is the
|
|
162
|
+
* point: `fold()` has no general `CallExpression` case by construction
|
|
163
|
+
* (epic #1019), and this field is the only thing that admits one. It is a
|
|
164
|
+
* closed, lexicon-declared allowlist, decided one intrinsic at a time —
|
|
165
|
+
* never inferred from `isTag`, from the name, or from the call's shape. An
|
|
166
|
+
* intrinsic with no `foldsAsCall` behaves exactly as it did before #1044.
|
|
167
|
+
*
|
|
168
|
+
* Only set it when calling the intrinsic is a pure function of its
|
|
169
|
+
* arguments that builds a deterministic data envelope — the whole
|
|
170
|
+
* correctness argument is that invoking it while folding is
|
|
171
|
+
* indistinguishable from invoking it during a real run of the file. An
|
|
172
|
+
* intrinsic that reads the environment, mutates state, or depends on
|
|
173
|
+
* anything but its arguments does not qualify, and neither does a tagged
|
|
174
|
+
* template (see {@link isTag}: the two forms are mutually exclusive, and
|
|
175
|
+
* `chant dev check-lexicon` rejects `isTag: true` + `foldsAsCall: true`).
|
|
176
|
+
*
|
|
177
|
+
* Registration is by name. It is not permission to invoke whatever that
|
|
178
|
+
* name happens to be bound to: `fold()` reduces the call to a symbolic
|
|
179
|
+
* envelope executing nothing, and `../discovery/fold-import.ts` resolves
|
|
180
|
+
* the name through the folding FILE'S OWN imports before invoking the real
|
|
181
|
+
* function — so the function that runs while folding is the same one the
|
|
182
|
+
* run path would have called, from the module the source itself named.
|
|
183
|
+
*/
|
|
184
|
+
readonly foldsAsCall?: boolean;
|
|
156
185
|
}
|
|
157
186
|
|
|
158
187
|
/**
|
|
159
|
-
* Whether `chant build --fold` can ever fold a
|
|
160
|
-
* (chant #1062, epic #1019)
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* a
|
|
165
|
-
*
|
|
188
|
+
* Whether `chant build --fold` can ever fold a use of this intrinsic
|
|
189
|
+
* (chant #1062, epic #1019) — in EITHER authored form.
|
|
190
|
+
*
|
|
191
|
+
* Two disjoint ways to qualify, one per form:
|
|
192
|
+
*
|
|
193
|
+
* - a registered tagged-template intrinsic (`Sub\`...\``) folds because
|
|
194
|
+
* `foldTaggedTemplate` recognizes its tag and recurses into the interior
|
|
195
|
+
* ({@link intrinsicTagFolds});
|
|
196
|
+
* - a registered plain-call intrinsic (`Ref(...)`, `Concat(...)`) folds
|
|
197
|
+
* only when its lexicon opted it in with `foldsAsCall`
|
|
198
|
+
* ({@link intrinsicCallFolds}, chant #1044). Before #1044 no plain call
|
|
199
|
+
* folded at all, whatever it was named or registered as.
|
|
166
200
|
*
|
|
167
|
-
* This function is the single predicate
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
* foldable; every caller keeps working unchanged, and the generated matrix
|
|
174
|
-
* updates the moment a lexicon's registration says a given intrinsic now
|
|
175
|
-
* folds — no doc rewrite, no second code path to remember.
|
|
201
|
+
* This function is the single predicate the generated per-lexicon intrinsics
|
|
202
|
+
* page (`../codegen/docs-sections.ts`'s "Folds?" column) calls — never a
|
|
203
|
+
* restated copy that could silently drift from the code. `fold()` itself
|
|
204
|
+
* calls the two form-specific predicates below rather than this one, because
|
|
205
|
+
* it always knows which form it is looking at, and a tag must not fold as a
|
|
206
|
+
* call (or vice versa) merely because the other form was opted in.
|
|
176
207
|
*
|
|
177
|
-
* Takes `{ isTag
|
|
178
|
-
* deliberately: `IntrinsicDef.isTag` is required
|
|
179
|
-
* (chant #1067), but
|
|
180
|
-
* possibly-older parsed JSON (`ManifestJSON` in
|
|
181
|
-
* on disk as a published lexicon's
|
|
182
|
-
*
|
|
183
|
-
* did — not a tag
|
|
208
|
+
* Takes a structural `{ isTag?, foldsAsCall? }` rather than
|
|
209
|
+
* `Pick<IntrinsicDef, ...>` deliberately: `IntrinsicDef.isTag` is required
|
|
210
|
+
* for new registrations (chant #1067), but these predicates also read off
|
|
211
|
+
* untrusted, possibly-older parsed JSON (`ManifestJSON` in
|
|
212
|
+
* `./codegen/docs-types.ts`, on disk as a published lexicon's
|
|
213
|
+
* `dist/manifest.json`) that may predate either field. `undefined` there
|
|
214
|
+
* means what it always did — not a tag, not opted in.
|
|
184
215
|
*/
|
|
185
|
-
export function intrinsicFolds(def: { isTag?: boolean }): boolean {
|
|
216
|
+
export function intrinsicFolds(def: { isTag?: boolean; foldsAsCall?: boolean }): boolean {
|
|
217
|
+
return intrinsicTagFolds(def) || intrinsicCallFolds(def);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** True when this intrinsic's TAGGED-TEMPLATE form folds (`Sub\`...\``) — see {@link intrinsicFolds}. */
|
|
221
|
+
export function intrinsicTagFolds(def: { isTag?: boolean }): boolean {
|
|
186
222
|
return def.isTag === true;
|
|
187
223
|
}
|
|
188
224
|
|
|
225
|
+
/**
|
|
226
|
+
* True when this intrinsic's PLAIN-CALL form folds (`Ref(...)`) — i.e. the
|
|
227
|
+
* lexicon opted it in via {@link IntrinsicDef.foldsAsCall} (chant #1044).
|
|
228
|
+
*
|
|
229
|
+
* `isTag: true` disqualifies regardless: a tagged template is invoked as
|
|
230
|
+
* `` Name`...` ``, so a call to it isn't the registered authoring form at
|
|
231
|
+
* all. Keeping that here rather than trusting registrations means a lexicon
|
|
232
|
+
* that declares both flags cannot quietly widen `fold()`'s call case — and
|
|
233
|
+
* `chant dev check-lexicon` fails the registration outright.
|
|
234
|
+
*/
|
|
235
|
+
export function intrinsicCallFolds(def: { isTag?: boolean; foldsAsCall?: boolean }): boolean {
|
|
236
|
+
return def.isTag !== true && def.foldsAsCall === true;
|
|
237
|
+
}
|
|
238
|
+
|
|
189
239
|
/**
|
|
190
240
|
* Options passed to a MigrationSource by `chant migrate`.
|
|
191
241
|
*/
|
package/src/runtime.ts
CHANGED
|
@@ -54,18 +54,27 @@ export function createResource(
|
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* Create a property-kind class for a given property type.
|
|
57
|
+
*
|
|
58
|
+
* Instances carry DECLARABLE_MARKER/lexicon/entityType/kind at runtime (set
|
|
59
|
+
* via defineProperty below), so they already satisfy `Declarable` — the
|
|
60
|
+
* return type just needs to say so. Before this, the signature was
|
|
61
|
+
* `Record<string, unknown>` with no `Declarable`, so any composite that
|
|
62
|
+
* returned a property-kind instance as a top-level member only type-checked
|
|
63
|
+
* by accident: either the caller went through an untyped `require()` (losing
|
|
64
|
+
* the type entirely) or never assigned the instance where its `Declarable`-ness
|
|
65
|
+
* was checked statically. See chant #1068.
|
|
57
66
|
*/
|
|
58
67
|
export function createProperty(
|
|
59
68
|
type: string,
|
|
60
69
|
lexicon: string,
|
|
61
|
-
): new (props: Record<string, unknown>) => Record<string, unknown> {
|
|
70
|
+
): new (props: Record<string, unknown>) => Declarable & Record<string, unknown> {
|
|
62
71
|
const PropertyClass = function (this: Record<string, unknown>, props: Record<string, unknown>) {
|
|
63
72
|
Object.defineProperty(this, DECLARABLE_MARKER, { value: true, enumerable: false });
|
|
64
73
|
Object.defineProperty(this, "lexicon", { value: lexicon, enumerable: false });
|
|
65
74
|
Object.defineProperty(this, "entityType", { value: type, enumerable: false });
|
|
66
75
|
Object.defineProperty(this, "kind", { value: "property", enumerable: false });
|
|
67
76
|
Object.defineProperty(this, "props", { value: props ?? {}, enumerable: false, configurable: true });
|
|
68
|
-
} as unknown as new (props: Record<string, unknown>) => Record<string, unknown>;
|
|
77
|
+
} as unknown as new (props: Record<string, unknown>) => Declarable & Record<string, unknown>;
|
|
69
78
|
|
|
70
79
|
Object.defineProperty(PropertyClass, "name", { value: type.split(".").pop() ?? type });
|
|
71
80
|
|
package/src/serializer-walker.ts
CHANGED
|
@@ -61,6 +61,20 @@ export function walkValue(
|
|
|
61
61
|
if (name) {
|
|
62
62
|
return visitor.resourceRef(name);
|
|
63
63
|
}
|
|
64
|
+
// A resource-kind Declarable constructed inline rather than exported as
|
|
65
|
+
// its own top-level entity (e.g. K8s `new PersistentVolumeClaim({...})`
|
|
66
|
+
// embedded directly in a StatefulSet's `volumeClaimTemplates`) has no
|
|
67
|
+
// logical name to Ref — it was never a key in `entities`, so
|
|
68
|
+
// resolveAttrRefs() never assigns one. Falling through to the generic
|
|
69
|
+
// "object" branch below would walk the Declarable's own enumerable
|
|
70
|
+
// properties, which for a Declarable are its self-referencing attribute
|
|
71
|
+
// accessors (AttrRef instances whose parent is the Declarable itself,
|
|
72
|
+
// still unresolved) — never its authored `.props`. That either threw
|
|
73
|
+
// "logical name not set" (when the resource type declares any
|
|
74
|
+
// attributes) or silently serialized as `{}` (when it doesn't), instead
|
|
75
|
+
// of the embedded spec the caller wrote. Embed its own props inline
|
|
76
|
+
// instead, exactly like a property-kind Declarable already does.
|
|
77
|
+
return visitor.propertyDeclarable(decl, (v) => walkValue(v, entityNames, visitor));
|
|
64
78
|
}
|
|
65
79
|
|
|
66
80
|
// Handle arrays
|