@go-to-k/cdkd 0.128.0 → 0.129.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/cli.js CHANGED
@@ -39795,6 +39795,398 @@ function resolveFnSubInvokeArn(arg) {
39795
39795
  };
39796
39796
  }
39797
39797
 
39798
+ //#endregion
39799
+ //#region src/local/httpv2-service-integration.ts
39800
+ const logger = getLogger();
39801
+ /**
39802
+ * Full list of subtypes cdkd recognizes as supported. Mirrors AWS docs.
39803
+ */
39804
+ const SUPPORTED_SUBTYPES = [
39805
+ "EventBridge-PutEvents",
39806
+ "SQS-SendMessage",
39807
+ "SQS-ReceiveMessage",
39808
+ "SQS-DeleteMessage",
39809
+ "SQS-PurgeQueue",
39810
+ "Kinesis-PutRecord",
39811
+ "StepFunctions-StartExecution",
39812
+ "StepFunctions-StartSyncExecution",
39813
+ "StepFunctions-StopExecution",
39814
+ "AppConfig-GetConfiguration"
39815
+ ];
39816
+ /**
39817
+ * Type guard: is the string an AWS-recognized service-integration subtype?
39818
+ *
39819
+ * Used by route discovery to classify routes — recognized subtypes go
39820
+ * through dispatch, anything else (typo, future-AWS-subtype-not-yet-supported)
39821
+ * falls back to deferred-501.
39822
+ */
39823
+ function isSupportedSubtype(value) {
39824
+ return typeof value === "string" && SUPPORTED_SUBTYPES.includes(value);
39825
+ }
39826
+ /**
39827
+ * Lazy-loaded SDK clients keyed by `<service>:<region>`. SDK packages
39828
+ * are heavyweight (~5-10 MB each); per-process per-region caching
39829
+ * avoids re-instantiating the AWS Signer + middleware stack on every
39830
+ * request.
39831
+ */
39832
+ const clientCache = /* @__PURE__ */ new Map();
39833
+ async function getClient(service, region) {
39834
+ const key = `${service}:${region}`;
39835
+ const cached = clientCache.get(key);
39836
+ if (cached) return cached;
39837
+ let client;
39838
+ switch (service) {
39839
+ case "sqs":
39840
+ client = new (await (import("@aws-sdk/client-sqs"))).SQSClient({ region });
39841
+ break;
39842
+ case "sns":
39843
+ client = new (await (import("@aws-sdk/client-sns"))).SNSClient({ region });
39844
+ break;
39845
+ case "eventbridge":
39846
+ client = new (await (import("@aws-sdk/client-eventbridge"))).EventBridgeClient({ region });
39847
+ break;
39848
+ case "kinesis":
39849
+ client = new (await (import("@aws-sdk/client-kinesis"))).KinesisClient({ region });
39850
+ break;
39851
+ case "sfn":
39852
+ client = new (await (import("@aws-sdk/client-sfn"))).SFNClient({ region });
39853
+ break;
39854
+ case "ssm":
39855
+ client = new (await (import("@aws-sdk/client-ssm"))).SSMClient({ region });
39856
+ break;
39857
+ default: throw new Error(`unknown service '${service}'`);
39858
+ }
39859
+ clientCache.set(key, client);
39860
+ return client;
39861
+ }
39862
+ /**
39863
+ * Dispatch a service integration: build the SDK input from the
39864
+ * pre-resolved parameter map, invoke the SDK, translate the response
39865
+ * to HTTP shape.
39866
+ *
39867
+ * `defaultRegion` is the cdkd process's default AWS region (from
39868
+ * `AWS_REGION` / profile / `--region`). When the resolved parameter
39869
+ * map includes a non-empty `Region`, that value overrides the default
39870
+ * for this single call — matches AWS API Gateway behavior.
39871
+ *
39872
+ * Returns a `ServiceIntegrationResult` for the HTTP server to write
39873
+ * to the client. SDK-level errors are caught and translated to
39874
+ * HTTP 4xx / 5xx — never thrown.
39875
+ */
39876
+ async function dispatchServiceIntegration(subtype, resolvedParameters, defaultRegion) {
39877
+ const region = (resolvedParameters["Region"] || defaultRegion).trim();
39878
+ if (!region) return errorResponse(400, "No AWS region configured. Set --region, AWS_REGION, or pass a 'Region' RequestParameter.");
39879
+ try {
39880
+ switch (subtype) {
39881
+ case "EventBridge-PutEvents": return await dispatchEventBridgePutEvents(resolvedParameters, region);
39882
+ case "SQS-SendMessage": return await dispatchSqsSendMessage(resolvedParameters, region);
39883
+ case "SQS-ReceiveMessage": return await dispatchSqsReceiveMessage(resolvedParameters, region);
39884
+ case "SQS-DeleteMessage": return await dispatchSqsDeleteMessage(resolvedParameters, region);
39885
+ case "SQS-PurgeQueue": return await dispatchSqsPurgeQueue(resolvedParameters, region);
39886
+ case "Kinesis-PutRecord": return await dispatchKinesisPutRecord(resolvedParameters, region);
39887
+ case "StepFunctions-StartExecution": return await dispatchSfnStartExecution(resolvedParameters, region);
39888
+ case "StepFunctions-StartSyncExecution": return await dispatchSfnStartSyncExecution(resolvedParameters, region);
39889
+ case "StepFunctions-StopExecution": return await dispatchSfnStopExecution(resolvedParameters, region);
39890
+ case "AppConfig-GetConfiguration": return await dispatchAppConfigGetConfiguration(resolvedParameters, region);
39891
+ }
39892
+ } catch (err) {
39893
+ return translateSdkError(subtype, err);
39894
+ }
39895
+ }
39896
+ async function dispatchEventBridgePutEvents(params, region) {
39897
+ requireParams(params, [
39898
+ "Detail",
39899
+ "DetailType",
39900
+ "Source"
39901
+ ]);
39902
+ const mod = await import("@aws-sdk/client-eventbridge");
39903
+ const client = await getClient("eventbridge", region);
39904
+ const entry = {
39905
+ Detail: params["Detail"],
39906
+ DetailType: params["DetailType"],
39907
+ Source: params["Source"]
39908
+ };
39909
+ if (params["Time"]) entry["Time"] = new Date(params["Time"]);
39910
+ if (params["EventBusName"]) entry["EventBusName"] = params["EventBusName"];
39911
+ if (params["Resources"]) entry["Resources"] = splitCsv(params["Resources"]);
39912
+ if (params["TraceHeader"]) entry["TraceHeader"] = params["TraceHeader"];
39913
+ return okJson(await client.send(new mod.PutEventsCommand({ Entries: [entry] })));
39914
+ }
39915
+ async function dispatchSqsSendMessage(params, region) {
39916
+ requireParams(params, ["QueueUrl", "MessageBody"]);
39917
+ const mod = await import("@aws-sdk/client-sqs");
39918
+ const client = await getClient("sqs", region);
39919
+ const input = {
39920
+ QueueUrl: params["QueueUrl"],
39921
+ MessageBody: params["MessageBody"]
39922
+ };
39923
+ if (params["DelaySeconds"]) input["DelaySeconds"] = Number(params["DelaySeconds"]);
39924
+ if (params["MessageDeduplicationId"]) input["MessageDeduplicationId"] = params["MessageDeduplicationId"];
39925
+ if (params["MessageGroupId"]) input["MessageGroupId"] = params["MessageGroupId"];
39926
+ if (params["MessageAttributes"]) input["MessageAttributes"] = parseJsonOrEmpty(params["MessageAttributes"]);
39927
+ if (params["MessageSystemAttributes"]) input["MessageSystemAttributes"] = parseJsonOrEmpty(params["MessageSystemAttributes"]);
39928
+ return okJson(await client.send(new mod.SendMessageCommand(input)));
39929
+ }
39930
+ async function dispatchSqsReceiveMessage(params, region) {
39931
+ requireParams(params, ["QueueUrl"]);
39932
+ const mod = await import("@aws-sdk/client-sqs");
39933
+ const client = await getClient("sqs", region);
39934
+ const input = { QueueUrl: params["QueueUrl"] };
39935
+ if (params["AttributeNames"]) input["AttributeNames"] = splitCsv(params["AttributeNames"]);
39936
+ if (params["MaxNumberOfMessages"]) input["MaxNumberOfMessages"] = Number(params["MaxNumberOfMessages"]);
39937
+ if (params["MessageAttributeNames"]) input["MessageAttributeNames"] = splitCsv(params["MessageAttributeNames"]);
39938
+ if (params["ReceiveRequestAttemptId"]) input["ReceiveRequestAttemptId"] = params["ReceiveRequestAttemptId"];
39939
+ if (params["VisibilityTimeout"]) input["VisibilityTimeout"] = Number(params["VisibilityTimeout"]);
39940
+ if (params["WaitTimeSeconds"]) input["WaitTimeSeconds"] = Number(params["WaitTimeSeconds"]);
39941
+ return okJson(await client.send(new mod.ReceiveMessageCommand(input)));
39942
+ }
39943
+ async function dispatchSqsDeleteMessage(params, region) {
39944
+ requireParams(params, ["QueueUrl", "ReceiptHandle"]);
39945
+ const mod = await import("@aws-sdk/client-sqs");
39946
+ return okJson(await (await getClient("sqs", region)).send(new mod.DeleteMessageCommand({
39947
+ QueueUrl: params["QueueUrl"],
39948
+ ReceiptHandle: params["ReceiptHandle"]
39949
+ })));
39950
+ }
39951
+ async function dispatchSqsPurgeQueue(params, region) {
39952
+ requireParams(params, ["QueueUrl"]);
39953
+ const mod = await import("@aws-sdk/client-sqs");
39954
+ return okJson(await (await getClient("sqs", region)).send(new mod.PurgeQueueCommand({ QueueUrl: params["QueueUrl"] })));
39955
+ }
39956
+ async function dispatchKinesisPutRecord(params, region) {
39957
+ requireParams(params, [
39958
+ "StreamName",
39959
+ "Data",
39960
+ "PartitionKey"
39961
+ ]);
39962
+ const mod = await import("@aws-sdk/client-kinesis");
39963
+ const client = await getClient("kinesis", region);
39964
+ const dataBytes = decodeBase64OrUtf8(params["Data"] ?? "");
39965
+ const input = {
39966
+ StreamName: params["StreamName"],
39967
+ Data: dataBytes,
39968
+ PartitionKey: params["PartitionKey"]
39969
+ };
39970
+ if (params["SequenceNumberForOrdering"]) input["SequenceNumberForOrdering"] = params["SequenceNumberForOrdering"];
39971
+ if (params["ExplicitHashKey"]) input["ExplicitHashKey"] = params["ExplicitHashKey"];
39972
+ return okJson(await client.send(new mod.PutRecordCommand(input)));
39973
+ }
39974
+ async function dispatchSfnStartExecution(params, region) {
39975
+ requireParams(params, ["StateMachineArn"]);
39976
+ const mod = await import("@aws-sdk/client-sfn");
39977
+ const client = await getClient("sfn", region);
39978
+ const input = { stateMachineArn: params["StateMachineArn"] };
39979
+ if (params["Name"]) input["name"] = params["Name"];
39980
+ if (params["Input"]) input["input"] = params["Input"];
39981
+ return okJson(await client.send(new mod.StartExecutionCommand(input)));
39982
+ }
39983
+ async function dispatchSfnStartSyncExecution(params, region) {
39984
+ requireParams(params, ["StateMachineArn"]);
39985
+ const mod = await import("@aws-sdk/client-sfn");
39986
+ const client = await getClient("sfn", region);
39987
+ const input = { stateMachineArn: params["StateMachineArn"] };
39988
+ if (params["Name"]) input["name"] = params["Name"];
39989
+ if (params["Input"]) input["input"] = params["Input"];
39990
+ if (params["TraceHeader"]) input["traceHeader"] = params["TraceHeader"];
39991
+ return okJson(await client.send(new mod.StartSyncExecutionCommand(input)));
39992
+ }
39993
+ async function dispatchSfnStopExecution(params, region) {
39994
+ requireParams(params, ["ExecutionArn"]);
39995
+ const mod = await import("@aws-sdk/client-sfn");
39996
+ const client = await getClient("sfn", region);
39997
+ const input = { executionArn: params["ExecutionArn"] };
39998
+ if (params["Cause"]) input["cause"] = params["Cause"];
39999
+ if (params["Error"]) input["error"] = params["Error"];
40000
+ return okJson(await client.send(new mod.StopExecutionCommand(input)));
40001
+ }
40002
+ async function dispatchAppConfigGetConfiguration(params, region) {
40003
+ return errorResponse(501, "AppConfig-GetConfiguration is recognized but cdkd does not yet bundle @aws-sdk/client-appconfig. Use the deployed API for this subtype, or open an issue if you need local emulation.");
40004
+ }
40005
+ function requireParams(params, required) {
40006
+ const missing = required.filter((k) => !params[k] || params[k].trim() === "");
40007
+ if (missing.length > 0) {
40008
+ const err = /* @__PURE__ */ new Error(`missing required RequestParameter(s): ${missing.join(", ")}`);
40009
+ err.statusCode = 400;
40010
+ throw err;
40011
+ }
40012
+ }
40013
+ function splitCsv(value) {
40014
+ return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
40015
+ }
40016
+ function parseJsonOrEmpty(value) {
40017
+ try {
40018
+ const parsed = JSON.parse(value);
40019
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
40020
+ return {};
40021
+ } catch {
40022
+ return {};
40023
+ }
40024
+ }
40025
+ function decodeBase64OrUtf8(value) {
40026
+ const trimmed = value.trim();
40027
+ if (/^[A-Za-z0-9+/]+=*$/.test(trimmed) && trimmed.length % 4 === 0 && trimmed.length > 0) try {
40028
+ return Buffer.from(trimmed, "base64");
40029
+ } catch {}
40030
+ return Buffer.from(value, "utf8");
40031
+ }
40032
+ function okJson(response) {
40033
+ const stripped = stripSdkMetadata(response);
40034
+ return {
40035
+ statusCode: 200,
40036
+ body: JSON.stringify(stripped),
40037
+ headers: { "content-type": "application/json" }
40038
+ };
40039
+ }
40040
+ function stripSdkMetadata(obj) {
40041
+ if (obj === null || obj === void 0 || typeof obj !== "object") return obj;
40042
+ if (Array.isArray(obj)) return obj;
40043
+ const { $metadata: _meta, ...rest } = obj;
40044
+ return rest;
40045
+ }
40046
+ function errorResponse(statusCode, message) {
40047
+ return {
40048
+ statusCode,
40049
+ body: JSON.stringify({ message }),
40050
+ headers: { "content-type": "application/json" }
40051
+ };
40052
+ }
40053
+ /**
40054
+ * Translate an AWS SDK error to an HTTP response. AWS SDK v3 surfaces
40055
+ * errors as instances carrying `$metadata.httpStatusCode` + `name`;
40056
+ * we honor the status code when present, default to 500.
40057
+ */
40058
+ function translateSdkError(subtype, err) {
40059
+ if (err && typeof err === "object") {
40060
+ const e = err;
40061
+ const status = typeof e.statusCode === "number" && e.statusCode >= 100 && e.statusCode < 600 ? e.statusCode : e.$metadata?.httpStatusCode ?? 500;
40062
+ const body = {
40063
+ message: e.message ?? "AWS SDK call failed",
40064
+ code: e.name ?? "UnknownError"
40065
+ };
40066
+ logger.debug(`[${subtype}] SDK error (${status}): ${stringifyValue(body)}`);
40067
+ return {
40068
+ statusCode: status,
40069
+ body: JSON.stringify(body),
40070
+ headers: { "content-type": "application/json" }
40071
+ };
40072
+ }
40073
+ return errorResponse(500, `Unexpected error invoking ${subtype}: ${String(err)}`);
40074
+ }
40075
+ /**
40076
+ * Apply HTTP API v2 `ResponseParameters` mapping (per AWS docs:
40077
+ * https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html#http-api-mapping-supported-values).
40078
+ *
40079
+ * Keys are `<op>:header.<name>` or `overwrite:statuscode`. Values can
40080
+ * carry `$response.header.<name>`, `$context.<X>`, `$stageVariables.<X>`,
40081
+ * or be a static literal. JSONPath against `$response.body.X` is NOT
40082
+ * supported (would require SDK response parsing into the same shape
40083
+ * every subtype produces; deferred). Reserved headers (per AWS docs)
40084
+ * are rejected at this layer with a single-line debug log.
40085
+ *
40086
+ * Status-code lookup is by the SDK-returned `statusCode`. When the
40087
+ * exact code has no entry, the wildcard `'default'` entry is applied
40088
+ * if present (matches AWS deployed behavior).
40089
+ */
40090
+ function applyResponseParameters(base, responseParameters, responseCtx) {
40091
+ if (!responseParameters) return base;
40092
+ const overlay = responseParameters[String(base.statusCode)] ?? responseParameters["default"] ?? void 0;
40093
+ if (!overlay) return base;
40094
+ let statusCode = base.statusCode;
40095
+ const headers = { ...base.headers };
40096
+ for (const [key, value] of Object.entries(overlay)) {
40097
+ if (typeof value !== "string") continue;
40098
+ const resolved = resolveResponseValue(value, responseCtx, base);
40099
+ if (key === "overwrite:statuscode") {
40100
+ const next = Number(resolved);
40101
+ if (Number.isInteger(next) && next >= 100 && next < 600) statusCode = next;
40102
+ continue;
40103
+ }
40104
+ const headerMatch = /^(append|overwrite|remove):header\.(.+)$/i.exec(key);
40105
+ if (!headerMatch || !headerMatch[1] || !headerMatch[2]) continue;
40106
+ const op = headerMatch[1].toLowerCase();
40107
+ const name = headerMatch[2].toLowerCase();
40108
+ if (isReservedHeader(name)) {
40109
+ logger.debug(`ResponseParameters: header '${name}' is reserved by API Gateway and was skipped`);
40110
+ continue;
40111
+ }
40112
+ if (op === "remove") delete headers[name];
40113
+ else if (op === "overwrite") headers[name] = resolved;
40114
+ else if (op === "append") headers[name] = headers[name] ? `${headers[name]},${resolved}` : resolved;
40115
+ }
40116
+ return {
40117
+ statusCode,
40118
+ body: base.body,
40119
+ headers
40120
+ };
40121
+ }
40122
+ function resolveResponseValue(value, ctx, base) {
40123
+ if (value.startsWith("$") && !value.includes("${")) {
40124
+ const r = resolveSingleResponseRef(value, ctx, base);
40125
+ return r !== void 0 ? r : value;
40126
+ }
40127
+ if (value.includes("${")) {
40128
+ let out = "";
40129
+ let i = 0;
40130
+ while (i < value.length) {
40131
+ const next = value.indexOf("${", i);
40132
+ if (next === -1) {
40133
+ out += value.slice(i);
40134
+ break;
40135
+ }
40136
+ out += value.slice(i, next);
40137
+ const end = value.indexOf("}", next + 2);
40138
+ if (end === -1) return value;
40139
+ const r = resolveSingleResponseRef("$" + value.slice(next + 2, end), ctx, base);
40140
+ out += r ?? "";
40141
+ i = end + 1;
40142
+ }
40143
+ return out;
40144
+ }
40145
+ return value;
40146
+ }
40147
+ function resolveSingleResponseRef(ref, ctx, base) {
40148
+ if (ref.startsWith("$response.header.")) {
40149
+ const name = ref.substring(17).toLowerCase();
40150
+ return base.headers[name] ?? "";
40151
+ }
40152
+ if (ref.startsWith("$context.")) return ctx.context[ref.substring(9)] ?? "";
40153
+ if (ref.startsWith("$stageVariables.")) return ctx.stageVariables[ref.substring(16)] ?? "";
40154
+ }
40155
+ /**
40156
+ * Subset of AWS's reserved-headers list relevant to response mapping.
40157
+ * https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html#http-api-mapping-reserved-headers
40158
+ */
40159
+ const RESERVED_HEADER_PREFIXES = [
40160
+ "access-control-",
40161
+ "apigw-",
40162
+ "x-amz-",
40163
+ "x-amzn-"
40164
+ ];
40165
+ const RESERVED_HEADER_EXACT = [
40166
+ "authorization",
40167
+ "connection",
40168
+ "content-encoding",
40169
+ "content-length",
40170
+ "content-location",
40171
+ "forwarded",
40172
+ "keep-alive",
40173
+ "origin",
40174
+ "proxy-authenticate",
40175
+ "proxy-authorization",
40176
+ "te",
40177
+ "trailers",
40178
+ "transfer-encoding",
40179
+ "upgrade",
40180
+ "x-forwarded-for",
40181
+ "x-forwarded-host",
40182
+ "x-forwarded-proto",
40183
+ "via"
40184
+ ];
40185
+ function isReservedHeader(lowerName) {
40186
+ if (RESERVED_HEADER_EXACT.includes(lowerName)) return true;
40187
+ return RESERVED_HEADER_PREFIXES.some((p) => lowerName.startsWith(p));
40188
+ }
40189
+
39798
40190
  //#endregion
