@composurecdk/cloudformation 0.8.3 → 0.8.5

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 (49) hide show
  1. package/dist/commonjs/constraints/char-sets.d.ts +22 -0
  2. package/dist/commonjs/constraints/char-sets.d.ts.map +1 -0
  3. package/dist/commonjs/constraints/char-sets.js +30 -0
  4. package/dist/commonjs/constraints/char-sets.js.map +1 -0
  5. package/dist/commonjs/constraints/index.d.ts +4 -0
  6. package/dist/commonjs/constraints/index.d.ts.map +1 -0
  7. package/dist/commonjs/constraints/index.js +10 -0
  8. package/dist/commonjs/constraints/index.js.map +1 -0
  9. package/dist/commonjs/constraints/namespace.d.ts +17 -0
  10. package/dist/commonjs/constraints/namespace.d.ts.map +1 -0
  11. package/dist/commonjs/constraints/namespace.js +3 -0
  12. package/dist/commonjs/constraints/namespace.js.map +1 -0
  13. package/dist/commonjs/constraints/string-constraint.d.ts +73 -0
  14. package/dist/commonjs/constraints/string-constraint.d.ts.map +1 -0
  15. package/dist/commonjs/constraints/string-constraint.js +78 -0
  16. package/dist/commonjs/constraints/string-constraint.js.map +1 -0
  17. package/dist/commonjs/index.d.ts +1 -0
  18. package/dist/commonjs/index.d.ts.map +1 -1
  19. package/dist/commonjs/index.js +6 -1
  20. package/dist/commonjs/index.js.map +1 -1
  21. package/dist/commonjs/tag-validator.d.ts +5 -7
  22. package/dist/commonjs/tag-validator.d.ts.map +1 -1
  23. package/dist/commonjs/tag-validator.js +39 -32
  24. package/dist/commonjs/tag-validator.js.map +1 -1
  25. package/dist/esm/constraints/char-sets.d.ts +22 -0
  26. package/dist/esm/constraints/char-sets.d.ts.map +1 -0
  27. package/dist/esm/constraints/char-sets.js +27 -0
  28. package/dist/esm/constraints/char-sets.js.map +1 -0
  29. package/dist/esm/constraints/index.d.ts +4 -0
  30. package/dist/esm/constraints/index.d.ts.map +1 -0
  31. package/dist/esm/constraints/index.js +3 -0
  32. package/dist/esm/constraints/index.js.map +1 -0
  33. package/dist/esm/constraints/namespace.d.ts +17 -0
  34. package/dist/esm/constraints/namespace.d.ts.map +1 -0
  35. package/dist/esm/constraints/namespace.js +2 -0
  36. package/dist/esm/constraints/namespace.js.map +1 -0
  37. package/dist/esm/constraints/string-constraint.d.ts +73 -0
  38. package/dist/esm/constraints/string-constraint.d.ts.map +1 -0
  39. package/dist/esm/constraints/string-constraint.js +73 -0
  40. package/dist/esm/constraints/string-constraint.js.map +1 -0
  41. package/dist/esm/index.d.ts +1 -0
  42. package/dist/esm/index.d.ts.map +1 -1
  43. package/dist/esm/index.js +1 -0
  44. package/dist/esm/index.js.map +1 -1
  45. package/dist/esm/tag-validator.d.ts +5 -7
  46. package/dist/esm/tag-validator.d.ts.map +1 -1
  47. package/dist/esm/tag-validator.js +39 -32
  48. package/dist/esm/tag-validator.js.map +1 -1
  49. package/package.json +14 -5
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Character-class fragments shared across multiple AWS-property constraints.
3
+ *
4
+ * Each value is pre-escaped and ordered for use *inside* a `[...]` character
5
+ * class (the `-` is escaped, so position is irrelevant). A constraint spreads
6
+ * these into its own class alongside any property-specific characters, so the
7
+ * common spine is declared once and the per-property tail stays local.
8
+ *
9
+ * They are grouped under a single {@link charSets} export to keep the package
10
+ * surface tidy. A fragment graduates here only once a *second* property needs
11
+ * it — promotion is a one-line move plus an import change in the owning
12
+ * packages. See ADR-0010.
13
+ */
14
+ /**
15
+ * Shared character-class fragments. Spread one into a constraint's `charClass`:
16
+ * `` charClass: `${charSets.ALNUM}${charSets.AWS_NAME_PUNCT}...` ``.
17
+ */
18
+ export declare const charSets: {
19
+ readonly ALNUM: "A-Za-z0-9";
20
+ readonly AWS_NAME_PUNCT: " _./:+=@#()\\-";
21
+ };
22
+ //# sourceMappingURL=char-sets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"char-sets.d.ts","sourceRoot":"","sources":["../../../src/constraints/char-sets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAYH;;;GAGG;AACH,eAAO,MAAM,QAAQ;;;CAAqC,CAAC"}
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ /**
3
+ * Character-class fragments shared across multiple AWS-property constraints.
4
+ *
5
+ * Each value is pre-escaped and ordered for use *inside* a `[...]` character
6
+ * class (the `-` is escaped, so position is irrelevant). A constraint spreads
7
+ * these into its own class alongside any property-specific characters, so the
8
+ * common spine is declared once and the per-property tail stays local.
9
+ *
10
+ * They are grouped under a single {@link charSets} export to keep the package
11
+ * surface tidy. A fragment graduates here only once a *second* property needs
12
+ * it — promotion is a one-line move plus an import change in the owning
13
+ * packages. See ADR-0010.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.charSets = void 0;
17
+ /** ASCII letters and digits — the base of nearly every AWS name/description. */
18
+ const ALNUM = "A-Za-z0-9";
19
+ /**
20
+ * The punctuation common to AlarmName, SecurityGroup descriptions, and tags
21
+ * (the measured intersection). Individual properties extend this with their
22
+ * own additional characters; they do not redefine the shared set.
23
+ */
24
+ const AWS_NAME_PUNCT = " _./:+=@#()\\-";
25
+ /**
26
+ * Shared character-class fragments. Spread one into a constraint's `charClass`:
27
+ * `` charClass: `${charSets.ALNUM}${charSets.AWS_NAME_PUNCT}...` ``.
28
+ */
29
+ exports.charSets = { ALNUM, AWS_NAME_PUNCT };
30
+ //# sourceMappingURL=char-sets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"char-sets.js","sourceRoot":"","sources":["../../../src/constraints/char-sets.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;;AAEH,gFAAgF;AAChF,MAAM,KAAK,GAAG,WAAW,CAAC;AAE1B;;;;GAIG;AACH,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAExC;;;GAGG;AACU,QAAA,QAAQ,GAAG,EAAE,KAAK,EAAE,cAAc,EAAW,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { type StringConstraint, stringConstraint, validateString, sanitizeString, } from "./string-constraint.js";
2
+ export { charSets } from "./char-sets.js";
3
+ export { type ConstraintNamespace } from "./namespace.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/constraints/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EACrB,gBAAgB,EAChB,cAAc,EACd,cAAc,GACf,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.charSets = exports.sanitizeString = exports.validateString = exports.stringConstraint = void 0;
4
+ var string_constraint_js_1 = require("./string-constraint.js");
5
+ Object.defineProperty(exports, "stringConstraint", { enumerable: true, get: function () { return string_constraint_js_1.stringConstraint; } });
6
+ Object.defineProperty(exports, "validateString", { enumerable: true, get: function () { return string_constraint_js_1.validateString; } });
7
+ Object.defineProperty(exports, "sanitizeString", { enumerable: true, get: function () { return string_constraint_js_1.sanitizeString; } });
8
+ var char_sets_js_1 = require("./char-sets.js");
9
+ Object.defineProperty(exports, "charSets", { enumerable: true, get: function () { return char_sets_js_1.charSets; } });
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/constraints/index.ts"],"names":[],"mappings":";;;AAAA,+DAKgC;AAH9B,wHAAA,gBAAgB,OAAA;AAChB,sHAAA,cAAc,OAAA;AACd,sHAAA,cAAc,OAAA;AAEhB,+CAA0C;AAAjC,wGAAA,QAAQ,OAAA"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The shape every package's `constraints` export conforms to.
3
+ *
4
+ * Discoverability is a convention, not a runtime aggregate: each builder
5
+ * package exposes its own `constraints` object of this shape, so the calling
6
+ * pattern (`constraints.validate.*` / `constraints.sanitize.*`) is identical
7
+ * everywhere and a consumer imports only the package they already use. The
8
+ * browsable index of the whole catalogue is a generated doc, not an import.
9
+ * See ADR-0010.
10
+ */
11
+ export interface ConstraintNamespace {
12
+ /** Throwing validators for user-authored values the author can fix. */
13
+ readonly validate: Readonly<Record<string, (raw: string) => void>>;
14
+ /** Transforming sanitisers for derived values the author does not control. */
15
+ readonly sanitize: Readonly<Record<string, (raw: string) => string>>;
16
+ }
17
+ //# sourceMappingURL=namespace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"namespace.d.ts","sourceRoot":"","sources":["../../../src/constraints/namespace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAClC,uEAAuE;IACvE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC;IACnE,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC;CACtE"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=namespace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"namespace.js","sourceRoot":"","sources":["../../../src/constraints/namespace.ts"],"names":[],"mappings":""}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The shared mechanism behind the AWS-property constraint catalogue.
3
+ *
4
+ * AWS rejects malformed property strings (bad character sets, over-length
5
+ * values) at deploy time, after a successful `cdk synth`. A {@link StringConstraint}
6
+ * captures one AWS property's character-set and length rules as data, so a
7
+ * builder can fail at synth — at the authoring call site — instead.
8
+ *
9
+ * The catalogue is deliberately split: this mechanism lives here, while the
10
+ * per-resource constraint *data* lives in the package that owns the builder
11
+ * (e.g. `SECURITY_GROUP_DESCRIPTION` in `@composurecdk/ec2`). Cross-cutting
12
+ * constraints that apply to every resource (such as tags) live alongside this
13
+ * mechanism instead. See ADR-0010.
14
+ */
15
+ /**
16
+ * A single AWS-property constraint, expressed as data. One entry per
17
+ * `(resource, property)` pair.
18
+ *
19
+ * Both regexes are compiled once. `validate*` helpers test {@link pattern};
20
+ * `sanitize*` helpers replace runs matched by {@link sanitizePattern}. Use
21
+ * {@link stringConstraint} to build one so the two stay in sync.
22
+ */
23
+ export interface StringConstraint {
24
+ /** Human-readable property identifier, e.g. `"EC2 SecurityGroup GroupDescription"`. */
25
+ readonly name: string;
26
+ /** Anchored full-match pattern used by `validate*`. */
27
+ readonly pattern: RegExp;
28
+ /** Global negated-character-class pattern used by `sanitize*`; absent for pattern-only constraints. */
29
+ readonly sanitizePattern?: RegExp;
30
+ readonly minLength?: number;
31
+ readonly maxLength?: number;
32
+ /** Human-readable allowed-set, surfaced in validation error messages. */
33
+ readonly allowed: string;
34
+ /** AWS doc / CFN reference URL, surfaced in validation error messages. */
35
+ readonly source: string;
36
+ }
37
+ /**
38
+ * Builds a {@link StringConstraint} from a character class and bounds. The
39
+ * anchored validation pattern and the negated sanitisation pattern are both
40
+ * derived from `charClass` and compiled once here, so a single declaration
41
+ * drives both validation and sanitisation and the two cannot drift apart.
42
+ *
43
+ * @param spec - The constraint's character class, length bounds, and metadata.
44
+ * @returns A constraint ready for {@link validateString} / {@link sanitizeString}.
45
+ */
46
+ export declare function stringConstraint(spec: {
47
+ name: string;
48
+ charClass: string;
49
+ minLength?: number;
50
+ maxLength?: number;
51
+ allowed: string;
52
+ source: string;
53
+ flags?: string;
54
+ }): StringConstraint;
55
+ /**
56
+ * Validates `value` against `constraint`, throwing synchronously on the first
57
+ * violation. Use for **user-authored** values the author can fix — the error
58
+ * fires at the call site, naming the allowed set and linking the AWS doc.
59
+ *
60
+ * @throws If `value` is shorter than `minLength`, longer than `maxLength`, or
61
+ * contains characters outside the constraint's pattern.
62
+ */
63
+ export declare function validateString(value: string, constraint: StringConstraint): void;
64
+ /**
65
+ * Returns a copy of `value` made legal for `constraint` by replacing runs of
66
+ * disallowed characters with `replacement` and truncating to `maxLength`. Use
67
+ * for **derived** values the author does not control (e.g. a DNS name composed
68
+ * into a construct ID), where rewriting is the only sensible move.
69
+ *
70
+ * @throws If the constraint is pattern-only and declares no sanitisation pattern.
71
+ */
72
+ export declare function sanitizeString(value: string, constraint: StringConstraint, replacement?: string): string;
73
+ //# sourceMappingURL=string-constraint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"string-constraint.d.ts","sourceRoot":"","sources":["../../../src/constraints/string-constraint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uFAAuF;IACvF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,uGAAuG;IACvG,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,gBAAgB,CAWnB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAgBhF;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,gBAAgB,EAC5B,WAAW,SAAM,GAChB,MAAM,CAWR"}
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ /**
3
+ * The shared mechanism behind the AWS-property constraint catalogue.
4
+ *
5
+ * AWS rejects malformed property strings (bad character sets, over-length
6
+ * values) at deploy time, after a successful `cdk synth`. A {@link StringConstraint}
7
+ * captures one AWS property's character-set and length rules as data, so a
8
+ * builder can fail at synth — at the authoring call site — instead.
9
+ *
10
+ * The catalogue is deliberately split: this mechanism lives here, while the
11
+ * per-resource constraint *data* lives in the package that owns the builder
12
+ * (e.g. `SECURITY_GROUP_DESCRIPTION` in `@composurecdk/ec2`). Cross-cutting
13
+ * constraints that apply to every resource (such as tags) live alongside this
14
+ * mechanism instead. See ADR-0010.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.stringConstraint = stringConstraint;
18
+ exports.validateString = validateString;
19
+ exports.sanitizeString = sanitizeString;
20
+ /**
21
+ * Builds a {@link StringConstraint} from a character class and bounds. The
22
+ * anchored validation pattern and the negated sanitisation pattern are both
23
+ * derived from `charClass` and compiled once here, so a single declaration
24
+ * drives both validation and sanitisation and the two cannot drift apart.
25
+ *
26
+ * @param spec - The constraint's character class, length bounds, and metadata.
27
+ * @returns A constraint ready for {@link validateString} / {@link sanitizeString}.
28
+ */
29
+ function stringConstraint(spec) {
30
+ const quantifier = `{${String(spec.minLength ?? 0)},${spec.maxLength === undefined ? "" : String(spec.maxLength)}}`;
31
+ return {
32
+ name: spec.name,
33
+ pattern: new RegExp(`^[${spec.charClass}]${quantifier}$`, spec.flags),
34
+ sanitizePattern: new RegExp(`[^${spec.charClass}]+`, spec.flags?.includes("u") ? "gu" : "g"),
35
+ minLength: spec.minLength,
36
+ maxLength: spec.maxLength,
37
+ allowed: spec.allowed,
38
+ source: spec.source,
39
+ };
40
+ }
41
+ /**
42
+ * Validates `value` against `constraint`, throwing synchronously on the first
43
+ * violation. Use for **user-authored** values the author can fix — the error
44
+ * fires at the call site, naming the allowed set and linking the AWS doc.
45
+ *
46
+ * @throws If `value` is shorter than `minLength`, longer than `maxLength`, or
47
+ * contains characters outside the constraint's pattern.
48
+ */
49
+ function validateString(value, constraint) {
50
+ if (constraint.minLength !== undefined && value.length < constraint.minLength) {
51
+ throw new Error(`${constraint.name} "${value}" is shorter than the ${String(constraint.minLength)}-character minimum. See ${constraint.source}.`);
52
+ }
53
+ if (constraint.maxLength !== undefined && value.length > constraint.maxLength) {
54
+ throw new Error(`${constraint.name} "${value}" exceeds the ${String(constraint.maxLength)}-character limit. See ${constraint.source}.`);
55
+ }
56
+ if (!constraint.pattern.test(value)) {
57
+ throw new Error(`${constraint.name} "${value}" is invalid. Allowed: ${constraint.allowed}. See ${constraint.source}.`);
58
+ }
59
+ }
60
+ /**
61
+ * Returns a copy of `value` made legal for `constraint` by replacing runs of
62
+ * disallowed characters with `replacement` and truncating to `maxLength`. Use
63
+ * for **derived** values the author does not control (e.g. a DNS name composed
64
+ * into a construct ID), where rewriting is the only sensible move.
65
+ *
66
+ * @throws If the constraint is pattern-only and declares no sanitisation pattern.
67
+ */
68
+ function sanitizeString(value, constraint, replacement = "-") {
69
+ if (constraint.sanitizePattern === undefined) {
70
+ throw new Error(`${constraint.name} cannot be sanitised: the constraint has no character class.`);
71
+ }
72
+ let out = value.replace(constraint.sanitizePattern, replacement);
73
+ if (constraint.maxLength !== undefined && out.length > constraint.maxLength) {
74
+ out = out.slice(0, constraint.maxLength);
75
+ }
76
+ return out;
77
+ }
78
+ //# sourceMappingURL=string-constraint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"string-constraint.js","sourceRoot":"","sources":["../../../src/constraints/string-constraint.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;AAkCH,4CAmBC;AAUD,wCAgBC;AAUD,wCAeC;AA/ED;;;;;;;;GAQG;AACH,SAAgB,gBAAgB,CAAC,IAQhC;IACC,MAAM,UAAU,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC;IACpH,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,OAAO,EAAE,IAAI,MAAM,CAAC,KAAK,IAAI,CAAC,SAAS,IAAI,UAAU,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC;QACrE,eAAe,EAAE,IAAI,MAAM,CAAC,KAAK,IAAI,CAAC,SAAS,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;QAC5F,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,cAAc,CAAC,KAAa,EAAE,UAA4B;IACxE,IAAI,UAAU,CAAC,SAAS,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,SAAS,EAAE,CAAC;QAC9E,MAAM,IAAI,KAAK,CACb,GAAG,UAAU,CAAC,IAAI,KAAK,KAAK,yBAAyB,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,2BAA2B,UAAU,CAAC,MAAM,GAAG,CACjI,CAAC;IACJ,CAAC;IACD,IAAI,UAAU,CAAC,SAAS,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,SAAS,EAAE,CAAC;QAC9E,MAAM,IAAI,KAAK,CACb,GAAG,UAAU,CAAC,IAAI,KAAK,KAAK,iBAAiB,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,yBAAyB,UAAU,CAAC,MAAM,GAAG,CACvH,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CACb,GAAG,UAAU,CAAC,IAAI,KAAK,KAAK,0BAA0B,UAAU,CAAC,OAAO,SAAS,UAAU,CAAC,MAAM,GAAG,CACtG,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,cAAc,CAC5B,KAAa,EACb,UAA4B,EAC5B,WAAW,GAAG,GAAG;IAEjB,IAAI,UAAU,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CACb,GAAG,UAAU,CAAC,IAAI,8DAA8D,CACjF,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC;IACjE,IAAI,UAAU,CAAC,SAAS,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,GAAG,UAAU,CAAC,SAAS,EAAE,CAAC;QAC5E,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -5,4 +5,5 @@ export { taggedBuilder, type ITaggedBuilder, TAG_OVERRIDE_WARNING_NAME } from ".
5
5
  export { applyBuilderTags } from "./apply-builder-tags.js";
