首页
/ Directus Sandbox 的 sandboxes() 多沙箱并行 API:从函数签名到多数据库 e2e 实践

Directus Sandbox 的 sandboxes() 多沙箱并行 API:从函数签名到多数据库 e2e 实践

2026-09-08 11:38:08作者:凤尚柏Louis

@directus/sandbox 是 Directus 仓库内用于「快速拉起/销毁一套可直接访问的 Directus 实例」的测试工具集(源码位于 tests/sandbox),其入口 API 除单个实例的 sandbox() 外,还包括本文核心的 sandboxes()——一次并行启动多套、可跨不同数据库的 Directus 沙箱。读完本文,你将掌握 sandboxes() 的函数签名与参数语义、Sandboxes 返回值结构、其底层 docker→bootstrap→schema→api 的启动流水线,并能直接参考 tests/e2e/setup/global-setup-all.ts 写出「一套代码跑遍 7 种数据库」的端到端测试基建。

一、什么是 sandboxes():与单实例 sandbox() 的分工

sandbox.ts 通过 tests/sandbox/src/index.ts 对外导出全部类型与函数,其中提供了两个 JS API:

  • sandbox(database, options?):针对单一数据库启动一组 Directus 实例;
  • sandboxes(sandboxOptions, options?):针对一组数据库/多组配置并行启动多套沙箱,适合矩阵式测试与横向对比。

sandboxes() 的独立类型参考页位于 tests/sandbox/docs/_media/sandboxes.md,包内总览见 tests/sandbox/readme.md

二、函数签名逐参数解析

sandboxes() 的类型声明(来自 sandboxes.md)为:

sandboxes(
  sandboxes: SandboxesOptions,
  options?: Partial<Pick<Options, 'watch' | 'build' | 'dev'>>,
): Promise<Sandboxes>

参数 1:sandboxes —— 每套沙箱的规格清单

类型为 SandboxesOptions,即一组 { database, options } 对象的数组:

type SandboxesOptions = {
  database: Database;                                            // 该套沙箱使用的数据库
  options: DeepPartial<Omit<Options, 'build' | 'dev' | 'watch' | 'export'>>; // 该套沙箱各自的启动选项
}[];

其中 Database 由源码 sandbox.ts:158-166databases 常量限定为 'maria' | 'cockroachdb' | 'mssql' | 'mysql' | 'oracle' | 'postgres' | 'sqlite'注意builddevwatchexport 被排除在单套选项之外——它们是「全局级」行为,必须通过第二参数统一控制,避免在多套沙箱间出现状态不一致。传入的数据库若不在白名单内,源码会在启动前直接抛错:

if (!sandboxOptions.every((sandbox) => databases.includes(sandbox.database)))
  throw new Error('Invalid database provided');   // sandbox.ts:177-178

参数 2:options? —— 所有沙箱共享的运行模式

Partial<Pick<Options, 'watch' | 'build' | 'dev'>> 表示只允许覆写三个全局开关,含义(见 Options 类型 与 CLI 帮助文本):