39799
40191
  //#region src/local/route-discovery.ts
39800
40192
  /**
@@ -40090,11 +40482,7 @@ function discoverHttpApiRoute(logicalId, resource, template, stackName) {
40090
40482
  lambdaLogicalId: "",
40091
40483
  unsupported: { reason: `${stackName}/${logicalId}: HTTP API v2 integration type '${String(integrationType)}' is not supported (only AWS_PROXY).` }
40092
40484
  }];
40093
- if (integrationProps["IntegrationSubtype"] !== void 0) return [{
40094
- ...baseRoute,
40095
- lambdaLogicalId: "",
40096
- unsupported: { reason: `${stackName}/${logicalId}: HTTP API v2 service integration with IntegrationSubtype '${stringifyValue(integrationProps["IntegrationSubtype"])}' is not supported (cdkd cannot proxy directly to SQS / EventBridge / etc.).` }
40097
- }];
40485
+ if (integrationProps["IntegrationSubtype"] !== void 0) return [classifyServiceIntegrationRoute(baseRoute, integrationProps, stackName, logicalId, integrationLogicalId)];
40098
40486
  const arnOutcome = resolveLambdaArnOutcome(integrationProps["IntegrationUri"]);
40099
40487
  if (arnOutcome.kind === "unsupported") return [{
40100
40488
  ...baseRoute,
@@ -40107,6 +40495,45 @@ function discoverHttpApiRoute(logicalId, resource, template, stackName) {
40107
40495
  }];
40108
40496
  }
40109
40497
  /**
40498
+ * Classify an HTTP API v2 route whose `IntegrationSubtype` is set
40499
+ * (service integration, no Lambda backing). Recognized subtypes
40500
+ * become `serviceIntegration` routes the HTTP server dispatches via
40501
+ * the SDK adapter table in `httpv2-service-integration.ts`;
40502
+ * unrecognized subtypes (typo / future-AWS-subtype-cdkd-doesn't-bundle
40503
+ * an SDK for) become deferred-501 unsupported routes.
40504
+ *
40505
+ * `RequestParameters` is the load-bearing field for dispatch — it
40506
+ * carries the SDK input as a flat map keyed by SDK parameter name.
40507
+ * Missing / non-object RequestParameters surfaces as unsupported (the
40508
+ * SDK call would have nothing to send).
40509
+ */
40510
+ function classifyServiceIntegrationRoute(baseRoute, integrationProps, stackName, routeLogicalId, integrationLogicalId) {
40511
+ const subtypeRaw = integrationProps["IntegrationSubtype"];
40512
+ const declaredAt = `${stackName}/${routeLogicalId}`;
40513
+ if (!isSupportedSubtype(subtypeRaw)) return {
40514
+ ...baseRoute,
40515
+ lambdaLogicalId: "",
40516
+ unsupported: { reason: `${declaredAt}: HTTP API v2 service integration subtype '${stringifyValue(subtypeRaw)}' is not supported by cdkd local start-api (see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-aws-services-reference.html for the supported list).` }
40517
+ };
40518
+ const requestParameters = integrationProps["RequestParameters"];
40519
+ if (!requestParameters || typeof requestParameters !== "object" || Array.isArray(requestParameters)) return {
40520
+ ...baseRoute,
40521
+ lambdaLogicalId: "",
40522
+ unsupported: { reason: `${stackName}/${integrationLogicalId}: HTTP API v2 service integration '${subtypeRaw}' is missing RequestParameters or it is not an object — cannot dispatch to the SDK without input mapping.` }
40523
+ };
40524
+ const responseParameters = integrationProps["ResponseParameters"];
40525
+ const validatedResponseParameters = responseParameters && typeof responseParameters === "object" && !Array.isArray(responseParameters) ? responseParameters : void 0;
40526
+ return {
40527
+ ...baseRoute,
40528
+ lambdaLogicalId: "",
40529
+ serviceIntegration: {
40530
+ subtype: subtypeRaw,
40531
+ requestParameters,
40532
+ ...validatedResponseParameters && { responseParameters: validatedResponseParameters }
40533
+ }
40534
+ };
40535
+ }
40536
+ /**
40110
40537
  * Discover the synthetic `ANY /{proxy+}` route from an
40111
40538
  * `AWS::Lambda::Url` resource.
40112
40539
  *
@@ -41761,7 +42188,7 @@ function attachAuthorizers(stacks, routes) {
41761
42188
  const out = [];
41762
42189
  const errors = [];
41763
42190
  for (const route of routes) {
41764
- if (route.unsupported || route.mockCors) {
42191
+ if (route.unsupported || route.mockCors || route.serviceIntegration) {
41765
42192
  out.push({ route });
41766
42193
  continue;
41767
42194
  }
@@ -41791,6 +42218,45 @@ function attachAuthorizers(stacks, routes) {
41791
42218
  return out;
41792
42219
  }
41793
42220
  /**
42221
+ * Walk every discovered route and return the service-integration routes
42222
+ * whose source CFn resource declares an authorizer (HTTP API v2 routes
42223
+ * with `AuthorizationType !== 'NONE'`). Service-integration routes only
42224
+ * exist on `AWS::ApiGatewayV2::Route`; REST v1 service integrations are
42225
+ * a different shape and not yet supported.
42226
+ */
42227
+ function findIgnoredServiceIntegrationAuthorizers(stacks, routes) {
42228
+ const stackByRoute = /* @__PURE__ */ new Map();
42229
+ for (const stack of stacks) {
42230
+ const prefix = `${stack.stackName}/`;
42231
+ for (const route of routes) if (route.declaredAt.startsWith(prefix)) stackByRoute.set(route.declaredAt, stack);
42232
+ }
42233
+ const out = [];
42234
+ for (const route of routes) {
42235
+ if (!route.serviceIntegration) continue;
42236
+ const stack = stackByRoute.get(route.declaredAt);
42237
+ if (!stack) continue;
42238
+ const slash = route.declaredAt.indexOf("/");
42239
+ if (slash < 0) continue;
42240
+ const logicalId = route.declaredAt.slice(slash + 1);
42241
+ const resource = stack.template.Resources?.[logicalId];
42242
+ if (!resource || resource.Type !== "AWS::ApiGatewayV2::Route") continue;
42243
+ const props = resource.Properties ?? {};
42244
+ const authType = props["AuthorizationType"];
42245
+ if (typeof authType !== "string" || authType === "" || authType.toUpperCase() === "NONE") continue;
42246
+ let authorizerName;
42247
+ if (authType === "AWS_IAM") authorizerName = "AWS_IAM";
42248
+ else {
42249
+ const ref = props["AuthorizerId"];
42250
+ authorizerName = (ref && typeof ref === "object" && "Ref" in ref && typeof ref.Ref === "string" ? ref.Ref : void 0) ?? `<authType=${authType}, AuthorizerId malformed>`;
42251
+ }
42252
+ out.push({
42253
+ declaredAt: route.declaredAt,
42254
+ authorizerName
42255
+ });
42256
+ }
42257
+ return out;
42258
+ }
42259
+ /**
41794
42260
  * Detect the authorizer (if any) attached to a discovered route.
41795
42261
  * Walks the original CFn resource for the route in `stack.template`.
41796
42262
  */