6
6
  export { validateTag } from "./tag-validator.js";
7
7
  export { tags, type TagDefinitions } from "./tags.js";
8
+ export { type StringConstraint, stringConstraint, validateString, sanitizeString, charSets, type ConstraintNamespace, } from "./constraints/index.js";
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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"}
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;AACtD,OAAO,EACL,KAAK,gBAAgB,EACrB,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,QAAQ,EACR,KAAK,mBAAmB,GACzB,MAAM,wBAAwB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
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;
3
+ exports.charSets = exports.sanitizeString = exports.validateString = exports.stringConstraint = exports.tags = exports.validateTag = exports.applyBuilderTags = exports.TAG_OVERRIDE_WARNING_NAME = exports.taggedBuilder = exports.outputs = exports.groupedStacks = exports.singleStack = exports.createStackBuilder = void 0;
4
4
  var stack_builder_js_1 = require("./stack-builder.js");
5
5
  Object.defineProperty(exports, "createStackBuilder", { enumerable: true, get: function () { return stack_builder_js_1.createStackBuilder; } });
6
6
  var strategies_js_1 = require("./strategies.js");
@@ -17,4 +17,9 @@ var tag_validator_js_1 = require("./tag-validator.js");
17
17
  Object.defineProperty(exports, "validateTag", { enumerable: true, get: function () { return tag_validator_js_1.validateTag; } });
