@go-to-k/cdkd 0.129.0 → 0.130.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/README.md +13 -6
- package/dist/cli.js +227 -10
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-DWpeb9wT.js → deploy-engine-B-w4C_7O.js} +4 -3
- package/dist/deploy-engine-B-w4C_7O.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-DWpeb9wT.js.map +0 -1
package/README.md
CHANGED
|
@@ -468,14 +468,21 @@ preflight (HTTP API v2 `CorsConfiguration` + REST v1 OPTIONS MOCK
|
|
|
468
468
|
preflight from `defaultCorsPreflightOptions`); hot reload via `--watch`;
|
|
469
469
|
deploy-state-backed env var substitution via `--from-state`.
|
|
470
470
|
|
|
471
|
+
Function URL `InvokeMode: RESPONSE_STREAM` is supported (issue #467):
|
|
472
|
+
streaming Lambdas are invoked via the RIE streaming protocol and the
|
|
473
|
+
response is piped to the HTTP client with `Transfer-Encoding: chunked`.
|
|
474
|
+
Note that AWS's local RIE buffers the response — incremental chunk
|
|
475
|
+
delivery only manifests against the deployed Lambda runtime; locally
|
|
476
|
+
the response shape is correct but arrives in one block.
|
|
477
|
+
|
|
471
478
|
Routes whose integration cdkd cannot emulate (non-AWS_PROXY REST v1
|
|
472
479
|
types other than the MOCK CORS preflight subset, HTTP API v2 service
|
|
473
|
-
integrations, WebSocket APIs, Function URLs with IAM auth
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
480
|
+
integrations, WebSocket APIs, Function URLs with IAM auth, cross-stack
|
|
481
|
+
Lambda Arn references) **do not block boot** — the server starts with
|
|
482
|
+
a per-route `[warn]` summary and returns HTTP 501 + the reason in the
|
|
483
|
+
JSON body if and when the route is hit. This lets you run the rest of
|
|
484
|
+
your API surface locally while the unsupported routes stay on the
|
|
485
|
+
deployed API.
|
|
479
486
|
|
|
480
487
|
### `local run-task`
|
|
481
488
|
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { a as setAwsClients, i as resetAwsClients, r as getAwsClients, t as AwsClients } from "./aws-clients-CuHRHcyW.js";
|
|
3
|
-
import { A as AssetPublisher, B as getLegacyStateBucketName, C as applyRoleArnIfSet, Ct as generateResourceNameWithFallback, D as LockManager, E as TemplateParser, F as getDockerCmd, G as resolveStateBucketWithDefaultAndSource, H as resolveCaptureObservedState, I as runDockerForeground, K as warnDeprecatedNoPrefixCliFlag, L as runDockerStreaming, M as WorkGraph, N as buildDockerImage, O as S3StateBackend, P as formatDockerLoginError, R as Synthesizer, S as IntrinsicFunctionResolver, St as generateResourceName, T as DagBuilder, Tt as withStackName, U as resolveSkipPrefix, V as resolveApp, W as resolveStateBucketWithDefault, Y as resolveBucketRegion, Z as CdkdError, _ as normalizeAwsTagsToCfn, a as withRetry, at as ResourceUpdateNotSupportedError, b as CloudControlProvider, bt as PATTERN_B_NAME_PROPERTIES, c as cyan, ct as StackTerminationProtectionError, d as red, et as LocalInvokeBuildError, f as yellow, g as matchesCdkPath, gt as getLogger, h as CDK_PATH_TAG, i as withResourceDeadline, it as ResourceTimeoutError, j as stringifyValue, k as shouldRetainResource, l as gray, m as collectInlinePolicyNamesManagedBySiblings, mt as withErrorHandling, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as PartialFailureError, o as IMPLICIT_DELETE_DEPENDENCIES, ot as RouteDiscoveryError, p as IAMRoleProvider, pt as normalizeAwsError, q as AssemblyReader, r as DeployEngine, rt as ProvisioningError, s as bold, st as StackHasActiveImportsError, t as DEFAULT_RESOURCE_TIMEOUT_MS, u as green, v as resolveExplicitPhysicalId, vt as runStackBuffered, w as DiffCalculator, wt as withSkipPrefix, x as assertRegionMatch, xt as PATTERN_B_RESOURCE_TYPES, y as ProviderRegistry, yt as getLiveRenderer, z as getDefaultStateBucketName } from "./deploy-engine-
|
|
3
|
+
import { A as AssetPublisher, B as getLegacyStateBucketName, C as applyRoleArnIfSet, Ct as generateResourceNameWithFallback, D as LockManager, E as TemplateParser, F as getDockerCmd, G as resolveStateBucketWithDefaultAndSource, H as resolveCaptureObservedState, I as runDockerForeground, K as warnDeprecatedNoPrefixCliFlag, L as runDockerStreaming, M as WorkGraph, N as buildDockerImage, O as S3StateBackend, P as formatDockerLoginError, R as Synthesizer, S as IntrinsicFunctionResolver, St as generateResourceName, T as DagBuilder, Tt as withStackName, U as resolveSkipPrefix, V as resolveApp, W as resolveStateBucketWithDefault, Y as resolveBucketRegion, Z as CdkdError, _ as normalizeAwsTagsToCfn, a as withRetry, at as ResourceUpdateNotSupportedError, b as CloudControlProvider, bt as PATTERN_B_NAME_PROPERTIES, c as cyan, ct as StackTerminationProtectionError, d as red, et as LocalInvokeBuildError, f as yellow, g as matchesCdkPath, gt as getLogger, h as CDK_PATH_TAG, i as withResourceDeadline, it as ResourceTimeoutError, j as stringifyValue, k as shouldRetainResource, l as gray, m as collectInlinePolicyNamesManagedBySiblings, mt as withErrorHandling, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as PartialFailureError, o as IMPLICIT_DELETE_DEPENDENCIES, ot as RouteDiscoveryError, p as IAMRoleProvider, pt as normalizeAwsError, q as AssemblyReader, r as DeployEngine, rt as ProvisioningError, s as bold, st as StackHasActiveImportsError, t as DEFAULT_RESOURCE_TIMEOUT_MS, u as green, v as resolveExplicitPhysicalId, vt as runStackBuffered, w as DiffCalculator, wt as withSkipPrefix, x as assertRegionMatch, xt as PATTERN_B_RESOURCE_TYPES, y as ProviderRegistry, yt as getLiveRenderer, z as getDefaultStateBucketName } from "./deploy-engine-B-w4C_7O.js";
|
|
4
4
|
import { createHash, createHmac, createPublicKey, createVerify, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
5
5
|
import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListBucketsCommand, ListDirectoryBucketsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
6
6
|
import { AddRoleToInstanceProfileCommand, AddUserToGroupCommand, AttachGroupPolicyCommand, AttachUserPolicyCommand, CreateGroupCommand, CreateInstanceProfileCommand, CreateLoginProfileCommand, CreateUserCommand, DeleteAccessKeyCommand, DeleteGroupCommand, DeleteGroupPolicyCommand, DeleteInstanceProfileCommand, DeleteLoginProfileCommand, DeleteRolePolicyCommand, DeleteUserCommand, DeleteUserPermissionsBoundaryCommand, DeleteUserPolicyCommand, DetachGroupPolicyCommand, DetachUserPolicyCommand, GetGroupCommand, GetGroupPolicyCommand, GetInstanceProfileCommand, GetRolePolicyCommand, GetUserCommand, GetUserPolicyCommand, IAMClient, ListAccessKeysCommand, ListAttachedGroupPoliciesCommand, ListAttachedUserPoliciesCommand, ListGroupPoliciesCommand, ListGroupsForUserCommand, ListInstanceProfilesCommand, ListUserPoliciesCommand, ListUserTagsCommand, ListUsersCommand, NoSuchEntityException, PutGroupPolicyCommand, PutRolePolicyCommand, PutUserPermissionsBoundaryCommand, PutUserPolicyCommand, RemoveRoleFromInstanceProfileCommand, RemoveUserFromGroupCommand, TagUserCommand, UntagUserCommand, UpdateLoginProfileCommand } from "@aws-sdk/client-iam";
|
|
@@ -39464,13 +39464,16 @@ async function invokeRie(host, port, event, timeoutMs) {
|
|
|
39464
39464
|
* something we should retry. Abort errors propagate immediately so the
|
|
39465
39465
|
* outer timeout still wins.
|
|
39466
39466
|
*/
|
|
39467
|
-
async function fetchWithStartupRetry(url, body, signal) {
|
|
39467
|
+
async function fetchWithStartupRetry(url, body, signal, extraHeaders) {
|
|
39468
39468
|
const maxAttempts = 3;
|
|
39469
39469
|
let lastError;
|
|
39470
39470
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
|
|
39471
39471
|
return await fetch(url, {
|
|
39472
39472
|
method: "POST",
|
|
39473
|
-
headers: {
|
|
39473
|
+
headers: {
|
|
39474
|
+
"Content-Type": "application/json",
|
|
39475
|
+
...extraHeaders
|
|
39476
|
+
},
|
|
39474
39477
|
body,
|
|
39475
39478
|
signal
|
|
39476
39479
|
});
|
|
@@ -39482,6 +39485,157 @@ async function fetchWithStartupRetry(url, body, signal) {
|
|
|
39482
39485
|
}
|
|
39483
39486
|
throw lastError;
|
|
39484
39487
|
}
|
|
39488
|
+
/**
|
|
39489
|
+
* The 8-NULL-byte separator AWS Lambda RIE writes between the JSON
|
|
39490
|
+
* metadata prelude and the streaming body chunks. Empirically verified
|
|
39491
|
+
* — see `StreamingPrelude` docstring above.
|
|
39492
|
+
*/
|
|
39493
|
+
const STREAM_PRELUDE_SEPARATOR = Buffer.from([
|
|
39494
|
+
0,
|
|
39495
|
+
0,
|
|
39496
|
+
0,
|
|
39497
|
+
0,
|
|
39498
|
+
0,
|
|
39499
|
+
0,
|
|
39500
|
+
0,
|
|
39501
|
+
0
|
|
39502
|
+
]);
|
|
39503
|
+
/**
|
|
39504
|
+
* Maximum bytes we'll buffer searching for the prelude separator before
|
|
39505
|
+
* giving up. 1 MiB is far past anything Lambda's streamifyResponse would
|
|
39506
|
+
* emit as metadata (typical preludes are <500 bytes) — a runaway here
|
|
39507
|
+
* indicates the handler didn't call `HttpResponseStream.from` at all, in
|
|
39508
|
+
* which case we want to fail fast rather than buffer the whole body.
|
|
39509
|
+
*/
|
|
39510
|
+
const STREAM_PRELUDE_MAX_BYTES = 1024 * 1024;
|
|
39511
|
+
/**
|
|
39512
|
+
* POST the event payload to RIE with the `streaming` response-mode
|
|
39513
|
+
* header, parse the JSON prelude out of the response bytes, and return
|
|
39514
|
+
* a Readable carrying the post-separator body chunks.
|
|
39515
|
+
*
|
|
39516
|
+
* Why a separate function from `invokeRie`: the prelude/separator/body
|
|
39517
|
+
* framing is incompatible with the buffered-response `text()` consumer.
|
|
39518
|
+
* Buffered routes still use `invokeRie`; only Function URLs with
|
|
39519
|
+
* `InvokeMode: RESPONSE_STREAM` use this path.
|
|
39520
|
+
*
|
|
39521
|
+
* The `Lambda-Runtime-Function-Response-Mode: streaming` request header
|
|
39522
|
+
* tells RIE we want the streaming protocol. (RIE happens to emit the
|
|
39523
|
+
* same protocol for `streamifyResponse`-wrapped handlers regardless of
|
|
39524
|
+
* the header, but setting it makes the contract explicit and survives
|
|
39525
|
+
* future RIE behavior changes.)
|
|
39526
|
+
*
|
|
39527
|
+
* `timeoutMs` bounds the total wall time including the prelude wait —
|
|
39528
|
+
* the handler can stream for the full Lambda timeout, so callers should
|
|
39529
|
+
* pass a generous value (typically the function's configured Timeout * 2
|
|
39530
|
+
* with a 30s floor, matching `invokeRie`'s convention).
|
|
39531
|
+
*/
|
|
39532
|
+
async function invokeRieStreaming(host, port, event, timeoutMs) {
|
|
39533
|
+
const url = `http://${host}:${port}${INVOKE_PATH}`;
|
|
39534
|
+
const body = JSON.stringify(event ?? {});
|
|
39535
|
+
const controller = new AbortController();
|
|
39536
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
39537
|
+
let response;
|
|
39538
|
+
try {
|
|
39539
|
+
response = await fetchWithStartupRetry(url, body, controller.signal, { "Lambda-Runtime-Function-Response-Mode": "streaming" });
|
|
39540
|
+
} catch (err) {
|
|
39541
|
+
clearTimeout(timer);
|
|
39542
|
+
if (err.name === "AbortError") throw new Error(`RIE streaming invoke at ${url} timed out after ${timeoutMs}ms. The handler may be hung; check container logs.`);
|
|
39543
|
+
throw err;
|
|
39544
|
+
}
|
|
39545
|
+
if (!response.body) {
|
|
39546
|
+
clearTimeout(timer);
|
|
39547
|
+
throw new Error(`RIE streaming invoke at ${url} returned no response body.`);
|
|
39548
|
+
}
|
|
39549
|
+
const reader = response.body.getReader();
|
|
39550
|
+
let preludeBytes = Buffer.alloc(0);
|
|
39551
|
+
let bodyTail;
|
|
39552
|
+
let separatorIdx = -1;
|
|
39553
|
+
while (separatorIdx < 0) {
|
|
39554
|
+
const { value, done } = await reader.read();
|
|
39555
|
+
if (done) break;
|
|
39556
|
+
const chunk = Buffer.from(value);
|
|
39557
|
+
preludeBytes = Buffer.concat([preludeBytes, chunk]);
|
|
39558
|
+
separatorIdx = preludeBytes.indexOf(STREAM_PRELUDE_SEPARATOR);
|
|
39559
|
+
if (separatorIdx >= 0) {
|
|
39560
|
+
bodyTail = preludeBytes.subarray(separatorIdx + STREAM_PRELUDE_SEPARATOR.length);
|
|
39561
|
+
preludeBytes = preludeBytes.subarray(0, separatorIdx);
|
|
39562
|
+
break;
|
|
39563
|
+
}
|
|
39564
|
+
if (preludeBytes.length > STREAM_PRELUDE_MAX_BYTES) {
|
|
39565
|
+
clearTimeout(timer);
|
|
39566
|
+
reader.cancel().catch(() => void 0);
|
|
39567
|
+
throw new Error(`RIE streaming response did not emit the prelude/body separator within ${STREAM_PRELUDE_MAX_BYTES} bytes. The handler likely did not call awslambda.HttpResponseStream.from(stream, metadata).`);
|
|
39568
|
+
}
|
|
39569
|
+
}
|
|
39570
|
+
if (separatorIdx < 0) {
|
|
39571
|
+
clearTimeout(timer);
|
|
39572
|
+
throw new Error(`RIE streaming response ended before the prelude/body separator (got ${preludeBytes.length} bytes). The handler likely threw before streaming the prelude — check container logs.`);
|
|
39573
|
+
}
|
|
39574
|
+
let prelude;
|
|
39575
|
+
try {
|
|
39576
|
+
prelude = parseStreamingPrelude(preludeBytes.toString("utf8"));
|
|
39577
|
+
} catch (err) {
|
|
39578
|
+
clearTimeout(timer);
|
|
39579
|
+
reader.cancel().catch(() => void 0);
|
|
39580
|
+
throw new Error(`RIE streaming response prelude is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
39581
|
+
}
|
|
39582
|
+
const stream = new Readable({ read() {} });
|
|
39583
|
+
(async () => {
|
|
39584
|
+
try {
|
|
39585
|
+
if (bodyTail && bodyTail.length > 0) stream.push(bodyTail);
|
|
39586
|
+
while (true) {
|
|
39587
|
+
const { value, done } = await reader.read();
|
|
39588
|
+
if (done) break;
|
|
39589
|
+
stream.push(Buffer.from(value));
|
|
39590
|
+
}
|
|
39591
|
+
stream.push(null);
|
|
39592
|
+
} catch (err) {
|
|
39593
|
+
stream.destroy(err instanceof Error ? err : new Error(String(err)));
|
|
39594
|
+
} finally {
|
|
39595
|
+
clearTimeout(timer);
|
|
39596
|
+
}
|
|
39597
|
+
})();
|
|
39598
|
+
return {
|
|
39599
|
+
prelude,
|
|
39600
|
+
body: stream
|
|
39601
|
+
};
|
|
39602
|
+
}
|
|
39603
|
+
/**
|
|
39604
|
+
* Parse a streaming prelude payload (JSON text). Normalizes the shape
|
|
39605
|
+
* the http-server consumes: `statusCode` is coerced to a number (RIE
|
|
39606
|
+
* sometimes emits it as a string), `headers` is always an object (the
|
|
39607
|
+
* handler may omit it), `cookies` is preserved only when an array.
|
|
39608
|
+
*
|
|
39609
|
+
* Exported for unit tests. Throws on invalid JSON or a non-numeric
|
|
39610
|
+
* statusCode (cdkd cannot map that to HTTP).
|
|
39611
|
+
*/
|
|
39612
|
+
function parseStreamingPrelude(text) {
|
|
39613
|
+
const trimmed = text.trim();
|
|
39614
|
+
if (trimmed.length === 0) throw new Error("empty prelude");
|
|
39615
|
+
const raw = JSON.parse(trimmed);
|
|
39616
|
+
if (!raw || typeof raw !== "object") throw new Error("prelude is not a JSON object");
|
|
39617
|
+
const obj = raw;
|
|
39618
|
+
const statusRaw = obj["statusCode"];
|
|
39619
|
+
let statusCode;
|
|
39620
|
+
if (typeof statusRaw === "number" && Number.isFinite(statusRaw)) statusCode = Math.trunc(statusRaw);
|
|
39621
|
+
else if (typeof statusRaw === "string" && /^[0-9]+$/.test(statusRaw)) statusCode = Number.parseInt(statusRaw, 10);
|
|
39622
|
+
else throw new Error(`statusCode must be a number (got ${typeof statusRaw})`);
|
|
39623
|
+
const headers = {};
|
|
39624
|
+
const headersRaw = obj["headers"];
|
|
39625
|
+
if (headersRaw && typeof headersRaw === "object") for (const [k, v] of Object.entries(headersRaw)) {
|
|
39626
|
+
if (v === null || v === void 0) continue;
|
|
39627
|
+
if (typeof v === "string") headers[k] = v;
|
|
39628
|
+
else if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint") headers[k] = String(v);
|
|
39629
|
+
else headers[k] = JSON.stringify(v) ?? "";
|
|
39630
|
+
}
|
|
39631
|
+
const result = {
|
|
39632
|
+
statusCode,
|
|
39633
|
+
headers
|
|
39634
|
+
};
|
|
39635
|
+
const cookiesRaw = obj["cookies"];
|
|
39636
|
+
if (Array.isArray(cookiesRaw)) result.cookies = cookiesRaw.map((c) => String(c));
|
|
39637
|
+
return result;
|
|
39638
|
+
}
|
|
39485
39639
|
|
|
39486
39640
|
//#endregion
|
|
39487
39641
|
//#region src/assets/asset-manifest-loader.ts
|
|
@@ -40538,12 +40692,15 @@ function classifyServiceIntegrationRoute(baseRoute, integrationProps, stackName,
|
|
|
40538
40692
|
* `AWS::Lambda::Url` resource.
|
|
40539
40693
|
*
|
|
40540
40694
|
* Per-shape classification:
|
|
40541
|
-
* - `AuthType === 'NONE'` + `InvokeMode
|
|
40695
|
+
* - `AuthType === 'NONE'` + `InvokeMode === 'BUFFERED'` (or unset) → normal route.
|
|
40696
|
+
* - `AuthType === 'NONE'` + `InvokeMode === 'RESPONSE_STREAM'` → normal route
|
|
40697
|
+
* dispatched via the RIE streaming protocol (the response body is a
|
|
40698
|
+
* JSON prelude — `{statusCode, headers, cookies?}` — followed by 8
|
|
40699
|
+
* NULL bytes and then the raw body chunks). The HTTP server pipes
|
|
40700
|
+
* the chunks to the client with `Transfer-Encoding: chunked` (#467).
|
|
40542
40701
|
* - `AuthType !== 'NONE'` (e.g. `AWS_IAM`) → deferred-error
|
|
40543
40702
|
* unsupported. Boot proceeds; HTTP 501 + `reason` at request time.
|
|
40544
40703
|
* IAM auth would need SigV4 verification cdkd cannot emulate.
|
|
40545
|
-
* - `InvokeMode === 'RESPONSE_STREAM'` → deferred-error unsupported.
|
|
40546
|
-
* The RIE container does not implement `InvokeWithResponseStream`.
|
|
40547
40704
|
*
|
|
40548
40705
|
* The Lambda Arn intrinsic resolution still **hard-errors** when it
|
|
40549
40706
|
* cannot pin down a same-template Lambda — Function URLs have no other
|
|
@@ -40573,14 +40730,18 @@ function discoverFunctionUrl(logicalId, resource, template, stackName) {
|
|
|
40573
40730
|
lambdaLogicalId,
|
|
40574
40731
|
unsupported: { reason: `${stackName}/${logicalId}: AuthType '${String(authType)}' is not supported (only NONE — IAM auth requires SigV4 verification cdkd cannot emulate locally).` }
|
|
40575
40732
|
}];
|
|
40576
|
-
|
|
40733
|
+
const invokeModeRaw = props["InvokeMode"];
|
|
40734
|
+
let invokeMode = "BUFFERED";
|
|
40735
|
+
if (invokeModeRaw === "RESPONSE_STREAM") invokeMode = "RESPONSE_STREAM";
|
|
40736
|
+
else if (invokeModeRaw !== void 0 && invokeModeRaw !== "BUFFERED") return [{
|
|
40577
40737
|
...baseRoute,
|
|
40578
40738
|
lambdaLogicalId,
|
|
40579
|
-
unsupported: { reason: `${stackName}/${logicalId}: InvokeMode
|
|
40739
|
+
unsupported: { reason: `${stackName}/${logicalId}: InvokeMode ${shortJson$1(invokeModeRaw)} is not a recognized value (expected 'BUFFERED' or 'RESPONSE_STREAM').` }
|
|
40580
40740
|
}];
|
|
40581
40741
|
return [{
|
|
40582
40742
|
...baseRoute,
|
|
40583
|
-
lambdaLogicalId
|
|
40743
|
+
lambdaLogicalId,
|
|
40744
|
+
invokeMode
|
|
40584
40745
|
}];
|
|
40585
40746
|
}
|
|
40586
40747
|
/**
|
|
@@ -43717,6 +43878,26 @@ async function handleRequest(req, res, state, opts) {
|
|
|
43717
43878
|
writeError(res, 502);
|
|
43718
43879
|
return;
|
|
43719
43880
|
}
|
|
43881
|
+
if (match.route.invokeMode === "RESPONSE_STREAM") {
|
|
43882
|
+
let streamResult;
|
|
43883
|
+
try {
|
|
43884
|
+
streamResult = await invokeRieStreaming(handle.containerHost, handle.hostPort, baseEvent, opts.rieTimeoutMs);
|
|
43885
|
+
try {
|
|
43886
|
+
writeStreamingResponse(res, streamResult, () => state.pool.release(handle));
|
|
43887
|
+
} catch (writeErr) {
|
|
43888
|
+
streamResult.body.on("error", () => {});
|
|
43889
|
+
streamResult.body.destroy(writeErr instanceof Error ? writeErr : new Error(String(writeErr)));
|
|
43890
|
+
throw writeErr;
|
|
43891
|
+
}
|
|
43892
|
+
return;
|
|
43893
|
+
} catch (err) {
|
|
43894
|
+
logger.error(`RIE streaming invoke failed for ${match.route.lambdaLogicalId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
43895
|
+
if (!res.headersSent) writeError(res, 502);
|
|
43896
|
+
else res.end();
|
|
43897
|
+
state.pool.release(handle);
|
|
43898
|
+
return;
|
|
43899
|
+
}
|
|
43900
|
+
}
|
|
43720
43901
|
try {
|
|
43721
43902
|
const translated = translateLambdaResponse((await invokeRie(handle.containerHost, handle.hostPort, baseEvent, opts.rieTimeoutMs)).payload, match.route.apiVersion);
|
|
43722
43903
|
res.statusCode = translated.statusCode;
|
|
@@ -43732,6 +43913,42 @@ async function handleRequest(req, res, state, opts) {
|
|
|
43732
43913
|
}
|
|
43733
43914
|
}
|
|
43734
43915
|
/**
|
|
43916
|
+
* Pipe a streaming RIE response into an `http.ServerResponse`. The
|
|
43917
|
+
* prelude's status + headers are written via `res.writeHead(...)`; the
|
|
43918
|
+
* body Readable is `pipe`'d through to the response so Node's
|
|
43919
|
+
* `Transfer-Encoding: chunked` machinery handles backpressure +
|
|
43920
|
+
* chunked framing automatically.
|
|
43921
|
+
*
|
|
43922
|
+
* `releasePool` runs in a `finally`-equivalent path so the warm
|
|
43923
|
+
* container is returned to the pool whether the stream ends cleanly
|
|
43924
|
+
* (`'end'` event) or errors mid-body (`'error'` event). Errors after
|
|
43925
|
+
* the prelude has been written can no longer be reported as HTTP
|
|
43926
|
+
* status — the stream is destroyed and the connection aborts.
|
|
43927
|
+
*
|
|
43928
|
+
* Cookies in the prelude are emitted as multiple `Set-Cookie` headers
|
|
43929
|
+
* (HTTP API v2 semantics — matching the buffered path's behavior).
|
|
43930
|
+
*/
|
|
43931
|
+
function writeStreamingResponse(res, result, releasePool) {
|
|
43932
|
+
const logger = getLogger().child("start-api");
|
|
43933
|
+
const { prelude, body } = result;
|
|
43934
|
+
const headersOut = { ...prelude.headers };
|
|
43935
|
+
if (prelude.cookies && prelude.cookies.length > 0) headersOut["set-cookie"] = prelude.cookies;
|
|
43936
|
+
res.writeHead(prelude.statusCode, headersOut);
|
|
43937
|
+
let released = false;
|
|
43938
|
+
const releaseOnce = () => {
|
|
43939
|
+
if (released) return;
|
|
43940
|
+
released = true;
|
|
43941
|
+
releasePool();
|
|
43942
|
+
};
|
|
43943
|
+
body.on("error", (err) => {
|
|
43944
|
+
logger.error(`Streaming Lambda response body errored mid-stream: ${err instanceof Error ? err.message : String(err)}`);
|
|
43945
|
+
res.destroy(err);
|
|
43946
|
+
releaseOnce();
|
|
43947
|
+
});
|
|
43948
|
+
res.on("close", releaseOnce);
|
|
43949
|
+
body.pipe(res);
|
|
43950
|
+
}
|
|
43951
|
+
/**
|
|
43735
43952
|
* Attempt CORS preflight interception. Returns `true` when the
|
|
43736
43953
|
* preflight response was written (caller must NOT continue to route
|
|
43737
43954
|
* dispatch); `false` when no preflight match (caller falls through to
|
|
@@ -49094,7 +49311,7 @@ function reorderArgs(argv) {
|
|
|
49094
49311
|
*/
|
|
49095
49312
|
async function main() {
|
|
49096
49313
|
const program = new Command();
|
|
49097
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
49314
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.130.0");
|
|
49098
49315
|
program.addCommand(createBootstrapCommand());
|
|
49099
49316
|
program.addCommand(createSynthCommand());
|
|
49100
49317
|
program.addCommand(createListCommand());
|