/** * chant #1045 (Phase 1) — the JSON wire format for a discovered, named, * ref-resolved entity set. Split out of `./entity-wire.ts` in Phase 2 so this * codec (`encodeEntitySet`/`decodeEntitySet`, no dependency on `discover()` * or anything that pulls in the `typescript` compiler package) can be * bundled ALONE into the sandboxed child's driver (`./sandbox/driver.ts`) * without dragging in `discover()`'s whole fold/import graph — `entity- * wire.ts` re-exports everything here unchanged, so nothing outside this * pair of files needs to know about the split. * * `discover()` (./index.ts) produces a `Map` whose cross- * entity references are live object identity: an `AttrRef.parent` is a * `WeakRef`, and a resource can embed ANOTHER resource directly as a * prop value (e.g. `DependsOn: [otherResource]`) — the lexicon serializers * detect this by looking the embedded object up (by identity) in a * `Map` built from the very same entities map * (`serializer-walker.ts`'s `resourceRef` dispatch; see also * `resource-attributes.ts`'s `resolveDependsOn`). Neither a `WeakRef` nor bare * object identity survives a process boundary. * * {@link encodeEntitySet} converts that live map into plain, JSON-safe data: * every identity-based reference (an `AttrRef`, or a whole entity embedded by * value) becomes a name-keyed marker instead. {@link decodeEntitySet} is the * inverse — it rebuilds a live `Map` whose entities are * BEHAVIORALLY indistinguishable from what `discover()` would have produced * in-process: real `AttrRef` instances, not a duck-typed `{__attrRef}` * envelope alone. `new AttrRef(...)` here is plain, direct construction from * this module's own class — this codec runs inside the same module graph as * every downstream reader (`intrinsic-interpolation.ts`'s * `defaultInterpolationSerializer`, `discovery/graph.ts`'s * `buildDependencyGraph`, `build.ts`'s `detectCrossLexiconRefs`/ * `computeStackGraph`, all converted to `isAttrRefLike` duck-typing by chant * #1137 for the OTHER hazard, a separately-loaded lexicon copy) — so there is * no dual-package boundary to duck-type across here, and building the real * class is simply less code than hand-assembling a shape-alike stand-in with * matching methods, and whole-entity embeds restored to the SAME object * reference (not a structurally-equal clone), so `entityNames.get(decl)` * keeps working by identity exactly as it does today. * * `serializer-walker.ts`'s `walkValue` needs NO changes for this: it already * falls back to reading a plain `{__attrRef}` envelope (added for intrinsics * whose own `toJSON()` embeds one). `decodeEntitySet` goes further and * reconstructs the real class, both simpler here and a belt-and-suspenders * match for any call site that still checks `instanceof AttrRef` directly. * * Naming happens exactly once, inside the boundary — `resolveAttrRefs` * (./resolve.ts) runs as part of `discover()`, before `encodeEntitySet` is * ever called (in-process), or inside the sandboxed child, over just the * run-fallback subset (chant #1045 Phase 2, `./sandbox/run.ts`). This module * does not re-derive names; it only carries already-resolved ones across. * * Scope (see the chant#1045 PR description for the full list): resource and * property `Declarable`s, `StackOutput` (itself a marked `Declarable`), and * `LexiconOutput` (not a `Declarable` — it is carried as its own wire form) * all round-trip. A `ChildProjectInstance` (`nestedStack()`) does not — its * `outputs` field is a lazy `Proxy`, not data — and {@link encodeEntitySet} * throws rather than silently mis-encoding it. No corpus entry under * `examples/` or any lexicon's `examples/` directory uses `nestedStack()` * today. */ import { type Declarable } from "../declarable.js"; /** * A JSON-safe value tree: primitives, arrays/objects, and four marker shapes * that stand in for what can't cross a process boundary by identity: * * - `__attrRef` — an `AttrRef` (attribute reference), keyed by the parent * entity's already-resolved logical name. * - `__entityRef` — a WHOLE entity embedded by value (e.g. `DependsOn: * [otherResource]`), keyed by that entity's logical name. * - `__property` — a nested, unnamed property-kind `Declarable` (not tracked * in the entities map, so it carries its own `lexicon`/`entityType`/`props` * inline rather than by reference). * - `__intrinsic` — a lexicon intrinsic (`Sub`, `Join`, `Ref`, a pseudo- * parameter, gitlab's `!reference`, github's `${{ }}` `Expression`, …). * `value` is its already-called `toJSON()` output (Phase 0 confirmed every * intrinsic in this codebase implements `toJSON` and holds no * function/closure fields, so this is lossless). `yaml` is present only * when the intrinsic ALSO implements the optional `toYAML()` method the * gitlab and github serializers duck-type (`"toYAML" in value`) ahead of * the generic `toJSON` dispatch, for a YAML-native form that differs from * the JSON one (gitlab's `!reference [a, b]` tag vs. its plain `["a","b"]` * JSON array) — capturing only `toJSON()` would silently lose that native * form. `refs` additionally captures any `AttrRef`/whole-entity reference * found while walking the intrinsic's OWN fields (not through `toJSON()`) * — `buildDependencyGraph` and `detectCrossLexiconRefs`/`computeStackGraph` * walk raw entity property trees looking for an `AttrRef`-like value/a * tracked `Declarable`, not through `toJSON()`, so a ref nested inside e.g. * a `Sub` template needs to still be discoverable post-decode for * cross-lexicon output auto-detection and dependency ordering to keep * working. */ export type WireValue = null | string | number | boolean | WireValue[] | { __attrRef: { entity: string; attribute: string; }; } | { __entityRef: { entity: string; }; } | { __property: { lexicon: string; entityType: string; props?: WireValue; }; } | { __intrinsic: { value: WireValue; yaml?: WireValue; refs: WireValue[]; }; } | { [key: string]: WireValue; }; /** Wire form of one named, top-level `Declarable` entity (resource, property, or marked-Declarable output like `StackOutput`). */ export interface WireDeclarableEntity { form: "declarable"; name: string; lexicon: string; entityType: string; kind?: "resource" | "property" | "output"; props?: WireValue; attributes?: WireValue; /** * `.description` of every truthy own marker symbol on the entity (e.g. * `"chant.declarable"`, `"chant.stackOutput"`, a lexicon-specific one like * `"chant.aws.defaultTags"`). Every marker symbol in this codebase is * created with `Symbol.for(...)`, so `Symbol.for(description)` on decode * reliably recovers the SAME symbol a `X_MARKER in value` check looks for — * core doesn't need to know what any lexicon-specific marker means. */ markers: string[]; /** * Every other own enumerable field, beyond the fixed `lexicon`/`entityType`/ * `kind`/`props`/`attributes` shape — covers both a resource's per-attribute * `AttrRef` fields (`vpcId`, `arn`, …) and plain-data fields on marker- * Declarables that aren't built via `createResource`/`createProperty` * (`StackOutput.sourceRef`/`description`, `DefaultTags.tags`, * `Parameter.parameterType`/`description`/`defaultValue`, …). */ extra?: Record; } /** Wire form of one named `LexiconOutput` entity — not a `Declarable`, so it's carried as its own shape rather than forced into {@link WireDeclarableEntity}. */ export interface WireLexiconOutputEntity { form: "lexiconOutput"; name: string; outputName: string; /** The wrapped `AttrRef`, `Intrinsic`, or literal — encodes to `{__attrRef}`, `{__intrinsic}`, or a plain JSON primitive respectively (chant #1121; see {@link WireValue}). */ ref: WireValue; } export type WireEntity = WireDeclarableEntity | WireLexiconOutputEntity; /** A discovered, named, ref-resolved entity set, as pure JSON. */ export interface EntitySetWire { entities: WireEntity[]; } /** * Encode a discovered, named, ref-resolved entities map into pure JSON. * * @throws if any entity is a `ChildProjectInstance` (`nestedStack()`) — its * `outputs` field is a lazy `Proxy`, not representable as data. No corpus * entry uses it today (chant#1045 Phase 1). */ export declare function encodeEntitySet(entities: Map): EntitySetWire; /** * Decode a JSON entity set (see {@link encodeEntitySet}) back into a live * `Map`, functionally indistinguishable from what * `discover()` would have produced in-process. */ export declare function decodeEntitySet(wire: EntitySetWire): Map; //# sourceMappingURL=entity-wire-codec.d.ts.map