18
18
  var tags_js_1 = require("./tags.js");
19
19
  Object.defineProperty(exports, "tags", { enumerable: true, get: function () { return tags_js_1.tags; } });
20
+ var index_js_1 = require("./constraints/index.js");
21
+ Object.defineProperty(exports, "stringConstraint", { enumerable: true, get: function () { return index_js_1.stringConstraint; } });
22
+ Object.defineProperty(exports, "validateString", { enumerable: true, get: function () { return index_js_1.validateString; } });
23
+ Object.defineProperty(exports, "sanitizeString", { enumerable: true, get: function () { return index_js_1.sanitizeString; } });
24
+ Object.defineProperty(exports, "charSets", { enumerable: true, get: function () { return index_js_1.charSets; } });
20
25
  //# sourceMappingURL=index.js.map
@@ -1 +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"}
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;AACb,mDAOgC;AAL9B,4GAAA,gBAAgB,OAAA;AAChB,0GAAA,cAAc,OAAA;AACd,0GAAA,cAAc,OAAA;AACd,oGAAA,QAAQ,OAAA"}
@@ -4,14 +4,12 @@
4
4
  * Throws synchronously at the call site so authors see the failure where the
5
5
  * bad value was written, not at deploy time. Validates:
6
6
  *
7
- * - `key` is non-empty and at most {@link KEY_MAX} characters.
8
- * - `key` does not start with the reserved `aws:` prefix (case-insensitive).
9
- * - `value` is at most {@link VALUE_MAX} characters.
10
- * - both `key` and `value` use only the AWS-permitted character set.
7
+ * - `key` is non-empty and does not start with the reserved `aws:` prefix
8
+ * (case-insensitive) both tag-specific rules.
9
+ * - `key` and `value` length and character set, via the shared catalogue
10
+ * mechanism. Empty values are permitted; empty keys are not.
11
11
  *
