@composurecdk/cloudformation 0.7.0 → 0.8.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 (83) hide show
  1. package/dist/commonjs/apply-builder-tags.d.ts.map +1 -0
  2. package/dist/commonjs/apply-builder-tags.js +69 -0
  3. package/dist/commonjs/apply-builder-tags.js.map +1 -0
  4. package/dist/commonjs/index.d.ts.map +1 -0
  5. package/dist/commonjs/index.js +20 -0
  6. package/dist/commonjs/index.js.map +1 -0
  7. package/dist/commonjs/outputs.d.ts.map +1 -0
  8. package/dist/commonjs/outputs.js +77 -0
  9. package/dist/commonjs/outputs.js.map +1 -0
  10. package/dist/commonjs/package.json +3 -0
  11. package/dist/commonjs/stack-builder.d.ts.map +1 -0
  12. package/dist/commonjs/stack-builder.js +47 -0
  13. package/dist/commonjs/stack-builder.js.map +1 -0
  14. package/dist/commonjs/strategies.d.ts.map +1 -0
  15. package/dist/commonjs/strategies.js +80 -0
  16. package/dist/commonjs/strategies.js.map +1 -0
  17. package/dist/commonjs/tag-validator.d.ts.map +1 -0
  18. package/dist/commonjs/tag-validator.js +67 -0
  19. package/dist/commonjs/tag-validator.js.map +1 -0
  20. package/dist/commonjs/tagged-builder.d.ts.map +1 -0
  21. package/dist/commonjs/tagged-builder.js +121 -0
  22. package/dist/commonjs/tagged-builder.js.map +1 -0
  23. package/dist/commonjs/tags.d.ts.map +1 -0
  24. package/dist/commonjs/tags.js +86 -0
  25. package/dist/commonjs/tags.js.map +1 -0
  26. package/dist/esm/apply-builder-tags.d.ts +32 -0
  27. package/dist/esm/apply-builder-tags.d.ts.map +1 -0
  28. package/dist/esm/apply-builder-tags.js.map +1 -0
  29. package/dist/esm/index.d.ts +8 -0
  30. package/dist/esm/index.d.ts.map +1 -0
  31. package/dist/esm/index.js.map +1 -0
  32. package/dist/esm/outputs.d.ts +84 -0
  33. package/dist/esm/outputs.d.ts.map +1 -0
  34. package/dist/esm/outputs.js.map +1 -0
  35. package/dist/esm/package.json +3 -0
  36. package/dist/esm/stack-builder.d.ts +79 -0
  37. package/dist/esm/stack-builder.d.ts.map +1 -0
  38. package/dist/esm/stack-builder.js.map +1 -0
  39. package/dist/esm/strategies.d.ts +70 -0
  40. package/dist/esm/strategies.d.ts.map +1 -0
  41. package/dist/esm/strategies.js.map +1 -0
  42. package/dist/esm/tag-validator.d.ts +22 -0
  43. package/dist/esm/tag-validator.d.ts.map +1 -0
  44. package/dist/esm/tag-validator.js.map +1 -0
  45. package/dist/esm/tagged-builder.d.ts +112 -0
  46. package/dist/esm/tagged-builder.d.ts.map +1 -0
  47. package/dist/esm/tagged-builder.js.map +1 -0
  48. package/dist/esm/tags.d.ts +91 -0
  49. package/dist/esm/tags.d.ts.map +1 -0
  50. package/dist/esm/tags.js.map +1 -0
  51. package/package.json +33 -15
  52. package/dist/apply-builder-tags.d.ts.map +0 -1
  53. package/dist/apply-builder-tags.js.map +0 -1
  54. package/dist/index.d.ts.map +0 -1
  55. package/dist/index.js.map +0 -1
  56. package/dist/outputs.d.ts.map +0 -1
  57. package/dist/outputs.js.map +0 -1
  58. package/dist/stack-builder.d.ts.map +0 -1
  59. package/dist/stack-builder.js.map +0 -1
  60. package/dist/strategies.d.ts.map +0 -1
  61. package/dist/strategies.js.map +0 -1
  62. package/dist/tag-validator.d.ts.map +0 -1
  63. package/dist/tag-validator.js.map +0 -1
  64. package/dist/tagged-builder.d.ts.map +0 -1
  65. package/dist/tagged-builder.js.map +0 -1
  66. package/dist/tags.d.ts.map +0 -1
  67. package/dist/tags.js.map +0 -1
  68. /package/dist/{apply-builder-tags.d.ts → commonjs/apply-builder-tags.d.ts} +0 -0
  69. /package/dist/{index.d.ts → commonjs/index.d.ts} +0 -0
  70. /package/dist/{outputs.d.ts → commonjs/outputs.d.ts} +0 -0
  71. /package/dist/{stack-builder.d.ts → commonjs/stack-builder.d.ts} +0 -0
  72. /package/dist/{strategies.d.ts → commonjs/strategies.d.ts} +0 -0
  73. /package/dist/{tag-validator.d.ts → commonjs/tag-validator.d.ts} +0 -0
  74. /package/dist/{tagged-builder.d.ts → commonjs/tagged-builder.d.ts} +0 -0
  75. /package/dist/{tags.d.ts → commonjs/tags.d.ts} +0 -0
  76. /package/dist/{apply-builder-tags.js → esm/apply-builder-tags.js} +0 -0
  77. /package/dist/{index.js → esm/index.js} +0 -0
  78. /package/dist/{outputs.js → esm/outputs.js} +0 -0
  79. /package/dist/{stack-builder.js → esm/stack-builder.js} +0 -0
  80. /package/dist/{strategies.js → esm/strategies.js} +0 -0
  81. /package/dist/{tag-validator.js → esm/tag-validator.js} +0 -0
  82. /package/dist/{tagged-builder.js → esm/tagged-builder.js} +0 -0
  83. /package/dist/{tags.js → esm/tags.js} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-builder-tags.d.ts","sourceRoot":"","sources":["../../src/apply-builder-tags.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAMxD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAGxF;AAcD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI,CAK/F"}
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyBuilderTags = applyBuilderTags;
4
+ exports.applyTagsToConstruct = applyTagsToConstruct;
5
+ const aws_cdk_lib_1 = require("aws-cdk-lib");
6
+ const constructs_1 = require("constructs");
7
+ function isConstruct(value) {
8
+ return value !== null && typeof value === "object" && constructs_1.Construct.isConstruct(value);
9
+ }
10
+ /**
11
+ * Applies every accumulated tag to every {@link IConstruct} reachable in a
12
+ * builder result.
13
+ *
14
+ * Recursively descends through plain-object literals, tagging every
15
+ * `IConstruct` it finds. Stops at:
16
+ *
17
+ * - **Constructs** — tagged via `Tags.of(...).add(...)`. The CDK Aspect
18
+ * schedules tag application across the construct's subtree at
19
+ * synth-prepare time, so the walker does not recurse into the construct's
20
+ * internals.
21
+ * - **Class instances that aren't constructs** (e.g. `PolicyDocument`) —
22
+ * skipped. Plain-object detection requires `Object.prototype` as the
23
+ * prototype, so class instances are opaque to the walker.
24
+ * - **Arrays and primitives** — skipped.
25
+ *
26
+ * The contract this implements: every construct exposed in a builder's
27
+ * result type is a tag target. Wrapper shapes such as
28
+ * `Record<string, { construct: ..., metadata: ... }>` are unwrapped
29
+ * naturally — the walker descends through the plain-object value and tags
30
+ * the construct field. Authors do not need an opt-in marker; if a construct
31
+ * appears in the result, it is tagged.
32
+ */
33
+ function applyBuilderTags(result, tags) {
34
+ if (tags.size === 0)
35
+ return;
36
+ walkAndTag(result, tags);
37
+ }
38
+ function walkAndTag(value, tags) {
39
+ if (isConstruct(value)) {
40
+ applyTagsToConstruct(value, tags);
41
+ return;
42
+ }
43
+ if (isPlainObject(value)) {
44
+ for (const inner of Object.values(value)) {
45
+ walkAndTag(inner, tags);
46
+ }
47
+ }
48
+ }
49
+ /**
50
+ * Applies every entry of `tags` to `target` via `Tags.of(target).add(...)`.
51
+ * Accepts any iterable of `[key, value]` pairs so callers can pass `Map`,
52
+ * `Object.entries(record)`, or other compatible sources without copying.
53
+ */
54
+ function applyTagsToConstruct(target, tags) {
55
+ const t = aws_cdk_lib_1.Tags.of(target);
56
+ for (const [key, value] of tags) {
57
+ t.add(key, value);
58
+ }
59
+ }
60
+ function isPlainObject(value) {
61
+ if (value === null || typeof value !== "object" || Array.isArray(value))
62
+ return false;
63
+ // Accept both `{...}` literals and `Object.create(null)` dictionaries — the
64
+ // latter is a common idiom for prototype-pollution-safe key/value maps and
65
+ // would otherwise be silently skipped here.
66
+ const proto = Object.getPrototypeOf(value);
67
+ return proto === null || proto === Object.prototype;
68
+ }
69
+ //# sourceMappingURL=apply-builder-tags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-builder-tags.js","sourceRoot":"","sources":["../../src/apply-builder-tags.ts"],"names":[],"mappings":";;AA8BA,4CAGC;AAmBD,oDAKC;AAzDD,6CAAmC;AACnC,2CAAwD;AAExD,SAAS,WAAW,CAAC,KAAc;IACjC,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,sBAAS,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;AACrF,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,SAAgB,gBAAgB,CAAC,MAAc,EAAE,IAAiC;IAChF,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO;IAC5B,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,UAAU,CAAC,KAAc,EAAE,IAAiC;IACnE,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;QACvB,oBAAoB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAClC,OAAO;IACT,CAAC;IACD,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YACzC,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAgB,oBAAoB,CAAC,MAAkB,EAAE,IAAgC;IACvF,MAAM,CAAC,GAAG,kBAAI,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;IAC1B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;QAChC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACtF,4EAA4E;IAC5E,2EAA2E;IAC3E,4CAA4C;IAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAkB,CAAC;IAC5D,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,MAAM,CAAC,SAAS,CAAC;AACtD,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,KAAK,aAAa,EAClB,KAAK,kBAAkB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,IAAI,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tags = exports.validateTag = exports.applyBuilderTags = exports.TAG_OVERRIDE_WARNING_NAME = exports.taggedBuilder = exports.outputs = exports.groupedStacks = exports.singleStack = exports.createStackBuilder = void 0;
4
+ var stack_builder_js_1 = require("./stack-builder.js");
5
+ Object.defineProperty(exports, "createStackBuilder", { enumerable: true, get: function () { return stack_builder_js_1.createStackBuilder; } });
6
+ var strategies_js_1 = require("./strategies.js");
7
+ Object.defineProperty(exports, "singleStack", { enumerable: true, get: function () { return strategies_js_1.singleStack; } });
8
+ Object.defineProperty(exports, "groupedStacks", { enumerable: true, get: function () { return strategies_js_1.groupedStacks; } });
9
+ var outputs_js_1 = require("./outputs.js");
10
+ Object.defineProperty(exports, "outputs", { enumerable: true, get: function () { return outputs_js_1.outputs; } });
11
+ var tagged_builder_js_1 = require("./tagged-builder.js");
12
+ Object.defineProperty(exports, "taggedBuilder", { enumerable: true, get: function () { return tagged_builder_js_1.taggedBuilder; } });
13
+ Object.defineProperty(exports, "TAG_OVERRIDE_WARNING_NAME", { enumerable: true, get: function () { return tagged_builder_js_1.TAG_OVERRIDE_WARNING_NAME; } });
14
+ var apply_builder_tags_js_1 = require("./apply-builder-tags.js");
15
+ Object.defineProperty(exports, "applyBuilderTags", { enumerable: true, get: function () { return apply_builder_tags_js_1.applyBuilderTags; } });
16
+ var tag_validator_js_1 = require("./tag-validator.js");
17
+ Object.defineProperty(exports, "validateTag", { enumerable: true, get: function () { return tag_validator_js_1.validateTag; } });
18
+ var tags_js_1 = require("./tags.js");
19
+ Object.defineProperty(exports, "tags", { enumerable: true, get: function () { return tags_js_1.tags; } });
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,uDAI4B;AAH1B,sHAAA,kBAAkB,OAAA;AAIpB,iDAA6D;AAApD,4GAAA,WAAW,OAAA;AAAE,8GAAA,aAAa,OAAA;AACnC,2CAAsF;AAA7E,qGAAA,OAAO,OAAA;AAChB,yDAAoG;AAA3F,kHAAA,aAAa,OAAA;AAAuB,8HAAA,yBAAyB,OAAA;AACtE,iEAA2D;AAAlD,yHAAA,gBAAgB,OAAA;AACzB,uDAAiD;AAAxC,+GAAA,WAAW,OAAA;AACpB,qCAAsD;AAA7C,+FAAA,IAAI,OAAA"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outputs.d.ts","sourceRoot":"","sources":["../../src/outputs.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,UAAU,EAAW,MAAM,oBAAoB,CAAC;AAEnF;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IACzD,uDAAuD;IACvD,KAAK,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAE1B,mCAAmC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;;;;;;OAUG;IACH,KAAK,CAAC,EAAE,UAAU,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;AAE/F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CA0BhG"}
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.outputs = outputs;
4
+ const aws_cdk_lib_1 = require("aws-cdk-lib");
5
+ const core_1 = require("@composurecdk/core");
6
+ /**
7
+ * Returns an {@link AfterBuildHook} that creates CloudFormation stack outputs
8
+ * from the composed system's build results.
9
+ *
10
+ * Each output definition's `value` can be a concrete string or a {@link Ref}
11
+ * that is resolved against the build results. An optional `scope` routes
12
+ * individual outputs to specific stacks — either as a direct `IConstruct`
13
+ * or as a component key string (statically typed against the composed
14
+ * system's component keys).
15
+ *
16
+ * Intended for use with {@link ComposedSystem.afterBuild}.
17
+ *
18
+ * @param defs - A record of output definitions keyed by logical name.
19
+ * @returns An {@link AfterBuildHook} that creates `CfnOutput` constructs.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * import { compose, ref } from "@composurecdk/core";
24
+ * import { outputs } from "@composurecdk/cloudformation";
25
+ *
26
+ * compose(
27
+ * { site: createBucketBuilder(), cdn: createDistributionBuilder(), dns: createZoneBuilder() },
28
+ * { site: [], cdn: ["site"], dns: [] },
29
+ * )
30
+ * .withStacks({ site: siteStack, cdn: siteStack, dns: dnsStack })
31
+ * .afterBuild(outputs({
32
+ * DistributionUrl: {
33
+ * value: ref("cdn", (r: DistributionBuilderResult) =>
34
+ * `https://${r.distribution.distributionDomainName}`),
35
+ * scope: "cdn",
36
+ * },
37
+ * BucketName: {
38
+ * value: ref("site", (r: BucketBuilderResult) => r.bucket.bucketName),
39
+ * scope: siteStack,
40
+ * },
41
+ * NameServers: {
42
+ * value: ref("dns", (r: ZoneBuilderResult) =>
43
+ * Fn.join(",", r.zone.hostedZoneNameServers!)),
44
+ * scope: "dns",
45
+ * },
46
+ * }))
47
+ * .build(app, "StaticWebsite");
48
+ * ```
49
+ */
50
+ function outputs(defs) {
51
+ return (scope, _id, results, componentScopes) => {
52
+ const resultAsContext = results;
53
+ const scopesByKey = componentScopes;
54
+ for (const [name, def] of Object.entries(defs)) {
55
+ let target;
56
+ if (typeof def.scope === "string") {
57
+ const resolved = scopesByKey[def.scope];
58
+ if (resolved === undefined) {
59
+ throw new Error(`outputs(): "${name}" refers to unknown component "${def.scope}".`);
60
+ }
61
+ target = resolved;
62
+ }
63
+ else if (def.scope !== undefined) {
64
+ target = def.scope;
65
+ }
66
+ else {
67
+ target = scope;
68
+ }
69
+ new aws_cdk_lib_1.CfnOutput(target, name, {
70
+ value: (0, core_1.resolve)(def.value, resultAsContext),
71
+ ...(def.description !== undefined ? { description: def.description } : {}),
72
+ ...(def.exportName !== undefined ? { exportName: def.exportName } : {}),
73
+ });
74
+ }
75
+ };
76
+ }
77
+ //# sourceMappingURL=outputs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outputs.js","sourceRoot":"","sources":["../../src/outputs.ts"],"names":[],"mappings":";;AAyFA,0BA0BC;AAnHD,6CAAwC;AAExC,6CAAmF;AA2CnF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,SAAgB,OAAO,CAA4B,IAA0B;IAC3E,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,eAAe,EAAE,EAAE;QAC9C,MAAM,eAAe,GAAG,OAAiC,CAAC;QAC1D,MAAM,WAAW,GAAG,eAAyD,CAAC;QAE9E,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/C,IAAI,MAAkB,CAAC;YACvB,IAAI,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAClC,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBACxC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;oBAC3B,MAAM,IAAI,KAAK,CAAC,eAAe,IAAI,kCAAkC,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC;gBACtF,CAAC;gBACD,MAAM,GAAG,QAAQ,CAAC;YACpB,CAAC;iBAAM,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBACnC,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC;YACrB,CAAC;iBAAM,CAAC;gBACN,MAAM,GAAG,KAAK,CAAC;YACjB,CAAC;YAED,IAAI,uBAAS,CAAC,MAAM,EAAE,IAAI,EAAE;gBAC1B,KAAK,EAAE,IAAA,cAAO,EAAC,GAAG,CAAC,KAAK,EAAE,eAAe,CAAC;gBAC1C,GAAG,CAAC,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC1E,GAAG,CAAC,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxE,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack-builder.d.ts","sourceRoot":"","sources":["../../src/stack-builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,KAAK,cAAc,EAAiB,MAAM,qBAAqB,CAAC;AAEzE;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,4CAA4C;IAC5C,KAAK,EAAE,KAAK,CAAC;CACd;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;AAErE,cAAM,YAAa,YAAW,SAAS,CAAC,kBAAkB,CAAC;IACzD,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,CAAM;IAEhC,KAAK,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,kBAAkB;CAGzD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,kBAAkB,IAAI,aAAa,CAElD"}
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createStackBuilder = createStackBuilder;
4
+ const aws_cdk_lib_1 = require("aws-cdk-lib");
5
+ const tagged_builder_js_1 = require("./tagged-builder.js");
6
+ class StackBuilder {
7
+ props = {};
8
+ build(scope, id) {
9
+ return { stack: new aws_cdk_lib_1.Stack(scope, id, this.props) };
10
+ }
11
+ }
12
+ /**
13
+ * Creates a new {@link IStackBuilder} for configuring a CloudFormation Stack.
14
+ *
15
+ * This is the entry point for declarative stack configuration. The returned
16
+ * builder exposes every {@link StackProps} property as a fluent setter/getter,
17
+ * `.tag(key, value)` / `.tags({...})` for stack-level tagging, and `.copy()`
18
+ * for variant authoring. It implements {@link Lifecycle}, so it composes
19
+ * naturally and can be passed to {@link singleStack} or
20
+ * {@link groupedStacks}.
21
+ *
22
+ * @returns A fluent builder for a CloudFormation Stack.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * // Build a stack directly
27
+ * const { stack } = createStackBuilder()
28
+ * .description("Service layer")
29
+ * .terminationProtection(true)
30
+ * .tag("Owner", "platform")
31
+ * .build(app, "ServiceStack");
32
+ *
33
+ * // Hand a configured builder to a strategy. Use `.copy()` to snapshot
34
+ * // when the original may be mutated further.
35
+ * const base = createStackBuilder()
36
+ * .terminationProtection(true)
37
+ * .tag("team", "platform");
38
+ *
39
+ * compose({ ... }, { ... })
40
+ * .withStackStrategy(singleStack(base.copy()))
41
+ * .build(app, "MySystem");
42
+ * ```
43
+ */
44
+ function createStackBuilder() {
45
+ return (0, tagged_builder_js_1.taggedBuilder)(StackBuilder);
46
+ }
47
+ //# sourceMappingURL=stack-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack-builder.js","sourceRoot":"","sources":["../../src/stack-builder.ts"],"names":[],"mappings":";;AAmFA,gDAEC;AArFD,6CAAqD;AAGrD,2DAAyE;AAwCzE,MAAM,YAAY;IAChB,KAAK,GAAwB,EAAE,CAAC;IAEhC,KAAK,CAAC,KAAiB,EAAE,EAAU;QACjC,OAAO,EAAE,KAAK,EAAE,IAAI,mBAAK,CAAC,KAAK,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;IACrD,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,SAAgB,kBAAkB;IAChC,OAAO,IAAA,iCAAa,EAA2B,YAAY,CAAC,CAAC;AAC/D,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strategies.d.ts","sourceRoot":"","sources":["../../src/strategies.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,aAAa,EAGnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAEjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,kBAAkB,CAAC,GAAG,aAAa,CAGlF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,EAC1C,OAAO,CAAC,EAAE,SAAS,CAAC,kBAAkB,CAAC,GACtC,aAAa,CAGf"}
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.singleStack = singleStack;
4
+ exports.groupedStacks = groupedStacks;
5
+ const core_1 = require("@composurecdk/core");
6
+ const stack_builder_js_1 = require("./stack-builder.js");
7
+ /**
8
+ * Creates a strategy that places all components in a single Stack.
9
+ *
10
+ * The Stack is created lazily on the first call to `resolve` and reused
11
+ * for all subsequent components. The supplied `builder` is invoked at that
12
+ * point as `builder.build(scope, id).stack`, so any tags applied via the
13
+ * builder's `.tag()` method land on the resulting Stack.
14
+ *
15
+ * The builder's `build()` is called lazily. If the original may be mutated
16
+ * after this call, pass `builder.copy()` to snapshot the configuration.
17
+ *
18
+ * @param builder - Optional builder for configuring the Stack. Defaults to
19
+ * a fresh {@link createStackBuilder} per call, producing a plain Stack
20
+ * with no extra configuration. For non-Stack scope types, use
21
+ * `singleStack` from `@composurecdk/core` directly with a `ScopeFactory`.
22
+ * @returns A {@link StackStrategy} that groups all components into one Stack.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * compose({ handler, api }, { handler: [], api: ["handler"] })
27
+ * .withStackStrategy(singleStack())
28
+ * .build(app, "MySystem");
29
+ *
30
+ * // With a configured builder:
31
+ * const base = createStackBuilder().tag("team", "platform");
32
+ * compose({ ... }, { ... })
33
+ * .withStackStrategy(singleStack(base.copy()))
34
+ * .build(app, "MySystem");
35
+ * ```
36
+ */
37
+ function singleStack(builder) {
38
+ const stackBuilder = builder ?? (0, stack_builder_js_1.createStackBuilder)();
39
+ return (0, core_1.singleStack)((scope, id) => stackBuilder.build(scope, id).stack);
40
+ }
41
+ /**
42
+ * Creates a strategy that groups components into named Stacks determined by
43
+ * a classifier function.
44
+ *
45
+ * Components that return the same group key share a Stack. Stacks are
46
+ * created lazily as new group keys are encountered. The supplied `builder`
47
+ * is invoked once per group as `builder.build(scope, id).stack` with
48
+ * `id = ${systemId}-${group}`, so any configured tags propagate to every
49
+ * Stack the strategy creates.
50
+ *
51
+ * The builder's `build()` is called lazily. If the original may be mutated
52
+ * after this call, pass `builder.copy()` to snapshot the configuration.
53
+ *
54
+ * @param classify - A function that maps a component key to a group name.
55
+ * @param builder - Optional builder for configuring each Stack. Defaults to
56
+ * a fresh {@link createStackBuilder} per call. For non-Stack scope types,
57
+ * use `groupedStacks` from `@composurecdk/core` directly with a
58
+ * `ScopeFactory`.
59
+ * @returns A {@link StackStrategy} that groups components by classifier output.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * compose({ handler, api, table }, { ... })
64
+ * .withStackStrategy(groupedStacks(key => key === "table" ? "persistence" : "service"))
65
+ * .build(app, "MySystem");
66
+ *
67
+ * // With a configured builder, snapshotted via .copy():
68
+ * const base = createStackBuilder().tag("team", "platform");
69
+ * compose({ ... }, { ... })
70
+ * .withStackStrategy(
71
+ * groupedStacks(key => key === "table" ? "persistence" : "service", base.copy()),
72
+ * )
73
+ * .build(app, "MySystem");
74
+ * ```
75
+ */
76
+ function groupedStacks(classify, builder) {
77
+ const stackBuilder = builder ?? (0, stack_builder_js_1.createStackBuilder)();
78
+ return (0, core_1.groupedStacks)(classify, (scope, id) => stackBuilder.build(scope, id).stack);
79
+ }
80
+ //# sourceMappingURL=strategies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strategies.js","sourceRoot":"","sources":["../../src/strategies.ts"],"names":[],"mappings":";;AAsCA,kCAGC;AAqCD,sCAMC;AApFD,6CAK4B;AAC5B,yDAAiF;AAEjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,SAAgB,WAAW,CAAC,OAAuC;IACjE,MAAM,YAAY,GAAG,OAAO,IAAI,IAAA,qCAAkB,GAAE,CAAC;IACrD,OAAO,IAAA,kBAAe,EAAC,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;AAC7E,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,SAAgB,aAAa,CAC3B,QAA0C,EAC1C,OAAuC;IAEvC,MAAM,YAAY,GAAG,OAAO,IAAI,IAAA,qCAAkB,GAAE,CAAC;IACrD,OAAO,IAAA,oBAAiB,EAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;AACzF,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag-validator.d.ts","sourceRoot":"","sources":["../../src/tag-validator.ts"],"names":[],"mappings":"AAkBA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CA2B5D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAItE"}
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateTag = validateTag;
4
+ exports.validateTagRecord = validateTagRecord;
5
+ /**
6
+ * AWS allows letters, digits, whitespace, and the symbols `_ . : / = + - @`
7
+ * in tag keys and values, with non-ASCII letters/digits permitted via
8
+ * Unicode classes. Empty values are permitted by AWS for `Value`, but
9
+ * empty `Key` is not. The character set is validated against this regex.
10
+ *
11
+ * `\p{Z}` matches the full Unicode separator class — slightly more
12
+ * permissive than AWS's documented "white space," but errors on the side
13
+ * of accepting input that the AWS API may yet reject at deploy time. The
14
+ * extra rejections happen later but are reported in the API response.
15
+ *
16
+ * @see https://docs.aws.amazon.com/general/latest/gr/aws_tagging.html
17
+ */
18
+ const TAG_CHAR_RE = /^[\p{L}\p{Z}\p{N}_.:/=+\-@]*$/u;
19
+ const KEY_MAX = 128;
20
+ const VALUE_MAX = 256;
21
+ /**
22
+ * Validates a single tag key/value pair against AWS tag constraints.
23
+ *
24
+ * Throws synchronously at the call site so authors see the failure where the
25
+ * bad value was written, not at deploy time. Validates:
26
+ *
27
+ * - `key` is non-empty and at most {@link KEY_MAX} characters.
28
+ * - `key` does not start with the reserved `aws:` prefix (case-insensitive).
29
+ * - `value` is at most {@link VALUE_MAX} characters.
30
+ * - both `key` and `value` use only the AWS-permitted character set.
31
+ *
32
+ * The regex matches Unicode letters, digits, and whitespace plus the
33
+ * documented punctuation set, so non-ASCII tags are accepted as AWS
34
+ * supports them.
35
+ */
36
+ function validateTag(key, value) {
37
+ if (key.length === 0) {
38
+ throw new Error("Tag key must be non-empty.");
39
+ }
40
+ if (key.length > KEY_MAX) {
41
+ throw new Error(`Tag key "${key}" exceeds ${String(KEY_MAX)}-character limit.`);
42
+ }
43
+ if (key.toLowerCase().startsWith("aws:")) {
44
+ throw new Error(`Tag key "${key}" uses reserved "aws:" prefix; AWS rejects user tags with this prefix.`);
45
+ }
46
+ if (!TAG_CHAR_RE.test(key)) {
47
+ throw new Error(`Tag key "${key}" contains characters outside the AWS tag character set ` +
48
+ "(letters, digits, whitespace, and `_ . : / = + - @`).");
49
+ }
50
+ if (value.length > VALUE_MAX) {
51
+ throw new Error(`Tag value for key "${key}" exceeds ${String(VALUE_MAX)}-character limit.`);
52
+ }
53
+ if (!TAG_CHAR_RE.test(value)) {
54
+ throw new Error(`Tag value for key "${key}" contains characters outside the AWS tag character set ` +
55
+ "(letters, digits, whitespace, and `_ . : / = + - @`).");
56
+ }
57
+ }
58
+ /**
59
+ * Validates every entry of a record via {@link validateTag}, throwing on the
60
+ * first invalid pair so the failure surfaces at the configuring call site.
61
+ */
62
+ function validateTagRecord(values) {
63
+ for (const [key, value] of Object.entries(values)) {
64
+ validateTag(key, value);
65
+ }
66
+ }
67
+ //# sourceMappingURL=tag-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag-validator.js","sourceRoot":"","sources":["../../src/tag-validator.ts"],"names":[],"mappings":";;AAiCA,kCA2BC;AAMD,8CAIC;AAtED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,GAAG,gCAAgC,CAAC;AAErD,MAAM,OAAO,GAAG,GAAG,CAAC;AACpB,MAAM,SAAS,GAAG,GAAG,CAAC;AAEtB;;;;;;;;;;;;;;GAcG;AACH,SAAgB,WAAW,CAAC,GAAW,EAAE,KAAa;IACpD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,GAAG,OAAO,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,YAAY,GAAG,aAAa,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAClF,CAAC;IACD,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CACb,YAAY,GAAG,wEAAwE,CACxF,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,YAAY,GAAG,0DAA0D;YACvE,uDAAuD,CAC1D,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,sBAAsB,GAAG,aAAa,MAAM,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;IAC9F,CAAC;IACD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACb,sBAAsB,GAAG,0DAA0D;YACjF,uDAAuD,CAC1D,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAgB,iBAAiB,CAAC,MAA8B;IAC9D,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tagged-builder.d.ts","sourceRoot":"","sources":["../../src/tagged-builder.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,4BAA4B,CAAC;AAEnE,KAAK,WAAW,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC;AAElC,UAAU,eAAe,CAAC,KAAK,SAAS,MAAM;IAC5C,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,cAAc,CAAC,KAAK,SAAS,MAAM,EAAE,CAAC,IAAI;KACnD,CAAC,IAAI,MAAM,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC;CACvF,GAAG;KACD,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,GAChD,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,GACxC,CAAC,CAAC,CAAC,CAAC;CACT,GAAG;IACF;;;;;;;;;;;OAWG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAE1D;;;;;;;OAOG;IACH,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAE/D;;;;;;;OAOG;IACH,IAAI,IAAI,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;CAClC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,wBAAgB,aAAa,CAAC,KAAK,SAAS,MAAM,EAAE,CAAC,SAAS,eAAe,CAAC,KAAK,CAAC,EAClF,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC,GAC1B,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,CAG1B"}
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TAG_OVERRIDE_WARNING_NAME = void 0;
4
+ exports.taggedBuilder = taggedBuilder;
5
+ const core_1 = require("@composurecdk/core");
6
+ const apply_builder_tags_js_1 = require("./apply-builder-tags.js");
7
+ const tag_validator_js_1 = require("./tag-validator.js");
8
+ /**
9
+ * The `name` attached to the `process.emitWarning` call when `.tag(k, v)` /
10
+ * `.tags({...})` overwrites an existing key. Exported so callers with
11
+ * layered configuration (a base builder plus per-environment refinements
12
+ * that intentionally override) can filter via
13
+ * `process.on("warning", w => { if (w.name !== TAG_OVERRIDE_WARNING_NAME) ... })`
14
+ * or the Node 21.3+ `--disable-warning=ComposureCDKTagOverride` CLI flag.
15
+ */
16
+ exports.TAG_OVERRIDE_WARNING_NAME = "ComposureCDKTagOverride";
17
+ /**
18
+ * Wraps {@link Builder} to add the {@link ITaggedBuilder.tag | .tag()} and
19
+ * {@link ITaggedBuilder.tags | .tags()} accumulators and apply the
20
+ * collected tags to every construct in the build result.
21
+ *
22
+ * The wrapper maintains its own outer Proxy around the inner builder Proxy
23
+ * created by `@composurecdk/core`. The outer Proxy:
24
+ *
25
+ * 1. Intercepts `.tag(k, v)` and `.tags({...})` to validate inputs and
26
+ * accumulate them in an insertion-ordered map. Repeated keys overwrite
27
+ * earlier values and emit `process.emitWarning` so the override is
28
+ * visible at the configuring call site.
29
+ * 2. Intercepts `build()` to call {@link applyBuilderTags} on the result
30
+ * after the inner build completes.
31
+ * 3. Intercepts `copy()` so the returned builder is itself a tagged builder
32
+ * with an independent clone of the accumulator. Without this, `.copy()`
33
+ * on a tagged builder would surface the bare inner Proxy and silently
34
+ * drop both the tag methods and the build-time walker.
35
+ * 4. Passes every other access through to the inner Proxy unchanged. Inner
36
+ * methods that return the inner Proxy (chainable setters) are
37
+ * re-wrapped so the chain returns the outer Proxy and the new tag
38
+ * methods stay reachable.
39
+ *
40
+ * Each builder factory in the library opts in by calling `taggedBuilder()`
41
+ * instead of `Builder()`. Custom builders authored outside the library can
42
+ * use plain `Builder()` and forgo tagging.
43
+ *
44
+ * @typeParam Props - The configurable properties.
45
+ * @typeParam T - The target class the builder wraps.
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * export function createBucketBuilder(): IBucketBuilder {
50
+ * return taggedBuilder<BucketBuilderProps, BucketBuilder>(BucketBuilder);
51
+ * }
52
+ *
53
+ * createBucketBuilder()
54
+ * .tag("Project", "claude-rig")
55
+ * .tags({ Owner: "platform", Environment: "prod" })
56
+ * .build(stack, "Bucket");
57
+ * ```
58
+ */
59
+ function taggedBuilder(constructor) {
60
+ const inner = (0, core_1.Builder)(constructor);
61
+ return wrapTagged(inner, new Map());
62
+ }
63
+ function wrapTagged(inner, accumulator) {
64
+ const recordTag = (key, value) => {
65
+ if (accumulator.has(key)) {
66
+ const previous = accumulator.get(key);
67
+ process.emitWarning(`Tag "${key}" was already set to "${previous ?? ""}" and is being overwritten with "${value}". ` +
68
+ "Last write wins; remove the duplicate to silence this warning.", { type: exports.TAG_OVERRIDE_WARNING_NAME });
69
+ }
70
+ accumulator.set(key, value);
71
+ };
72
+ const buildFn = (...args) => {
73
+ const target = inner;
74
+ const result = target.build(...args);
75
+ (0, apply_builder_tags_js_1.applyBuilderTags)(result, accumulator);
76
+ return result;
77
+ };
78
+ const copyFn = () => {
79
+ const innerCopy = inner;
80
+ return wrapTagged(innerCopy.copy(), new Map(accumulator));
81
+ };
82
+ // `tagFn` / `tagsFn` and `outer` form a cycle: the interceptors return
83
+ // `outer` for chaining, and `outer`'s `get` trap returns the interceptors.
84
+ // Resolved by Proxy laziness: the `get` trap closes over the names but
85
+ // does not read them until a property is accessed, by which point the
86
+ // `const`s below have initialised.
87
+ const outer = new Proxy(inner, {
88
+ get(target, prop, receiver) {
89
+ if (prop === "tag")
90
+ return tagFn;
91
+ if (prop === "tags")
92
+ return tagsFn;
93
+ if (prop === "build")
94
+ return buildFn;
95
+ if (prop === "copy")
96
+ return copyFn;
97
+ const value = Reflect.get(target, prop, receiver);
98
+ if (typeof value === "function") {
99
+ return (...args) => {
100
+ const ret = value.apply(target, args);
101
+ return ret === inner ? outer : ret;
102
+ };
103
+ }
104
+ return value;
105
+ },
106
+ });
107
+ const tagFn = (key, value) => {
108
+ (0, tag_validator_js_1.validateTag)(key, value);
109
+ recordTag(key, value);
110
+ return outer;
111
+ };
112
+ const tagsFn = (values) => {
113
+ (0, tag_validator_js_1.validateTagRecord)(values);
114
+ for (const [key, value] of Object.entries(values)) {
115
+ recordTag(key, value);
116
+ }
117
+ return outer;
118
+ };
119
+ return outer;
120
+ }
121
+ //# sourceMappingURL=tagged-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tagged-builder.js","sourceRoot":"","sources":["../../src/tagged-builder.ts"],"names":[],"mappings":";;;AAyHA,sCAKC;AA9HD,6CAA4D;AAC5D,mEAA2D;AAC3D,yDAAoE;AAEpE;;;;;;;GAOG;AACU,QAAA,yBAAyB,GAAG,yBAAyB,CAAC;AAmEnE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,SAAgB,aAAa,CAC3B,WAA2B;IAE3B,MAAM,KAAK,GAAG,IAAA,cAAO,EAAW,WAAW,CAAC,CAAC;IAC7C,OAAO,UAAU,CAAW,KAAK,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,UAAU,CACjB,KAAyB,EACzB,WAAgC;IAEhC,MAAM,SAAS,GAAG,CAAC,GAAW,EAAE,KAAa,EAAQ,EAAE;QACrD,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACtC,OAAO,CAAC,WAAW,CACjB,QAAQ,GAAG,yBAAyB,QAAQ,IAAI,EAAE,oCAAoC,KAAK,KAAK;gBAC9F,gEAAgE,EAClE,EAAE,IAAI,EAAE,iCAAyB,EAAE,CACpC,CAAC;QACJ,CAAC;QACD,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC9B,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,CAAC,GAAG,IAAe,EAAU,EAAE;QAC7C,MAAM,MAAM,GAAG,KAA0D,CAAC;QAC1E,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QACrC,IAAA,wCAAgB,EAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QACtC,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,GAA6B,EAAE;QAC5C,MAAM,SAAS,GAAG,KAAsD,CAAC;QACzE,OAAO,UAAU,CAAW,SAAS,CAAC,IAAI,EAAE,EAAE,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC;IACtE,CAAC,CAAC;IAEF,uEAAuE;IACvE,2EAA2E;IAC3E,uEAAuE;IACvE,sEAAsE;IACtE,mCAAmC;IACnC,MAAM,KAAK,GAA6B,IAAI,KAAK,CAAC,KAAK,EAAE;QACvD,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ;YACxB,IAAI,IAAI,KAAK,KAAK;gBAAE,OAAO,KAAK,CAAC;YACjC,IAAI,IAAI,KAAK,MAAM;gBAAE,OAAO,MAAM,CAAC;YACnC,IAAI,IAAI,KAAK,OAAO;gBAAE,OAAO,OAAO,CAAC;YACrC,IAAI,IAAI,KAAK,MAAM;gBAAE,OAAO,MAAM,CAAC;YAEnC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAY,CAAC;YAC7D,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;gBAChC,OAAO,CAAC,GAAG,IAAe,EAAE,EAAE;oBAC5B,MAAM,GAAG,GAAI,KAAsC,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;oBACxE,OAAO,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;gBACrC,CAAC,CAAC;YACJ,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;KACF,CAA6B,CAAC;IAE/B,MAAM,KAAK,GAAG,CAAC,GAAW,EAAE,KAAa,EAA4B,EAAE;QACrE,IAAA,8BAAW,EAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACxB,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACtB,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IACF,MAAM,MAAM,GAAG,CAAC,MAA8B,EAA4B,EAAE;QAC1E,IAAA,oCAAiB,EAAC,MAAM,CAAC,CAAC;QAC1B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAClD,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACxB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tags.d.ts","sourceRoot":"","sources":["../../src/tags.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIzD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IACvD;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEhC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;CACzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CA+B1F"}
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tags = tags;
4
+ const apply_builder_tags_js_1 = require("./apply-builder-tags.js");
5
+ const tag_validator_js_1 = require("./tag-validator.js");
6
+ /**
7
+ * Returns an {@link AfterBuildHook} that applies cross-cutting tags to a
8
+ * composed system. Modelled on {@link outputs} — both share the same
9
+ * `(scope, id, results, componentScopes)` shape and live alongside the
10
+ * other CloudFormation-flavoured composition helpers in this package.
11
+ *
12
+ * The hook walks the supplied {@link TagDefinitions} once per build:
13
+ *
14
+ * - `system` entries are applied to the top-level `scope` via
15
+ * `Tags.of(scope).add(...)`. CDK's tag aspect propagates each tag
16
+ * through the construct subtree to every taggable descendant.
17
+ * - `byComponent` entries are applied to `componentScopes[key]` —
18
+ * each component's own scope, which under
19
+ * {@link ComposedSystem.withStacks} or
20
+ * {@link ComposedSystem.withStackStrategy} may be a per-component
21
+ * stack rather than the top-level scope.
22
+ *
23
+ * Tag keys and values are validated synchronously inside the hook before
24
+ * any `Tags.of(...).add(...)` call. Invalid tags throw and surface at the
25
+ * `compose(...).afterBuild(tags({...}))` site rather than at deploy time.
26
+ *
27
+ * Builder-level tags (set via `.tag()` / `.tags()` on individual builders)
28
+ * land on closer-scoped constructs and therefore win on key collision —
29
+ * CDK's tag priority resolves the collision automatically. Use builder
30
+ * tags for selector tags that must match exactly one resource type;
31
+ * use this helper for system-wide concerns like ownership, environment,
32
+ * and cost-allocation dimensions.
33
+ *
34
+ * @param defs - System-wide and per-component tag definitions.
35
+ * @returns An `AfterBuildHook` to pass to {@link ComposedSystem.afterBuild}.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * import { compose } from "@composurecdk/core";
40
+ * import { tags } from "@composurecdk/cloudformation";
41
+ *
42
+ * compose(
43
+ * { agent: createInstanceBuilder(), bucket: createBucketBuilder() },
44
+ * { agent: [], bucket: [] },
45
+ * )
46
+ * .afterBuild(
47
+ * tags({
48
+ * system: { Owner: "platform", Environment: "prod" },
49
+ * byComponent: { agent: { Project: "claude-rig" } },
50
+ * }),
51
+ * )
52
+ * .build(stack, "MySystem");
53
+ * ```
54
+ */
55
+ function tags(defs) {
56
+ // Validate eagerly so configuration errors surface at the call site.
57
+ if (defs.system) {
58
+ (0, tag_validator_js_1.validateTagRecord)(defs.system);
59
+ }
60
+ const byComponent = defs.byComponent;
61
+ if (byComponent) {
62
+ for (const componentTags of Object.values(byComponent)) {
63
+ if (componentTags === undefined)
64
+ continue;
65
+ (0, tag_validator_js_1.validateTagRecord)(componentTags);
66
+ }
67
+ }
68
+ return (scope, _id, _results, componentScopes) => {
69
+ if (defs.system) {
70
+ (0, apply_builder_tags_js_1.applyTagsToConstruct)(scope, Object.entries(defs.system));
71
+ }
72
+ if (byComponent) {
73
+ const scopesByKey = componentScopes;
74
+ for (const [componentKey, componentTags] of Object.entries(byComponent)) {
75
+ if (componentTags === undefined)
76
+ continue;
77
+ const target = scopesByKey[componentKey];
78
+ if (target === undefined) {
79
+ throw new Error(`tags(): byComponent entry "${componentKey}" is not a known component.`);
80
+ }
81
+ (0, apply_builder_tags_js_1.applyTagsToConstruct)(target, Object.entries(componentTags));
82
+ }
83
+ }
84
+ };
85
+ }
86
+ //# sourceMappingURL=tags.js.map