@suss/contract-terraform 0.32.0 → 0.33.1

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/index.js CHANGED
@@ -1,15 +1,371 @@
1
1
  // src/index.ts
2
2
  import fs from "fs";
3
3
  import path from "path";
4
- import hcl2 from "hcl2-parser";
5
4
  import semver from "semver";
6
5
  import {
6
+ ecsContainerInstanceName,
7
+ hasNameHole,
7
8
  messageBusBinding,
8
9
  metricBinding,
9
10
  namePatternFromSub,
10
- storageBinding
11
+ PLATFORM_INJECTED_ENV_VARS,
12
+ parseHandler,
13
+ runtimeConfigBinding,
14
+ storageBinding,
15
+ withRuntimeContractMetadata
11
16
  } from "@suss/behavioral-ir";
12
17
 
18
+ // src/hclDocument.ts
19
+ import hcl2 from "hcl2-parser";
20
+ var WRAPPER = "value";
21
+ function parseHclDocument(source) {
22
+ let read;
23
+ try {
24
+ const [parsed] = hcl2.parseToObject(source);
25
+ read = parsed;
26
+ } catch {
27
+ return null;
28
+ }
29
+ return typeof read === "object" && read !== null && !Array.isArray(read) ? read : null;
30
+ }
31
+ function parseHclExpression(expression) {
32
+ return parseHclDocument(`${WRAPPER} = ${expression}`)?.[WRAPPER] ?? null;
33
+ }
34
+
35
+ // src/hclValue.ts
36
+ function arrayOf(value) {
37
+ if (value === void 0 || value === null) {
38
+ return [];
39
+ }
40
+ return Array.isArray(value) ? value : [value];
41
+ }
42
+ function asRecord(value) {
43
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
44
+ }
45
+ function stringOf(value) {
46
+ return typeof value === "string" ? value : null;
47
+ }
48
+ function recordsIn(value) {
49
+ return arrayOf(value).map(asRecord).filter((record) => record !== null);
50
+ }
51
+ function mapStrings(value, leaf) {
52
+ if (typeof value === "string") {
53
+ return leaf(value);
54
+ }
55
+ if (Array.isArray(value)) {
56
+ return value.map((entry) => mapStrings(entry, leaf));
57
+ }
58
+ const record = asRecord(value);
59
+ if (record === null) {
60
+ return value;
61
+ }
62
+ return Object.fromEntries(
63
+ Object.entries(record).map(([name, entry]) => [
64
+ name,
65
+ mapStrings(entry, leaf)
66
+ ])
67
+ );
68
+ }
69
+ function someString(value, test) {
70
+ if (typeof value === "string") {
71
+ return test(value);
72
+ }
73
+ if (Array.isArray(value)) {
74
+ return value.some((entry) => someString(entry, test));
75
+ }
76
+ return Object.values(asRecord(value) ?? {}).some(
77
+ (entry) => someString(entry, test)
78
+ );
79
+ }
80
+
81
+ // src/mapValue.ts
82
+ var WHOLE_INTERPOLATION = /^\$\{([\s\S]*)\}$/;
83
+ var MERGE_CALL = /^merge\(([\s\S]*)\)$/;
84
+ var LOCAL_MAP = /^local\.([A-Za-z_][\w-]*)$/;
85
+ var VARIABLE_MAP = /^var\.([A-Za-z_][\w-]*)$/;
86
+ var CHAIN_LIMIT = 4;
87
+ var OPENERS = "([{";
88
+ var CLOSERS = ")]}";
89
+ function statedMap(value, scope) {
90
+ return mapFrom(value, scope, 0);
91
+ }
92
+ function mapFrom(value, scope, depth) {
93
+ if (depth > CHAIN_LIMIT) {
94
+ return null;
95
+ }
96
+ const record = asRecord(value);
97
+ if (record !== null) {
98
+ return record;
99
+ }
100
+ if (typeof value !== "string") {
101
+ return null;
102
+ }
103
+ const inner = WHOLE_INTERPOLATION.exec(value.trim());
104
+ return inner === null ? null : mapFromExpression(inner[1].trim(), scope, depth);
105
+ }
106
+ function mapFromExpression(expression, scope, depth) {
107
+ const merged = MERGE_CALL.exec(expression);
108
+ if (merged !== null) {
109
+ return mergedMaps(splitArguments(merged[1]), scope, depth);
110
+ }
111
+ const local = LOCAL_MAP.exec(expression);
112
+ if (local !== null) {
113
+ return mapFrom(scope.locals[local[1]], scope, depth + 1);
114
+ }
115
+ const variable = VARIABLE_MAP.exec(expression);
116
+ if (variable !== null) {
117
+ const name = variable[1];
118
+ return mapFrom(scope.arguments[name], scope, depth + 1) ?? mapFrom(scope.defaults[name], scope, depth + 1);
119
+ }
120
+ return null;
121
+ }
122
+ function mergedMaps(argumentTexts, scope, depth) {
123
+ const merged = {};
124
+ for (const text of argumentTexts) {
125
+ const stated = mapFrom(parseHclExpression(text), scope, depth + 1);
126
+ if (stated === null) {
127
+ return null;
128
+ }
129
+ Object.assign(merged, stated);
130
+ }
131
+ return merged;
132
+ }
133
+ function splitArguments(text) {
134
+ const found = [];
135
+ let depth = 0;
136
+ let quoted = false;
137
+ let start = 0;
138
+ for (let at = 0; at < text.length; at += 1) {
139
+ const character = text[at];
140
+ if (quoted) {
141
+ quoted = character !== '"' || text[at - 1] === "\\";
142
+ continue;
143
+ }
144
+ if (character === '"') {
145
+ quoted = true;
146
+ } else if (OPENERS.includes(character)) {
147
+ depth += 1;
148
+ } else if (CLOSERS.includes(character)) {
149
+ depth -= 1;
150
+ } else if (character === "," && depth === 0) {
151
+ found.push(text.slice(start, at));
152
+ start = at + 1;
153
+ }
154
+ }
155
+ found.push(text.slice(start));
156
+ return found.map((argument) => argument.trim()).filter((argument) => argument.length > 0);
157
+ }
158
+
159
+ // src/references.ts
160
+ var SUB_TOKEN = /\$\{([^}]*)\}/g;
161
+ function replaceInterpolations(text, settle) {
162
+ return text.replace(
163
+ SUB_TOKEN,
164
+ (written, inner) => settle(inner.trim()) ?? written
165
+ );
166
+ }
167
+ function interpolatedReferences(text) {
168
+ return [...text.matchAll(SUB_TOKEN)].map(
169
+ (match) => match[1].trim()
170
+ );
171
+ }
172
+ var RESOURCE_ATTRIBUTE = /^([A-Za-z][\w-]*)\.([A-Za-z_][\w-]*)\.([A-Za-z_][\w-]*)$/;
173
+ var WHOLE_REFERENCE = /^\$\{([^}]*)\}$/;
174
+ var LOCAL_VALUE = /^local\.([A-Za-z_][\w-]*)$/;
175
+ var VARIABLE_VALUE = /^var\.([A-Za-z_][\w-]*)$/;
176
+ var CHAIN_LIMIT2 = 4;
177
+ function referenceScope(opts) {
178
+ const resources = /* @__PURE__ */ new Map();
179
+ for (const [resourceType, label, body] of opts.resources) {
180
+ const address = `${resourceType}.${label}`;
181
+ if (!resources.has(address)) {
182
+ resources.set(address, body);
183
+ }
184
+ }
185
+ return {
186
+ resources,
187
+ locals: firstOfEach(opts.locals ?? []),
188
+ arguments: opts.arguments ?? {},
189
+ defaults: opts.defaults ?? {},
190
+ namePrefix: opts.namePrefix ?? ""
191
+ };
192
+ }
193
+ function firstOfEach(blocks) {
194
+ const stated = {};
195
+ for (const block of blocks) {
196
+ for (const [name, value] of Object.entries(block)) {
197
+ if (!(name in stated)) {
198
+ stated[name] = value;
199
+ }
200
+ }
201
+ }
202
+ return stated;
203
+ }
204
+ function referencedResource(value, scope) {
205
+ const whole = WHOLE_REFERENCE.exec(value.trim());
206
+ if (whole === null) {
207
+ return null;
208
+ }
209
+ const parsed = RESOURCE_ATTRIBUTE.exec(whole[1].trim());
210
+ if (parsed === null) {
211
+ return null;
212
+ }
213
+ const [, resourceType, label] = parsed;
214
+ return scope.resources.has(`${resourceType}.${label}`) ? `${scope.namePrefix}${label}` : null;
215
+ }
216
+ function resolveReferences(value, scope) {
217
+ return expand(value, scope, []) ?? value;
218
+ }
219
+ function expand(value, scope, resolving) {
220
+ let cycled = false;
221
+ const expandOne = (reference) => {
222
+ if (resolving.includes(reference)) {
223
+ cycled = true;
224
+ return null;
225
+ }
226
+ const stated = statedValue(reference, scope);
227
+ if (stated === null || resolving.length >= CHAIN_LIMIT2) {
228
+ return null;
229
+ }
230
+ const nested = expand(stated, scope, [...resolving, reference]);
231
+ if (nested === null) {
232
+ cycled = true;
233
+ return null;
234
+ }
235
+ return nested;
236
+ };
237
+ const expanded = replaceInterpolations(value, expandOne);
238
+ return cycled ? null : expanded;
239
+ }
240
+ function statedValue(reference, scope) {
241
+ const local = LOCAL_VALUE.exec(reference);
242
+ if (local !== null) {
243
+ return stringOrNull(scope.locals[local[1]]);
244
+ }
245
+ const variable = VARIABLE_VALUE.exec(reference);
246
+ if (variable !== null) {
247
+ return stringOrNull(scope.arguments[variable[1]]);
248
+ }
249
+ const parsed = RESOURCE_ATTRIBUTE.exec(reference);
250
+ if (parsed === null) {
251
+ return null;
252
+ }
253
+ const [, resourceType, label, attribute] = parsed;
254
+ return stringOrNull(
255
+ scope.resources.get(`${resourceType}.${label}`)?.[attribute]
256
+ );
257
+ }
258
+ function stringOrNull(value) {
259
+ return typeof value === "string" ? value : null;
260
+ }
261
+
262
+ // src/blockExpansion.ts
263
+ var DYNAMIC = "dynamic";
264
+ var BARE_NAME = /^\$\{([A-Za-z_][\w-]*)\}$/;
265
+ var FOR_EXPRESSION = /^\$\{\s*\[\s*for\s+([A-Za-z_]\w*)\s*,\s*([A-Za-z_]\w*)\s+in\s+([^:]+):([\s\S]*)\]\s*\}$/;
266
+ function dynamicBlocks(body, block, scope) {
267
+ const written = [];
268
+ const labelled = arrayOf(body[DYNAMIC]).flatMap(
269
+ (entry) => recordsIn(asRecord(entry)?.[block])
270
+ );
271
+ for (const declared of labelled) {
272
+ const stated = statedMap(declared.for_each, scope);
273
+ if (stated === null) {
274
+ continue;
275
+ }
276
+ const iterator = iteratorName(declared, block);
277
+ for (const [key, value] of Object.entries(stated)) {
278
+ const settle = entrySettle(iterator, key, value);
279
+ for (const content of recordsIn(declared.content)) {
280
+ written.push(...filledBlock(content, settle, [iterator]));
281
+ }
282
+ }
283
+ }
284
+ return written;
285
+ }
286
+ function filledBlock(content, settle, iterators) {
287
+ const filled = substituted(content, settle);
288
+ return mentions(filled, iterators) ? [] : [filled];
289
+ }
290
+ function iteratedRecords(value, scope) {
291
+ if (typeof value !== "string") {
292
+ return null;
293
+ }
294
+ const parsed = FOR_EXPRESSION.exec(value.trim());
295
+ if (parsed === null) {
296
+ return null;
297
+ }
298
+ const [, keyName, valueName, collection, body] = parsed;
299
+ const stated = statedMap(`\${${collection.trim()}}`, scope);
300
+ if (stated === null) {
301
+ return null;
302
+ }
303
+ const template = parseBody(body);
304
+ if (template === null) {
305
+ return null;
306
+ }
307
+ return Object.entries(stated).flatMap(
308
+ ([key, entry]) => filledBlock(
309
+ template,
310
+ pairSettle(keyName, valueName, key, entry),
311
+ [keyName, valueName]
312
+ )
313
+ );
314
+ }
315
+ function parseBody(body) {
316
+ const trimmed = body.trim();
317
+ return trimmed.startsWith("{") ? asRecord(parseHclExpression(trimmed)) : null;
318
+ }
319
+ function iteratorName(declared, block) {
320
+ const stated = declared.iterator;
321
+ if (typeof stated !== "string") {
322
+ return block;
323
+ }
324
+ const bare = BARE_NAME.exec(stated.trim());
325
+ return bare === null ? block : bare[1];
326
+ }
327
+ function entrySettle(iterator, key, value) {
328
+ return (reference) => {
329
+ const steps = reference.split(".");
330
+ if (steps[0] !== iterator) {
331
+ return null;
332
+ }
333
+ if (steps.length === 2 && steps[1] === "key") {
334
+ return key;
335
+ }
336
+ return steps[1] === "value" ? fieldOf(value, steps.slice(2)) : null;
337
+ };
338
+ }
339
+ function pairSettle(keyName, valueName, key, value) {
340
+ return (reference) => {
341
+ const steps = reference.split(".");
342
+ if (steps.length === 1 && steps[0] === keyName) {
343
+ return key;
344
+ }
345
+ return steps[0] === valueName ? fieldOf(value, steps.slice(1)) : null;
346
+ };
347
+ }
348
+ function fieldOf(value, steps) {
349
+ if (steps.length === 0) {
350
+ return typeof value === "string" ? value : null;
351
+ }
352
+ if (steps.length > 1) {
353
+ return null;
354
+ }
355
+ const field = asRecord(value)?.[steps[0]];
356
+ return typeof field === "string" ? field : null;
357
+ }
358
+ function substituted(value, settle) {
359
+ const fill = (text) => replaceInterpolations(text, settle);
360
+ return mapStrings(value, fill);
361
+ }
362
+ function mentions(value, iterators) {
363
+ const mentionsOne = (text) => interpolatedReferences(text).some(
364
+ (reference) => iterators.includes(reference.split(".")[0] ?? "")
365
+ );
366
+ return someString(value, mentionsOne);
367
+ }
368
+
13
369
  // src/filterQuery.ts