12
- * The regex matches Unicode letters, digits, and whitespace plus the
13
- * documented punctuation set, so non-ASCII tags are accepted as AWS
14
- * supports them.
12
+ * Non-ASCII letters, digits, and whitespace are accepted, matching AWS.
15
13
  */
16
14
  export declare function validateTag(key: string, value: string): void;
17
15
  /**
@@ -1 +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"}
1
+ {"version":3,"file":"tag-validator.d.ts","sourceRoot":"","sources":["../../src/tag-validator.ts"],"names":[],"mappings":"AAyCA;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAW5D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAItE"}
@@ -2,58 +2,65 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validateTag = validateTag;
4
4
  exports.validateTagRecord = validateTagRecord;
5
+ const index_js_1 = require("./constraints/index.js");
5
6
  /**
6
7
  * AWS allows letters, digits, whitespace, and the symbols `_ . : / = + - @`
7
8
  * 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.
9
+ * Unicode classes.
10
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
11
+ * `\p{Z}` matches the full Unicode separator class — slightly more permissive
12
+ * than AWS's documented "white space," but errs on the side of accepting input
13
+ * the AWS API may yet reject at deploy time, where the failure is reported in
14
+ * the API response.
15
+ */
16
+ const TAG_CHARS = "\\p{L}\\p{Z}\\p{N}_.:/=+@\\-";
17
+ const TAG_ALLOWED = "the AWS tag character set: letters, digits, whitespace, and _ . : / = + - @";
18
+ const TAG_SOURCE = "https://docs.aws.amazon.com/general/latest/gr/aws_tagging.html";
19
+ /**
20
+ * Tags are cross-cutting — they apply to every resource — so unlike per-resource
21
+ * constraints they live alongside the catalogue mechanism rather than in a
22
+ * service package. Both entries are module-private and reached only through
23
+ * {@link validateTag}, which layers the tag-specific empty-key and reserved-
24
+ * prefix rules on top of {@link validateString}. See ADR-0010.
17
25
  */
18
- const TAG_CHAR_RE = /^[\p{L}\p{Z}\p{N}_.:/=+\-@]*$/u;
19
- const KEY_MAX = 128;
20
- const VALUE_MAX = 256;
26
+ const TAG_KEY = (0, index_js_1.stringConstraint)({
27
+ name: "Tag key",
28
+ charClass: TAG_CHARS,
29
+ maxLength: 128,
30
+ allowed: TAG_ALLOWED,
31
+ source: TAG_SOURCE,
32
+ flags: "u",
33
+ });
34
+ const TAG_VALUE = (0, index_js_1.stringConstraint)({
35
+ name: "Tag value",
36
+ charClass: TAG_CHARS,
37
+ maxLength: 256,
38
+ allowed: TAG_ALLOWED,
39
+ source: TAG_SOURCE,
40
+ flags: "u",
41
+ });
21
42
  /**
22
43
  * Validates a single tag key/value pair against AWS tag constraints.
23
44
  *
24
45
  * Throws synchronously at the call site so authors see the failure where the
25
46
  * bad value was written, not at deploy time. Validates:
26
47
  *
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.
48
+ * - `key` is non-empty and does not start with the reserved `aws:` prefix
49
+ * (case-insensitive) both tag-specific rules.
50
+ * - `key` and `value` length and character set, via the shared catalogue
51
+ * mechanism. Empty values are permitted; empty keys are not.
31
52
  *
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.
53
+ * Non-ASCII letters, digits, and whitespace are accepted, matching AWS.
35
54
  */
36
55
  function validateTag(key, value) {
37
56
  if (key.length === 0) {
38
57
  throw new Error("Tag key must be non-empty.");
39
58
  }
40
- if (key.length > KEY_MAX) {
41
- throw new Error(`Tag key "${key}" exceeds ${String(KEY_MAX)}-character limit.`);
42
- }
43
59
  if (key.toLowerCase().startsWith("aws:")) {
44
60
  throw new Error(`Tag key "${key}" uses reserved "aws:" prefix; AWS rejects user tags with this prefix.`);
45
61
  }
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
- }
62
+ (0, index_js_1.validateString)(key, TAG_KEY);
63
+ (0, index_js_1.validateString)(value, TAG_VALUE);
57
64
  }