选项 类型 说明
build boolean 每次启动前从源码重新构建 Directus(对应 CLI -b, --build
dev boolean 以开发者模式启动 API;与 build 互斥(对应 -d, --dev
watch boolean 文件变更时自动重启 API,用于快速迭代(对应 -w, --watch

源码 getOptions() 会将默认值与用户传入做深合并,未提供时各默认值为:build/dev/watch=false、端口取 options.port ?? process.env['PORT'] ?? 8055instances='1'app=falseschema=undefined、全部 extras(redis/maildev/minio/saml/license)为 falsecache=falseknex=false。若传入 schema: true,会被归一化为默认快照文件 snapshot.json(仓库根已提供 tests/sandbox/snapshot.json)。

三、返回值:Sandboxes 结构与能力

Promise<Sandboxes> 的具体结构见 Sandboxes 类型,对应源码 sandbox.ts:92-101

type Sandboxes = {
  sandboxes: {
    apis: [Api, ...Api[]];  // 至少一个已启动的 API 进程
    env: Env;               // 该套沙箱生效的完整环境变量(含自动分配的端口)
    logger: Logger;
    knex?: Knex;            // 仅当 options.knex=true 时存在,可直接访问数据库
  }[];
  restartApis(): Promise<void>;  // 重启所有套下的所有 API 进程
  stop(): Promise<void>;         // 统一销毁构建进程、knex、API 与 license server
};

关键语义:

  • sandboxes 数组顺序与传入的 SandboxesOptions 一一对应,可用下标建立「数据库 → env/apis」映射;
  • 每套里的 apis 是元组 [Api, ...Api[]],配合 Options.instances 可实现单库多实例水平扩容(源码注释见 sandbox.ts:52);
  • env 是调试与请求时最常用的对象——sb.env.PUBLIC_URL 即该实例的对外地址,README 示例即用 fetch(sb.env.PUBLIC_URL + '/items/articles') 访问 REST 接口。

四、启动流水线:每套沙箱背后发生了什么

包主文档 readme.md 的 "Inner workings" 一节将单套沙箱的生命周期归纳为若干可裁剪步骤,而 sandboxes()每一套沙箱都会执行相同管线(见 sandbox.ts:207-228):

  1. (可选)重建 Directus:全局 options.build && !options.dev 成立时先执行 buildApi(源码在 tests/sandbox/src/steps 下);
  2. 拉起 docker 容器dockerUp(database, opts, env, logger) 按数据库启动容器并等待其 healthy。若同名容器仍在运行则直接复用而非重建——这正是并发跑多套沙箱时能显著提速的关键;容器编排模板位于 tests/sandbox/src/docker(如 postgres.ymlmysql.ymlredis.yml);
  3. 引导数据库bootstrap(...) 确保系统表齐全;未初始化则自动建表;
  4. 加载 schema 快照:若该套 options.schema 有值,则 loadSchema(...) 在 API 启动前把快照灌入库(同一快照文件可共享给不同数据库,用于验证跨库一致性);
  5. 可选建 knexoptions.knex=true 时用 createDatabase(env, logger) 打开直连句柄,随后会触发 options.hooks.beforeApi?.({ env, logger, knex }) 生命周期钩子(在 API 启动前做数据准备);
  6. 启动 APIstartApi(...) 以正确环境变量拉起一个或多个 API 进程;
  7. 若开启了 extras.license,还会并行拉起 tests/sandbox/src/steps/license.ts 对应的 mock license server,并在 stop() 时一并回收。

多套沙箱彼此以 Promise.all 并行启动,相互独立;日志支持 prefix 前缀区分(源码 sandbox.ts:211),当你在终端同时观察多个数据库沙箱时,前缀能让你一眼分清输出来源。

restartApis 与 stop 的语义

  • restartApis()sandbox.ts:234-240)会先 kill 所有既有 API 进程,再基于每套各自保存的 opts/env 重新 startApi,因此它保留的是各套沙箱的原配置,适合「改完代码/权限后热重启全部实例」;
  • stop()sandbox.ts:242-253)负责完整清理:kill 构建进程 → 逐套 knex.destroy() → kill 各 API → kill license server。测试 suite 结束时务必调用它,否则子进程与 docker 容器会残留。

五、仓库内的真实用法:e2e 多数据库矩阵

sandboxes() 在仓库里最典型的落地场景是端到端测试的全局 setup。以 tests/e2e/setup/global-setup-all.ts 为例:

import { type Database, databases, sandboxes, type Sandboxes } from '@directus/sandbox';

const dbs = databases.map((database, index) => {
  const port = 8000 + index * 100;          // 每库错开端口段
  return {
    database,
    options: {
      prefix: database,                     // 日志前缀便于区分
      port,
      env: { CACHE_SCHEMA: 'false', LICENSE_KEY: 'D0000-...' },
      docker: { port: port + 10, keep: true }, // docker 端口也错开且停止后保留
      extras: { license: true },
      killPorts: true,
    },
  };
});

sb = await sandboxes(dbs);   // 7 种数据库并行拉起

// 将每套 env 按数据库名提供给测试项目
project.provide('envs', Object.fromEntries(
  sb.sandboxes.map((sandbox, index) => [dbs[index]!.database, sandbox.env]),
));
// 对应 teardown 里:await sb.stop();

这段代码展示了 sandboxes() 的完整协作模式:

  • 用源码导出的 databases 常量(见 databases 变量文档)自动生成 7 库矩阵,未来新增数据库无需手改测试文件;
  • 每库用 prefixportdocker.port 隔离,互不干扰;
  • docker.keep: true 让容器在 stop() 后依然存活,下一次运行直接复用容器,只重建 API 进程,把重复测试的冷启动成本降到最低;
  • killPorts 会强制清理目标端口的占用进程(对应 CLI --killPorts);
  • setup 完成后通过 sb.sandboxes[i].env 把各自 PUBLIC_URL 等交给具体用例。

六、CLI 对照:命令行形态的 sandboxes

虽然 sandboxes() 是纯 JS 多实例 API,但若你只需要「以某数据库快速起一套」,可使用同包 CLI(bin.sandbox 指向 dist/cli.js,包清单见 tests/sandbox/package.json):

Usage: sandbox [options] <database>
# 例:以 postgres 起开发者沙箱并开启文件监听
pnpm sandbox postgres --dev --watch

CLI 的 database 参数同样限定为 maria/cockroachdb/mssql/mysql/oracle/postgres/sqlite-i, --instances 对应水平扩容、-e, --extras <extras> 对应 redis/maildev/saml/minio/license 等附加服务、-x/--export 会每 2 秒导出一次 schema 快照。CLI 顶层选项中的多数(如 build/dev/watch/export)即映射为 sandboxes() 第二参数可覆写的全局行为。

七、使用建议与注意事项

  • builddev 互斥:源码中构建仅在 opts.build && !opts.dev 时发生(sandbox.ts:203),日常迭代优先 dev/watch,只有需要验证产物时才用 build
  • 并行度权衡sandboxes() 同时拉起多套数据库 + 多个 API 进程,对内存与 docker 资源消耗显著,请按机器能力裁剪 SandboxesOptions 数组长度(例如仅测试自己关心的 ['postgres', 'sqlite']);
  • 端口规划:多套沙箱必须显式分配不同 portdocker.port,否则会互相抢占;也可留给 getPort 自动探测空闲端口(底层使用 get-port 包);
  • 善用 docker.keep:容器复用让「再次运行同一批测试」几乎只花 API 启动的时间,代价是 stop() 不会清理容器,需要时可用 --docker.keep=false 或手动回收;
  • 清理务必成对:在 vitest teardownafterAll 中调用 await sb.stop(),避免遗留子进程与 license server。

sandboxes() 的价值在于把「多数据库 × 多实例」这套复杂编排压缩成一个 Promise 调用:容器编排、数据库引导、快照灌入、API 启动与统一回收全部交给 tests/sandbox/src/sandbox.ts 的固定管线处理,让测试作者只需关心 { database, options } 的声明式清单——这正是 Directus 仓库能对其 API 兼容的 7 种数据库做矩阵式端到端验证的工程基础。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391