14
370
  var OPERATORS = ["!=", ">=", "<=", "=", ">", "<", ":"];
15
371
  var KEYWORDS = /* @__PURE__ */ new Set(["AND", "OR", "NOT"]);
@@ -271,96 +627,147 @@ var TokenReader = class {
271
627
  }
272
628
  };
273
629
 
274
- // src/references.ts
275
- var SUB_TOKEN = /\$\{([^}]*)\}/g;
276
- var RESOURCE_ATTRIBUTE = /^([A-Za-z][\w-]*)\.([A-Za-z_][\w-]*)\.([A-Za-z_][\w-]*)$/;
277
- var LOCAL_VALUE = /^local\.([A-Za-z_][\w-]*)$/;
278
- var LOCALS = "local";
279
- var CHAIN_LIMIT = 4;
280
- function referenceScope(resources, locals = []) {
281
- const scope = /* @__PURE__ */ new Map();
282
- for (const [resourceType, label, body] of resources) {
283
- const address = `${resourceType}.${label}`;
284
- if (!scope.has(address)) {
285
- scope.set(address, body);
286
- }
630
+ // src/jsonAttribute.ts
631
+ var JSON_ENCODE = /^\$\{\s*jsonencode\((.*)\)\s*\}$/s;
632
+ function jsonAttributeValue(value) {
633
+ if (typeof value !== "string") {
634
+ return null;
287
635
  }
288
- const stated = {};
289
- for (const block of locals) {
290
- for (const [name, value] of Object.entries(block)) {
291
- if (!(name in stated)) {
292
- stated[name] = value;
293
- }
294
- }
636
+ const literal = parseJson(value);
637
+ if (literal !== null) {
638
+ return literal;
295
639
  }
296
- scope.set(LOCALS, stated);
297
- return scope;
298
- }
299
- function resolveReferences(value, scope) {
300
- return expand(value, scope, []) ?? value;
301
- }
302
- function expand(value, scope, resolving) {
303
- let cycled = false;
304
- const expanded = value.replace(SUB_TOKEN, (written, inner) => {
305
- const reference = inner.trim();
306
- if (resolving.includes(reference)) {
307
- cycled = true;
308
- return written;
309
- }
310
- const stated = statedValue(reference, scope);
311
- if (stated === null || resolving.length >= CHAIN_LIMIT) {
312
- return written;
313
- }
314
- const nested = expand(stated, scope, [...resolving, reference]);
315
- if (nested === null) {
316
- cycled = true;
317
- return written;
318
- }
319
- return nested;
320
- });
321
- return cycled ? null : expanded;
640
+ const encoded = JSON_ENCODE.exec(value);
641
+ return encoded === null ? null : parseHclExpression(encoded[1]);
322
642
  }
323
- function statedValue(reference, scope) {
324
- const local = LOCAL_VALUE.exec(reference);
325
- if (local !== null) {
326
- const stated2 = scope.get(LOCALS)?.[local[1]];
327
- return typeof stated2 === "string" ? stated2 : null;
328
- }
329
- const parsed = RESOURCE_ATTRIBUTE.exec(reference);
330
- if (parsed === null) {
643
+ function parseJson(text) {
644
+ try {
645
+ return JSON.parse(text);
646
+ } catch {
331
647
  return null;
332
648
  }
333
- const [, resourceType, label, attribute] = parsed;
334
- const stated = scope.get(`${resourceType}.${label}`)?.[attribute];
335
- return typeof stated === "string" ? stated : null;
336
649
  }
337
650
 
338
651
  // src/index.ts
339
652
  function terraformToSummaries(source, sourceFile, options) {
340
- return summariesForFiles([{ source, sourceFile }], options);
653
+ return moduleSummaries(
654
+ moduleOf({
655
+ files: [{ source, sourceFile }],
656
+ directory: path.dirname(sourceFile),
657
+ namePrefix: "",
658
+ arguments: {},
659
+ ancestors: []
660
+ }),
661
+ options
662
+ );
341
663
  }
342
664
  function terraformFileToSummaries(target, options) {
343
- const files = fs.statSync(target).isDirectory() ? fs.readdirSync(target).filter((name) => name.endsWith(".tf")).map((name) => path.join(target, name)) : [target];
344
- return summariesForFiles(
345
- files.map((file) => ({
346
- source: fs.readFileSync(file, "utf8"),
347
- sourceFile: file
348
- })),
665
+ const directory = fs.statSync(target).isDirectory() ? target : path.dirname(target);
666
+ const files = fs.statSync(target).isDirectory() ? sourceFilesIn(target) : [readSourceFile(target)];
667
+ return moduleSummaries(
668
+ moduleOf({
669
+ files,
670
+ directory,
671
+ namePrefix: "",
672
+ arguments: {},
673
+ ancestors: [directory]
674
+ }),
349
675
  options
350
676
  );
351
677
  }
352
- function summariesForFiles(files, options) {
353
- const parsed = files.map((file) => parseSource(file)).filter((file) => file !== null);
354
- const scope = referenceScope(
355
- parsed.flatMap((file) => file.resources),
356
- parsed.flatMap((file) => file.locals)
357
- );
678
+ var LOCAL_SOURCE = /^\.\.?[/\\]/;
679
+ var MODULE_SOURCE = "source";
680
+ var MODULE_DEPTH_LIMIT = 8;
681
+ function sourceFilesIn(directory) {
682
+ return fs.readdirSync(directory).filter((name) => name.endsWith(".tf")).map((name) => readSourceFile(path.join(directory, name)));
683
+ }
684
+ function readSourceFile(file) {
685
+ return { source: fs.readFileSync(file, "utf8"), sourceFile: file };
686
+ }
687
+ function moduleOf(opts) {
688
+ const files = opts.files.map((file) => parseSource(file)).filter((file) => file !== null);
689
+ const scope = referenceScope({
690
+ resources: files.flatMap((file) => file.resources),
691
+ locals: files.flatMap((file) => file.locals),
692
+ defaults: Object.assign({}, ...files.map((file) => file.defaults)),
693
+ arguments: opts.arguments,
694
+ namePrefix: opts.namePrefix
695
+ });
696
+ const children = [];
697
+ for (const [label, call] of files.flatMap((file) => file.modules)) {
698
+ const child = childModule({ ...opts, scope, label, call });
699
+ if (child === null) {
700
+ continue;
701
+ }
702
+ children.push(child);
703
+ scope.resources.set(`module.${label}`, outputValues(child));
704
+ }
705
+ return { files, scope, children };
706
+ }
707
+ function childModule(opts) {
708
+ const source = stringOf(opts.call[MODULE_SOURCE]);
709
+ if (source === null || !LOCAL_SOURCE.test(source)) {
710
+ return null;
711
+ }
712
+ const directory = path.resolve(opts.directory, source);
713
+ if (opts.ancestors.includes(directory) || opts.ancestors.length >= MODULE_DEPTH_LIMIT || !isDirectory(directory)) {
714
+ return null;
715
+ }
716
+ return moduleOf({
717
+ files: sourceFilesIn(directory),
718
+ directory,
719
+ namePrefix: `${opts.namePrefix}module.${opts.label}.`,
720
+ arguments: passedArguments(opts.call, opts.scope),
721
+ ancestors: [...opts.ancestors, directory]
722
+ });
723
+ }
724
+ function isDirectory(target) {
725
+ return fs.existsSync(target) && fs.statSync(target).isDirectory();
726
+ }
727
+ function passedArguments(call, parent) {
728
+ const passed = {};
729
+ for (const [name, value] of Object.entries(call)) {
730
+ if (name === MODULE_SOURCE) {
731
+ continue;
732
+ }
733
+ const stated = asRecord(value);
734
+ if (stated !== null) {
735
+ passed[name] = resolvedThroughout(stated, parent);
736
+ continue;
737
+ }
738
+ const written = stringOf(value);
739
+ const settled = written === null ? null : resolveReferences(written, parent);
740
+ if (settled !== null && !settled.includes("${")) {
741
+ passed[name] = settled;
742
+ }
743
+ }
744
+ return passed;
745
+ }
746
+ function resolvedThroughout(value, scope) {
747
+ const resolveHere = (text) => resolveReferences(text, scope);
748
+ return mapStrings(value, resolveHere);
749
+ }
750
+ function outputValues(child) {
751
+ const stated = {};
752
+ for (const [name, value] of child.files.flatMap((file) => file.outputs)) {
753
+ const written = stringOf(value);
754
+ if (written === null || name in stated) {
755
+ continue;
756
+ }
757
+ const settled = resolveReferences(written, child.scope);
758
+ if (!settled.includes("${")) {
759
+ stated[name] = settled;
760
+ }
761
+ }
762
+ return stated;
763
+ }
764
+ function moduleSummaries(module, options) {
358
765
  const summaries = [];
359
- for (const file of parsed) {
766
+ for (const file of module.files) {
360
767
  for (const [resourceType, label, body] of file.resources) {
361
768
  for (const pack of options.packs) {
362
- const pattern = patternFor(pack, resourceType, file.constraints);
363
- if (pattern === void 0 || !entryApplies(pattern, body)) {
769
+ const pattern = patternFor(pack, resourceType, file.constraints, body);
770
+ if (pattern === void 0) {
364
771
  continue;
365
772
  }
366
773
  summaries.push(
@@ -370,23 +777,20 @@ function summariesForFiles(files, options) {
370
777
  body,
371
778
  sourceFile: file.sourceFile,
372
779
  resourceType,
373
- scope
780
+ scope: module.scope,
781
+ options
374
782
  })
375
783
  );
376
784
  }
377
785
  }
378
786
  }
787
+ for (const child of module.children) {
788
+ summaries.push(...moduleSummaries(child, options));
789
+ }
379
790
  return summaries;
380
791
  }
381
792
  function parseSource(file) {
382
- let read;
383
- try {
384
- const [parsed] = hcl2.parseToObject(file.source);
385
- read = parsed;
386
- } catch {
387
- return null;
388
- }
389
- const document = asRecord(read);
793
+ const document = parseHclDocument(file.source);
390
794
  if (document === null) {
391
795
  return null;
392
796
  }
@@ -394,9 +798,43 @@ function parseSource(file) {
394
798
  sourceFile: file.sourceFile,
395
799
  constraints: providerConstraints(document),
396
800
  resources: resourcesIn(document),
397
- locals: localsIn(document)
801
+ locals: localsIn(document),
802
+ defaults: variableDefaults(document),
803
+ modules: labelledBlocks(document.module),
804
+ outputs: labelledBlocks(document.output).map(([name, block]) => [
805
+ name,
806
+ block.value
807
+ ])
398
808
  };
399
809
  }
810
+ function labelledBlocks(declared) {
811
+ const found = [];
812
+ for (const group of arrayOf(declared)) {
813
+ for (const [label, bodies] of Object.entries(asRecord(group) ?? {})) {
814
+ for (const body of arrayOf(bodies)) {
815
+ const read = asRecord(body);
816
+ if (read !== null) {
817
+ found.push([label, read]);
818
+ }
819
+ }
820
+ }
821
+ }
822
+ return found;
823
+ }
824
+ function variableDefaults(document) {
825
+ const defaults = {};
826
+ for (const group of arrayOf(document.variable)) {
827
+ for (const [name, declared] of Object.entries(asRecord(group) ?? {})) {
828
+ for (const block of arrayOf(declared)) {
829
+ const stated = asRecord(block)?.default;
830
+ if (stated !== void 0 && !(name in defaults)) {
831
+ defaults[name] = stated;
832
+ }
833
+ }
834
+ }
835
+ }
836
+ return defaults;
837
+ }
400
838
  function providerConstraints(document) {
401
839
  const constraints = /* @__PURE__ */ new Map();
402
840
  for (const block of arrayOf(document.terraform)) {
@@ -417,23 +855,21 @@ function providerConstraints(document) {
417
855
  }
418
856
  return constraints;
419
857
  }
420
- function patternFor(pack, resourceType, constraints) {
858
+ function patternFor(pack, resourceType, constraints, body) {
421
859
  const pinned = constraints.get(pack.provider);
422
- return pack.resources.find((pattern) => {
423
- if (pattern.resource !== resourceType) {
424
- return false;
425
- }
426
- if (pinned === void 0) {
427
- return true;
428
- }
429
- try {
430
- return semver.intersects(pinned, pattern.providerVersions, {
431
- loose: true
432
- });
433
- } catch {
434
- return true;
435
- }
436
- });
860
+ return pack.resources.find(
861
+ (pattern) => pattern.resource === resourceType && versionAllows(pinned, pattern.providerVersions) && entryApplies(pattern, body)
862
+ );
863
+ }
864
+ function versionAllows(pinned, providerVersions) {
865
+ if (pinned === void 0) {
866
+ return true;
867
+ }
868
+ try {
869
+ return semver.intersects(pinned, providerVersions, { loose: true });
870
+ } catch {
871
+ return true;
872
+ }
437
873
  }
438
874
  function entryApplies(pattern, body) {
439
875
  const gate = pattern.appliesWhen;
@@ -445,7 +881,7 @@ function entryApplies(pattern, body) {
445
881
  return gate.whenUnset === "read";
446
882
  }
447
883
  const text = stringOf(value);
448
- return text !== null && gate.equals.includes(text);
884
+ return text !== null && (gate.equals ?? []).includes(text);
449
885
  }
450
886
  var READERS = {
451
887
  storage: (site, boundary) => {
@@ -456,7 +892,8 @@ var READERS = {
456
892
  },
457
893
  "message-bus": (site, boundary) => [busSummary({ ...site, boundary })],
458
894
  metric: (site, boundary) => [metricSummary({ ...site, boundary })],
459
- "metric-reading": (site, boundary) => readingSummaries({ ...site, boundary })
895
+ "metric-reading": (site, boundary) => readingSummaries({ ...site, boundary }),
896
+ deployable: (site, boundary) => deployableSummaries({ ...site, boundary })
460
897
  };
461
898
  function summariesFor(opts) {
462
899
  const { pattern, ...site } = opts;
@@ -465,9 +902,11 @@ function summariesFor(opts) {
465
902
  }
466
903
  function storageSummary(opts) {
467
904
  const { boundary, label, shape, types } = opts;
468
- const address = `${opts.resourceType}.${label}`;
905
+ const address = `${opts.scope.namePrefix}${opts.resourceType}.${label}`;
906
+ const storageSystem = storageSystemOf(opts.body, boundary, opts.scope);
469
907
  const declaresContainer = boundary.declares !== "store";
470
908
  const physicalTable = !declaresContainer || boundary.nameAttribute === void 0 ? null : namePattern(opts.body[boundary.nameAttribute], opts.scope);
909
+ const listedFields = jsonFields(opts.body, boundary);
471
910
  return {
472
911
  kind: "library",
473
912
  location: {
@@ -480,35 +919,102 @@ function storageSummary(opts) {
480
919
  exportPath: null,
481
920
  boundaryBinding: storageBinding({
482
921
  recognition: "terraform",
483
- storageSystem: boundary.storageSystem,
922
+ storageSystem,
484
923
  ...boundary.transport !== void 0 ? { transport: boundary.transport } : {},
485
- scope: "default",
924
+ scope: containerScope(opts.body, boundary, opts.scope),
486
925
  container: declaresContainer ? label : null,
487
926
  accessPath: shape.accessPath
488
927
  })
489
928
  },
490
929
  inputs: [],
491
930
  transitions: [],
492
- gaps: [],
931
+ gaps: unknownEngineGaps(storageSystem, boundary),
493
932
  confidence: { source: "declared", level: "high" },
494
933
  metadata: {
495
934
  storageContract: {
496
- // A way in that copies part of an item has every field it will
497
- // ever have, whatever the container itself stores.
498
- fieldSet: shape.serves === null ? boundary.fieldSet : "exhaustive",
499
- ...boundary.identifies === void 0 ? {} : {
500
- identifies: { kind: "keyFields", fields: shape.keyFields },
501
- fields: (shape.serves ?? shape.keyFields).map((field) => ({
502
- name: field,
503
- ...types.has(field) ? { type: types.get(field) } : {},
504
- ...shape.keyFields.includes(field) ? { primary: true } : {}
505
- }))
506
- },
935
+ fieldSet: declaredFieldSet(boundary, shape, listedFields),
936
+ ...contractFields(boundary, shape, types, listedFields),
507
937
  ...physicalTable !== null ? { physicalTable } : {}
508
938
  }
509
939
  }
510
940
  };
511
941
  }
942
+ function storageSystemOf(body, boundary, scope) {
943
+ const spec = boundary.storageSystem;
944
+ if (typeof spec === "string") {
945
+ return spec;
946
+ }
947
+ return meaningOf(body, spec, scope) ?? null;
948
+ }
949
+ function unknownEngineGaps(storageSystem, boundary) {
950
+ const spec = boundary.storageSystem;
951
+ if (storageSystem !== null || typeof spec === "string") {
952
+ return [];
953
+ }
954
+ return [
955
+ {
956
+ type: "unreadOutcome",
957
+ conditions: [],
958
+ consequence: "unknown",
959
+ description: `"${spec.attribute}" states an engine this run could not settle, so which store this is stays unknown and it pairs with an access on any engine.`
960
+ }
961
+ ];
962
+ }
963
+ function contractFields(boundary, shape, types, listedFields) {
964
+ if (boundary.identifies !== void 0) {
965
+ return {
966
+ identifies: { kind: "keyFields", fields: shape.keyFields },
967
+ fields: (shape.serves ?? shape.keyFields).map((field) => ({
968
+ name: field,
969
+ ...types.has(field) ? { type: types.get(field) } : {},
970
+ ...shape.keyFields.includes(field) ? { primary: true } : {}
971
+ }))
972
+ };
973
+ }
974
+ return listedFields === null ? {} : { fields: listedFields };
975
+ }
976
+ function declaredFieldSet(boundary, shape, listedFields) {
977
+ if (shape.serves !== null) {
978
+ return "exhaustive";
979
+ }
980
+ return listedFields === null ? boundary.fieldSet : "exhaustive";
981
+ }
982
+ function jsonFields(body, boundary) {
983
+ const spec = boundary.fieldsFromJson;
984
+ if (spec === void 0) {
985
+ return null;
986
+ }
987
+ const stated = jsonAttributeValue(valueAt(body, spec.attribute));
988
+ if (!Array.isArray(stated)) {
989
+ return null;
990
+ }
991
+ const fields = stated.flatMap((entry) => {
992
+ const field = asRecord(entry);
993
+ const name = field === null ? null : stringOf(field[spec.nameKey]);
994
+ if (field === null || name === null) {
995
+ return [];
996
+ }
997
+ const type = spec.typeKey === void 0 ? null : stringOf(field[spec.typeKey]);
998
+ return [
999
+ {
1000
+ name,
1001
+ ...type !== null ? { type } : {},
1002
+ ...spec.requires === void 0 ? {} : { nullable: !alwaysSet(field, spec.requires) }
1003
+ }
1004
+ ];
1005
+ });
1006
+ return fields.length === 0 ? null : fields;
1007
+ }
1008
+ function alwaysSet(field, requires) {
1009
+ const stated = stringOf(field[requires.key]);
1010
+ return stated !== null && requires.values.includes(stated);
1011
+ }
1012
+ function containerScope(body, boundary, scope) {
1013
+ if (boundary.scopeAttribute === void 0) {
1014
+ return "default";
1015
+ }
1016
+ return namePattern(valueAt(body, boundary.scopeAttribute), scope) ?? "default";
1017
+ }
512
1018
  function busSummary(opts) {
513
1019
  const { boundary, label } = opts;
514
1020
  const physicalName = boundary.nameAttribute === void 0 ? null : namePattern(opts.body[boundary.nameAttribute], opts.scope);
@@ -520,7 +1026,7 @@ function busSummary(opts) {
520
1026
  exportName: null
521
1027
  },
522
1028
  identity: {
523
- name: `${opts.resourceType}.${label}`,
1029
+ name: `${opts.scope.namePrefix}${opts.resourceType}.${label}`,
524
1030
  exportPath: null,
525
1031
  boundaryBinding: messageBusBinding({
526
1032
  recognition: "terraform",
@@ -541,14 +1047,26 @@ function busSummary(opts) {
541
1047
  }
542
1048
  };
543
1049
  }
544
- var NAME_HOLE = "{name}";
1050
+ var TEMPLATE_HOLE = /\{([^{}]+)\}/g;
1051
+ function metricTypeFrom(template, body, scope) {
1052
+ let missing = false;
1053
+ const spelled = template.replace(TEMPLATE_HOLE, (_whole, path2) => {
1054
+ const stated = namePattern(valueAt(body, path2), scope);
1055
+ if (stated === null) {
1056
+ missing = true;
1057
+ return "";
1058
+ }
1059
+ return stated;
1060
+ });
1061
+ return missing ? null : spelled;
1062
+ }
545
1063
  function metricSummary(opts) {
546
1064
  const { boundary } = opts;
547
- const declaredName = namePattern(
548
- opts.body[boundary.nameAttribute],
1065
+ const metricType = metricTypeFrom(
1066
+ boundary.metricTypeTemplate,
1067
+ opts.body,
549
1068
  opts.scope
550
1069
  );
551
- const metricType = declaredName === null ? null : boundary.metricTypeTemplate.replace(NAME_HOLE, declaredName);
552
1070
  return {
553
1071
  kind: "library",
554
1072
  location: {
@@ -557,7 +1075,7 @@ function metricSummary(opts) {
557
1075
  exportName: null
558
1076
  },
559
1077
  identity: {
560
- name: `${opts.resourceType}.${opts.label}`,
1078
+ name: `${opts.scope.namePrefix}${opts.resourceType}.${opts.label}`,
561
1079
  exportPath: null,
562
1080
  boundaryBinding: metricBinding({
563
1081
  recognition: "terraform",
@@ -569,12 +1087,14 @@ function metricSummary(opts) {
569
1087
  transitions: [],
570
1088
  gaps: [],
571
1089
  confidence: { source: "declared", level: "high" },
572
- metadata: { metricContract: metricContract(opts.body, boundary) }
1090
+ metadata: {
1091
+ metricContract: metricContract(opts.body, boundary, opts.scope)
1092
+ }
573
1093
  };
574
1094
  }
575
- function metricContract(body, boundary) {
576
- const values = meaningOf(body, boundary.values);
577
- const accumulates = meaningOf(body, boundary.accumulates);
1095
+ function metricContract(body, boundary, scope) {
1096
+ const values = meaningOf(body, boundary.values, scope);
1097
+ const accumulates = meaningOf(body, boundary.accumulates, scope);
578
1098
  return {
579
1099
  ...values !== void 0 ? { values } : {},
580
1100
  ...accumulates !== void 0 ? { accumulates } : {}
@@ -583,11 +1103,14 @@ function metricContract(body, boundary) {
583
1103
  function readingSummaries(opts) {
584
1104
  const { boundary } = opts;
585
1105
  const summaries = [];
586
- for (const reading of blocksAt(opts.body, boundary.readingBlocks)) {
587
- const query = stringOf(valueAt(reading, boundary.queryAttribute));
588
- for (const metricType of metricTypesIn(
589
- query,
590
- boundary.queryIdentityKey,
1106
+ for (const reading of blocksAt(
1107
+ opts.body,
1108
+ boundary.readingBlocks,
1109
+ opts.scope
1110
+ )) {
1111
+ for (const metricType of metricsRead(
1112
+ reading,
1113
+ boundary.identifies,
591
1114
  opts.scope
592
1115
  )) {
593
1116
  summaries.push({
@@ -598,7 +1121,7 @@ function readingSummaries(opts) {
598
1121
  exportName: null
599
1122
  },
600
1123
  identity: {
601
- name: `${opts.resourceType}.${opts.label}#${summaries.length}`,
1124
+ name: `${opts.scope.namePrefix}${opts.resourceType}.${opts.label}#${summaries.length}`,
602
1125
  exportPath: null,
603
1126
  boundaryBinding: metricBinding({
604
1127
  recognition: "terraform",
@@ -610,12 +1133,28 @@ function readingSummaries(opts) {
610
1133
  transitions: [],
611
1134
  gaps: [],
612
1135
  confidence: { source: "declared", level: "high" },
613
- metadata: { metricReading: metricReading(reading, boundary) }
1136
+ metadata: {
1137
+ metricReading: metricReading(reading, boundary, opts.scope)
1138
+ }
614
1139
  });
615
1140
  }
616
1141
  }
617
1142
  return summaries;
618
1143
  }
1144
+ var READING_IDENTITIES = {
1145
+ attributes: (reading, identity, scope) => [
1146
+ metricTypeFrom(identity.template, reading, scope)
1147
+ ],
1148
+ query: (reading, identity, scope) => metricTypesIn(
1149
+ stringOf(valueAt(reading, identity.attribute)),
1150
+ identity.key,
1151
+ scope
1152
+ )
1153
+ };
1154
+ function metricsRead(reading, identity, scope) {
1155
+ const read = READING_IDENTITIES[identity.from];
1156
+ return read(reading, identity, scope);
1157
+ }
619
1158
  function metricTypesIn(query, identityKey, scope) {
620
1159
  if (query === null) {
621
1160
  return [null];
@@ -627,23 +1166,212 @@ function metricTypesIn(query, identityKey, scope) {
627
1166
  const found = filterValuesFor(parsed.query, identityKey).map((value) => namePattern(value, scope)).filter((value) => value !== null);
628
1167
  return found.length === 0 ? [null] : found;
629
1168
  }
630
- function metricReading(reading, boundary) {
1169
+ function metricReading(reading, boundary, scope) {
631
1170
  const compares = boundary.comparesTo;
632
1171
  const comparesTo = compares !== void 0 && valueAt(reading, compares.attribute) !== void 0 ? compares.whenSet : void 0;
633
1172
  const reduces = boundary.reducesTo;
634
- const reducesTo = meaningOf(reading, reduces);
1173
+ const reducesTo = meaningOf(reading, reduces, scope);
635
1174
  return {
636
1175
  ...comparesTo !== void 0 ? { comparesTo } : {},
637
1176
  ...reducesTo !== void 0 ? { reducesTo } : {},
638
1177
  ...reduces === void 0 ? {} : { reduction: { setting: reduces.attribute, leaves: reduces.means } }
639
1178
  };
640
1179
  }
641
- function meaningOf(body, spec) {
1180
+ function deployableSummaries(opts) {
1181
+ const { boundary, label } = opts;
1182
+ return deployedProcesses(opts.body, boundary, opts.scope).map(
1183
+ (process) => deployableSummary({
1184
+ ...opts,
1185
+ boundary,
1186
+ process,
1187
+ instanceName: process.containerName === null ? label : ecsContainerInstanceName(label, process.containerName)
1188
+ })
1189
+ );
1190
+ }
1191
+ function deployableSummary(opts) {
1192
+ const { boundary, instanceName, process } = opts;
1193
+ const declared = declaredEnv(process.body, boundary.env ?? [], opts.scope);
1194
+ const platform = boundary.platformEnvVars ?? PLATFORM_INJECTED_ENV_VARS[boundary.deploymentTarget];
1195
+ const code = codePointer(process.body, boundary, opts.scope);
1196
+ const image = attributePattern(
1197
+ process.body,
1198
+ boundary.code?.imageAttribute,
1199
+ opts.scope
1200
+ );
1201
+ const runtime = attributePattern(
1202
+ process.body,
1203
+ boundary.runtimeAttribute,
1204
+ opts.scope
1205
+ );
1206
+ const prefix = opts.scope.namePrefix;
1207
+ const deployableUnit = {
1208
+ deploymentTarget: boundary.deploymentTarget,
1209
+ instanceName: `${prefix}${instanceName}`
1210
+ };
1211
+ return {
1212
+ kind: "library",
1213
+ location: {
1214
+ file: opts.sourceFile,
1215
+ range: { start: 1, end: 1 },
1216
+ exportName: null
1217
+ },
1218
+ identity: {
1219
+ name: `${prefix}${opts.resourceType}.${instanceName.replace("/", "#")}`,
1220
+ exportPath: null,
1221
+ boundaryBinding: runtimeConfigBinding({
1222
+ recognition: "terraform",
1223
+ ...deployableUnit
1224
+ }),
1225
+ deployableUnit
1226
+ },
1227
+ inputs: [],
1228
+ transitions: [],
1229
+ gaps: [],
1230
+ confidence: { source: "declared", level: "high" },
1231
+ metadata: withRuntimeContractMetadata(
1232
+ {
1233
+ codeScope: declaredCodeScope(
1234
+ opts.options.codeScopes?.[deployableUnit.instanceName],
1235
+ code.entry
1236
+ )
1237
+ },
1238
+ {
1239
+ envVars: [.../* @__PURE__ */ new Set([...declared.names, ...platform])].sort(),
1240
+ envVarSources: envVarSources(declared.names, platform),
1241
+ ...Object.keys(declared.targets).length > 0 ? { envVarTargets: declared.targets } : {},
1242
+ ...Object.keys(declared.values).length > 0 ? { envVarValues: declared.values } : {},
1243
+ ...code.entryPoint !== null ? { entryPoint: code.entryPoint } : {},
1244
+ ...image !== null ? { image } : {},
1245
+ ...runtime !== null ? { runtime } : {}
1246
+ }
1247
+ )
1248
+ };
1249
+ }
1250
+ function declaredCodeScope(directory, entry) {
1251
+ const written = entry !== null ? { entry } : {};
1252
+ if (directory !== void 0) {
1253
+ return { kind: "codeUri", path: directory, ...written };
1254
+ }
1255
+ return { kind: "unknown", ...written };
1256
+ }
1257
+ function envVarSources(declared, platform) {
1258
+ const sources = {};
1259
+ for (const name of declared) {
1260
+ sources[name] = "template";
1261
+ }
1262
+ for (const name of platform) {
1263
+ sources[name] ??= "platform";
1264
+ }
1265
+ return sources;
1266
+ }
1267
+ function deployedProcesses(body, boundary, scope) {
1268
+ const containers = boundary.containers;
1269
+ if (containers === void 0) {
1270
+ return [{ body, containerName: null }];
1271
+ }
1272
+ return blocksAt(body, containers.blocks, scope).map((container) => ({
1273
+ body: container,
1274
+ containerName: containers.nameAttribute === void 0 ? null : stringOf(container[containers.nameAttribute])
1275
+ }));
1276
+ }
1277
+ var HANDLER_MODULE = {
1278
+ "module.export": (written) => parseHandler(written)?.modulePath ?? null,
1279
+ name: () => null
1280
+ };
1281
+ function codePointer(body, boundary, scope) {
1282
+ const spec = boundary.code?.handler;
1283
+ const written = attributePattern(body, spec?.attribute, scope);
1284
+ if (spec === void 0 || written === null) {
1285
+ return { entryPoint: null, entry: null };
1286
+ }
1287
+ return {
1288
+ entryPoint: written,
1289
+ entry: hasNameHole(written) ? null : HANDLER_MODULE[spec.spelling](written)
1290
+ };
1291
+ }
1292
+ var ENV_READERS = {
1293
+ map: (body, declaration, into, scope) => {
1294
+ const stated = asRecord(valueAt(body, declaration.attribute)) ?? {};
1295
+ for (const [name, value] of Object.entries(stated)) {
1296
+ setVariable(into, name, stringOf(value), scope);
1297
+ }
1298
+ },
1299
+ entries: (body, declaration, into, scope) => {
1300
+ for (const entry of blocksAt(body, declaration.block.split("."), scope)) {
1301
+ const name = stringOf(entry[declaration.nameAttribute]);
1302
+ if (name === null) {
1303
+ continue;
1304
+ }
1305
+ setVariable(
1306
+ into,
1307
+ name,
1308
+ attributeText(entry, declaration.valueAttribute),
1309
+ scope
1310
+ );
1311
+ setTarget(
1312
+ into,
1313
+ name,
1314
+ attributeText(entry, declaration.secretAttribute),
1315
+ scope
1316
+ );
1317
+ }
1318
+ }
1319
+ };
1320
+ function declaredEnv(body, declarations, scope) {
1321
+ const declared = { names: [], values: {}, targets: {} };
1322
+ for (const declaration of declarations) {
1323
+ const read = ENV_READERS[declaration.style];
1324
+ read(body, declaration, declared, scope);
1325
+ }
1326
+ return declared;
1327
+ }
1328
+ function setVariable(into, name, written, scope) {
1329
+ if (!into.names.includes(name)) {
1330
+ into.names.push(name);
1331
+ }
1332
+ if (written === null) {
1333
+ return;
1334
+ }
1335
+ const pattern = namePattern(written, scope);
1336
+ if (pattern !== null) {
1337
+ into.values[name] = pattern;
1338
+ }
1339
+ setTarget(into, name, written, scope);
1340
+ }
1341
+ function setTarget(into, name, written, scope) {
1342
+ const target = written === null ? null : referencedResource(written, scope);
1343
+ if (target !== null) {
1344
+ into.targets[name] = { kind: "ref", logicalId: target };
1345
+ }
1346
+ }
1347
+ function attributeText(body, attribute) {
1348
+ return attribute === void 0 ? null : stringOf(valueAt(body, attribute));
1349
+ }
1350
+ function attributePattern(body, attribute, scope) {
1351
+ return attribute === void 0 ? null : namePattern(valueAt(body, attribute), scope);
1352
+ }
1353
+ var MEANING_KEYS = {
1354
+ value: (stated, keys) => keys.find((key) => key === stated),
1355
+ prefix: (stated, keys) => keys.find((key) => stated.startsWith(key))
1356
+ };
1357
+ function meaningOf(body, spec, scope) {
642
1358
  if (spec === void 0) {
643
1359
  return void 0;
644
1360
  }
645
- const stated = stringOf(valueAt(body, spec.attribute));
646
- return stated === null ? void 0 : spec.means[stated];
1361
+ const value = valueAt(body, spec.attribute);
1362
+ if (value === void 0) {
1363
+ return spec.whenUnset;
1364
+ }
1365
+ const written = stringOf(value);
1366
+ const stated = written === null ? null : resolveReferences(written, scope);
1367
+ if (stated === null || stated.includes("${")) {
1368
+ return void 0;
1369
+ }
1370
+ const key = MEANING_KEYS[spec.matches ?? "value"](
1371
+ stated,
1372
+ Object.keys(spec.means)
1373
+ );
1374
+ return key === void 0 ? void 0 : spec.means[key];
647
1375
  }
648
1376
  function valueAt(body, path2) {
649
1377
  const steps = path2.split(".");
@@ -658,15 +1386,24 @@ function valueAt(body, path2) {
658
1386
  }
659
1387
  return current[last];
660
1388
  }
661
- function blocksAt(body, blocks) {
1389
+ function blocksAt(body, blocks, scope) {
662
1390
  let found = [body];
663
1391
  for (const block of blocks) {
664
- found = found.flatMap(
665
- (record) => arrayOf(record[block]).map(asRecord).filter((nested) => nested !== null)
666
- );
1392
+ found = found.flatMap((record) => [
1393
+ ...nestedRecords(record[block], scope),
1394
+ ...dynamicBlocks(record, block, scope)
1395
+ ]);
667
1396
  }
668
1397
  return found;
669
1398
  }
1399
+ function nestedRecords(value, scope) {
1400
+ const iterated = iteratedRecords(value, scope);
1401
+ if (iterated !== null) {
1402
+ return iterated;
1403
+ }
1404
+ const stated = typeof value === "string" ? jsonAttributeValue(value) : value;
1405
+ return arrayOf(stated).map(asRecord).filter((nested) => nested !== null);
1406
+ }
670
1407
  function keyedShapes(body, boundary) {
671
1408
  if (boundary.identifies === void 0) {
672
1409
  return [{ accessPath: null, keyFields: [], serves: null }];
@@ -772,22 +1509,11 @@ function namePattern(value, scope) {
772
1509
  typeof value === "string" ? resolveReferences(value, scope) : value
773
1510
  );
774
1511
  }
775
- function arrayOf(value) {
776
- if (value === void 0 || value === null) {
777
- return [];
778
- }
779
- return Array.isArray(value) ? value : [value];
780
- }
781
- function asRecord(value) {
782
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
783
- }
784
- function stringOf(value) {
785
- return typeof value === "string" ? value : null;
786
- }
787
1512
  export {
788
1513
  filterCalls,
789
1514
  filterTerms,
790
1515
  filterValuesFor,
1516
+ jsonAttributeValue,
791
1517
  parseFilterQuery,
792
1518
  terraformFileToSummaries,
793
1519
  terraformToSummaries