58
65
  /**
59
66
  * Validates every entry of a record via {@link validateTag}, throwing on the
@@ -1 +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"}
1
+ {"version":3,"file":"tag-validator.js","sourceRoot":"","sources":["../../src/tag-validator.ts"],"names":[],"mappings":";;AAsDA,kCAWC;AAMD,8CAIC;AA3ED,qDAA0E;AAE1E;;;;;;;;;GASG;AACH,MAAM,SAAS,GAAG,8BAA8B,CAAC;AACjD,MAAM,WAAW,GAAG,6EAA6E,CAAC;AAClG,MAAM,UAAU,GAAG,gEAAgE,CAAC;AAEpF;;;;;;GAMG;AACH,MAAM,OAAO,GAAG,IAAA,2BAAgB,EAAC;IAC/B,IAAI,EAAE,SAAS;IACf,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,GAAG;IACd,OAAO,EAAE,WAAW;IACpB,MAAM,EAAE,UAAU;IAClB,KAAK,EAAE,GAAG;CACX,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG,IAAA,2BAAgB,EAAC;IACjC,IAAI,EAAE,WAAW;IACjB,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,GAAG;IACd,OAAO,EAAE,WAAW;IACpB,MAAM,EAAE,UAAU;IAClB,KAAK,EAAE,GAAG;CACX,CAAC,CAAC;AAEH;;;;;;;;;;;;GAYG;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,WAAW,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CACb,YAAY,GAAG,wEAAwE,CACxF,CAAC;IACJ,CAAC;IACD,IAAA,yBAAc,EAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC7B,IAAA,yBAAc,EAAC,KAAK,EAAE,SAAS,CAAC,CAAC;AACnC,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,22 @@
1
+ /**
2
+ * Character-class fragments shared across multiple AWS-property constraints.
3
+ *
4
+ * Each value is pre-escaped and ordered for use *inside* a `[...]` character
5
+ * class (the `-` is escaped, so position is irrelevant). A constraint spreads
6
+ * these into its own class alongside any property-specific characters, so the
7
+ * common spine is declared once and the per-property tail stays local.
8
+ *
9
+ * They are grouped under a single {@link charSets} export to keep the package
10
+ * surface tidy. A fragment graduates here only once a *second* property needs
11
+ * it — promotion is a one-line move plus an import change in the owning
12
+ * packages. See ADR-0010.
13
+ */
14
+ /**
15
+ * Shared character-class fragments. Spread one into a constraint's `charClass`:
16
+ * `` charClass: `${charSets.ALNUM}${charSets.AWS_NAME_PUNCT}...` ``.
17
+ */
18
+ export declare const charSets: {
19
+ readonly ALNUM: "A-Za-z0-9";
20
+ readonly AWS_NAME_PUNCT: " _./:+=@#()\\-";
21
+ };
22
+ //# sourceMappingURL=char-sets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"char-sets.d.ts","sourceRoot":"","sources":["../../../src/constraints/char-sets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAYH;;;GAGG;AACH,eAAO,MAAM,QAAQ;;;CAAqC,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Character-class fragments shared across multiple AWS-property constraints.
3
+ *
4
+ * Each value is pre-escaped and ordered for use *inside* a `[...]` character
5
+ * class (the `-` is escaped, so position is irrelevant). A constraint spreads
6
+ * these into its own class alongside any property-specific characters, so the
7
+ * common spine is declared once and the per-property tail stays local.
8
+ *
9
+ * They are grouped under a single {@link charSets} export to keep the package
10
+ * surface tidy. A fragment graduates here only once a *second* property needs
11
+ * it — promotion is a one-line move plus an import change in the owning
12
+ * packages. See ADR-0010.
13
+ */
14
+ /** ASCII letters and digits — the base of nearly every AWS name/description. */
15
+ const ALNUM = "A-Za-z0-9";
16
+ /**
17
+ * The punctuation common to AlarmName, SecurityGroup descriptions, and tags
18
+ * (the measured intersection). Individual properties extend this with their
19
+ * own additional characters; they do not redefine the shared set.
20
+ */
21
+ const AWS_NAME_PUNCT = " _./:+=@#()\\-";
22
+ /**
23
+ * Shared character-class fragments. Spread one into a constraint's `charClass`:
24
+ * `` charClass: `${charSets.ALNUM}${charSets.AWS_NAME_PUNCT}...` ``.
25
+ */
26
+ export const charSets = { ALNUM, AWS_NAME_PUNCT };
27
+ //# sourceMappingURL=char-sets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"char-sets.js","sourceRoot":"","sources":["../../../src/constraints/char-sets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,gFAAgF;AAChF,MAAM,KAAK,GAAG,WAAW,CAAC;AAE1B;;;;GAIG;AACH,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAExC;;;GAGG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,EAAE,KAAK,EAAE,cAAc,EAAW,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { type StringConstraint, stringConstraint, validateString, sanitizeString, } from "./string-constraint.js";
2
+ export { charSets } from "./char-sets.js";
3
+ export { type ConstraintNamespace } from "./namespace.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/constraints/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EACrB,gBAAgB,EAChB,cAAc,EACd,cAAc,GACf,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { stringConstraint, validateString, sanitizeString, } from "./string-constraint.js";
2
+ export { charSets } from "./char-sets.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/constraints/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,gBAAgB,EAChB,cAAc,EACd,cAAc,GACf,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The shape every package's `constraints` export conforms to.
3
+ *
4
+ * Discoverability is a convention, not a runtime aggregate: each builder
5
+ * package exposes its own `constraints` object of this shape, so the calling
6
+ * pattern (`constraints.validate.*` / `constraints.sanitize.*`) is identical
7
+ * everywhere and a consumer imports only the package they already use. The
8
+ * browsable index of the whole catalogue is a generated doc, not an import.
9
+ * See ADR-0010.
10
+ */
11
+ export interface ConstraintNamespace {
12
+ /** Throwing validators for user-authored values the author can fix. */
13
+ readonly validate: Readonly<Record<string, (raw: string) => void>>;
14
+ /** Transforming sanitisers for derived values the author does not control. */
15
+ readonly sanitize: Readonly<Record<string, (raw: string) => string>>;
16
+ }
17
+ //# sourceMappingURL=namespace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"namespace.d.ts","sourceRoot":"","sources":["../../../src/constraints/namespace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAClC,uEAAuE;IACvE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC;IACnE,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC;CACtE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=namespace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"namespace.js","sourceRoot":"","sources":["../../../src/constraints/namespace.ts"],"names":[],"mappings":""}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The shared mechanism behind the AWS-property constraint catalogue.
3
+ *
4
+ * AWS rejects malformed property strings (bad character sets, over-length
5
+ * values) at deploy time, after a successful `cdk synth`. A {@link StringConstraint}
6
+ * captures one AWS property's character-set and length rules as data, so a
7
+ * builder can fail at synth — at the authoring call site — instead.
8
+ *
9
+ * The catalogue is deliberately split: this mechanism lives here, while the
10
+ * per-resource constraint *data* lives in the package that owns the builder
11
+ * (e.g. `SECURITY_GROUP_DESCRIPTION` in `@composurecdk/ec2`). Cross-cutting
12
+ * constraints that apply to every resource (such as tags) live alongside this
13
+ * mechanism instead. See ADR-0010.
14
+ */
15
+ /**
16
+ * A single AWS-property constraint, expressed as data. One entry per
17
+ * `(resource, property)` pair.
18
+ *
19
+ * Both regexes are compiled once. `validate*` helpers test {@link pattern};
20
+ * `sanitize*` helpers replace runs matched by {@link sanitizePattern}. Use
21
+ * {@link stringConstraint} to build one so the two stay in sync.
22
+ */
23
+ export interface StringConstraint {
24
+ /** Human-readable property identifier, e.g. `"EC2 SecurityGroup GroupDescription"`. */
25
+ readonly name: string;
26
+ /** Anchored full-match pattern used by `validate*`. */
27
+ readonly pattern: RegExp;
28
+ /** Global negated-character-class pattern used by `sanitize*`; absent for pattern-only constraints. */
29
+ readonly sanitizePattern?: RegExp;
30
+ readonly minLength?: number;
31
+ readonly maxLength?: number;
32
+ /** Human-readable allowed-set, surfaced in validation error messages. */
33
+ readonly allowed: string;
34
+ /** AWS doc / CFN reference URL, surfaced in validation error messages. */
35
+ readonly source: string;
36
+ }
37
+ /**
38
+ * Builds a {@link StringConstraint} from a character class and bounds. The
39
+ * anchored validation pattern and the negated sanitisation pattern are both
40
+ * derived from `charClass` and compiled once here, so a single declaration
41
+ * drives both validation and sanitisation and the two cannot drift apart.
42
+ *
43
+ * @param spec - The constraint's character class, length bounds, and metadata.
44
+ * @returns A constraint ready for {@link validateString} / {@link sanitizeString}.
45
+ */
46
+ export declare function stringConstraint(spec: {
47
+ name: string;
48
+ charClass: string;
49
+ minLength?: number;
50
+ maxLength?: number;
51
+ allowed: string;
52
+ source: string;
53
+ flags?: string;
54
+ }): StringConstraint;
55
+ /**
56
+ * Validates `value` against `constraint`, throwing synchronously on the first
57
+ * violation. Use for **user-authored** values the author can fix — the error
58
+ * fires at the call site, naming the allowed set and linking the AWS doc.
59
+ *
60
+ * @throws If `value` is shorter than `minLength`, longer than `maxLength`, or
61
+ * contains characters outside the constraint's pattern.
62
+ */
63
+ export declare function validateString(value: string, constraint: StringConstraint): void;
64
+ /**
65
+ * Returns a copy of `value` made legal for `constraint` by replacing runs of
66
+ * disallowed characters with `replacement` and truncating to `maxLength`. Use
67
+ * for **derived** values the author does not control (e.g. a DNS name composed
68
+ * into a construct ID), where rewriting is the only sensible move.
69
+ *
70
+ * @throws If the constraint is pattern-only and declares no sanitisation pattern.
71
+ */
72
+ export declare function sanitizeString(value: string, constraint: StringConstraint, replacement?: string): string;
73
+ //# sourceMappingURL=string-constraint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"string-constraint.d.ts","sourceRoot":"","sources":["../../../src/constraints/string-constraint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uFAAuF;IACvF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,uGAAuG;IACvG,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,gBAAgB,CAWnB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAgBhF;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,gBAAgB,EAC5B,WAAW,SAAM,GAChB,MAAM,CAWR"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The shared mechanism behind the AWS-property constraint catalogue.
3
+ *
4
+ * AWS rejects malformed property strings (bad character sets, over-length
5
+ * values) at deploy time, after a successful `cdk synth`. A {@link StringConstraint}
6
+ * captures one AWS property's character-set and length rules as data, so a
7
+ * builder can fail at synth — at the authoring call site — instead.
8
+ *
9
+ * The catalogue is deliberately split: this mechanism lives here, while the
10
+ * per-resource constraint *data* lives in the package that owns the builder
11
+ * (e.g. `SECURITY_GROUP_DESCRIPTION` in `@composurecdk/ec2`). Cross-cutting
12
+ * constraints that apply to every resource (such as tags) live alongside this
13
+ * mechanism instead. See ADR-0010.
14
+ */
15
+ /**
16
+ * Builds a {@link StringConstraint} from a character class and bounds. The
17
+ * anchored validation pattern and the negated sanitisation pattern are both
18
+ * derived from `charClass` and compiled once here, so a single declaration
19
+ * drives both validation and sanitisation and the two cannot drift apart.
20
+ *
21
+ * @param spec - The constraint's character class, length bounds, and metadata.
22
+ * @returns A constraint ready for {@link validateString} / {@link sanitizeString}.
23
+ */
24
+ export function stringConstraint(spec) {
25
+ const quantifier = `{${String(spec.minLength ?? 0)},${spec.maxLength === undefined ? "" : String(spec.maxLength)}}`;
26
+ return {
27
+ name: spec.name,
28
+ pattern: new RegExp(`^[${spec.charClass}]${quantifier}$`, spec.flags),
29
+ sanitizePattern: new RegExp(`[^${spec.charClass}]+`, spec.flags?.includes("u") ? "gu" : "g"),
30
+ minLength: spec.minLength,
31
+ maxLength: spec.maxLength,
32
+ allowed: spec.allowed,
33
+ source: spec.source,
34
+ };
35
+ }
36
+ /**
37
+ * Validates `value` against `constraint`, throwing synchronously on the first
38
+ * violation. Use for **user-authored** values the author can fix — the error
39
+ * fires at the call site, naming the allowed set and linking the AWS doc.
40
+ *
41
+ * @throws If `value` is shorter than `minLength`, longer than `maxLength`, or
42
+ * contains characters outside the constraint's pattern.
43
+ */
44
+ export function validateString(value, constraint) {
45
+ if (constraint.minLength !== undefined && value.length < constraint.minLength) {
46
+ throw new Error(`${constraint.name} "${value}" is shorter than the ${String(constraint.minLength)}-character minimum. See ${constraint.source}.`);
47
+ }
48
+ if (constraint.maxLength !== undefined && value.length > constraint.maxLength) {
49
+ throw new Error(`${constraint.name} "${value}" exceeds the ${String(constraint.maxLength)}-character limit. See ${constraint.source}.`);
50
+ }
51
+ if (!constraint.pattern.test(value)) {
52
+ throw new Error(`${constraint.name} "${value}" is invalid. Allowed: ${constraint.allowed}. See ${constraint.source}.`);
53
+ }
54
+ }
55
+ /**
56
+ * Returns a copy of `value` made legal for `constraint` by replacing runs of
57
+ * disallowed characters with `replacement` and truncating to `maxLength`. Use
58
+ * for **derived** values the author does not control (e.g. a DNS name composed
59
+ * into a construct ID), where rewriting is the only sensible move.
60
+ *
61
+ * @throws If the constraint is pattern-only and declares no sanitisation pattern.
62
+ */
63
+ export function sanitizeString(value, constraint, replacement = "-") {
64
+ if (constraint.sanitizePattern === undefined) {
65
+ throw new Error(`${constraint.name} cannot be sanitised: the constraint has no character class.`);
66
+ }
67
+ let out = value.replace(constraint.sanitizePattern, replacement);
68
+ if (constraint.maxLength !== undefined && out.length > constraint.maxLength) {
69
+ out = out.slice(0, constraint.maxLength);
70
+ }
71
+ return out;
72
+ }
73
+ //# sourceMappingURL=string-constraint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"string-constraint.js","sourceRoot":"","sources":["../../../src/constraints/string-constraint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAyBH;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAQhC;IACC,MAAM,UAAU,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC;IACpH,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,OAAO,EAAE,IAAI,MAAM,CAAC,KAAK,IAAI,CAAC,SAAS,IAAI,UAAU,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC;QACrE,eAAe,EAAE,IAAI,MAAM,CAAC,KAAK,IAAI,CAAC,SAAS,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;QAC5F,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa,EAAE,UAA4B;IACxE,IAAI,UAAU,CAAC,SAAS,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,SAAS,EAAE,CAAC;QAC9E,MAAM,IAAI,KAAK,CACb,GAAG,UAAU,CAAC,IAAI,KAAK,KAAK,yBAAyB,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,2BAA2B,UAAU,CAAC,MAAM,GAAG,CACjI,CAAC;IACJ,CAAC;IACD,IAAI,UAAU,CAAC,SAAS,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,SAAS,EAAE,CAAC;QAC9E,MAAM,IAAI,KAAK,CACb,GAAG,UAAU,CAAC,IAAI,KAAK,KAAK,iBAAiB,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,yBAAyB,UAAU,CAAC,MAAM,GAAG,CACvH,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CACb,GAAG,UAAU,CAAC,IAAI,KAAK,KAAK,0BAA0B,UAAU,CAAC,OAAO,SAAS,UAAU,CAAC,MAAM,GAAG,CACtG,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAa,EACb,UAA4B,EAC5B,WAAW,GAAG,GAAG;IAEjB,IAAI,UAAU,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CACb,GAAG,UAAU,CAAC,IAAI,8DAA8D,CACjF,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC;IACjE,IAAI,UAAU,CAAC,SAAS,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,GAAG,UAAU,CAAC,SAAS,EAAE,CAAC;QAC5E,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -5,4 +5,5 @@ export { taggedBuilder, type ITaggedBuilder, TAG_OVERRIDE_WARNING_NAME } from ".
5
5
  export { applyBuilderTags } from "./apply-builder-tags.js";