@@ -42920,6 +43386,191 @@ function amzDateOutsideSkew(amzDate, now) {
42920
43386
  return Math.abs(ts.getTime() - now.getTime()) > 900 * 1e3;
42921
43387
  }
42922
43388
 
43389
+ //#endregion
43390
+ //#region src/local/parameter-mapping.ts
43391
+ /**
43392
+ * Parameter-mapping resolver for HTTP API v2 service integrations
43393
+ * (`IntegrationSubtype` set, no Lambda).
43394
+ *
43395
+ * AWS API Gateway HTTP APIs let you wire a route directly to an AWS
43396
+ * SDK call via `RequestParameters` — a flat map whose KEY is the SDK
43397
+ * input parameter name (`QueueUrl`, `MessageBody`, `Source`, ...) and
43398
+ * whose VALUE is either a literal string or one of the dollar-prefixed
43399
+ * "selection expression" forms documented at
43400
+ * https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html
43401
+ *
43402
+ * Supported value forms (all bare and `${...}`-wrapped variants):
43403
+ * - `$request.header.<name>` — case-insensitive header lookup; multi-values comma-joined
43404
+ * - `$request.querystring.<key>` — case-SENSITIVE; multi-values comma-joined
43405
+ * - `$request.path.<param>` — path parameter from `{param}` route patterns
43406
+ * - `$request.path` — full request path (without stage)
43407
+ * - `$request.body` — entire request body as a string
43408
+ * - `$request.body.<jsonpath>` — JSONPath against the parsed JSON body
43409
+ * (recursive descent `..` and filter `?()` are NOT supported)
43410
+ * - `$context.<key>` — supported context variables
43411
+ * (requestId / accountId / domainName / identity.sourceIp / etc.)
43412
+ * - `$stageVariables.<key>` — values from the route's selected stage
43413
+ *
43414
+ * `${X} ${Y}` interpolation: any string containing `${...}` is treated
43415
+ * as a template literal — each placeholder is resolved independently
43416
+ * and the surrounding literal characters are preserved.
43417
+ *
43418
+ * Anything that is not a recognized selection expression AND does not
43419
+ * contain `${...}` is returned as a literal string.
43420
+ *
43421
+ * This module is **pure-functional and async-free** — every input is
43422
+ * derived from the HTTP request snapshot, route match, and the
43423
+ * pre-discovered route state. SDK invocation happens in the dispatcher
43424
+ * (see `httpv2-service-integration.ts`).
43425
+ *
43426
+ * Unresolved references (e.g. `$request.querystring.url` against a
43427
+ * request with no `url` parameter) resolve to the empty string —
43428
+ * matches deployed API Gateway behavior, which treats absent values
43429
+ * as `""` and passes them to the SDK call. The dispatcher's
43430
+ * per-subtype validator then surfaces the SDK's typed rejection.
43431
+ */
43432
+ /**
43433
+ * Resolve a `RequestParameters` map (HTTP API v2 service integration
43434
+ * shape) against the incoming HTTP request. Keys are passed through
43435
+ * verbatim (they identify the SDK input parameter); only the VALUES
43436
+ * go through selection-expression resolution.
43437
+ */
43438
+ function resolveServiceIntegrationParameters(parameters, ctx) {
43439
+ const resolved = {};
43440
+ for (const [key, rawValue] of Object.entries(parameters)) {
43441
+ if (typeof rawValue !== "string") return {
43442
+ kind: "error",
43443
+ reason: `RequestParameters[${JSON.stringify(key)}] must be a string (got ${typeof rawValue}: ${stringifyValue(rawValue)}).`
43444
+ };
43445
+ try {
43446
+ resolved[key] = resolveSelectionExpression(rawValue, ctx);
43447
+ } catch (err) {
43448
+ return {
43449
+ kind: "error",
43450
+ reason: `RequestParameters[${JSON.stringify(key)}]: ${err instanceof Error ? err.message : String(err)}`
43451
+ };
43452
+ }
43453
+ }
43454
+ return {
43455
+ kind: "ok",
43456
+ resolved
43457
+ };
43458
+ }
43459
+ /**
43460
+ * Resolve a single selection-expression string. Public for unit
43461
+ * testing — production callers use {@link resolveServiceIntegrationParameters}.
43462
+ *
43463
+ * Three shapes:
43464
+ * 1. Pure bare reference (`$request.querystring.url`) — resolved
43465
+ * and returned as-is. Whole-string match.
43466
+ * 2. Embedded `${...}` interpolation — every placeholder is resolved
43467
+ * and concatenated with the surrounding literals.
43468
+ * 3. Anything else — returned verbatim as a literal.
43469
+ */
43470
+ function resolveSelectionExpression(input, ctx) {
43471
+ if (input.startsWith("$") && !input.includes("${")) {
43472
+ const resolved = resolveSingleReference(input, ctx);
43473
+ if (resolved !== void 0) return resolved;
43474
+ return input;
43475
+ }
43476
+ if (input.includes("${")) return interpolate(input, ctx);
43477
+ return input;
43478
+ }
43479
+ /**
43480
+ * Walk a `${...}`-templated string and emit the concatenated result.
43481
+ * Per AWS docs, `${X}` may contain any of the selection-expression
43482
+ * forms recognized in bare form.
43483
+ */
43484
+ function interpolate(input, ctx) {
43485
+ let out = "";
43486
+ let i = 0;
43487
+ while (i < input.length) {
43488
+ const next = input.indexOf("${", i);
43489
+ if (next === -1) {
43490
+ out += input.slice(i);
43491
+ break;
43492
+ }
43493
+ out += input.slice(i, next);
43494
+ const end = input.indexOf("}", next + 2);
43495
+ if (end === -1) throw new Error(`unclosed '\${...}' interpolation in selection expression`);
43496
+ const resolved = resolveSingleReference("$" + input.slice(next + 2, end), ctx);
43497
+ out += resolved ?? "";
43498
+ i = end + 1;
43499
+ }
43500
+ return out;
43501
+ }
43502
+ /**
43503
+ * Resolve one selection-expression reference (without `${...}`
43504
+ * wrapping). Returns `undefined` when the reference's PREFIX is not
43505
+ * a recognized form (so the bare-form caller can fall through to
43506
+ * literal-pass-through); returns `""` when the prefix matched but
43507
+ * the referenced datum was absent (AWS-deployed behavior).
43508
+ */
43509
+ function resolveSingleReference(ref, ctx) {
43510
+ if (ref === "$request.body") return ctx.body;
43511
+ if (ref === "$request.path") return ctx.requestPath;
43512
+ if (ref.startsWith("$request.body.")) {
43513
+ const path = ref.substring(14);
43514
+ return resolveBodyJsonPath(ctx.body, path);
43515
+ }
43516
+ if (ref.startsWith("$request.header.")) {
43517
+ const name = ref.substring(16).toLowerCase();
43518
+ return ctx.headers[name] ?? "";
43519
+ }
43520
+ if (ref.startsWith("$request.querystring.")) {
43521
+ const key = ref.substring(21);
43522
+ return ctx.queryString[key] ?? "";
43523
+ }
43524
+ if (ref.startsWith("$request.path.")) {
43525
+ const key = ref.substring(14);
43526
+ return ctx.pathParameters[key] ?? "";
43527
+ }
43528
+ if (ref.startsWith("$context.")) {
43529
+ const key = ref.substring(9);
43530
+ return ctx.context[key] ?? "";
43531
+ }
43532
+ if (ref.startsWith("$stageVariables.")) {
43533
+ const key = ref.substring(16);
43534
+ return ctx.stageVariables[key] ?? "";
43535
+ }
43536
+ }
43537
+ /**
43538
+ * Resolve a simple JSON path against the request body. Per AWS docs:
43539
+ *
43540
+ * - The body is JSON-parsed (best-effort; non-JSON → empty string).
43541
+ * - Path segments are `.`-separated. Array indexing via `[N]` is
43542
+ * supported.
43543
+ * - Recursive descent `..` and filter expressions `?()` are NOT
43544
+ * supported and produce `""` (matches AWS rejection at deploy
43545
+ * time, but we degrade gracefully at runtime).
43546
+ *
43547
+ * Non-string leaves are stringified with `JSON.stringify` so they can
43548
+ * round-trip into the SDK call's string-typed input fields.
43549
+ */
43550
+ function resolveBodyJsonPath(body, path) {
43551
+ if (path.startsWith(".") || path.includes("..") || path.includes("?(")) return "";
43552
+ let parsed;
43553
+ try {
43554
+ parsed = JSON.parse(body);
43555
+ } catch {
43556
+ return "";
43557
+ }
43558
+ const segments = path.split(/\.|\[(\d+)\]/).filter((s) => s !== void 0 && s !== "");
43559
+ let cursor = parsed;
43560
+ for (const seg of segments) {
43561
+ if (cursor === null || cursor === void 0) return "";
43562
+ if (typeof cursor !== "object") return "";
43563
+ if (Array.isArray(cursor)) {
43564
+ const idx = Number(seg);
43565
+ if (!Number.isInteger(idx)) return "";
43566
+ cursor = cursor[idx];
43567
+ } else cursor = cursor[seg];
43568
+ }
43569
+ if (cursor === void 0 || cursor === null) return "";
43570
+ if (typeof cursor === "string") return cursor;
43571
+ return JSON.stringify(cursor);
43572
+ }
43573
+
42923
43574
  //#endregion
