@mettlecast/domain-cdk-packer 0.2.60 → 0.2.61
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/DomainStack.js +114 -33
- package/dist/__tests__/action-construct.test.d.ts +1 -0
- package/dist/__tests__/action-construct.test.js +159 -0
- package/dist/__tests__/domain-stack.test.js +234 -1
- package/dist/__tests__/grouped-lambda-factory-action-wrapper.test.d.ts +11 -0
- package/dist/__tests__/grouped-lambda-factory-action-wrapper.test.js +47 -0
- package/dist/__tests__/grouped-lambda-factory.test.d.ts +1 -0
- package/dist/__tests__/grouped-lambda-factory.test.js +147 -0
- package/dist/__tests__/registry.test.js +108 -0
- package/dist/__tests__/security-assertion-aspect.test.d.ts +1 -0
- package/dist/__tests__/security-assertion-aspect.test.js +200 -0
- package/dist/aspects/index.d.ts +4 -0
- package/dist/aspects/index.js +2 -0
- package/dist/aspects/security-assertion-aspect.d.ts +191 -0
- package/dist/aspects/security-assertion-aspect.js +297 -0
- package/dist/constructs/action-construct.d.ts +16 -6
- package/dist/constructs/action-construct.js +10 -11
- package/dist/grouped-lambda-factory.d.ts +69 -0
- package/dist/grouped-lambda-factory.js +99 -10
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/registry.d.ts +110 -2
- package/package.json +1 -1
|
@@ -10,7 +10,60 @@ function camelCase(str) {
|
|
|
10
10
|
function toPascalCase(str) {
|
|
11
11
|
return str.charAt(0).toUpperCase() + camelCase(str).slice(1);
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Per-primitive-type default adapter exported from `@mettlecast/domain-runtime`.
|
|
15
|
+
*
|
|
16
|
+
* Exported (Wave 7 Task 7.3, #4619) so unit tests can pin the adapter
|
|
17
|
+
* selection logic without instantiating CDK or esbuild. The mapping here
|
|
18
|
+
* is the source of truth for the default wrap used by
|
|
19
|
+
* `buildDedicatedEntryContent`; it does NOT cover the per-entry override
|
|
20
|
+
* used by API-exposed actions (see `EXPOSED_ACTION_ADAPTER`).
|
|
21
|
+
*/
|
|
22
|
+
export const ADAPTER_BY_PRIMITIVE = {
|
|
23
|
+
api: 'createApiLambdaHandler',
|
|
24
|
+
subscriber: 'createSubscriberLambdaHandler',
|
|
25
|
+
job: 'createJobLambdaHandler',
|
|
26
|
+
webhook: 'createWebhookLambdaHandler',
|
|
27
|
+
action: 'createActionLambdaHandler',
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Adapter used for API-exposed actions (Wave 4 Task 4.1, #4619).
|
|
31
|
+
*
|
|
32
|
+
* API-exposed actions receive raw HTTP API v2 events, so they must wrap
|
|
33
|
+
* with `createExposedActionApiHandler` instead of the envelope-based
|
|
34
|
+
* `createActionLambdaHandler` that internal actions use.
|
|
35
|
+
*
|
|
36
|
+
* Exported (Wave 7 Task 7.3) so tests can assert the override behaviour
|
|
37
|
+
* without depending on the published runtime's barrel.
|
|
38
|
+
*/
|
|
39
|
+
export const EXPOSED_ACTION_ADAPTER = 'createExposedActionApiHandler';
|
|
40
|
+
/**
|
|
41
|
+
* Pure, side-effect-free entry-content generator for a single dedicated-mode
|
|
42
|
+
* Lambda handler.
|
|
43
|
+
*
|
|
44
|
+
* Returns the TypeScript source string that `NodejsFunction` will bundle.
|
|
45
|
+
* Exported (Wave 7 Task 7.3, #4619) so unit tests can assert adapter
|
|
46
|
+
* selection and import wiring without paying the cost of esbuild bundling
|
|
47
|
+
* on every `DomainStack` synth.
|
|
48
|
+
*
|
|
49
|
+
* Note: Wave 7 Task 7.1 may extend this signature with `domainId` (and
|
|
50
|
+
* rewrite the action-adapter wrappers) so the runtime can plumb
|
|
51
|
+
* `definingDomain` / `domainId` into the dedicated Lambda. The narrow
|
|
52
|
+
* tests in `grouped-lambda-factory.test.ts` deliberately target only the
|
|
53
|
+
* parts that are stable across that change so they keep passing through
|
|
54
|
+
* the merge.
|
|
55
|
+
*
|
|
56
|
+
* @param handlerImportPath Relative path from the temp entry directory
|
|
57
|
+
* to the handler file. Must already be normalised
|
|
58
|
+
* to start with `./` or `../`.
|
|
59
|
+
* @param entry The handler entry whose `id` and `adapter`
|
|
60
|
+
* drive the export name and adapter choice.
|
|
61
|
+
* @param primitiveType The primitive type — selects the default
|
|
62
|
+
* adapter when `entry.adapter` is unset, and
|
|
63
|
+
* switches to the `hydrateCtx` envelope for
|
|
64
|
+
* schedules.
|
|
65
|
+
*/
|
|
66
|
+
export function buildDedicatedEntryContent(handlerImportPath, entry, primitiveType, domainId) {
|
|
14
67
|
const exportName = camelCase(entry.id);
|
|
15
68
|
if (primitiveType === 'schedule') {
|
|
16
69
|
return [
|
|
@@ -26,14 +79,50 @@ function generateDedicatedEntry(handlerImportPath, entry, primitiveType) {
|
|
|
26
79
|
`};`,
|
|
27
80
|
].join('\n');
|
|
28
81
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
82
|
+
// Per-handler adapter override (Wave 4 Task 4.1) takes precedence over the
|
|
83
|
+
// primitive-type default. Used so API-exposed actions wrap with
|
|
84
|
+
// `createExposedActionApiHandler` while internal actions in the same domain
|
|
85
|
+
// continue to use `createActionLambdaHandler`.
|
|
86
|
+
const adapter = entry.adapter ?? ADAPTER_BY_PRIMITIVE[primitiveType] ?? 'createApiLambdaHandler';
|
|
87
|
+
// Wave 7 Task 7.1 (#4619): the action adapters take a (registry, options)
|
|
88
|
+
// pair / (action, options) pair, NOT a bare handler export. Generating
|
|
89
|
+
// `${adapter}(${exportName})` would produce invalid JS for actions — the
|
|
90
|
+
// bundles were silently broken (esbuild succeeded but the runtime crashed
|
|
91
|
+
// on first invocation). We now emit the correct wrapper for each adapter:
|
|
92
|
+
// * internal action → `createActionLambdaHandler(registry, options)`
|
|
93
|
+
// * api-exposed action → `createExposedActionApiHandler(action, options)`
|
|
94
|
+
if (primitiveType === 'action' && adapter !== 'createExposedActionApiHandler') {
|
|
95
|
+
return [
|
|
96
|
+
`import { createActionLambdaHandler } from '@mettlecast/domain-runtime';`,
|
|
97
|
+
`import { ${exportName} } from '${handlerImportPath}';`,
|
|
98
|
+
'',
|
|
99
|
+
`const registry = { ${JSON.stringify(domainId)}: { ${JSON.stringify(entry.id)}: ${exportName} } };`,
|
|
100
|
+
'',
|
|
101
|
+
`export const handler = createActionLambdaHandler(registry, {`,
|
|
102
|
+
` domainId: ${JSON.stringify(domainId)},`,
|
|
103
|
+
` databaseUrl: process.env.DATABASE_URL,`,
|
|
104
|
+
` eventBusName: process.env.EVENT_BUS_NAME,`,
|
|
105
|
+
` idempotencyTableName: process.env.IDEMPOTENCY_TABLE,`,
|
|
106
|
+
`});`,
|
|
107
|
+
].join('\n');
|
|
108
|
+
}
|
|
109
|
+
if (primitiveType === 'action' && adapter === 'createExposedActionApiHandler') {
|
|
110
|
+
return [
|
|
111
|
+
`import { createExposedActionApiHandler } from '@mettlecast/domain-runtime';`,
|
|
112
|
+
`import { ${exportName} } from '${handlerImportPath}';`,
|
|
113
|
+
'',
|
|
114
|
+
// Build a single-action registry so the public handler can call its
|
|
115
|
+
// own action via `ctx.actions[domainId][actionId]` (Wave 7 Task 7.1).
|
|
116
|
+
`const actionRegistry = { ${JSON.stringify(domainId)}: { ${JSON.stringify(entry.id)}: ${exportName} } };`,
|
|
117
|
+
'',
|
|
118
|
+
`export const handler = createExposedActionApiHandler(${exportName}, {`,
|
|
119
|
+
` definingDomain: ${JSON.stringify(domainId)},`,
|
|
120
|
+
` domainId: ${JSON.stringify(domainId)},`,
|
|
121
|
+
` actionRegistry,`,
|
|
122
|
+
` callerDomainId: ${JSON.stringify(domainId)},`,
|
|
123
|
+
`});`,
|
|
124
|
+
].join('\n');
|
|
125
|
+
}
|
|
37
126
|
return [
|
|
38
127
|
`import { ${adapter} } from '@mettlecast/domain-runtime';`,
|
|
39
128
|
`import { ${exportName} } from '${handlerImportPath}';`,
|
|
@@ -76,7 +165,7 @@ export function createGroupedLambdas(scope, props) {
|
|
|
76
165
|
const entryDir = createTempEntryDir();
|
|
77
166
|
const entryPath = join(entryDir, 'entry.ts');
|
|
78
167
|
const handlerImportPath = relative(entryDir, join(domainRoot, entry.handlerFile)).replace(/\\/g, '/');
|
|
79
|
-
const entryContent =
|
|
168
|
+
const entryContent = buildDedicatedEntryContent(handlerImportPath.startsWith('.') ? handlerImportPath : `./${handlerImportPath}`, entry, props.primitiveType, props.domainId);
|
|
80
169
|
writeFileSync(entryPath, entryContent, 'utf8');
|
|
81
170
|
return new lambdaNode.NodejsFunction(scope, `${props.domainId}-${props.primitiveType}${groupSuffix}-${entry.id}`, {
|
|
82
171
|
runtime: lambda.Runtime.NODEJS_22_X,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export type { DomainRegistry, RegistryEntry, RegistryEntryKind, BaseRegistryEntry, SerialDeploymentConfig, ApiRegistryEntry, ApiVersionSnapshot, WebhookRegistryEntry, SubscriberRegistryEntry, ScheduleRegistryEntry, JobRegistryEntry, ActionRegistryEntry, IntegrationRegistryEntry, EventRegistryEntry, DomainRegistryEntry, SchemaSnapshot, } from './registry.js';
|
|
1
|
+
export type { DomainRegistry, RegistryEntry, RegistryEntryKind, BaseRegistryEntry, SerialDeploymentConfig, ApiRegistryEntry, ApiVersionSnapshot, WebhookRegistryEntry, SubscriberRegistryEntry, ScheduleRegistryEntry, JobRegistryEntry, ActionRegistryEntry, ActionBackendAccess, ActionExposure, ActionApiExposure, IntegrationRegistryEntry, EventRegistryEntry, DomainRegistryEntry, SchemaSnapshot, } from './registry.js';
|
|
2
2
|
export { LambdaFactory } from './lambda-factory.js';
|
|
3
3
|
export type { LambdaFactoryProps } from './lambda-factory.js';
|
|
4
|
-
export { createGroupedLambdas } from './grouped-lambda-factory.js';
|
|
4
|
+
export { createGroupedLambdas, buildDedicatedEntryContent, ADAPTER_BY_PRIMITIVE, EXPOSED_ACTION_ADAPTER } from './grouped-lambda-factory.js';
|
|
5
5
|
export type { PrimitiveType, HandlerEntry, GroupedLambdaProps } from './grouped-lambda-factory.js';
|
|
6
6
|
export { IamPolicyBuilder } from './iam/iam-policy-builder.js';
|
|
7
7
|
export type { WebhookPolicyParams, QueuePolicyParams } from './iam/iam-policy-builder.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { LambdaFactory } from './lambda-factory.js';
|
|
2
|
-
export { createGroupedLambdas } from './grouped-lambda-factory.js';
|
|
2
|
+
export { createGroupedLambdas, buildDedicatedEntryContent, ADAPTER_BY_PRIMITIVE, EXPOSED_ACTION_ADAPTER } from './grouped-lambda-factory.js';
|
|
3
3
|
export { IamPolicyBuilder } from './iam/iam-policy-builder.js';
|
|
4
4
|
export { ApiConstruct } from './constructs/api-construct.js';
|
|
5
5
|
export { WebhookConstruct } from './constructs/webhook-construct.js';
|
package/dist/registry.d.ts
CHANGED
|
@@ -63,6 +63,20 @@ export interface ApiRegistryEntry extends BaseRegistryEntry {
|
|
|
63
63
|
method: string;
|
|
64
64
|
/** Authentication type. */
|
|
65
65
|
authType: 'jwt' | 'api-key' | 'none';
|
|
66
|
+
/**
|
|
67
|
+
* Required for `authType: 'none'` (anonymous) APIs. Carries a free-text
|
|
68
|
+
* reason that documents the security exception so reviewers can audit
|
|
69
|
+
* the relaxation. The CDK synth-time aspect and the CLI validator both
|
|
70
|
+
* refuse to synthesize anonymous APIs without this field.
|
|
71
|
+
*
|
|
72
|
+
* Added in #4662 Task D — the legacy `defineApi` surface did not
|
|
73
|
+
* require a reason because early projects had only a handful of
|
|
74
|
+
* public routes; that did not scale and the runtime needs the
|
|
75
|
+
* documentation alongside the route.
|
|
76
|
+
*/
|
|
77
|
+
securityException?: {
|
|
78
|
+
reason: string;
|
|
79
|
+
};
|
|
66
80
|
/** Optional deployment overrides. */
|
|
67
81
|
deployment?: SerialDeploymentConfig;
|
|
68
82
|
/** Whether this handler may use NAT-backed public internet egress. */
|
|
@@ -153,16 +167,110 @@ export interface JobRegistryEntry extends BaseRegistryEntry {
|
|
|
153
167
|
/** Whether this handler may use NAT-backed public internet egress. */
|
|
154
168
|
outboundAccess?: RegistryOutboundAccess;
|
|
155
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Backend invocation permission for an action.
|
|
172
|
+
*
|
|
173
|
+
* Controls who can call the action through `ctx.actions` (cross-domain
|
|
174
|
+
* Lambda invocation or runtime action calls).
|
|
175
|
+
*
|
|
176
|
+
* - `private` = only same-domain/internal generated adapters
|
|
177
|
+
* - `domain` = callable by other domains through ctx.actions, subject to policy
|
|
178
|
+
* - `platform` = callable by system jobs, flows, admin tooling
|
|
179
|
+
*/
|
|
180
|
+
export type ActionBackendAccess = 'private' | 'domain' | 'platform';
|
|
181
|
+
/**
|
|
182
|
+
* API-exposure metadata for an action.
|
|
183
|
+
*
|
|
184
|
+
* Present only when `exposure.type === 'api'`. Carries the route, method,
|
|
185
|
+
* auth, tenancy, and any required role or documented security exception
|
|
186
|
+
* that downstream CDK/contract generation needs to wire the route safely.
|
|
187
|
+
*
|
|
188
|
+
* The optional `authDeclared` and `tenancyDeclared` flags are populated by
|
|
189
|
+
* the registry builder to record whether the source code explicitly declared
|
|
190
|
+
* each field, or whether the builder fell back to the safe default. They are
|
|
191
|
+
* consumed by the domain CLI's validate command (Wave 6 Task 6.1) to enforce
|
|
192
|
+
* the action-first security model (#4619).
|
|
193
|
+
*/
|
|
194
|
+
export interface ActionApiExposure {
|
|
195
|
+
/** Discriminant. */
|
|
196
|
+
type: 'api';
|
|
197
|
+
/** HTTP route path, e.g. '/v1/tenants/{tenantId}/billing/invoices'. */
|
|
198
|
+
path: string;
|
|
199
|
+
/** HTTP method. One of: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS. */
|
|
200
|
+
method: string;
|
|
201
|
+
/** Authentication requirement for the route. */
|
|
202
|
+
auth: 'required' | 'none' | 'service';
|
|
203
|
+
/** Tenancy requirement for the route. */
|
|
204
|
+
tenancy: 'required' | 'none' | 'system';
|
|
205
|
+
/** Required role(s) for `auth: 'required'` routes (system tenancy requires this). */
|
|
206
|
+
roles?: string[];
|
|
207
|
+
/** Documented exception when auth or tenancy is intentionally not required. */
|
|
208
|
+
securityException?: {
|
|
209
|
+
reason: string;
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* True if the source code explicitly declared `auth`. False if the
|
|
213
|
+
* builder fell back to the safe default (`'required'`). Validation-only.
|
|
214
|
+
*/
|
|
215
|
+
authDeclared?: boolean;
|
|
216
|
+
/**
|
|
217
|
+
* True if the source code explicitly declared `tenancy`. False if the
|
|
218
|
+
* builder fell back to the safe default (`'required'`). Validation-only.
|
|
219
|
+
*/
|
|
220
|
+
tenancyDeclared?: boolean;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Discriminated union of all supported action exposure shapes.
|
|
224
|
+
*
|
|
225
|
+
* `internal` actions are not exposed externally; they are reachable only
|
|
226
|
+
* through `ctx.actions`. `api` actions are exposed via API Gateway and
|
|
227
|
+
* carry the route + auth metadata needed for safe route generation.
|
|
228
|
+
*/
|
|
229
|
+
export type ActionExposure = ActionApiExposure | {
|
|
230
|
+
type: 'internal';
|
|
231
|
+
};
|
|
156
232
|
/**
|
|
157
233
|
* Action registry entry for callable domain actions.
|
|
234
|
+
*
|
|
235
|
+
* `backendAccess` is the canonical permission field for backend invocation
|
|
236
|
+
* and replaces the legacy `visibility` field during the
|
|
237
|
+
* action-first migration (#4619). `visibility` is preserved as an optional
|
|
238
|
+
* deprecated field so existing CDK constructs continue to compile until
|
|
239
|
+
* they are updated to consume `backendAccess` directly.
|
|
158
240
|
*/
|
|
159
241
|
export interface ActionRegistryEntry extends BaseRegistryEntry {
|
|
160
242
|
/** Discriminant. */
|
|
161
243
|
kind: 'action';
|
|
162
244
|
/** Handler file path (required for action). */
|
|
163
245
|
handlerFile: string;
|
|
164
|
-
/**
|
|
165
|
-
|
|
246
|
+
/**
|
|
247
|
+
* Backend invocation permission for the action.
|
|
248
|
+
* Replaces legacy `visibility` for the action-first migration.
|
|
249
|
+
*/
|
|
250
|
+
backendAccess: ActionBackendAccess;
|
|
251
|
+
/**
|
|
252
|
+
* External exposure shape for the action. New-style actions must declare
|
|
253
|
+
* an exposure; legacy actions default to `{ type: 'internal' }` during
|
|
254
|
+
* migration to preserve existing behavior.
|
|
255
|
+
*/
|
|
256
|
+
exposure: ActionExposure;
|
|
257
|
+
/**
|
|
258
|
+
* True if the source code explicitly declared `exposure`. False (or
|
|
259
|
+
* omitted) if the builder fell back to the default `{ type: 'internal' }`
|
|
260
|
+
* because the source did not opt in. Consumed by the domain CLI's
|
|
261
|
+
* validate command (Wave 6 Task 6.1) to enforce `ACTION_EXPOSURE_REQUIRED`.
|
|
262
|
+
*/
|
|
263
|
+
exposureDeclared?: boolean;
|
|
264
|
+
/**
|
|
265
|
+
* Legacy visibility scope. Retained during the action-first migration so
|
|
266
|
+
* CDK constructs that still read this field (e.g. action-construct.ts)
|
|
267
|
+
* keep compiling. New source code should set `backendAccess` instead;
|
|
268
|
+
* the registry builder maps legacy `visibility` -> `backendAccess` with
|
|
269
|
+
* `workspace` collapsing to `domain` per the migration spec.
|
|
270
|
+
*
|
|
271
|
+
* @deprecated Use `backendAccess` instead.
|
|
272
|
+
*/
|
|
273
|
+
visibility?: 'private' | 'domain' | 'workspace';
|
|
166
274
|
/** Whether the action enforces idempotency. */
|
|
167
275
|
idempotent: boolean;
|
|
168
276
|
/** Optional deployment overrides. */
|