6
6
  export { validateTag } from "./tag-validator.js";
7
7
  export { tags, type TagDefinitions } from "./tags.js";
8
+ export { type StringConstraint, stringConstraint, validateString, sanitizeString, charSets, type ConstraintNamespace, } from "./constraints/index.js";
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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"}
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;AACtD,OAAO,EACL,KAAK,gBAAgB,EACrB,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,QAAQ,EACR,KAAK,mBAAmB,GACzB,MAAM,wBAAwB,CAAC"}
package/dist/esm/index.js CHANGED
@@ -5,4 +5,5 @@ export { taggedBuilder, TAG_OVERRIDE_WARNING_NAME } from "./tagged-builder.js";
5
5
  export { applyBuilderTags } from "./apply-builder-tags.js";
6
6
  export { validateTag } from "./tag-validator.js";
7
7
  export { tags } from "./tags.js";
8
+ export { stringConstraint, validateString, sanitizeString, charSets, } from "./constraints/index.js";
8
9
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,GAGnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAiD,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,aAAa,EAAuB,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,EAAuB,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,GAGnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAiD,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,aAAa,EAAuB,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,EAAuB,MAAM,WAAW,CAAC;AACtD,OAAO,EAEL,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,QAAQ,GAET,MAAM,wBAAwB,CAAC"}
@@ -4,14 +4,12 @@
4
4
  * Throws synchronously at the call site so authors see the failure where the