42924
43575
  //#region src/local/http-server.ts
42925
43576
  /**
@@ -43020,6 +43671,10 @@ async function handleRequest(req, res, state, opts) {
43020
43671
  writeNotImplemented(res, match.route.unsupported.reason);
43021
43672
  return;
43022
43673
  }
43674
+ if (match.route.serviceIntegration) {
43675
+ await handleServiceIntegrationRequest(req, res, match, bodyBuf, opts);
43676
+ return;
43677
+ }
43023
43678
  const authorizer = state.routes.find((r) => r.route.declaredAt === match.route.declaredAt && r.route.method === match.route.method)?.authorizer;
43024
43679
  const clientCert = opts.mtls ? extractClientCert(req) : void 0;
43025
43680
  const snapshot = {
@@ -43397,9 +44052,12 @@ function lowercaseSingularHeaders(raw) {
43397
44052
  return out;
43398
44053
  }
43399
44054
  /**
43400
- * Parse query string into a singular (last-wins) map. Used by the
43401
- * authorizer pass separate from api-gateway-event because the
43402
- * authorizer needs raw header values too.
44055
+ * Parse query string into a singular map. Multi-value keys are
44056
+ * comma-joined in order of appearance (`?foo=a&foo=b` -> `foo: 'a,b'`)
44057
+ * matches the contract documented at
44058
+ * `src/local/parameter-mapping.ts:14` ("multi-values comma-joined")
44059
+ * AND deployed API Gateway behavior. Used by both the authorizer pass
44060
+ * and the HTTP API v2 service-integration parameter mapper.
43403
44061
  */
