十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Windmill 中的 TypeScript (Deno) 脚本编写指南:运行时选择、函数结构、资源类型与 S3 操作

Windmill 中的 TypeScript (Deno) 脚本编写指南:运行时选择、函数结构、资源类型与 S3 操作 Windmill 中的 TypeScript (Deno) 脚本编写指南运行时选择、函数结构、资源类型与 S3 操作【免费下载链接】windmillOpen-source developer platform to power your entire infra and turn scripts into webhooks, workflows and UIs. Fastest workflow engine (13x vs Airflow). Open-source alternative to Retool and Temporal.项目地址: https://gitcode.com/GitHub_Trending/wi/windmill导读本文以 Windmill 官方系统提示词文档 system_prompts/languages/deno.md 为主体讲解在 Windmill 开发者平台中编写 Deno 运行时 TypeScript 脚本的完整规范何时该选 Deno 而非 Bun、main函数结构、RT资源类型命名空间、npm:前缀导入、windmill-client平台客户端、预处理器preprocessor脚本以及 S3 对象操作。文章同时结合仓库中 deno_executor.rs 与 s3Types.ts 等源码揭示这些写法约定背后的 Worker 执行原理。读完本文你将能够编写出可被 Windmill Worker 正确解析、执行并安全访问平台资源的 Deno 脚本。一、先决判断什么时候才用 DenoWindmill 的 TypeScript 脚本有两个运行时——Deno 与 Bun原生运行时另有bunnative。官方语言规范文档中给出了明确的使用优先级优先使用 Bunwrite-script-bun编写 TypeScript。仅当脚本明确需要 Deno 运行时——即需要 Deno 标准库或没有 npm 等价物的deno.landURL 导入——时才使用 Deno。其他所有 TypeScript 一律使用 Bun。也就是说Deno 在 Windmill 中是一个按需选用的运行时而非默认选项。对比同目录下的 system_prompts/languages/bun.md 可以看到Bun 被定位为默认且优先的 TypeScript 运行时并且文档进一步建议只要脚本只依赖fetch与 JS 标准库包括使用windmill-client就应该加//native首行注释改写成原生脚本以获得更快的冷启动与更高的并发度。因此在动手写脚本前请先按下表决策场景推荐的运行时普通业务脚本、npm 生态依赖Bun默认需要 Deno 标准库或deno.landURL 导入Deno仅用fetch JS 标准库含windmill-clientBun Native//native需要node:*模块、文件系统、子进程、原生插件Bun非常规运行时Windmill 之所以保留 Deno 运行时是因为 Deno 原生支持通过 URL 直接导入模块如https://deno.land/std/...这对某些没有 npm 包的 Deno 生态库是唯一途径。二、脚本结构导出一个 asyncmain函数Deno 脚本的入口约定与 Bun 完全一致导出一个名为main的异步函数Windmill 会读取函数的参数签名作为脚本的输入参数函数返回值作为脚本的运行结果。export async function main(param1: string, param2: number) { // Your code here return { result: param1, count: param2 }; }关键约定不要手动调用main。Windmill Worker 会在运行时通过生成的 wrapper 代码来调用它。库会自动安装。脚本中导入的 npm 依赖由平台自动解析与缓存无需手动执行安装命令。从源码看这一约定由 Worker 的 wrapper 生成逻辑强制保证。deno_executor.rs 中Worker 将脚本内容写入main.ts后会生成一个独立的 wrapper 文件import { main } from ./main.ts; let args await Deno.readTextFile(args.json) .then(JSON.parse); function argsObjToArr({ param1, param2 }) { return [ param1, param2 ]; }Wrapper 从args.json读取入参 JSON按参数名解构后按位置传给main。这意味着脚本作者绝不能自己调用main否则会造成重复执行。2.1 Worker 对脚本签名的解析Worker 并非简单地把main当作黑盒调用而是先通过 TypeScript 解析器读取main函数的参数签名。在 deno_executor.rs 中可以看到使用windmill_parser_ts::parse_deno_signature解析main的参数列表若检测到参数类型为Datetime日期时间wrapper 会在调用前将字符串参数转换为Date对象args[x] args[x] ? new Date(args[x]) : undefined同时 wrapper 里还补了BigInt.prototype.toJSON ...的序列化兼容处理确保BigInt返回值能被正确序列化。这意味着脚本的参数类型声明并非可有可无——类型注解是 Windmill 推断输入表单与类型转换的依据。三、资源类型Resource Types用RT命名空间注入凭据在 Windmill 中凭据与配置存放在资源Resource中通过参数传入main。Deno 脚本中应使用RT命名空间来引用资源类型export async function main(stripe: RT.Stripe) { // stripe contains API key and config from the resource }使用要点仅在确实需要满足业务需求时才使用资源类型不要滥用始终使用RT命名空间不要自己声明结构体使用某个资源类型之前先查看项目根目录下的rt.d.ts文件确认该资源类型可用及其字段。该文件由命令wmill resource-type generate-namespace生成。RT命名空间让 Windmill 在 UI 层将资源选择器直接绑定到参数上用户在运行脚本时只需从已配置的资源列表中选择一个具体实例例如某个 Stripe 账号Windmill 便将其凭据注入到main的参数中脚本内部无需关心密钥管理。四、导入方式npm:前缀与 Deno URL 导入Deno 脚本支持两种导入来源// npm packages use npm: prefix import Stripe from npm:stripe; import { someFunction } from npm:some-package; // Deno standard library import { serve } from https://deno.land/std/http/server.ts;npm 包必须使用npm:前缀这是 Deno 读取 Node.js 生态的标准方式Deno 标准库通过https://deno.land/std/...URL 直接导入这也是选择 Deno 运行时的主要理由。从 Worker 实现看deno_executor.rs 中的generate_deno_lock函数会以deno cache命令配合--locklock.json、--allow-import与一组 unstable 参数在任务执行前预取并锁定全部依赖生成 lockfile 以便复用DENO_UNSTABLE_ARGS常量如--unstable-unsafe-proto、--unstable-bare-node-builtins、--unstable-ffi等会被拼接进deno cache与执行命令。此外Worker 还会通过环境变量注入DENO_AUTH_TOKENS形如{token}hostname用于鉴权访问 Windmill 内部 URL、NPM_CONFIG_REGISTRY支持通过 workspace 级npmrc覆盖私有 npm 镜像源以及DENO_CERT/DENO_TLS_CA_STORE等 TLS 配置。五、平台交互优先使用windmill-client所有与 Windmill 平台本身的交互都应通过官方客户端完成import * as wmill from windmill-client;规则凡是要与 Windmill 通信的场景——读取资源/变量/状态、运行脚本与流程、执行 S3 对象操作等——优先使用windmill-client而不是裸写fetch。客户端会自动处理鉴权auth、当前工作区workspace与基础 URL无需手工拼接 token 与地址。fetch只应保留给调用外部HTTP API 的场景。文档同时强调windmill-client的完整 API 参考每个导出函数及其签名随 skill 提供遇到具体方法时应查阅参考确认精确签名而不是凭猜测实现或退回到fetch。客户端实现在 typescript-client/client.ts类型声明见 typescript-client/client.d.ts。六、预处理器脚本Preprocessor Scripts在触发器Trigger场景中脚本还可以作为预处理器使用函数名必须为preprocessor接收一个event参数返回一个对象——该返回值会被用作下游脚本的入参。type Event { kind: | webhook | http | websocket | kafka | email | nats | postgres | sqs | mqtt | gcp; body: any; headers: Recordstring, string; query: Recordstring, string; }; export async function preprocessor(event: Event) { return { param1: event.body.field1, param2: event.query.id, }; }Event对象携带触发事件的类型kind涵盖 webhook、http、websocket、kafka、email、nats、postgres、sqs、mqtt、gcp 十类触发器、原始请求体body、请求头headers与查询参数query。预处理器是事件 → 规范化入参的转换层例如从 webhook 的body中提取字段映射成下游脚本的参数。从 deno_executor.rs 的 wrapper 生成逻辑可以看到预处理器的真实调用链当任务的flow_step_id不是preprocessor且尚未预处理器时Worker 会在 wrapper 中额外导入并执行preprocessorimport { preprocessor } from ./main.ts; // ... if (preprocessor undefined || typeof preprocessor ! function) { throw new Error(preprocessor function is missing); } args await preprocessor(...preArgsObjToArr(args)); const args_json JSON.stringify(args ?? null, ...); await Deno.writeTextFile(args.json, args_json);预处理器执行后其结果会写回args.json作为后续步骤的输入。注意wrapper 会先解析preprocessor的签名parse_deno_signature(..., Some(preprocessor))因此preprocessor函数的参数类型声明同样会影响入参的转换。七、S3 对象操作内置的存储读写能力Windmill 为 S3 兼容存储提供了内置支持。统一使用wmill.S3Object类型它同时覆盖两种表示形式定义见 s3Types.tsURI 字符串形式s3://storage/key其中s3:///keystorage 为空表示工作区默认存储记录对象形式{ s3: string; storage?: string; presigned?: string }s3为文件 key、storage为存储后端标识可选、presigned为公网访问的预签名 URL 查询串可选。始终使用该内置类型不要自行重定义以保证 Windmill 能正确识别参数并渲染 S3 选择器。7.1 将 S3Object 作为脚本参数接收import * as wmill from windmill-client; export async function main(file: wmill.S3Object) { const content await wmill.loadS3File(file); // ... }将参数类型声明为wmill.S3Object后Windmill UI 会把该参数渲染为 S3 文件选择控件用户可直接从已配置的存储中挑选文件。7.2 三类核心 S3 操作import * as wmill from windmill-client; // Load file content from S3 const content: Uint8Array await wmill.loadS3File(s3object); // Load file as stream const blob: Blob await wmill.loadS3FileStream(s3object); // Write file to S3 const result: wmill.S3Object await wmill.writeS3File( s3object, // Target path (or undefined to auto-generate) fileContent, // string or Blob s3ResourcePath // Optional: specific S3 resource to use );三个 API 的分工函数返回类型用途wmill.loadS3File(s3object)Uint8Array一次性读取文件全部内容为字节数组wmill.loadS3FileStream(s3object)Blob以流形式加载文件适合大文件处理wmill.writeS3File(target, content, s3ResourcePath?)wmill.S3Object写入文件目标路径传undefined时自动生成可显式指定使用的 S3 资源writeS3File的第三个参数用于在存在多个 S3 资源时指定具体使用哪一个省略时使用默认存储。S3 相关的类型声明与测试可分别在 typescript-client/s3Types.d.ts 与 typescript-client/tests/s3Types.test.ts 中继续深入。八、源码视角Deno 任务在 Worker 中的完整执行链路将以上约定串起来一个 Deno 脚本在 Windmill 中的生命周期如下核心实现见 backend/windmill-worker/src/deno_executor.rs写入与解析handle_deno_job将脚本写入工作目录main.ts并解析TypeScriptAnnotations如是否启用 sandbox随后用windmill_parser_ts解析main及可选的preprocessor签名依赖锁定generate_deno_lock通过deno cache预取依赖并生成lock.json实现跨任务依赖缓存复用Wrapper 生成根据解析出的参数名生成 wrapper 代码完成args.json读取、Datetime转换、BigInt序列化修补与可选的预处理器调用沙箱执行依据全局开关is_sandboxing_enabled或脚本注解进入 nsjail 沙箱隔离执行deno_executor.rs日志通过流式通道回传结果回写main的返回值被序列化并作为任务输出。其中 Worker 还会注入一组 Deno 专属环境变量deno_executor.rsDENO_DIR依赖缓存目录、DENO_AUTH_TOKENS内部 URL 鉴权、BASE_INTERNAL_URL指向 Windmill 内部服务地址import map 也以它为基础imports: { /: {base_internal_url}/api/scripts_u/empty_ts/ }这解释了为什么脚本内可以放心使用windmill-client——其底层调用的内部 API 已由运行时环境打通。九、最佳实践小结运行时选择默认 Bun只有需要 Deno 标准库或deno.landURL 导入时才选 Deno参见 bun.md 的对比与原生脚本建议。入口约定只导出异步main不要手动调用参数类型声明是入参表单与类型转换的依据。凭据注入通过RT.XXX引用资源类型事前查阅生成的rt.d.ts确认字段。平台交互一律走windmill-clientfetch仅用于外部 API。事件接入触发器场景使用preprocessor(event)将原始事件转换为下游入参。S3 存储统一用wmill.S3Object类型与loadS3File/loadS3FileStream/writeS3File三个内置函数。遵循上述约定你的 Deno 脚本即可无缝融入 Windmill 的调度、触发器、流程与资源体系并充分复用 Worker 的依赖缓存、沙箱隔离与平台鉴权能力。【免费下载链接】windmillOpen-source developer platform to power your entire infra and turn scripts into webhooks, workflows and UIs. Fastest workflow engine (13x vs Airflow). Open-source alternative to Retool and Temporal.项目地址: https://gitcode.com/GitHub_Trending/wi/windmill创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表