5
5
  * bad value was written, not at deploy time. Validates:
6
6
  *
7
- * - `key` is non-empty and at most {@link KEY_MAX} characters.
8
- * - `key` does not start with the reserved `aws:` prefix (case-insensitive).
9
- * - `value` is at most {@link VALUE_MAX} characters.
10
- * - both `key` and `value` use only the AWS-permitted character set.
7
+ * - `key` is non-empty and does not start with the reserved `aws:` prefix
8
+ * (case-insensitive) both tag-specific rules.
9
+ * - `key` and `value` length and character set, via the shared catalogue
10
+ * mechanism. Empty values are permitted; empty keys are not.
11
11
  *
12
- * The regex matches Unicode letters, digits, and whitespace plus the
13
- * documented punctuation set, so non-ASCII tags are accepted as AWS
14
- * supports them.
12
+ * Non-ASCII letters, digits, and whitespace are accepted, matching AWS.
15
13
  */
16
14
  export declare function validateTag(key: string, value: string): void;
17
15
  /**
@@ -1 +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"}
1
+ {"version":3,"file":"tag-validator.d.ts","sourceRoot":"","sources":["../../src/tag-validator.ts"],"names":[],"mappings":"AAyCA;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAW5D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAItE"}
@@ -1,55 +1,62 @@
1
+ import { stringConstraint, validateString } from "./constraints/index.js";
1
2
  /**
2
3
  * AWS allows letters, digits, whitespace, and the symbols `_ . : / = + - @`
3
4
  * in tag keys and values, with non-ASCII letters/digits permitted via
4
- * Unicode classes. Empty values are permitted by AWS for `Value`, but
5
- * empty `Key` is not. The character set is validated against this regex.
5
+ * Unicode classes.
6
6
  *
7
- * `\p{Z}` matches the full Unicode separator class — slightly more
8
- * permissive than AWS's documented "white space," but errors on the side
9
- * of accepting input that the AWS API may yet reject at deploy time. The
10
- * extra rejections happen later but are reported in the API response.
11
- *
12
- * @see https://docs.aws.amazon.com/general/latest/gr/aws_tagging.html
7
+ * `\p{Z}` matches the full Unicode separator class — slightly more permissive
8
+ * than AWS's documented "white space," but errs on the side of accepting input
9
+ * the AWS API may yet reject at deploy time, where the failure is reported in
10
+ * the API response.
11
+ */
12
+ const TAG_CHARS = "\\p{L}\\p{Z}\\p{N}_.:/=+@\\-";
13
+ const TAG_ALLOWED = "the AWS tag character set: letters, digits, whitespace, and _ . : / = + - @";
14
+ const TAG_SOURCE = "https://docs.aws.amazon.com/general/latest/gr/aws_tagging.html";
15
+ /**
16
+ * Tags are cross-cutting — they apply to every resource — so unlike per-resource
17
+ * constraints they live alongside the catalogue mechanism rather than in a
18
+ * service package. Both entries are module-private and reached only through
19
+ * {@link validateTag}, which layers the tag-specific empty-key and reserved-
20
+ * prefix rules on top of {@link validateString}. See ADR-0010.
13
21
  */
