@composurecdk/core 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/construct-id.js +1 -1
- package/dist/construct-id.js.map +1 -1
- package/dist/testing.d.ts +71 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +121 -0
- package/dist/testing.js.map +1 -0
- package/package.json +5 -1
package/dist/construct-id.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* These helpers consolidate that sanitization in one place so every builder
|
|
11
11
|
* in the monorepo applies the same constraints.
|
|
12
12
|
*/
|
|
13
|
-
// eslint-disable-next-line no-control-regex
|
|
13
|
+
// eslint-disable-next-line no-control-regex -- the regex must literally match control characters to strip them from construct IDs
|
|
14
14
|
const UNSAFE = /[/\x00-\x1f\x7f]/g;
|
|
15
15
|
/**
|
|
16
16
|
* Return a construct-ID-safe copy of `raw` by replacing unsafe characters
|
package/dist/construct-id.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"construct-id.js","sourceRoot":"","sources":["../src/construct-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,
|
|
1
|
+
{"version":3,"file":"construct-id.js","sourceRoot":"","sources":["../src/construct-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,kIAAkI;AAClI,MAAM,MAAM,GAAG,mBAAmB,CAAC;AAEnC;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC7C,OAAO,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,WAAW,CAAC,GAAG,KAAqD;IAClF,OAAO,KAAK;SACT,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;SACjE,GAAG,CAAC,mBAAmB,CAAC;SACxB,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asserts that {@link IBuilder.copy | `.copy()`} returns an independent
|
|
3
|
+
* builder that preserves non-`props` state set via {@link COPY_STATE}.
|
|
4
|
+
*
|
|
5
|
+
* The helper is the standard way per-package tests verify each accumulator
|
|
6
|
+
* a builder holds outside `props`. It executes the following sequence and
|
|
7
|
+
* asserts the two invariants implied by ADR-0005:
|
|
8
|
+
*
|
|
9
|
+
* 1. Build a **baseline**: `factory()` then `configure()`.
|
|
10
|
+
* 2. Build the **copy**: `factory()`, `configure()`, then `.copy()`.
|
|
11
|
+
* Apply `mutate()` to the original *after* the copy is taken.
|
|
12
|
+
* 3. Build the **original** (now carrying both `configure` and `mutate`).
|
|
13
|
+
*
|
|
14
|
+
* Note that the baseline is constructed via `factory()` rather than via
|
|
15
|
+
* `.copy()` — using `.copy()` would make the helper unable to detect a
|
|
16
|
+
* broken `[COPY_STATE]`, since both the "baseline" and the "copy" would
|
|
17
|
+
* drop the same state and still match.
|
|
18
|
+
*
|
|
19
|
+
* Then:
|
|
20
|
+
*
|
|
21
|
+
* - `inspect(copyResult)` must deep-equal `inspect(baselineResult)` —
|
|
22
|
+
* the copy preserved exactly the state that `configure` set up.
|
|
23
|
+
* A failure here means `[COPY_STATE]` is missing, incomplete, or buggy.
|
|
24
|
+
* - `inspect(originalResult)` must deep-not-equal `inspect(baselineResult)` —
|
|
25
|
+
* `mutate` actually changed inspectable state. Without this sanity
|
|
26
|
+
* check, a no-op `mutate` would let the first assertion pass
|
|
27
|
+
* trivially, hiding an isolation bug.
|
|
28
|
+
*
|
|
29
|
+
* @param args.factory - Returns a fresh, unconfigured builder. Called
|
|
30
|
+
* twice — the helper builds two independent instances so that the
|
|
31
|
+
* build calls don't share construct scopes and so the baseline does
|
|
32
|
+
* not depend on `.copy()`.
|
|
33
|
+
* @param args.configure - Applies the state under test (e.g. adds a
|
|
34
|
+
* `customAlarm`, configures `#vpc`, appends a `subscription`). Called
|
|
35
|
+
* on the baseline and on the original.
|
|
36
|
+
* @param args.mutate - Applied to the original *after* the copy is taken.
|
|
37
|
+
* Must change something the `inspect` callback can see — typically a
|
|
38
|
+
* second `configure`-shaped call that adds another item to the
|
|
39
|
+
* accumulator under test.
|
|
40
|
+
* @param args.build - Calls `.build(scope, id)` against a fresh CDK
|
|
41
|
+
* scope. Three separate scopes are required (one per build) — return a
|
|
42
|
+
* freshly constructed scope each call. Reusing a scope across calls
|
|
43
|
+
* surfaces as a CDK duplicate-id error rather than a silent leak.
|
|
44
|
+
* @param args.inspect - Extracts the slice of the build result whose
|
|
45
|
+
* shape depends on the state under test (e.g.
|
|
46
|
+
* `result => Object.keys(result.alarms)`).
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* import { App, Stack } from "aws-cdk-lib";
|
|
51
|
+
* import { assertCopyPreservesState } from "@composurecdk/core/testing";
|
|
52
|
+
*
|
|
53
|
+
* assertCopyPreservesState({
|
|
54
|
+
* factory: () => createCertificateBuilder().domainName("example.com"),
|
|
55
|
+
* configure: (b) => b.customAlarm({ id: "FirstAlarm", ... }),
|
|
56
|
+
* mutate: (b) => b.customAlarm({ id: "SecondAlarm", ... }),
|
|
57
|
+
* build: (b) => b.build(new Stack(new App(), "S"), "Cert"),
|
|
58
|
+
* inspect: (r) => Object.keys(r.alarms).sort(),
|
|
59
|
+
* });
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare function assertCopyPreservesState<B extends {
|
|
63
|
+
copy(): B;
|
|
64
|
+
}, R>(args: {
|
|
65
|
+
factory: () => B;
|
|
66
|
+
configure: (builder: B) => void;
|
|
67
|
+
mutate: (builder: B) => void;
|
|
68
|
+
build: (builder: B) => R;
|
|
69
|
+
inspect: (result: R) => unknown;
|
|
70
|
+
}): void;
|
|
71
|
+
//# sourceMappingURL=testing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,SAAS;IAAE,IAAI,IAAI,CAAC,CAAA;CAAE,EAAE,CAAC,EAAE,IAAI,EAAE;IACzE,OAAO,EAAE,MAAM,CAAC,CAAC;IACjB,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,CAAC;IAChC,MAAM,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,CAAC;IAC7B,KAAK,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;IACzB,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC;CACjC,GAAG,IAAI,CAsCP"}
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asserts that {@link IBuilder.copy | `.copy()`} returns an independent
|
|
3
|
+
* builder that preserves non-`props` state set via {@link COPY_STATE}.
|
|
4
|
+
*
|
|
5
|
+
* The helper is the standard way per-package tests verify each accumulator
|
|
6
|
+
* a builder holds outside `props`. It executes the following sequence and
|
|
7
|
+
* asserts the two invariants implied by ADR-0005:
|
|
8
|
+
*
|
|
9
|
+
* 1. Build a **baseline**: `factory()` then `configure()`.
|
|
10
|
+
* 2. Build the **copy**: `factory()`, `configure()`, then `.copy()`.
|
|
11
|
+
* Apply `mutate()` to the original *after* the copy is taken.
|
|
12
|
+
* 3. Build the **original** (now carrying both `configure` and `mutate`).
|
|
13
|
+
*
|
|
14
|
+
* Note that the baseline is constructed via `factory()` rather than via
|
|
15
|
+
* `.copy()` — using `.copy()` would make the helper unable to detect a
|
|
16
|
+
* broken `[COPY_STATE]`, since both the "baseline" and the "copy" would
|
|
17
|
+
* drop the same state and still match.
|
|
18
|
+
*
|
|
19
|
+
* Then:
|
|
20
|
+
*
|
|
21
|
+
* - `inspect(copyResult)` must deep-equal `inspect(baselineResult)` —
|
|
22
|
+
* the copy preserved exactly the state that `configure` set up.
|
|
23
|
+
* A failure here means `[COPY_STATE]` is missing, incomplete, or buggy.
|
|
24
|
+
* - `inspect(originalResult)` must deep-not-equal `inspect(baselineResult)` —
|
|
25
|
+
* `mutate` actually changed inspectable state. Without this sanity
|
|
26
|
+
* check, a no-op `mutate` would let the first assertion pass
|
|
27
|
+
* trivially, hiding an isolation bug.
|
|
28
|
+
*
|
|
29
|
+
* @param args.factory - Returns a fresh, unconfigured builder. Called
|
|
30
|
+
* twice — the helper builds two independent instances so that the
|
|
31
|
+
* build calls don't share construct scopes and so the baseline does
|
|
32
|
+
* not depend on `.copy()`.
|
|
33
|
+
* @param args.configure - Applies the state under test (e.g. adds a
|
|
34
|
+
* `customAlarm`, configures `#vpc`, appends a `subscription`). Called
|
|
35
|
+
* on the baseline and on the original.
|
|
36
|
+
* @param args.mutate - Applied to the original *after* the copy is taken.
|
|
37
|
+
* Must change something the `inspect` callback can see — typically a
|
|
38
|
+
* second `configure`-shaped call that adds another item to the
|
|
39
|
+
* accumulator under test.
|
|
40
|
+
* @param args.build - Calls `.build(scope, id)` against a fresh CDK
|
|
41
|
+
* scope. Three separate scopes are required (one per build) — return a
|
|
42
|
+
* freshly constructed scope each call. Reusing a scope across calls
|
|
43
|
+
* surfaces as a CDK duplicate-id error rather than a silent leak.
|
|
44
|
+
* @param args.inspect - Extracts the slice of the build result whose
|
|
45
|
+
* shape depends on the state under test (e.g.
|
|
46
|
+
* `result => Object.keys(result.alarms)`).
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* import { App, Stack } from "aws-cdk-lib";
|
|
51
|
+
* import { assertCopyPreservesState } from "@composurecdk/core/testing";
|
|
52
|
+
*
|
|
53
|
+
* assertCopyPreservesState({
|
|
54
|
+
* factory: () => createCertificateBuilder().domainName("example.com"),
|
|
55
|
+
* configure: (b) => b.customAlarm({ id: "FirstAlarm", ... }),
|
|
56
|
+
* mutate: (b) => b.customAlarm({ id: "SecondAlarm", ... }),
|
|
57
|
+
* build: (b) => b.build(new Stack(new App(), "S"), "Cert"),
|
|
58
|
+
* inspect: (r) => Object.keys(r.alarms).sort(),
|
|
59
|
+
* });
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function assertCopyPreservesState(args) {
|
|
63
|
+
const { factory, configure, mutate, build, inspect } = args;
|
|
64
|
+
const baseline = factory();
|
|
65
|
+
configure(baseline);
|
|
66
|
+
const baselineState = inspect(build(baseline));
|
|
67
|
+
const original = factory();
|
|
68
|
+
configure(original);
|
|
69
|
+
if (typeof original.copy !== "function") {
|
|
70
|
+
throw new Error("assertCopyPreservesState: builder returned by `factory` has no `.copy()` method. " +
|
|
71
|
+
"Pass a builder produced by `Builder()` / `taggedBuilder()` from `@composurecdk/core` / `@composurecdk/cloudformation`.");
|
|
72
|
+
}
|
|
73
|
+
const copy = original.copy();
|
|
74
|
+
mutate(original);
|
|
75
|
+
const originalState = inspect(build(original));
|
|
76
|
+
const copyState = inspect(build(copy));
|
|
77
|
+
if (deepEqual(originalState, baselineState)) {
|
|
78
|
+
throw new Error("assertCopyPreservesState: `mutate` did not change inspectable state on the original — " +
|
|
79
|
+
"the test cannot detect a leak through `.copy()`. Make `mutate` and `inspect` cover " +
|
|
80
|
+
"the same accumulator.\n" +
|
|
81
|
+
` baseline: ${format(baselineState)}\n` +
|
|
82
|
+
` original: ${format(originalState)}`);
|
|
83
|
+
}
|
|
84
|
+
if (!deepEqual(copyState, baselineState)) {
|
|
85
|
+
throw new Error("assertCopyPreservesState: `.copy()` did not preserve state set by `configure`. " +
|
|
86
|
+
"The builder is missing, incomplete, or has a buggy `[COPY_STATE]` hook (see ADR-0005).\n" +
|
|
87
|
+
` baseline: ${format(baselineState)}\n` +
|
|
88
|
+
` copy: ${format(copyState)}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function deepEqual(a, b) {
|
|
92
|
+
if (Object.is(a, b))
|
|
93
|
+
return true;
|
|
94
|
+
if (typeof a !== "object" || a === null)
|
|
95
|
+
return false;
|
|
96
|
+
if (typeof b !== "object" || b === null)
|
|
97
|
+
return false;
|
|
98
|
+
if (Array.isArray(a)) {
|
|
99
|
+
if (!Array.isArray(b) || a.length !== b.length)
|
|
100
|
+
return false;
|
|
101
|
+
return a.every((item, i) => deepEqual(item, b[i]));
|
|
102
|
+
}
|
|
103
|
+
if (Array.isArray(b))
|
|
104
|
+
return false;
|
|
105
|
+
const aKeys = Object.keys(a);
|
|
106
|
+
const bKeys = Object.keys(b);
|
|
107
|
+
if (aKeys.length !== bKeys.length)
|
|
108
|
+
return false;
|
|
109
|
+
const bRec = b;
|
|
110
|
+
return aKeys.every((k) => Object.prototype.hasOwnProperty.call(bRec, k) &&
|
|
111
|
+
deepEqual(a[k], bRec[k]));
|
|
112
|
+
}
|
|
113
|
+
function format(value) {
|
|
114
|
+
try {
|
|
115
|
+
return JSON.stringify(value);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return String(value);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=testing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@composurecdk/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Composable CDK component system — lifecycle, dependency resolution, and builder pattern",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
".": {
|
|
14
14
|
"import": "./dist/index.js",
|
|
15
15
|
"types": "./dist/index.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./testing": {
|
|
18
|
+
"import": "./dist/testing.js",
|
|
19
|
+
"types": "./dist/testing.d.ts"
|
|
16
20
|
}
|
|
17
21
|
},
|
|
18
22
|
"files": [
|