Directus 沙箱 sandbox() 函数全解析:为测试与开发一键拉起可编程的 API 实例
【免费下载链接】directusThe flexible backend for all your projects 🐰 Turn your DB into a headless CMS, admin panels, or apps with a custom UI, instant APIs, auth & more.项目地址: https://gitcode.com/GitHub_Trending/di/directus
在 Directus 的 tests/sandbox/readme.md 中,官方提供了一套名为@directus/sandbox的工具包:它把“准备数据库、引导 schema、以指定环境变量启动 API”这一整套繁琐流程封装成可复用的函数与 CLI,用于黑盒测试、开发调试与 CI 场景。本文以 TypeDoc 自动生成的 API 参考文档 tests/sandbox/docs/_media/sandbox.md 为核心骨架,结合 sandbox() 的源码实现 与配套的dockercompose 文件、环境变量生成逻辑,系统讲解sandbox()函数的全部参数、返回对象与底层工作流,帮助你掌握如何用几行 TypeScript 代码按需拉起/销毁一套隔离的 Directus 运行环境。
sandbox()是什么:函数签名与定位
@directus/sandbox是 Directus 仓库内用于测试与开发的沙箱工具,提供两种交互方式:CLI 命令行与JS/TS API。其中 JS API 的入口就是本文主角sandbox():
sandbox(database, options?): Promise<Sandbox>在代码层面,它定义于 tests/sandbox/src/sandbox.ts:258,完整的类型约束为:
export type Database = Exclude<DatabaseClient, 'redshift'> | 'maria';即第一个参数必须取自 tests/sandbox/src/sandbox.ts:26 声明的类型;运行时函数还会做一次白名单校验,传入非受支持的值会直接抛出Invalid database provided:
export const databases: Database[] = ['maria', 'cockroachdb', 'mssql', 'mysql', 'oracle', 'postgres', 'sqlite'] as const;从源码结构(tests/sandbox/src/sandbox.ts:258-347)可以推断,sandbox()内部遵循一条顺序固定的启动流水线:build → license → dockerUp → bootstrap → loadSchema → startApi → startApp,任一步骤抛出异常都会触发stop()做全量清理后再向上抛出,保证失败场景下不留残留进程。
参数 database:支持哪些数据库引擎
database为必填参数,可选项与 tests/sandbox/src/index.ts 导出、并在 tests/sandbox/src/cli.ts:8 中作为 CLI 的choices保持一致:
| database 值 | 底层连接 | 默认镜像/版本(见 config.ts) |
|---|---|---|
maria | mysql | MariaDB(DB_VERSION: 11) |
mysql | mysql | MySQL8.4 |
postgres | pg | PostgreSQL18-3.6-alpine |
cockroachdb | cockroachdb | latest-v25.3 |
mssql | mssql | SQL Server2022-latest |
oracle | oracledb | Oracle21-slim-faststart |
sqlite | sqlite3 | 无需 Docker(文件型./test.db) |
各 Docker 化数据库的启动编排定义在 tests/sandbox/src/docker/ 目录下的 compose 文件里,例如postgres.yml、mysql.yml、oracle.yml、cockroachdb.yml等,沙箱启动时会按需读取并执行。
options 详解:从build到hooks的全部开关
options是可选的深度部分覆盖对象(类型上为DeepPartial<Options>,Options完整定义见 tests/sandbox/src/sandbox.ts:28-90)。其默认值由getOptions()使用lodash-es的merge合并生成(tests/sandbox/src/sandbox.ts:112-152)。下面按官方 API 文档 tests/sandbox/docs/_media/sandbox.md 的参数顺序逐项展开。
构建与运行模式:build/dev/watch
build?: boolean:是否每次启动沙箱前从源码重新构建 Directus。默认false。dev?: boolean:以开发模式启动 Directus。与build互斥,源码中通过if (opts.build && !opts.dev)条件保证二者不会同时生效;对应NODE_ENV会被设为development。watch?: boolean:监听源码改动并自动重启 API,适合快速迭代。构建流程会随之联动(buildApi(opts, logger, restartApis)把重启回调传入构建进程)。
API 端口与实例:port/instances/killPorts
port?: string:API 启动端口。默认取8055(process.env.PORT存在时优先,见 tests/sandbox/src/sandbox.ts:115)。getPort()会自动寻找空闲端口。instances?: string:水平扩展的 API 实例数量,默认'1'。当实例数 > 1 时redis会被强制开启(用于缓存同步)。killPorts?: boolean:强制杀掉占用 API 所需端口的所有进程后再启动。
Docker 行为:docker
docker是一个子对象(默认见 tests/sandbox/src/sandbox.ts:42-51):
docker.keep?: boolean:调用stop()时是否保留容器运行。默认false,即停止沙箱时容器也会被回收;置为true可在下次启动时复用已有容器,大幅缩短启动耗时。docker.name?: string:覆盖 Docker 项目名。docker.suffix?: string:为 Docker 项目名追加后缀,可保证多套沙箱互不冲突;该选项存在于源码Options类型中(见 sandbox.ts:49),CLI 侧对应--docker.suffix。docker.basePort?: string:Docker 容器端口分配的下限。CLI 默认使用8100–8200区间(见 tests/sandbox/src/cli.ts:46),端口内存在$PORT、$PORT_LICENSE之类的占位符会被动态替换。
环境变量注入:env
env?: Record<string, string>:以键值对形式追加 API 启动所需的环境变量。在getEnv()的合并顺序中处于较高优先级(见 tests/sandbox/src/config.ts:148-177),可覆盖数据库连接、认证、缓存等配置。
日志辅助:prefix
prefix?: string:为日志加前缀。当同时启动多个沙箱时非常有用,可区分不同实例的输出。
Schema 快照:schema
schema?: string:启动时额外加载一份 schema snapshot(JSON 快照文件路径)。注意一处便捷行为:当传入schema: true时,源码会自动将其改写为'snapshot.json'(tests/sandbox/src/sandbox.ts:113)。其加载发生在bootstrap之后、API 启动之前。
调试与诊断:inspect
inspect?: boolean:是否以调试器模式(--inspect)启动 API,默认true。CLI 中该选项同样默认开启(tests/sandbox/src/cli.ts:14)。
周期导出:export
export?: boolean:每 2 秒导出一份 schema snapshot 与类型定义。源码通过saveSchema(env)返回的setInterval句柄实现,在stop()中会被clearInterval清理(见 sandbox.ts:289 与 sandbox.ts:321)。适合在开发过程中持续观察 schema 变化产物。
附加服务:extras
extras子对象用于开启可选的“周边”容器(默认全部false,定义见 tests/sandbox/src/sandbox.ts:66-78):
| 字段 | 类型 | 作用 | 对应 docker 编排 |
|---|---|---|---|
extras.redis | boolean | 缓存用 Redis;实例数 > 1 时被强制置为true | tests/sandbox/src/docker/redis.yml |
extras.maildev | boolean | SMTP 邮件服务器(开发时拦截邮件) | tests/sandbox/src/docker/maildev.yml |
extras.minio | boolean | S3 兼容对象存储(文件上传测试用) | tests/sandbox/src/docker/minio.yml |
extras.saml | boolean | SAML 认证服务 | tests/sandbox/src/docker/saml.yml |
extras对应的环境变量注入在 config.ts 中集中定义:例如开启minio会注入STORAGE_LOCATIONS: 'minio,local'及完整的 S3 驱动参数;开启saml会注入AUTH_PROVIDERS: 'saml'与 SP/IdP 两段 metadata 证书。
缓存开关:cache
cache?: boolean:是否启用缓存,默认false。该值会映射为环境变量CACHE_ENABLED与REDIS_ENABLED(见 config.ts:154-155)。
其他可用选项(源码补充)
API 文档未逐条列出、但在Options类型与 CLI 中存在的补充选项还包括:
app?: boolean | Port:是否同时以开发模式拉起前端 app 并连接 API(sandbox()中默认在opts.app !== false时启动,见 sandbox.ts:287)。CLI 对应-a, --app [port]。dbVersion?: string:覆盖数据库镜像版本(CLI--db-version),设置后覆盖DB_VERSION(见 config.ts:179-181)。silent?: boolean:除错误外静默全部日志(CLI--silent)。skipSetup?: boolean:跳过初始 admin/owner 的创建;否则默认注入admin@example.com/ 密码pw/ tokenadmin(见 config.ts:157-164)。knex?: boolean:额外打开一条 Knex 连接,通过sandbox.knex直接访问数据库,便于断言库内数据。hooks.beforeApi?:在 bootstrap(与 schema 加载)之后、API 启动之前执行的生命周期钩子,回调上下文携带{ env, logger, knex? }。
返回值:Sandbox对象结构
sandbox()返回Promise<Sandbox>。Sandbox类型别名(见 tests/sandbox/docs/type-aliases/Sandbox.md 与 sandbox.ts:103-110)包含以下成员:
| 成员 | 类型 | 说明 |
|---|---|---|
env | Env | 沙箱实际使用的完整环境变量(含数据库连接、PORT、PUBLIC_URL、admin 凭据等) |
logger | Logger | 当前沙箱的日志器实例 |
apis | [Api, ...Api[]] | 已启动的 API 进程/端口列表,测试代码通过apis[0].port拼接请求地址 |
knex | Knex \| undefined | 开启knex选项时的数据库直连句柄 |
restartApi() | () => Promise<void> | 杀死当前 API 进程并重启。注意实现细节(sandbox.ts:296-316):重启前会重新解析端口——被杀的 API 可能让旧端口短暂处于TIME_WAIT,此时getPort会自动回退到空闲端口,并把新端口同步回opts.port、env.PORT与env.PUBLIC_URL |
stop() | () => Promise<void> | 停止整个沙箱:清理导出定时器、杀掉 build/API/app/license 子进程、销毁 knex 连接,并打印耗时统计 |
apis通过 getter 暴露(sandbox.ts:343-345),保证restartApi()重新赋值后调用方始终拿到最新实例。
最小可运行示例
参照 tests/sandbox/readme.md 中给出的用法,一个完整的起停流程如下:
import { sandbox } from '@directus/sandbox'; const sb = await sandbox('postgres', { dev: true }); // 通过 REST / GraphQL / WebSocket 与实例交互 const result = await fetch(`http://localhost:${sb.apis[0].port}/items/articles`); console.log(await result.json()); // 结束时清理全部资源 await sb.close();若需要直接断言数据库状态,可叠加knex与hooks:
const sb = await sandbox('postgres', { knex: true, hooks: { beforeApi: async ({ env, logger }) => { logger.info(`API 即将监听 ${env.PORT}`); }, }, }); // 直连数据库做数据校验 const rows = await sb.knex('articles').select('*'); await sb.stop();多实例场景:sandboxes()与 CLI
并行拉起多套沙箱
如需同时对多个数据库运行同一套测试,使用sandboxes()(签名见 tests/sandbox/docs/_media/sandboxes.md,实现见 sandbox.ts:173-256)。它接收一个由{ database, options }组成的数组,内部通过Promise.all并发启动,并提供统一的stop()与restartApis():
const multi = await sandboxes([ { database: 'sqlite', options: { schema: 'snapshot.json' } }, { database: 'postgres', options: { env: { DB_PASSWORD: 'secret' } } }, ], { dev: true });值得注意的是,其公共 options 只允许覆盖build、dev、watch三项,各沙箱自身的差异化配置须放在各自的options中(类型定义见 sandbox.ts:168-171)。
与 CLI 的参数映射
CLI(入口 tests/sandbox/src/cli.ts)本质上是对sandbox()的一层薄封装,两者参数一一对应:
sandbox postgres \ --dev \ --port 8055 \ --instances 2 \ --extras redis,minio,maildev \ --env KEY=VALUE \ --schema snapshot.json \ --docker.port 8100 \ --docker.keepCLI 解析后调用sandbox(database, options),其中extras的a,b,c逗号串会被转换为{ a: true, b: true, c: true }(cli.ts:51-57)。CLI 进程会监听SIGINT/SIGTERM,在退出前调用sb.stop()做资源回收(cli.ts:60-69)——因此通过编程方式使用sandbox()时,同样记得在测试收尾或进程退出处显式调用stop()。
底层工作流:一次sandbox()调用做了什么
根据 tests/sandbox/readme.md 的 "Inner workings" 章节并结合源码,一次调用会按序执行以下步骤(依配置不同部分步骤会被跳过):
- 构建 API(可选):开启
build时每次启动都会重新构建 Directus;配合watch可快速迭代。 - 启动 Docker 容器:拉起所需数据库与
extras(redis/minio/saml/maildev 等)容器并等待其健康。若容器仍在运行则直接复用,不重复创建——这正是docker.keep存在的意义。 - 引导数据库(bootstrap):若库尚未引导,补齐 Directus 所需的全部系统表。
- 加载 schema 快照(可选):设置了
schema选项时,在启动前将快照应用到数据库。 - 启动 API:以正确的环境变量启动一个或多个 API 实例。
- 启动 app(可选):开启
app时以开发模式拉起前端并连接 API。
沙箱运行所需的整套环境变量由getEnv()生成(tests/sandbox/src/config.ts:148-201),它内置了一批贴近真实的默认值:SECRET: 'directus-test'、TELEMETRY: 'false'、RATE_LIMITER_ENABLED: 'false'、WEBSOCKETS_ENABLED: 'true'、ACCESS_TOKEN_TTL: '25d'等,并把env参数、PORT/PUBLIC_URL作为最终权威值合入,保证进程实际绑定端口与测试可访问端口始终一致。
何时使用sandbox():适用场景小结
综合官方定位("quickly spinning up and down instances of directus for usage such as testing or development")与仓库内实现,sandbox()最典型的落地场景包括:
- 黑盒 API 测试:在测试套件
beforeAll中拉起sqlite/postgres沙箱,通过真实 HTTP 请求断言/items、认证、GraphQL 与 WebSocket 行为; - 多数据库兼容性验证:利用
sandboxes()在maria、mssql、oracle等不同引擎上重复执行同一套用例(仓库的tests/blackbox与tests/e2e即遵循此思路); - 扩展开发与调试:用
dev+watch+inspect快速验证扩展代码,用app同时预览前端; - schema 演进观察:开启
export周期导出快照,或借助schema加载既有快照还原特定库结构。
需要提醒的是,使用本工具依赖 Docker 环境与仓库内预置的镜像编排文件,sqlite是唯一不依赖 Docker 的数据库选项,适合无容器环境下的快速验证。
【免费下载链接】directusThe flexible backend for all your projects 🐰 Turn your DB into a headless CMS, admin panels, or apps with a custom UI, instant APIs, auth & more.项目地址: https://gitcode.com/GitHub_Trending/di/directus
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考