43404
44062
  function parseQueryStringSingular(rawUrl) {
43405
44063
  const q = rawUrl.indexOf("?");
@@ -43420,7 +44078,8 @@ function parseQueryStringSingular(rawUrl) {
43420
44078
  try {
43421
44079
  value = decodeURIComponent(rawValue);
43422
44080
  } catch {}
43423
- out[key] = value;
44081
+ const prev = out[key];
44082
+ out[key] = prev === void 0 ? value : `${prev},${value}`;
43424
44083
  }
43425
44084
  return out;
43426
44085
  }
@@ -43466,6 +44125,120 @@ function writeError(res, statusCode, body = "{\"message\":\"Internal server erro
43466
44125
  res.end(body);
43467
44126
  }
43468
44127
  /**
44128
+ * Handle an HTTP API v2 service-integration route (#458). The route
44129
+ * carries `serviceIntegration: { subtype, requestParameters, responseParameters? }`
44130
+ * — no Lambda backs it. Flow:
44131
+ *
44132
+ * 1. Build a {@link RequestParameterContext} from the HTTP request +
44133
+ * route match (path parameters, query string, headers, context
44134
+ * variables, stage variables).
44135
+ * 2. Resolve every `RequestParameters` value against that context via
44136
+ * `parameter-mapping.ts`. Per-parameter unresolved refs degrade to
44137
+ * `""` (matches AWS deployed behavior).
44138
+ * 3. Dispatch to the per-subtype SDK adapter in
44139
+ * `httpv2-service-integration.ts`. Per-request `Region` parameter
44140
+ * overrides the server's `opts.defaultRegion`; absent both surfaces
44141
+ * a 400.
44142
+ * 4. Apply per-status-code `ResponseParameters` overlay (header /
44143
+ * statuscode overwrites).
44144
+ * 5. Write the result to the HTTP client.
44145
+ *
44146
+ * Authorizer pass: NOT yet wired (current scope is dispatch only). When
44147
+ * a service-integration route carries an authorizer, the discovery layer
44148
+ * leaves it in place but the server short-circuits to dispatch BEFORE the
44149
+ * auth pass. A follow-up PR can hoist the auth pass earlier — keeping it
44150
+ * out of this PR limits the blast radius.
44151
+ */
44152
+ async function handleServiceIntegrationRequest(req, res, match, bodyBuf, opts) {
44153
+ const route = match.route;
44154
+ const svc = route.serviceIntegration;
44155
+ if (!svc) {
44156
+ writeError(res, 500);
44157
+ return;
44158
+ }
44159
+ const rawUrl = req.url ?? "/";
44160
+ const headersFlat = flattenHeadersForMapping(req);
44161
+ const queryString = parseQueryStringSingular(rawUrl);
44162
+ const requestPath = rawUrl.split("?")[0] ?? "/";
44163
+ const context = buildServiceIntegrationContextVars(req, route);
44164
+ const ctx = {
44165
+ headers: headersFlat,
44166
+ queryString,
44167
+ pathParameters: match.pathParameters,
44168
+ requestPath,
44169
+ body: bodyBuf.toString("utf8"),
44170
+ context,
44171
+ stageVariables: route.stageVariables ?? {}
44172
+ };
44173
+ const outcome = resolveServiceIntegrationParameters(svc.requestParameters, ctx);
44174
+ if (outcome.kind === "error") {
44175
+ getLogger().warn(`[${route.declaredAt}] ${outcome.reason}`);
44176
+ const body = JSON.stringify({
44177
+ message: "Invalid integration mapping",
44178
+ reason: outcome.reason
44179
+ });
44180
+ res.statusCode = 500;
44181
+ res.setHeader("content-type", "application/json");
44182
+ res.setHeader("content-length", String(Buffer.byteLength(body, "utf-8")));
44183
+ res.end(body);
44184
+ return;
44185
+ }
44186
+ const result = await dispatchServiceIntegration(svc.subtype, outcome.resolved, opts.defaultRegion ?? "");
44187
+ const responseCtx = {
44188
+ context,
44189
+ stageVariables: route.stageVariables ?? {}
44190
+ };
44191
+ const finalResult = applyResponseParameters(result, svc.responseParameters, responseCtx);
44192
+ res.statusCode = finalResult.statusCode;
44193
+ for (const [name, value] of Object.entries(finalResult.headers)) res.setHeader(name, value);
44194
+ res.setHeader("content-length", String(Buffer.byteLength(finalResult.body, "utf-8")));
44195
+ res.end(finalResult.body);
44196
+ }
44197
+ /**
44198
+ * Flatten Node's `req.headers` to the single-string-per-key shape the
44199
+ * parameter-mapping resolver expects (`$request.header.<name>` is
44200
+ * documented as comma-joined multi-value). Header NAMES are lowercased
44201
+ * because AWS docs document `$request.header.<name>` as case-insensitive.
44202
+ */
44203
+ function flattenHeadersForMapping(req) {
44204
+ const out = {};
44205
+ for (const [name, value] of Object.entries(req.headers)) {
44206
+ const lower = name.toLowerCase();
44207
+ if (Array.isArray(value)) out[lower] = value.join(", ");
44208
+ else if (typeof value === "string") out[lower] = value;
44209
+ }
44210
+ return out;
44211
+ }
44212
+ /**
44213
+ * Build the subset of AWS context variables the service-integration
44214
+ * parameter-mapping resolver needs to surface (per AWS docs
44215
+ * https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-logging-variables.html).
44216
+ *
44217
+ * We populate the most-commonly-referenced fields with realistic values
44218
+ * (`requestId` is fresh per call, `accountId` / `domainName` are mock
44219
+ * but stable). Selection expressions against context variables we
44220
+ * don't model resolve to `""` — matches the AWS-deployed behavior of
44221
+ * absent values.
44222
+ */
44223
+ function buildServiceIntegrationContextVars(req, route) {
44224
+ const requestId = `cdkd-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
44225
+ const sourceIp = req.socket.remoteAddress ?? "127.0.0.1";
44226
+ return {
44227
+ requestId,
44228
+ accountId: "123456789012",
44229
+ apiId: route.apiLogicalId ?? "local",
44230
+ stage: route.stage,
44231
+ "identity.sourceIp": sourceIp,
44232
+ "identity.userAgent": Array.isArray(req.headers["user-agent"]) || typeof req.headers["user-agent"] === "string" ? String(req.headers["user-agent"]) : "",
44233
+ domainName: "localhost",
44234
+ httpMethod: req.method ?? "GET",
44235
+ path: (req.url ?? "/").split("?")[0] ?? "/",
44236
+ protocol: "HTTP/1.1",
44237
+ requestTime: (/* @__PURE__ */ new Date()).toISOString(),
44238
+ requestTimeEpoch: String(Date.now())
44239
+ };
44240
+ }
44241
+ /**
43469
44242
  * Write the 501 Not Implemented response surfaced for routes the
43470
44243
  * discovery layer flagged as `unsupported`. The integration's reason
43471
44244
  * (e.g. "MOCK integration is not emulated", "WebSocket APIs are not
@@ -44227,6 +45000,7 @@ async function localStartApiCommand(target, options) {
44227
45000
  warnVpcConfigLambdas(initialMaterial.routes, initialMaterial.stacks ?? []);
44228
45001
  sigV4CredentialsLoader = defaultCredentialsLoader();
44229
45002
  warnIamRoutes(initialMaterial.routes);
45003
+ warnIgnoredServiceIntegrationAuthorizers(initialMaterial.routes, initialMaterial.stacks ?? []);
44230
45004
  let maxTimeoutSec = 0;
44231
45005
  for (const spec of initialMaterial.specs.values()) if (spec.lambda.timeoutSec > maxTimeoutSec) maxTimeoutSec = spec.lambda.timeoutSec;
44232
45006
  const rieTimeoutMs = Math.max(3e4, maxTimeoutSec * 2 * 1e3);
@@ -44251,6 +45025,7 @@ async function localStartApiCommand(target, options) {
44251
45025
  for (const result of handles) if (result.status === "fulfilled") groupPool.release(result.value);
44252
45026
  else logger.warn(`Pre-warm failed for one Lambda in ${group.displayName} (cold start cost will apply on first request): ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`);
44253
45027
  }
45028
+ const defaultRegion = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? void 0;
44254
45029
  const started = await startApiServer({
44255
45030
  state: groupState,
44256
45031
  rieTimeoutMs,
@@ -44262,7 +45037,8 @@ async function localStartApiCommand(target, options) {
44262
45037
  jwksWarnedUrls,
44263
45038
  sigV4CredentialsLoader,
44264
45039
  sigV4WarnedForeignIds,
44265
- sigV4AllowUnverified: options.allowUnverifiedSigv4 === true
45040
+ sigV4AllowUnverified: options.allowUnverifiedSigv4 === true,
45041
+ ...defaultRegion && { defaultRegion }
44266
45042
  });
44267
45043
  servers.push({
44268
45044
  group,
@@ -44391,7 +45167,7 @@ function uniqueLambdaIds(routes, routesWithAuth) {
44391
45167
  const seen = /* @__PURE__ */ new Set();
44392
45168
  const out = [];
44393
45169
  for (const r of routes) {
44394
- if (r.unsupported || r.mockCors) continue;
45170
+ if (r.unsupported || r.mockCors || r.serviceIntegration) continue;
44395
45171
  if (r.lambdaLogicalId.length === 0) continue;
44396
45172
  if (!seen.has(r.lambdaLogicalId)) {
44397
45173
  seen.add(r.lambdaLogicalId);
@@ -44399,7 +45175,7 @@ function uniqueLambdaIds(routes, routesWithAuth) {
44399
45175
  }
44400
45176
  }
44401
45177
  for (const entry of routesWithAuth) {
44402
- if (entry.route.unsupported || entry.route.mockCors) continue;
45178
+ if (entry.route.unsupported || entry.route.mockCors || entry.route.serviceIntegration) continue;
44403
45179
  const auth = entry.authorizer;
44404
45180
  if (!auth) continue;
44405
45181
  if (auth.kind === "lambda-token" || auth.kind === "lambda-request") {
@@ -44484,6 +45260,27 @@ function warnIamRoutes(routesWithAuth) {
44484
45260
  return true;
44485
45261
  }
44486
45262
  /**
45263
+ * #458 / PR #500 review: emit a one-line warn naming every service-
45264
+ * integration route whose source CFn resource declares an authorizer
45265
+ * (HTTP API v2 routes with `AuthorizationType !== 'NONE'`). The
45266
+ * dispatcher in `http-server.ts` runs the SDK call BEFORE the
45267
+ * authorizer pass would fire, so without this warn a CDK app that
45268
+ * wires JWT / Lambda / Cognito / IAM authorizers onto service
45269
+ * integrations would silently let every local request reach the SDK
45270
+ * call without auth. Threading the auth pass through the
45271
+ * service-integration dispatcher is a follow-up issue. Returns the
45272
+ * number of warned routes so tests can assert the firing path; the
45273
+ * value is otherwise unused.
45274
+ */
45275
+ function warnIgnoredServiceIntegrationAuthorizers(routesWithAuth, stacks) {
45276
+ const logger = getLogger();
45277
+ const ignored = findIgnoredServiceIntegrationAuthorizers(stacks, routesWithAuth.map((entry) => entry.route));
45278
+ if (ignored.length === 0) return 0;
45279
+ logger.warn(`${ignored.length} HTTP API v2 service-integration route(s) declare an authorizer but cdkd local start-api dispatches the SDK call BEFORE the authorizer pass — every local request reaches the SDK call WITHOUT authentication. This is a deferred feature; see https://github.com/go-to-k/cdkd/issues/502 for the follow-up tracking issue.`);
45280
+ for (const entry of ignored) logger.warn(` - ${entry.declaredAt}: authorizer '${entry.authorizerName}' is configured but ignored`);
45281
+ return ignored.length;
45282
+ }
45283
+ /**
44487
45284
  * Build the per-Lambda container spec — code dir, env vars (template +
44488
45285
  * --env-vars overlay), STS-issued creds when --assume-role names this
44489
45286
  * Lambda, optional --debug-port reservation. Errors out with a clear
@@ -44799,7 +45596,7 @@ function printRouteTable(routes) {
44799
45596
  process.stdout.write("Discovered routes:\n");
44800
45597
  for (const r of sorted) {
44801
45598
  const sourceLabel = r.source === "http-api" ? "HTTP API" : r.source === "rest-v1" ? `REST v1, stage '${r.stage}'` : "Function URL";
44802
- const target = r.mockCors ? "[MOCK CORS preflight]" : r.unsupported ? "[501 Not Implemented]" : r.lambdaLogicalId;
45599
+ const target = r.mockCors ? "[MOCK CORS preflight]" : r.unsupported ? "[501 Not Implemented]" : r.serviceIntegration ? `[${r.serviceIntegration.subtype}]` : r.lambdaLogicalId;
44803
45600
  process.stdout.write(` ${r.method.padEnd(methodWidth)} ${r.pathPattern.padEnd(pathWidth)} -> ${target} (${sourceLabel})\n`);
44804
45601
  }
44805
45602
  process.stdout.write("\n");
@@ -48297,7 +49094,7 @@ function reorderArgs(argv) {
48297
49094
  */
48298
49095
  async function main() {
48299
49096
  const program = new Command();
48300
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.128.0");
49097
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.129.0");
48301
49098
  program.addCommand(createBootstrapCommand());
48302
49099
  program.addCommand(createSynthCommand());
48303
49100
  program.addCommand(createListCommand());