- 编程语言
- 编译器
- 云原生
- 语言运行时
【免费下载链接】wing
A programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡
本篇技术指南以 Wing(winglang)官方测试套件中的快照文件 put.test.w_compile_tf-aws.md 为解剖对象,完整解读一个带路径参数的 PUT 路由从 Wing 源码到 AWS 基础设施代码(Terraform JSON)的编译全过程。你将理解cloud.Api的抽象设计、tf-aws平台如何把一次api.put()调用展开为 API Gateway、Lambda、IAM、S3 等十余个底层资源,并能用快照反推 Wing 的部署与权限模型,最终在自己的 Wing 工程里复现并验证同样的编译产物。
快照的来龙去脉:hangar 编译快照与 put.test.w
本次分析的对象是tools/hangar/__snapshots__/test_corpus/sdk_tests/api/put.test.w_compile_tf-aws.md,它由 hangar 测试框架自动生成。从文件名的三段式命名即可读出它的身份:被测源文件为put.test.w,操作阶段为compile(编译),目标平台为tf-aws。
hangar 的生成逻辑位于 generated_test_targets.ts:compileTest函数会对每个.test.w入口文件执行wing compile -t tf-aws,将产物目录target/<name>.tfaws下的main.tf.json与.wing目录中的 preflight/inflight JavaScript 汇总成一个 Markdown 快照,写入__snapshots__目录(快照目录定义见 paths.ts)。test_corpus/sdk_tests/api前缀表明它属于 SDK 测试语料库中的api主题目录(源文件位于 tests/sdk_tests/api)。
值得注意的细节:快照中的<ASSET_KEY>、<ASSET_SOURCE>等占位符并非编译产物本来面目,而是 utils.ts 中sanitize_json_paths的清理结果——它把形如"asset.xxx.zip"的密钥与本地绝对源码路径统一替换为占位符,避免快照因本地路径差异而不稳定;同时该函数会删除terraform字段与 CDKTF 版本号,保证快照跨环境可复现。
被测 Wing 源码:put.test.w 的 PUT 路由定义
快照的主体是编译产物,但理解编译产物必须先回到源头。完整源码见 tests/sdk_tests/api/put.test.w,其核心逻辑如下:
bring cloud; bring http; bring util; let api = new cloud.Api(); let body = Json {"cat": "Tion"}; let user = "guy"; let _id = "12345"; api.put("/path/:id/nn/:user", inflight (req: cloud.ApiRequest): cloud.ApiResponse => { let path = "/path/{_id}/nn/{user}"; assert(req.method == cloud.HttpMethod.PUT); assert(req.vars?.get("id") == _id); assert(req.vars?.get("user") == user); assert(req.path == path); assert(req.body == Json.stringify(body)); assert(req.headers?.get("content-type") == "application/json"); return cloud.ApiResponse { status: 200, headers: {"content-type" => "application/json; charset=utf-8"}, body: req.vars?.get("id") }; }); test "http.put and http.fetch can preform a call to an api" { let url = "{api.url}/path/{_id}/nn/{user}"; let response: http.Response = http.put(url, headers: { "content-type" => "application/json" }, body: Json.stringify(body)); let fetchResponse: http.Response = http.put(url, method: http.HttpMethod.PUT, headers: { "content-type" => "application/json" }, body: Json.stringify(body)); assert(response.headers.get("content-type") == "application/json; charset=utf-8"); assert(response.body == _id); assert(response.status == 200); assert(response.url == url); assert(fetchResponse.headers.get("content-type") == "application/json; charset=utf-8"); assert(fetchResponse.body == _id); assert(fetchResponse.status == 200); assert(fetchResponse.url == url); }这段测试代码揭示了cloud.Api路由 API 的几个关键设计:
- 路径参数语法:Wing 使用
:id、:user声明路径参数。在云抽象基类 api.ts 中,renderOpenApiPath会把/:name形式的路径转换为 OpenAPI 规范的/{name}形式(快照中的 OpenAPI body 正是/{id}、/{user}形态)。路径合法性由_validatePath校验(cloud/api.ts),参数名只允许字母、数字、下划线与连字符。 - inflight 处理器:
api.put()接收一个inflight闭包,签名是(req: cloud.ApiRequest): cloud.ApiResponse。ApiRequest包含method、path、query、vars(路径参数)、body、headers等字段,ApiResponse支持status、body、headers(定义见 cloud/api.ts)。 - 测试即飞行前验证:Wing 的
test块在模拟器与真实云上均可执行,通过http.put和http.fetch两种途径发起真实 HTTP 请求,断言状态码、响应体、响应头与 URL,从而验证 PUT 路由的端到端行为。
编译产物全景:main.tf.json 完整快照
对put.test.w执行wing compile -t tf-aws后,hangar 将main.tf.json原样收录进快照。下面完整呈现这份产物(即关联文档的主体内容):
{ "//": { "metadata": { "backend": "local", "stackName": "root" }, "outputs": { "root": { "Default": { "Default": { "Api": { "Endpoint": { "Url": "Api_Endpoint_Url_473FEE9F" } } } } } } }, "data": { "aws_caller_identity": { "account": { "//": { "metadata": { "path": "root/Default/account", "uniqueId": "account" } } } }, "aws_region": { "Region": { "//": { "metadata": { "path": "root/Default/Region", "uniqueId": "Region" } } } } }, "output": { "Api_Endpoint_Url_473FEE9F": { "value": "https://${aws_api_gateway_rest_api.Api_api_91C07D84.id}.execute-api.${data.aws_region.Region.name}.amazonaws.com/${aws_api_gateway_stage.Api_api_stage_E0FA39D6.stage_name}" } }, "provider": { "aws": [ {} ] }, "resource": { "aws_api_gateway_deployment": { "Api_api_deployment_7FB64CC4": { "//": { "metadata": { "path": "root/Default/Default/Api/api/deployment", "uniqueId": "Api_api_deployment_7FB64CC4" } }, "lifecycle": { "create_before_destroy": true }, "rest_api_id": "${aws_api_gateway_rest_api.Api_api_91C07D84.id}", "triggers": { "redeployment": "${sha256(aws_api_gateway_rest_api.Api_api_91C07D84.body)}" } } }, "aws_api_gateway_rest_api": { "Api_api_91C07D84": { "//": { "metadata": { "path": "root/Default/Default/Api/api/api", "uniqueId": "Api_api_91C07D84" } }, "body": "{\"paths\":{\"/path/{id}/nn/{user}\":{\"put\":{\"operationId\":\"put-path/:id/nn/:user\",\"responses\":{\"200\":{\"description\":\"200 response\",\"content\":{}}},\"parameters\":[{\"name\":\"id\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"name\":\"user\",\"in\":\"path\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"x-amazon-apigateway-integration\":{\"uri\":\"arn:aws:apigateway:${data.aws_region.Region.name}:lambda:path/2015-03-31/functions/arn:aws:lambda:${data.aws_region.Region.name}:${data.aws_caller_identity.account.account_id}:function:put_path_-id_nn_-user0-c8982eb3/invocations\",\"type\":\"aws_proxy\",\"httpMethod\":\"POST\",\"responses\":{\"default\":{\"statusCode\":\"200\"}},\"passthroughBehavior\":\"when_no_match\",\"contentHandling\":\"CONVERT_TO_TEXT\"}}},\"/{proxy+}\":{\"x-amazon-apigateway-any-method\":{\"produces\":[\"application/json\"],\"x-amazon-apigateway-integration\":{\"type\":\"mock\",\"requestTemplates\":{\"application/json\":\"\\n {\\\"statusCode\\\": 404}\\n \"},\"passthroughBehavior\":\"never\",\"responses\":{\"404\":{\"statusCode\":\"404\",\"responseParameters\":{\"method.response.header.Content-Type\":\"'application/json'\"},\"responseTemplates\":{\"application/json\":\"{\\\"statusCode\\\": 404, \\\"message\\\": \\\"Error: Resource not found\\\"}\"}},\"default\":{\"statusCode\":\"404\",\"responseParameters\":{\"method.response.header.Content-Type\":\"'application/json'\"},\"responseTemplates\":{\"application/json\":\"{\\\"statusCode\\\": 404, \\\"message\\\": \\\"Error: Resource not found\\\"}\"}}}},\"responses\":{\"404\":{\"description\":\"404 response\",\"headers\":{\"Content-Type\":{\"type\":\"string\"}}}}}}},\"openapi\":\"3.0.3\"}", "lifecycle": { "create_before_destroy": true }, "name": "api-c8f613f0" } }, "aws_api_gateway_stage": { "Api_api_stage_E0FA39D6": { "//": { "metadata": { "path": "root/Default/Default/Api/api/stage", "uniqueId": "Api_api_stage_E0FA39D6" } }, "deployment_id": "${aws_api_gateway_deployment.Api_api_deployment_7FB64CC4.id}", "rest_api_id": "${aws_api_gateway_rest_api.Api_api_91C07D84.id}", "stage_name": "prod" } }, "aws_cloudwatch_log_group": { "Api_put_path_id_nn_user0_CloudwatchLogGroup_6FDE8C46": { "//": { "metadata": { "path": "root/Default/Default/Api/put_path_:id_nn_:user0/CloudwatchLogGroup", "uniqueId": "Api_put_path_id_nn_user0_CloudwatchLogGroup_6FDE8C46" } }, "name": "/aws/lambda/put_path_-id_nn_-user0-c8982eb3", "retention_in_days": 30 } }, "aws_iam_role": { "Api_put_path_id_nn_user0_IamRole_1C9DE02F": { "//": { "metadata": { "path": "root/Default/Default/Api/put_path_:id_nn_:user0/IamRole", "uniqueId": "Api_put_path_id_nn_user0_IamRole_1C9DE02F" } }, "assume_role_policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"},\"Effect\":\"Allow\"}]}" } }, "aws_iam_role_policy": { "Api_put_path_id_nn_user0_IamRolePolicy_0EB967A3": { "//": { "metadata": { "path": "root/Default/Default/Api/put_path_:id_nn_:user0/IamRolePolicy", "uniqueId": "Api_put_path_id_nn_user0_IamRolePolicy_0EB967A3" } }, "policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":\"none:null\",\"Resource\":\"*\"}]}", "role": "${aws_iam_role.Api_put_path_id_nn_user0_IamRole_1C9DE02F.name}" } }, "aws_iam_role_policy_attachment": { "Api_put_path_id_nn_user0_IamRolePolicyAttachment_045FFFB7": { "//": { "metadata": { "path": "root/Default/Default/Api/put_path_:id_nn_:user0/IamRolePolicyAttachment", "uniqueId": "Api_put_path_id_nn_user0_IamRolePolicyAttachment_045FFFB7" } }, "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", "role": "${aws_iam_role.Api_put_path_id_nn_user0_IamRole_1C9DE02F.name}" } }, "aws_lambda_function": { "Api_put_path_id_nn_user0_A9826CF0": { "//": { "metadata": { "path": "root/Default/Default/Api/put_path_:id_nn_:user0/Default", "uniqueId": "Api_put_path_id_nn_user0_A9826CF0" } }, "architectures": [ "arm64" ], "environment": { "variables": { "NODE_OPTIONS": "--enable-source-maps", "WING_FUNCTION_NAME": "put_path_-id_nn_-user0-c8982eb3", "WING_TARGET": "tf-aws" } }, "function_name": "put_path_-id_nn_-user0-c8982eb3", "handler": "index.handler", "logging_config": { "log_format": "JSON" }, "memory_size": 1024, "publish": true, "role": "${aws_iam_role.Api_put_path_id_nn_user0_IamRole_1C9DE02F.arn}", "runtime": "nodejs20.x", "s3_bucket": "${aws_s3_bucket.Code.bucket}", "s3_key": "${aws_s3_object.Api_put_path_id_nn_user0_S3Object_5E224671.key}", "timeout": 60, "vpc_config": { "security_group_ids": [], "subnet_ids": [] } } }, "aws_lambda_permission": { "Api_api_permission-PUT-526849ff_9087005A": { "//": { "metadata": { "path": "root/Default/Default/Api/api/permission-PUT-526849ff", "uniqueId": "Api_api_permission-PUT-526849ff_9087005A" } }, "action": "lambda:InvokeFunction", "function_name": "${aws_lambda_function.Api_put_path_id_nn_user0_A9826CF0.function_name}", "principal": "apigateway.amazonaws.com", "source_arn": "${aws_api_gateway_rest_api.Api_api_91C07D84.execution_arn}/*/PUT/path/{id}/nn/{user}", "statement_id": "AllowExecutionFromAPIGateway-PUT-526849ff" } }, "aws_s3_bucket": { "Code": { "//": { "metadata": { "path": "root/Default/Code", "uniqueId": "Code" } }, "bucket_prefix": "code-c84a50b1-" } }, "aws_s3_object": { "Api_put_path_id_nn_user0_S3Object_5E224671": { "//": { "metadata": { "path": "root/Default/Default/Api/put_path_:id_nn_:user0/S3Object", "uniqueId": "Api_put_path_id_nn_user0_S3Object_5E224671" } }, "bucket": "${aws_s3_bucket.Code.bucket}", "key": "<ASSET_KEY>", "source": "<ASSET_SOURCE>" } } } }这份快照包含 12 类资源,覆盖了一个 HTTP 端点从入口到后端的完整链路。下面逐资源结合 SDK 源码深度解析。
逐资源深度解析
元数据与数据源:确定性命名与区域探测
快照最上层的//.metadata记录了backend: "local"(本地 Terraform 状态后端)与stackName: "root",这是 CDKTF 合成 Terraform 配置时的标准头。outputs块则声明了本 stack 对外暴露的输出——Api/Endpoint/Url,其最终值指向 output 区块中的Api_Endpoint_Url_473FEE9F。
data区块中的aws_caller_identity(路径root/Default/account)与aws_region(路径root/Default/Region)是 AWS provider 的数据源:前者在部署期解析当前账号 ID,后者解析部署区域。它们被大量引用——Lambda 函数 ARN、API Gateway 集成 URI、Endpoint URL 都依赖这两个值。这意味着编译产物对账号/区域完全无感,同一份main.tf.json可在任意 AWS 账号与区域直接 apply。
API Gateway 三件套:RestApi、Deployment 与 Stage
tf-aws平台把cloud.Api实现为 API Gateway REST API(aws_api_gateway_rest_api)、部署(aws_api_gateway_deployment)与阶段(aws_api_gateway_stage)三件套,对应 target-tf-aws/api.ts 中WingRestApi构造器依次调用_initApiGatewayRestApi、_initApiGatewayDeployment、_initApiGatewayStage的逻辑。
RestApi 的 OpenAPI body 是最值得研读的部分。快照中body字段是一段完整的 OpenAPI 3.0.3 规范("openapi":"3.0.3"),由两条路径组成:
业务路径
/path/{id}/nn/{user}:由api.put("/path/:id/nn/:user", ...)生成。operationId为put-path/:id/nn/:user,两个路径参数id、user均被声明为required: true的 string 类型——这正是 cloud/api.ts 中_addToSpec解析:param语法并生成parameters数组的结果。每个方法(GET/POST/PUT 等)都会自动获得一个200响应定义。集成扩展
x-amazon-apigateway-integration:这是 AWS API Gateway 的 OpenAPI 扩展,将请求代理到 Lambda。其uri指向put_path_-id_nn_-user0-c8982eb3这个 Lambda 函数,type: "aws_proxy"表示全代理模式(请求原样转发、响应原样返回),httpMethod: "POST"是 Lambda 集成的内部调用方式,passthroughBehavior: "when_no_match"与contentHandling: "CONVERT_TO_TEXT"定义了映射行为。该扩展的生成逻辑在 target-tf-aws/api.ts 的_createApiSpecExtension中。兜底路径
/{proxy+}:对未匹配任何已定义路由的请求,Wing 注入了一个 mock 集成,直接返回404与{"statusCode": 404, "message": "Error: Resource not found"}。该逻辑由 shared-aws/api.default.ts 的createApiDefaultResponse生成,defaultKey会根据已有路径的最长参数前缀构造(本快照中无根级参数路径,故直接为/{proxy+})。若开启了 CORS,此兜底路径还会额外处理 OPTIONS 预检请求(返回 204 并注入 CORS 响应头)。
Deployment 与 Stage:aws_api_gateway_deployment通过triggers.redeployment = sha256(rest_api.body)实现"API 定义变化即自动重新部署",源码对应 target-tf-aws/api.ts;aws_api_gateway_stage的stage_name固定为prod,该常量定义于 shared-aws/api.ts。两个资源均使用lifecycle.create_before_destroy,避免更新时先删后建造成短暂停机。
后端 Lambda 函数:运行时、资源与环境变量
aws_lambda_function是本次编译为 PUT 路由生成的处理函数(Api_put_path_id_nn_user0_A9826CF0),其全部属性都可在 target-tf-aws/function.ts 中找到源码对应:
| 快照属性 | 值 | 源码说明 |
|---|---|---|
runtime | nodejs20.x | Wing inflight 代码编译为 JS 后在 Node.js 20 运行时执行 |
architectures | ["arm64"] | 默认使用 ARM64 架构,成本更低 |
memory_size | 1024 | 默认内存取自DEFAULT_MEMORY_SIZE(1024 MB) |
timeout | 60 | 默认超时 1 分钟(Duration.fromMinutes(1).seconds) |
handler | index.handler | SDK 生成的 Lambda 入口 |
publish | true | 发布版本,配合 Lambda 版本管理 |
logging_config.log_format | JSON | CloudWatch 日志采用 JSON 格式 |
环境变量揭示了三件事:WING_FUNCTION_NAME让运行时知道自己是哪个函数(function.ts 中注入);WING_TARGET: "tf-aws"标记部署目标,便于运行时分发;NODE_OPTIONS: "--enable-source-maps"让堆栈信息可映射回 Wing 源码行号,方便排查(function.ts)。
函数名put_path_-id_nn_-user0-c8982eb3也大有来头:前缀put_path_-id_nn_-user0来自 target-tf-aws/api.ts 中prefix = method.toLowerCase() + path.replace(/\//g, "_")的命名规则,尾缀是资源名哈希;长度上限 64 字符、只允许字母数字与-/_的约束定义在 function.ts。
IAM 三角:角色、内联策略与托管策略
Lambda 需要身份才能运行,快照中对应三个 IAM 资源:
aws_iam_role:信任策略只允许lambda.amazonaws.com服务代入(sts:AssumeRole),这是 Lambda 执行角色的标准形态(function.ts)。aws_iam_role_policy:内联策略中有一条Action: "none:null"的占位语句。这是 Wing 的刻意设计——当函数尚未绑定任何资源权限时,Terraform 不允许空策略,SDK 便注入这条无操作语句占位(function.ts)。一旦通过addPolicyStatements绑定了真实权限(例如访问 Bucket、Queue),这些语句会被合并进同一策略,none:null占位即被替换。aws_iam_role_policy_attachment:附加 AWS 托管策略AWSLambdaBasicExecutionRole,授予向 CloudWatch Logs 写日志的基础权限(function.ts)。
CloudWatch 日志组:默认 30 天保留
aws_cloudwatch_log_group的名称与 Lambda 函数名一致(/aws/lambda/put_path_-id_nn_-user0-c8982eb3),retention_in_days: 30是默认保留期。源码位于 function.ts:默认 30 天,可传logRetentionDays覆盖,传负值则改为无限期保留。
路由授权:aws_lambda_permission 的精确来源限制
aws_lambda_permission是 API Gateway 调用 Lambda 的"通行证",其四个关键字段展示了 Wing 的最小权限原则:
principal: "apigateway.amazonaws.com":只允许 API Gateway 服务调用。source_arn: "…execution_arn/*/PUT/path/{id}/nn/{user}":来源被精确限定到方法级路径——只有对/path/{id}/nn/{user}的 PUT 请求才能触发该函数,同一 API 下的其他路由无法调用。statement_id: "AllowExecutionFromAPIGateway-PUT-526849ff":由PUT方法与路径的 SHA-1 哈希后 8 位拼接而成(target-tf-aws/api.ts)。
这份资源与快照中//.metadata的路径root/Default/Default/Api/api/permission-PUT-526849ff相互印证:同一个哈希526849ff同时出现在资源名与 statement_id 中。
代码分发:S3 桶与对象
aws_s3_bucket(root/Default/Code)是承载所有 Lambda 代码包的共享桶(bucket_prefix: "code-c84a50b1-"),由 App 级别的codeBucket创建;aws_s3_object则把本次函数的 zip 包上传到该桶(function.ts)。对象key形如asset.<addr>.<bundleHash>.zip——只要代码变化,bundle 哈希即变化,对象键随之变化,从而驱动 Lambda 更新。
快照中的<ASSET_KEY>、<ASSET_SOURCE>是前文提到的sanitize_json_paths替换产物:真实产物里key是一个asset.xxx.zip字符串,source是本机打包目录的绝对路径。aws_lambda_function通过s3_bucket/s3_key引用该对象,形成了「S3 代码包 → Lambda」的部署链路。
Endpoint URL 输出
最后看output.Api_Endpoint_Url_473FEE9F,它拼接为:
https://<rest_api_id>.execute-api.<region>.amazonaws.com/prod注意该 URL 是手工构造的,而非直接引用invokeUrl属性。源码注释解释了原因(target-tf-aws/api.ts):invokeUrl与 deployment 共享依赖,若在栈中其他位置引用会引发 Terraform 循环依赖,故 SDK 自行拼装 URL 来打破依赖环。
从快照反推的一次 PUT 请求调用链
综合上述资源,可以完整还原一次 HTTP PUT 请求的生命周期:
- 客户端请求
https://<api-id>.execute-api.<region>.amazonaws.com/prod/path/12345/nn/guy(对应test块中http.put发起的请求); - API Gateway 依据 OpenAPI body 匹配
/path/{id}/nn/{user}的 PUT 方法,将12345与guy解析为路径参数id、user; x-amazon-apigateway-integration通过aws_proxy把请求连同参数转发给 Lambdaput_path_-id_nn_-user0-c8982eb3(授权由aws_lambda_permission保障);- Lambda 中的 inflight 处理器执行断言并返回
ApiResponse(status 200、content-type: application/json; charset=utf-8、body 为id参数值); - API Gateway 将响应原样回传,
http.put的调用方收到 200 与响应体12345,最终通过快照同源的测试断言。
若请求打到未定义路径,则落入/{proxy+}兜底 mock,直接返回 404{"statusCode": 404, "message": "Error: Resource not found"}。
在本地复现这份快照
你可以不依赖 hangar,直接在仓库中手动复现编译产物:
cd tests/sdk_tests/api wing compile -t tf-aws put.test.w执行后会在put.test.w同目录下生成target/put.test.tfaws/main.tf.json,其内容与快照一致(除被sanitize_json_paths替换的占位符外)。也可以运行 hangar 的测试套件来校验快照是否仍然匹配:
cd tools/hangar pnpm test -- sdk_tests/api如果 SDK 行为发生变化导致编译产物偏离快照,测试即失败,这正是快照测试的价值所在——它把「不可见的编译行为」固化为「可 diff 的文本资产」。相关实现参考 generated_test_targets.ts、utils.ts 与 generate_tests.ts。
小结
通过put.test.w这份 tf-aws 编译快照,我们完整看到了 Wing「一份语言、多种云」抽象的具体落地:一行api.put("/path/:id/nn/:user", inflight ...)在编译期被系统性地展开为 OpenAPI 规范、API Gateway 三件套、Lambda 函数、IAM 权限三角、CloudWatch 日志、S3 代码分发与最小权限授权共十余个 Terraform 资源。快照既是 SDK 行为的回归测试锚点,也是理解 Wing 资源命名规则(put_path_-id_nn_-user0)、默认值(1024 MB 内存、60 秒超时、30 天日志保留、prod阶段)与依赖编排思路(手工构造 Endpoint URL 规避循环依赖)的最佳教材。若想继续深入,可对照阅读 tests/sdk_tests/api 下其他路由方法(get/post/delete 等)的快照,以及 packages/@winglang/sdk/src/target-tf-aws/api.ts 中私有 API Gateway 的 VPC 配置分支,观察同一抽象在不同平台与配置下的形态差异。
- 编程语言
- 编译器
- 云原生
- 语言运行时
【免费下载链接】wing
A programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡
相关推荐
Wing 的 cloud.Api 在 tf-aws 目标下的编译产物:深入解读 aws-api.test.w 的 Terraform 快照
Wing 的 cloud.Api 在 tf aws 目标下的编译产物:深入解读 aws api.test.w 的 Terraform 快照 Wing 语言把基础
编程语言编译器云原生语言运行时Wing 云原生 API 的 CORS 配置解析:从 `cloud.Api(cors: true)` 到 tf-aws Terraform 编译产物
Wing 云原生 API 的 CORS 配置解析:从 cloud.Api cors: true 到 tf aws Terraform 编译产物 Wing 语言将
编程语言编译器云原生语言运行时Wing 语言 cloud.Api 的 OPTIONS/HEAD/CONNECT 路由:从测试用例到 tf-aws Terraform 编译产物全解析
Wing 语言 cloud.Api 的 OPTIONS/HEAD/CONNECT 路由:从测试用例到 tf aws Terraform 编译产物全解析 导读 本
编程语言编译器云原生语言运行时
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考