14
- const TAG_CHAR_RE = /^[\p{L}\p{Z}\p{N}_.:/=+\-@]*$/u;
15
- const KEY_MAX = 128;
16
- const VALUE_MAX = 256;
22
+ const TAG_KEY = stringConstraint({
23
+ name: "Tag key",
24
+ charClass: TAG_CHARS,
25
+ maxLength: 128,
26
+ allowed: TAG_ALLOWED,
27
+ source: TAG_SOURCE,
28
+ flags: "u",
29
+ });
30
+ const TAG_VALUE = stringConstraint({
31
+ name: "Tag value",
32
+ charClass: TAG_CHARS,
33
+ maxLength: 256,
34
+ allowed: TAG_ALLOWED,
35
+ source: TAG_SOURCE,
36
+ flags: "u",
37
+ });
17
38
  /**
18
39
  * Validates a single tag key/value pair against AWS tag constraints.
19
40
  *
20
41
  * Throws synchronously at the call site so authors see the failure where the
21
42
  * bad value was written, not at deploy time. Validates:
22
43
  *
23
- * - `key` is non-empty and at most {@link KEY_MAX} characters.
24
- * - `key` does not start with the reserved `aws:` prefix (case-insensitive).
25
- * - `value` is at most {@link VALUE_MAX} characters.
26
- * - both `key` and `value` use only the AWS-permitted character set.
44
+ * - `key` is non-empty and does not start with the reserved `aws:` prefix
45
+ * (case-insensitive) both tag-specific rules.
46
+ * - `key` and `value` length and character set, via the shared catalogue
47
+ * mechanism. Empty values are permitted; empty keys are not.
27
48
  *
28
- * The regex matches Unicode letters, digits, and whitespace plus the
29
- * documented punctuation set, so non-ASCII tags are accepted as AWS
30
- * supports them.
49
+ * Non-ASCII letters, digits, and whitespace are accepted, matching AWS.
31
50
  */
32
51
  export function validateTag(key, value) {
33
52
  if (key.length === 0) {
34
53
  throw new Error("Tag key must be non-empty.");
35
54
  }
36
- if (key.length > KEY_MAX) {
37
- throw new Error(`Tag key "${key}" exceeds ${String(KEY_MAX)}-character limit.`);
38
- }
39
55
  if (key.toLowerCase().startsWith("aws:")) {
40
56
  throw new Error(`Tag key "${key}" uses reserved "aws:" prefix; AWS rejects user tags with this prefix.`);
41
57
  }
42
- if (!TAG_CHAR_RE.test(key)) {
43
- throw new Error(`Tag key "${key}" contains characters outside the AWS tag character set ` +
44
- "(letters, digits, whitespace, and `_ . : / = + - @`).");
45
- }
46
- if (value.length > VALUE_MAX) {
47
- throw new Error(`Tag value for key "${key}" exceeds ${String(VALUE_MAX)}-character limit.`);
48
- }
49
- if (!TAG_CHAR_RE.test(value)) {
50
- throw new Error(`Tag value for key "${key}" contains characters outside the AWS tag character set ` +
51
- "(letters, digits, whitespace, and `_ . : / = + - @`).");
52
- }
58
+ validateString(key, TAG_KEY);
59
+ validateString(value, TAG_VALUE);
53
60
  }
54
61
  /**
55
62
  * Validates every entry of a record via {@link validateTag}, throwing on the
@@ -1 +1 @@
1
- {"version":3,"file":"tag-validator.js","sourceRoot":"","sources":["../../src/tag-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,GAAG,gCAAgC,CAAC;AAErD,MAAM,OAAO,GAAG,GAAG,CAAC;AACpB,MAAM,SAAS,GAAG,GAAG,CAAC;AAEtB;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,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,MAAM,UAAU,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"}
1
+ {"version":3,"file":"tag-validator.js","sourceRoot":"","sources":["../../src/tag-validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE1E;;;;;;;;;GASG;AACH,MAAM,SAAS,GAAG,8BAA8B,CAAC;AACjD,MAAM,WAAW,GAAG,6EAA6E,CAAC;AAClG,MAAM,UAAU,GAAG,gEAAgE,CAAC;AAEpF;;;;;;GAMG;AACH,MAAM,OAAO,GAAG,gBAAgB,CAAC;IAC/B,IAAI,EAAE,SAAS;IACf,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,GAAG;IACd,OAAO,EAAE,WAAW;IACpB,MAAM,EAAE,UAAU;IAClB,KAAK,EAAE,GAAG;CACX,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG,gBAAgB,CAAC;IACjC,IAAI,EAAE,WAAW;IACjB,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,GAAG;IACd,OAAO,EAAE,WAAW;IACpB,MAAM,EAAE,UAAU;IAClB,KAAK,EAAE,GAAG;CACX,CAAC,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,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,WAAW,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CACb,YAAY,GAAG,wEAAwE,CACxF,CAAC;IACJ,CAAC;IACD,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC7B,cAAc,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@composurecdk/cloudformation",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
4
4
  "description": "Composable CloudFormation stack builder and stack assignment strategies",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,7 +20,16 @@
20
20
  "test": "vitest run --passWithNoTests",
21
21
  "test:watch": "vitest"
22
22
  },
23
- "keywords": [],
23
+ "keywords": [
24
+ "aws",
25
+ "cdk",
26
+ "aws-cdk",
27
+ "infrastructure-as-code",
28
+ "iac",
29
+ "composurecdk",
30
+ "cloudformation",
31
+ "stack"
32
+ ],
24
33
  "author": "Jason Duffett (https://github.com/laazyj)",
25
34
  "license": "MIT",
26
35
  "publishConfig": {
@@ -42,11 +51,11 @@
42
51
  "constructs": "^10.0.0"
43
52
  },
44
53
  "devDependencies": {
45
- "@types/node": "^25.9.1",
46
- "aws-cdk-lib": "^2.257.0",
54
+ "@types/node": "^25.9.3",
55
+ "aws-cdk-lib": "^2.258.1",
47
56
  "constructs": "^10.6.0",
48
57
  "typescript": "^6.0.3",
49
- "vitest": "^4.1.7"
58
+ "vitest": "^4.1.8"
50
59
  },
51
60
  "exports": {
52
61
  "./package.json": "./package.json",