feat(core): Phase 1 — Core Types & Config Parsing #8

Merged
xiaomo merged 7 commits from feat/phase-1-core-types into main 2026-04-22 08:43:37 +00:00
11 changed files with 1658 additions and 46 deletions
+3
View File
@@ -1,5 +1,8 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
"files": {
"ignore": ["**/dist/**"]
},
"organizeImports": {
"enabled": true
},
+38
View File
@@ -40,6 +40,44 @@ class Signal implements ISignal {
| 无继承 | 不用 `extends``implements``abstract` |
| 组合优先 | 用函数组合代替继承层次 |
| 不可变优先 | 用 `Readonly<T>``as const`,避免 mutation |
| 禁用 optional properties | 不用 `?:`,用 `T \| null` 显式标记可空;多个互斥字段用 discriminated union |
### 禁用 Optional Properties
不使用 `?:`,所有可空字段显式标注 `T | null`
```typescript
// ✅ Good — 明确表达"可以为空"
type SenseConfig = {
group: string;
throttle: string | null;
timeout: string | null;
}
// ❌ Bad — optional 隐藏了"缺失"和"空值"的区别
type SenseConfig = {
group: string;
throttle?: string;
timeout?: string;
}
```
当多个字段互斥时,用 discriminated union 代替一堆 optional:
```typescript
// ✅ Good — 编译器保证 sense 和 workflow 不会同时出现
type ReflexConfig =
| { kind: "sense"; sense: string; interval: string | null; on: string[] | null }
| { kind: "workflow"; workflow: string; on: string[] | null }
// ❌ Bad — sense 和 workflow 都 optional,运行时才知道到底填了哪个
type ReflexConfig = {
sense?: string;
workflow?: string;
interval?: string;
on?: string[];
}
```
### 例外
+144 -39
View File
@@ -29,6 +29,26 @@
一个 Sense **怎么算**(compute)和**什么时候算**(reflex)是两个独立的关注点。同一个 Sense 可以被定时触发、被事件触发、被按需查询触发。
### 2.4 Log 是数据资产,不是触发源
系统运行过程中产生的各种记录(reflex 执行记录、workflow 状态变迁、错误日志等)统称为 **Log**。Log 是有价值的数据资产,用于审计、溯源、分析。
**Log 与 Signal 的本质区别:**
- **Signal** — 来自外部世界的感知数据,可以触发 Reflex
- **Log** — 系统内部产出的副作用记录,**不能触发 Reflex**
因果链是单向的:
```
外部世界 → Sense → Signal → Reflex → Action + Log
Reflex 可以读 Log(查询/聚合)
Log 不能触发 Reflex ✗
```
禁止 Log 触发 Reflex 是防止雪崩的关键约束——如果 reflex 执行产生的 log 又能触发 reflex,系统会形成无限循环。Log 是因果链的终点,不是起点。
## 3. 术语表
| 术语 | 隐喻 | 含义 |
@@ -36,6 +56,7 @@
| **Sense** | 感官 | 定义怎么感知——compute 函数,每种 Sense 有自己的数据类型和独立存储 |
| **Reflex** | 反射 | 定义什么时候感知——声明式触发条件 |
| **Signal** | 信号 | Sense compute 返回非 null 时发出的通知,其他 Reflex 可以监听 |
| **Log** | 日志 | 系统内部产出的记录(执行记录、状态变迁、错误等),数据资产,不触发 Reflex |
| **Workflow** | 行动 | 定义怎么做——内含 Moderator(调度)和 Role(执行)|
| **Moderator** | 协调者 | Workflow 内部概念,在 Role 之间递话筒 |
| **Role** | 执行者 | Workflow 内部概念,执行具体动作,有副作用 |
@@ -298,13 +319,13 @@ Signal 和 Thread 是两个独立的循环,单向桥接:
Signal 循环 ──→ ThreadStart ──→ Thread 循环
(无状态,幂等,可合并) (有状态,顺序,Command Event 驱动)
Thread 产出 Signal
Thread 产出 Log
(执行日志,供 retrospection)
```
**Signal 只负责 kickoff Thread**。Thread 启动后,由自己的事件循环驱动——Moderator 递话筒、Role 执行、Command Event 流转。Thread 内部不走 Signal 系统。
**Thread 产出的 Signal 是执行日志**,记录 Thread 的中间状态和最终结果。这些 Signal 可以被其他 Sense 监听用于 retrospection(如统计成功率、平均耗时),但不作为驱动 Workflow 的动力
**Thread 产出的 Log 是执行日志**,记录 Thread 的中间状态和最终结果。这些 Log 可以被 Sense 的 compute 查询用于 retrospection(如统计成功率、平均耗时),但 Log 不能触发 Reflex(见 §2.4)
这保证了两个循环的性质不被污染:
- Signal 循环:无状态、幂等、可合并、可丢弃
@@ -437,50 +458,126 @@ Sense 的运行时属性(`group`、`throttle`、`timeout`)在 `nerve.yaml`
- **timeout**:compute 超时上限(soft timeout),超时后 abort 当前 compute,记录错误 signal
- **grace_period**:soft timeout 后的宽限期(默认 timeout × 3),超过后 hard kill 整个 group worker 并 respawn。防止跑飞的 compute 堵住同 group
### 5.4 Thread 状态持久化与恢复
### 5.4 存储架构
Thread 是状态机。每一步转换之前持久化状态,确保崩溃后可恢复。
系统有两大类持久化数据,全部 append-only:
```typescript
interface ThreadState {
threadId: string
workflowId: string
currentStep: string // 状态机当前节点
context: Record<string, any> // 累积的上下文
history: CommandEvent[] // 已完成的步骤记录
status: 'running' | 'crashed' | 'completed' | 'failed'
updatedAt: number
}
#### Signal 存储
每个 Sense 独立一个 SQLite 文件(见 §8),由 Sense 自行管理 schema。这部分不变。
#### Log 存储
所有 Log 写入统一的 SQLite 文件 `data/logs.db`,单表:
```sql
CREATE TABLE logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source TEXT NOT NULL, -- "reflex", "workflow", "system"
type TEXT NOT NULL, -- "run_start", "run_complete", "error", "state_change"
ref_id TEXT, -- 关联的 reflex name / workflow run_id
payload TEXT, -- JSON
ts INTEGER NOT NULL -- unix ms
);
CREATE INDEX idx_logs_source_type ON logs(source, type);
CREATE INDEX idx_logs_ts ON logs(ts);
CREATE INDEX idx_logs_ref_id ON logs(ref_id);
```
执行流程:
- **统一一张表**,通过 `source + type` 区分 log 来源和类型
- Reflex 可以查询 logs 表(只读),但 log 不能触发 reflex(见 §2.4)
```
moderator 决定下一步 → 持久化状态 → execute role → 写结果 → 下一步
如果这里挂了
恢复时从这一步重试
#### Workflow 状态:事件溯源 + 物化表
Workflow Thread 的状态以 append-only 事件流为 source of truth:
```sql
-- 也在 logs 表中,source = "workflow"
-- type 取值:queued, started, step_complete, completed, failed, crashed
```
恢复流程
当前状态 = 该 run_id 最后一条 log entry。例
```
1. engine 检测到 workflow worker 挂了
2. respawn worker
3. worker 启动时扫描 db,找到 status=running 的 thread
4. 从 currentStep 恢复执行
source=workflow, type=queued, ref_id=run-7, ts=1000
source=workflow, type=started, ref_id=run-7, ts=1001
source=workflow, type=completed, ref_id=run-7, ts=1005
```
恢复要求 Role 的 execute 尽量幂等。非幂等的 Role 可以标记
为避免每次查活跃 workflow 都扫描全表,引擎维护一张 **物化表**,在写 log 的同一事务中 UPSERT
```typescript
roles: [
{ id: 'analyzer', execute: ..., idempotent: true }, // 可安全重试
{ id: 'deployer', execute: ..., idempotent: false }, // 崩溃后标记 crashed,等介入
]
```sql
CREATE TABLE workflow_runs (
run_id TEXT PRIMARY KEY,
workflow TEXT NOT NULL, -- workflow 名
status TEXT NOT NULL, -- 最新状态:queued, started, completed, failed, crashed
ts INTEGER NOT NULL -- 最新状态的时间戳
);
CREATE INDEX idx_workflow_runs_status ON workflow_runs(status);
```
写入流程(同一事务):
```sql
BEGIN;
INSERT INTO logs (source, type, ref_id, payload, ts) VALUES ('workflow', 'started', 'run-7', '{}', 1001);
INSERT OR REPLACE INTO workflow_runs (run_id, workflow, status, ts) VALUES ('run-7', 'alert', 'started', 1001);
COMMIT;
```
查询当前活跃 workflow 变为 O(活跃数):
```sql
SELECT * FROM workflow_runs WHERE status IN ('queued', 'started')
```
物化表是 logs 的派生数据——数据丢失时可从 logs 重建。logs 表仍是 source of truth。
进程重启时从 log 重建内存状态。运行时用内存 materialized view 进一步加速。
#### 冷归档
Engine 定期(cron 或内置 task)将超过 30 天的 log 和 signal 数据导出为按天 JSONL 文件归档:
```
data/
logs.db # 热数据(近 30 天)
archive/
logs/
2026-03-22.jsonl # 冷数据,按天归档
2026-03-23.jsonl
senses/
cpu-usage/
2026-03-22.jsonl
```
导出后从主库 DELETE + VACUUM。冷数据用 grep/jq 即可查询,不需要 SQL。
**水位标记**:归档进度记录在 meta 表中,确保任一步崩溃都能安全恢复:
```sql
CREATE TABLE meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
-- 归档水位:已成功归档到哪一天
-- key = "archived_up_to", value = "2026-03-22"
```
归档流程:
```
1. 读 meta.archived_up_to,确定从哪天开始
2. 导出该天数据到 JSONL(幂等:同一天重复导出会覆盖文件)
3. 同一事务:DELETE 该天数据 + UPDATE meta.archived_up_to
4. VACUUM(可选,非事务内)
```
任何一步崩溃,重启后从水位标记处继续,不会丢数据也不会重复删除。
### 5.5 热更新
主进程 watch `~/.uncaged-nerve/` 文件变化,按类型处理:
@@ -507,9 +604,9 @@ nerve.yaml diff 处理:
| 情况 | 处理 |
|------|------|
| compute 抛异常 | 记录错误 signal,下次触发重试 |
| compute 超时 | soft timeout → abort + 记录错误 signal;grace_period 后 hard kill worker + respawn |
| 存储写入失败 | 记录错误,不发 signal(未成功产出) |
| compute 抛异常 | 写 log(source=system, type=error),下次触发重试 |
| compute 超时 | soft timeout → abort + 写 error log;grace_period 后 hard kill worker + respawn |
| 存储写入失败 | 写 error log,不发 signal(未成功产出) |
| nerve.yaml 语法错误 | daemon 拒绝加载,保持当前配置 |
| sense ts 语法错误 | 该 group worker 加载失败,其他 group 正常 |
| workflow worker 崩溃 | 幂等 thread 自动恢复,非幂等标记 crashed |
@@ -629,10 +726,17 @@ Reflex ──→ Sense ──→ Sense (复合依赖)
workflows/ # post-MVP
cleanup.ts
data/ # ⛔ gitignored
logs.db # 统一 log 存储(append-only)
senses/
cpu-usage.db # 每个 sense 独立的 sqlite
disk-usage.db
active-tasks.db
archive/ # 冷归档(>30天)
logs/
2026-03-22.jsonl
senses/
cpu-usage/
2026-03-22.jsonl
blobs/ # CAS blob store,sha256 寻址
ab/
cd1234...
@@ -665,7 +769,8 @@ Reflex ──→ Sense ──→ Sense (复合依赖)
1. **Sense 是唯一的一等公民** — 原始采样和派生计算统一为 Sense,Sense 不知道下游
2. **计算与触发解耦** — compute 不知道自己什么时候被调用
3. **Reflex 是 Event Mesh** — 所有事件路由都声明在 Reflex 中,Sense 和 Workflow 之间无直连
4. **存储去中心化** — 每个 Sense 自管存储,没有统一 Event Store
5. **不删除只关停**历史不可变,生命周期通过 enabled 控制
6. **Workflow 是可选扩展** — MVP 不需要,后续按需加入
7. **引擎极简** — 只做调度,不做业务逻辑
4. **Log 是终点不是起点** — Log 是数据资产,Reflex 可读但不被 Log 触发,防止雪崩
5. **存储去中心化**每个 Sense 自管存储,Log 统一一张表,全部 append-only
6. **不删除只关停** — 历史不可变,生命周期通过 enabled 控制
7. **Workflow 是可选扩展** — MVP 不需要,后续按需加入
8. **引擎极简** — 只做调度,不做业务逻辑
+8 -1
View File
@@ -6,6 +6,13 @@
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsup"
"build": "tsup",
"test": "vitest run"
},
"dependencies": {
"yaml": "^2.8.3"
},
"devDependencies": {
"vitest": "^4.1.5"
}
}
+382
View File
@@ -0,0 +1,382 @@
import { describe, expect, it } from "vitest";
import { parseNerveConfig } from "../config.js";
const VALID_CONFIG = `
senses:
cpu:
group: system
throttle: 5s
memory:
group: system
timeout: 10s
grace_period: 3s
reflexes:
- sense: cpu
interval: 30s
- sense: memory
on:
- high_usage
- workflow: alert
on:
- cpu
workflows:
alert:
concurrency: 2
overflow: queue
max_queue: 10
`;
describe("parseNerveConfig", () => {
describe("valid configs", () => {
it("parses a full valid config", () => {
const result = parseNerveConfig(VALID_CONFIG);
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.value.senses.cpu).toEqual({
group: "system",
throttle: 5000,
timeout: null,
gracePeriod: null,
});
expect(result.value.senses.memory).toEqual({
group: "system",
throttle: null,
timeout: 10_000,
gracePeriod: 3000,
});
expect(result.value.reflexes).toHaveLength(3);
expect(result.value.reflexes[0]).toEqual({
kind: "sense",
sense: "cpu",
interval: 30_000,
on: null,
});
expect(result.value.reflexes[1]).toEqual({
kind: "sense",
sense: "memory",
interval: null,
on: ["high_usage"],
});
expect(result.value.reflexes[2]).toEqual({
kind: "workflow",
workflow: "alert",
on: ["cpu"],
});
expect(result.value.workflows?.alert).toEqual({
concurrency: 2,
overflow: "queue",
maxQueue: 10,
});
});
it("parses config with empty reflexes array", () => {
const yaml = `
senses:
cpu:
group: system
reflexes: []
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.value.reflexes).toEqual([]);
});
it("parses config without workflows section", () => {
const yaml = `
senses:
cpu:
group: system
reflexes:
- sense: cpu
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.value.workflows).toBeNull();
});
it("sense config has null for omitted throttle/timeout/gracePeriod", () => {
const yaml = `
senses:
net:
group: network
reflexes: []
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.value.senses.net).toEqual({
group: "network",
throttle: null,
timeout: null,
gracePeriod: null,
});
});
it("accepts all valid duration suffixes (s, m, h)", () => {
const yaml = `
senses:
a:
group: g
throttle: 5s
b:
group: g
throttle: 10m
c:
group: g
timeout: 1h
reflexes: []
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(true);
});
it("overflow: drop produces DropOverflowConfig (no maxQueue)", () => {
const yaml = `
senses:
cpu:
group: system
reflexes: []
workflows:
alert:
concurrency: 1
overflow: drop
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.value.workflows?.alert).toEqual({
concurrency: 1,
overflow: "drop",
});
expect("maxQueue" in (result.value.workflows?.alert ?? {})).toBe(false);
});
it("overflow: queue defaults maxQueue to 100", () => {
const yaml = `
senses:
cpu:
group: system
reflexes: []
workflows:
alert:
concurrency: 1
overflow: queue
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.value.workflows?.alert).toEqual({
concurrency: 1,
overflow: "queue",
maxQueue: 100,
});
});
});
describe("invalid configs", () => {
it("returns error on bad YAML syntax", () => {
const result = parseNerveConfig("senses: [\\nunclosed");
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error.message).toMatch(/YAML parse error/);
});
it("returns error when reflex references a non-existent sense", () => {
const yaml = `
senses:
cpu:
group: system
reflexes:
- sense: disk
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error.message).toMatch(/disk.*not found in senses/);
});
it("returns error when workflow reflex references a non-existent workflow", () => {
const yaml = `
senses:
cpu:
group: system
reflexes:
- workflow: missing_wf
on:
- cpu
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error.message).toMatch(/missing_wf.*not found in workflows/);
});
it("returns error when workflow reflex references non-existent workflow (with workflows defined)", () => {
const yaml = `
senses:
cpu:
group: system
reflexes:
- workflow: unknown
on:
- cpu
workflows:
alert:
concurrency: 1
overflow: drop
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error.message).toMatch(/unknown.*not found in workflows/);
});
it("returns error for invalid throttle format", () => {
const yaml = `
senses:
cpu:
group: system
throttle: 5sec
reflexes: []
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error.message).toMatch(/throttle.*invalid duration/);
});
it("returns error for invalid timeout format", () => {
const yaml = `
senses:
cpu:
group: system
timeout: two-minutes
reflexes: []
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error.message).toMatch(/timeout.*invalid duration/);
});
it("returns error for invalid interval format", () => {
const yaml = `
senses:
cpu:
group: system
reflexes:
- sense: cpu
interval: 30seconds
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error.message).toMatch(/interval.*invalid duration/);
});
it("returns error when senses is missing", () => {
const yaml = `
reflexes: []
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error.message).toMatch(/senses/);
});
it("returns error when reflexes is missing", () => {
const yaml = `
senses:
cpu:
group: system
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error.message).toMatch(/reflexes/);
});
it("returns error for invalid group name", () => {
const yaml = `
senses:
cpu:
group: "my group!"
reflexes: []
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error.message).toMatch(/group.*invalid name/);
});
it("returns error for invalid workflow overflow value", () => {
const yaml = `
senses:
cpu:
group: system
reflexes: []
workflows:
alert:
concurrency: 1
overflow: skip
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error.message).toMatch(/overflow.*"drop" or "queue"/);
});
it("returns error when max_queue used with overflow: drop", () => {
const yaml = `
senses:
cpu:
group: system
reflexes: []
workflows:
alert:
concurrency: 1
overflow: drop
max_queue: 10
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error.message).toMatch(/max_queue.*not allowed.*drop/);
});
it("returns error when reflex has both sense and workflow", () => {
const yaml = `
senses:
cpu:
group: system
reflexes:
- sense: cpu
workflow: alert
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error.message).toMatch(/cannot have both/);
});
it("returns error when reflex has neither sense nor workflow", () => {
const yaml = `
senses:
cpu:
group: system
reflexes:
- interval: 10s
`;
const result = parseNerveConfig(yaml);
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error.message).toMatch(/must have either/);
});
});
});
+312
View File
@@ -0,0 +1,312 @@
import { parse } from "yaml";
import type { Result } from "./result.js";
import { err, ok } from "./result.js";
import type { NerveConfig, ReflexConfig, SenseConfig, WorkflowConfig } from "./types.js";
const DURATION_RE = /^(\d+)([smh])$/;
const DURATION_MULTIPLIERS: Record<string, number> = {
s: 1_000,
m: 60_000,
h: 3_600_000,
};
function parseDurationToMs(value: string): number | null {
const match = DURATION_RE.exec(value);
if (!match) return null;
return Number(match[1]) * DURATION_MULTIPLIERS[match[2]];
}
function isValidGroupName(value: string): boolean {
return /^[a-zA-Z0-9_-]+$/.test(value);
}
function parseDurationField(field: unknown, label: string): Result<number | null> {
if (field === undefined || field === null) return ok(null);
if (typeof field !== "string") {
return err(
new Error(`${label}: invalid duration "${field}" (expected e.g. "5s", "10m", "1h")`),
);
}
const ms = parseDurationToMs(field);
if (ms === null) {
return err(
new Error(`${label}: invalid duration "${field}" (expected e.g. "5s", "10m", "1h")`),
);
}
return ok(ms);
}
function validateSenseConfig(name: string, raw: unknown): Result<SenseConfig> {
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
return err(new Error(`senses.${name}: must be an object`));
}
const obj = raw as Record<string, unknown>;
if (typeof obj.group !== "string" || obj.group.trim() === "") {
return err(new Error(`senses.${name}.group: required string`));
}
if (!isValidGroupName(obj.group)) {
return err(
new Error(
`senses.${name}.group: invalid name "${obj.group}" (only alphanumeric, underscore, hyphen allowed)`,
),
);
}
const throttleResult = parseDurationField(obj.throttle, `senses.${name}.throttle`);
if (!throttleResult.ok) return throttleResult;
const timeoutResult = parseDurationField(obj.timeout, `senses.${name}.timeout`);
if (!timeoutResult.ok) return timeoutResult;
const graceResult = parseDurationField(obj.grace_period, `senses.${name}.grace_period`);
if (!graceResult.ok) return graceResult;
return ok({
group: obj.group,
throttle: throttleResult.value,
timeout: timeoutResult.value,
gracePeriod: graceResult.value,
});
}
function parseOnField(index: number, obj: Record<string, unknown>): Result<string[] | null> {
if (obj.on === undefined || obj.on === null) return ok(null);
if (!Array.isArray(obj.on) || !obj.on.every((item) => typeof item === "string")) {
return err(new Error(`reflexes[${index}].on: must be an array of strings`));
}
return ok(obj.on as string[]);
}
function parseSenseReflex(
index: number,
obj: Record<string, unknown>,
senseNames: Set<string>,
on: string[] | null,
): Result<ReflexConfig> {
if (typeof obj.sense !== "string") {
return err(new Error(`reflexes[${index}].sense: must be a string`));
}
if (!senseNames.has(obj.sense)) {
return err(new Error(`reflexes[${index}].sense: "${obj.sense}" not found in senses`));
}
const intervalResult = parseDurationField(obj.interval, `reflexes[${index}].interval`);
if (!intervalResult.ok) return intervalResult;
return ok({
kind: "sense" as const,
sense: obj.sense,
interval: intervalResult.value,
on,
Review

Sense reflex 校验了 senseNames.has(obj.sense),但 workflow reflex 没有校验 workflow name 是否定义。建议加对称校验。

Sense reflex 校验了 `senseNames.has(obj.sense)`,但 workflow reflex 没有校验 workflow name 是否定义。建议加对称校验。
});
}
function parseWorkflowReflex(
index: number,
obj: Record<string, unknown>,
on: string[] | null,
): Result<ReflexConfig> {
if (typeof obj.workflow !== "string") {
return err(new Error(`reflexes[${index}].workflow: must be a string`));
}
if (obj.interval !== undefined) {
return err(
new Error(`reflexes[${index}]: workflow reflex does not support "interval" (use "on")`),
);
}
return ok({
kind: "workflow" as const,
workflow: obj.workflow,
on,
});
}
function validateReflexConfig(
index: number,
raw: unknown,
senseNames: Set<string>,
): Result<ReflexConfig> {
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
return err(new Error(`reflexes[${index}]: must be an object`));
}
const obj = raw as Record<string, unknown>;
const hasSense = obj.sense !== undefined;
const hasWorkflow = obj.workflow !== undefined;
if (hasSense && hasWorkflow) {
return err(new Error(`reflexes[${index}]: cannot have both "sense" and "workflow"`));
}
if (!hasSense && !hasWorkflow) {
return err(new Error(`reflexes[${index}]: must have either "sense" or "workflow"`));
}
const onResult = parseOnField(index, obj);
if (!onResult.ok) return onResult;
if (hasSense) {
return parseSenseReflex(index, obj, senseNames, onResult.value);
}
return parseWorkflowReflex(index, obj, onResult.value);
}
function validateWorkflowConfig(name: string, raw: unknown): Result<WorkflowConfig> {
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
return err(new Error(`workflows.${name}: must be an object`));
}
const obj = raw as Record<string, unknown>;
if (
typeof obj.concurrency !== "number" ||
!Number.isInteger(obj.concurrency) ||
obj.concurrency < 1
) {
return err(new Error(`workflows.${name}.concurrency: must be a positive integer`));
}
if (obj.overflow !== "drop" && obj.overflow !== "queue") {
Outdated
Review

RFC 说 overflow: queuemax_queue 默认 100。建议:

const maxQueue = obj.max_queue ?? (obj.overflow === "queue" ? 100 : null);
RFC 说 `overflow: queue` 时 `max_queue` 默认 100。建议: ```typescript const maxQueue = obj.max_queue ?? (obj.overflow === "queue" ? 100 : null); ```
return err(new Error(`workflows.${name}.overflow: must be "drop" or "queue"`));
}
if (obj.overflow === "drop") {
if (obj.max_queue !== undefined && obj.max_queue !== null) {
return err(new Error(`workflows.${name}: max_queue is not allowed with overflow "drop"`));
}
return ok({
concurrency: obj.concurrency,
overflow: "drop" as const,
});
}
// overflow: "queue"
let maxQueue = 100; // default
if (obj.max_queue !== undefined && obj.max_queue !== null) {
if (
typeof obj.max_queue !== "number" ||
!Number.isInteger(obj.max_queue) ||
obj.max_queue < 1
) {
return err(new Error(`workflows.${name}.max_queue: must be a positive integer`));
}
maxQueue = obj.max_queue;
}
return ok({
concurrency: obj.concurrency,
overflow: "queue" as const,
maxQueue,
});
}
function parseSenses(
obj: Record<string, unknown>,
): Result<{ senses: Record<string, SenseConfig>; senseNames: Set<string> }> {
if (obj.senses === null || typeof obj.senses !== "object" || Array.isArray(obj.senses)) {
return err(new Error("senses: required object"));
}
const sensesRaw = obj.senses as Record<string, unknown>;
const senses: Record<string, SenseConfig> = {};
const senseNames = new Set(Object.keys(sensesRaw));
for (const [name, senseRaw] of Object.entries(sensesRaw)) {
const result = validateSenseConfig(name, senseRaw);
if (!result.ok) return result;
senses[name] = result.value;
}
return ok({ senses, senseNames });
}
function parseReflexes(
obj: Record<string, unknown>,
senseNames: Set<string>,
): Result<ReflexConfig[]> {
if (!Array.isArray(obj.reflexes)) {
return err(new Error("reflexes: required array"));
}
const reflexes: ReflexConfig[] = [];
for (let i = 0; i < obj.reflexes.length; i++) {
const result = validateReflexConfig(i, obj.reflexes[i], senseNames);
if (!result.ok) return result;
reflexes.push(result.value);
}
return ok(reflexes);
}
function parseWorkflows(
obj: Record<string, unknown>,
): Result<Record<string, WorkflowConfig> | null> {
if (obj.workflows === undefined || obj.workflows === null) return ok(null);
if (typeof obj.workflows !== "object" || Array.isArray(obj.workflows)) {
return err(new Error("workflows: must be an object if provided"));
}
const workflowsRaw = obj.workflows as Record<string, unknown>;
const workflows: Record<string, WorkflowConfig> = {};
for (const [name, wfRaw] of Object.entries(workflowsRaw)) {
const result = validateWorkflowConfig(name, wfRaw);
if (!result.ok) return result;
workflows[name] = result.value;
}
return ok(workflows);
}
export function parseNerveConfig(raw: string): Result<NerveConfig> {
let parsed: unknown;
try {
parsed = parse(raw);
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
return err(new Error(`YAML parse error: ${message}`));
}
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
return err(new Error("Config must be a YAML object"));
}
const obj = parsed as Record<string, unknown>;
const sensesResult = parseSenses(obj);
if (!sensesResult.ok) return sensesResult;
const { senses, senseNames } = sensesResult.value;
const reflexesResult = parseReflexes(obj, senseNames);
if (!reflexesResult.ok) return reflexesResult;
const workflowsResult = parseWorkflows(obj);
if (!workflowsResult.ok) return workflowsResult;
// Cross-validate: workflow reflexes must reference defined workflows
const workflowNames = new Set(
workflowsResult.value ? Object.keys(workflowsResult.value) : [],
);
for (let i = 0; i < reflexesResult.value.length; i++) {
const reflex = reflexesResult.value[i];
if (reflex.kind === "workflow" && !workflowNames.has(reflex.workflow)) {
return err(
new Error(
`reflexes[${i}].workflow: "${reflex.workflow}" not found in workflows`,
),
);
}
}
return ok({
senses,
reflexes: reflexesResult.value,
workflows: workflowsResult.value,
});
}
+14 -1
View File
@@ -1 +1,14 @@
// TODO: implement
export type {
Signal,
SenseConfig,
SenseReflexConfig,
WorkflowReflexConfig,
ReflexConfig,
DropOverflowConfig,
QueueOverflowConfig,
WorkflowConfig,
NerveConfig,
} from "./types.js";
export type { Result } from "./result.js";
export { ok, err } from "./result.js";
export { parseNerveConfig } from "./config.js";
+9
View File
@@ -0,0 +1,9 @@
export type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
export function ok<T>(value: T): Result<T, never> {
return { ok: true, value };
}
export function err<E = Error>(error: E): Result<never, E> {
return { ok: false, error };
}
+47
View File
@@ -0,0 +1,47 @@
export type Signal = {
id: number;
senseId: string;
payload: unknown;
ts: number;
};
export type SenseConfig = {
group: string;
throttle: number | null;
timeout: number | null;
gracePeriod: number | null;
Outdated
Review

RFC §5.3 定义了 grace_period(soft timeout 后 hard kill worker),这里缺 gracePeriod: number | null。如果 Phase 1 不实现可以加个 // TODO: grace_period (§5.3) 注释。

RFC §5.3 定义了 `grace_period`(soft timeout 后 hard kill worker),这里缺 `gracePeriod: number | null`。如果 Phase 1 不实现可以加个 `// TODO: grace_period (§5.3)` 注释。
};
export type SenseReflexConfig = {
kind: "sense";
sense: string;
interval: number | null;
on: string[] | null;
};
export type WorkflowReflexConfig = {
kind: "workflow";
workflow: string;
on: string[] | null;
};
export type ReflexConfig = SenseReflexConfig | WorkflowReflexConfig;
export type DropOverflowConfig = {
concurrency: number;
overflow: "drop";
};
export type QueueOverflowConfig = {
concurrency: number;
overflow: "queue";
maxQueue: number;
};
export type WorkflowConfig = DropOverflowConfig | QueueOverflowConfig;
export type NerveConfig = {
senses: Record<string, SenseConfig>;
reflexes: ReflexConfig[];
workflows: Record<string, WorkflowConfig> | null;
};
+699 -5
View File
@@ -13,14 +13,22 @@ importers:
version: 1.9.4
tsup:
specifier: ^8.0.0
version: 8.5.1(typescript@5.9.3)
version: 8.5.1(postcss@8.5.10)(typescript@5.9.3)(yaml@2.8.3)
typescript:
specifier: ^5.5.0
version: 5.9.3
packages/cli: {}
packages/core: {}
packages/core:
dependencies:
yaml:
specifier: ^2.8.3
version: 2.8.3
devDependencies:
vitest:
specifier: ^4.1.5
version: 4.1.5(vite@8.0.9(esbuild@0.27.7)(yaml@2.8.3))
packages/daemon: {}
@@ -83,6 +91,15 @@ packages:
cpu: [x64]
os: [win32]
'@emnapi/core@1.9.2':
resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==}
'@emnapi/runtime@1.9.2':
resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==}
'@emnapi/wasi-threads@1.2.1':
resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==}
'@esbuild/aix-ppc64@0.27.7':
resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==}
engines: {node: '>=18'}
@@ -252,6 +269,113 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@napi-rs/wasm-runtime@1.1.4':
resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==}
peerDependencies:
'@emnapi/core': ^1.7.1
'@emnapi/runtime': ^1.7.1
'@oxc-project/types@0.126.0':
resolution: {integrity: sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==}
'@rolldown/binding-android-arm64@1.0.0-rc.16':
resolution: {integrity: sha512-rhY3k7Bsae9qQfOtph2Pm2jZEA+s8Gmjoz4hhmx70K9iMQ/ddeae+xhRQcM5IuVx5ry1+bGfkvMn7D6MJggVSA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
'@rolldown/binding-darwin-arm64@1.0.0-rc.16':
resolution: {integrity: sha512-rNz0yK078yrNn3DrdgN+PKiMOW8HfQ92jQiXxwX8yW899ayV00MLVdaCNeVBhG/TbH3ouYVObo8/yrkiectkcQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
'@rolldown/binding-darwin-x64@1.0.0-rc.16':
resolution: {integrity: sha512-r/OmdR00HmD4i79Z//xO06uEPOq5hRXdhw7nzkxQxwSavs3PSHa1ijntdpOiZ2mzOQ3fVVu8C1M19FoNM+dMUQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
'@rolldown/binding-freebsd-x64@1.0.0-rc.16':
resolution: {integrity: sha512-KcRE5w8h0OnjUatG8pldyD14/CQ5Phs1oxfR+3pKDjboHRo9+MkqQaiIZlZRpsxC15paeXme/I127tUa9TXJ6g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.16':
resolution: {integrity: sha512-bT0guA1bpxEJ/ZhTRniQf7rNF8ybvXOuWbNIeLABaV5NGjx4EtOWBTSRGWFU9ZWVkPOZ+HNFP8RMcBokBiZ0Kg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@rolldown/binding-linux-arm64-gnu@1.0.0-rc.16':
resolution: {integrity: sha512-+tHktCHWV8BDQSjemUqm/Jl/TPk3QObCTIjmdDy/nlupcujZghmKK2962LYrqFpWu+ai01AN/REOH3NEpqvYQg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.16':
resolution: {integrity: sha512-3fPzdREH806oRLxpTWW1Gt4tQHs0TitZFOECB2xzCFLPKnSOy90gwA7P29cksYilFO6XVRY1kzga0cL2nRjKPg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.16':
resolution: {integrity: sha512-EKwI1tSrLs7YVw+JPJT/G2dJQ1jl9qlTTTEG0V2Ok/RdOenRfBw2PQdLPyjhIu58ocdBfP7vIRN/pvMsPxs/AQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-s390x-gnu@1.0.0-rc.16':
resolution: {integrity: sha512-Uknladnb3Sxqu6SEcqBldQyJUpk8NleooZEc0MbRBJ4inEhRYWZX0NJu12vNf2mqAq7gsofAxHrGghiUYjhaLQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.16':
resolution: {integrity: sha512-FIb8+uG49sZBtLTn+zt1AJ20TqVcqWeSIyoVt0or7uAWesgKaHbiBh6OpA/k9v0LTt+PTrb1Lao133kP4uVxkg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-musl@1.0.0-rc.16':
resolution: {integrity: sha512-RuERhF9/EgWxZEXYWCOaViUWHIboceK4/ivdtQ3R0T44NjLkIIlGIAVAuCddFxsZ7vnRHtNQUrt2vR2n2slB2w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@rolldown/binding-openharmony-arm64@1.0.0-rc.16':
resolution: {integrity: sha512-mXcXnvd9GpazCxeUCCnZ2+YF7nut+ZOEbE4GtaiPtyY6AkhZWbK70y1KK3j+RDhjVq5+U8FySkKRb/+w0EeUwA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
'@rolldown/binding-wasm32-wasi@1.0.0-rc.16':
resolution: {integrity: sha512-3Q2KQxnC8IJOLqXmUMoYwyIPZU9hzRbnHaoV3Euz+VVnjZKcY8ktnNP8T9R4/GGQtb27C/UYKABxesKWb8lsvQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [wasm32]
'@rolldown/binding-win32-arm64-msvc@1.0.0-rc.16':
resolution: {integrity: sha512-tj7XRemQcOcFwv7qhpUxMTBbI5mWMlE4c1Omhg5+h8GuLXzyj8HviYgR+bB2DMDgRqUE+jiDleqSCRjx4aYk/Q==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
'@rolldown/binding-win32-x64-msvc@1.0.0-rc.16':
resolution: {integrity: sha512-PH5DRZT+F4f2PTXRXR8uJxnBq2po/xFtddyabTJVJs/ZYVHqXPEgNIr35IHTEa6bpa0Q8Awg+ymkTaGnKITw4g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
'@rolldown/pluginutils@1.0.0-rc.16':
resolution: {integrity: sha512-45+YtqxLYKDWQouLKCrpIZhke+nXxhsw+qAHVzHDVwttyBlHNBVs2K25rDXrZzhpTp9w1FlAlvweV1H++fdZoA==}
'@rollup/rollup-android-arm-eabi@4.60.2':
resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==}
cpu: [arm]
@@ -390,9 +514,50 @@ packages:
cpu: [x64]
os: [win32]
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
'@tybys/wasm-util@0.10.1':
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
'@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@vitest/expect@4.1.5':
resolution: {integrity: sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==}
'@vitest/mocker@4.1.5':
resolution: {integrity: sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==}
peerDependencies:
msw: ^2.4.9
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
msw:
optional: true
vite:
optional: true
'@vitest/pretty-format@4.1.5':
resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==}
'@vitest/runner@4.1.5':
resolution: {integrity: sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==}
'@vitest/snapshot@4.1.5':
resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==}
'@vitest/spy@4.1.5':
resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==}
'@vitest/utils@4.1.5':
resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==}
acorn@8.16.0:
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
engines: {node: '>=0.4.0'}
@@ -401,6 +566,10 @@ packages:
any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
bundle-require@5.1.0:
resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -411,6 +580,10 @@ packages:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
chai@6.2.2:
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
engines: {node: '>=18'}
chokidar@4.0.3:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
@@ -426,6 +599,9 @@ packages:
resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
engines: {node: ^14.18.0 || >=16.10.0}
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
@@ -435,11 +611,25 @@ packages:
supports-color:
optional: true
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
es-module-lexer@2.0.0:
resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==}
esbuild@0.27.7:
resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==}
engines: {node: '>=18'}
hasBin: true
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
expect-type@1.3.0:
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
engines: {node: '>=12.0.0'}
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
@@ -461,6 +651,80 @@ packages:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
lightningcss-android-arm64@1.32.0:
resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [android]
lightningcss-darwin-arm64@1.32.0:
resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [darwin]
lightningcss-darwin-x64@1.32.0:
resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [darwin]
lightningcss-freebsd-x64@1.32.0:
resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [freebsd]
lightningcss-linux-arm-gnueabihf@1.32.0:
resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
engines: {node: '>= 12.0.0'}
cpu: [arm]
os: [linux]
lightningcss-linux-arm64-gnu@1.32.0:
resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
lightningcss-linux-arm64-musl@1.32.0:
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
lightningcss-linux-x64-gnu@1.32.0:
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
lightningcss-linux-x64-musl@1.32.0:
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
lightningcss-win32-arm64-msvc@1.32.0:
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [win32]
lightningcss-win32-x64-msvc@1.32.0:
resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [win32]
lightningcss@1.32.0:
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
engines: {node: '>= 12.0.0'}
lilconfig@3.1.3:
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
engines: {node: '>=14'}
@@ -484,10 +748,18 @@ packages:
mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
obug@2.1.1:
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
@@ -523,6 +795,10 @@ packages:
yaml:
optional: true
postcss@8.5.10:
resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==}
engines: {node: ^10 || ^12 || >=14}
readdirp@4.1.2:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
@@ -531,15 +807,33 @@ packages:
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
engines: {node: '>=8'}
rolldown@1.0.0-rc.16:
resolution: {integrity: sha512-rzi5WqKzEZw3SooTt7cgm4eqIoujPIyGcJNGFL7iPEuajQw7vxMHUkXylu4/vhCkJGXsgRmxqMKXUpT6FEgl0g==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
rollup@4.60.2:
resolution: {integrity: sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
source-map@0.7.6:
resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
engines: {node: '>= 12'}
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
std-env@4.1.0:
resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==}
sucrase@3.35.1:
resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==}
engines: {node: '>=16 || 14 >=14.17'}
@@ -552,13 +846,24 @@ packages:
thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
tinyexec@1.1.1:
resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==}
engines: {node: '>=18'}
tinyglobby@0.2.16:
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
engines: {node: '>=12.0.0'}
tinyrainbow@3.1.0:
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
engines: {node: '>=14.0.0'}
tree-kill@1.2.2:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
@@ -566,6 +871,9 @@ packages:
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
tsup@8.5.1:
resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==}
engines: {node: '>=18'}
@@ -593,6 +901,100 @@ packages:
ufo@1.6.3:
resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
vite@8.0.9:
resolution: {integrity: sha512-t7g7GVRpMXjNpa67HaVWI/8BWtdVIQPCL2WoozXXA7LBGEFK4AkkKkHx2hAQf5x1GZSlcmEDPkVLSGahxnEEZw==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
'@types/node': ^20.19.0 || >=22.12.0
'@vitejs/devtools': ^0.1.0
esbuild: ^0.27.0 || ^0.28.0
jiti: '>=1.21.0'
less: ^4.0.0
sass: ^1.70.0
sass-embedded: ^1.70.0
stylus: '>=0.54.8'
sugarss: ^5.0.0
terser: ^5.16.0
tsx: ^4.8.1
yaml: ^2.4.2
peerDependenciesMeta:
'@types/node':
optional: true
'@vitejs/devtools':
optional: true
esbuild:
optional: true
jiti:
optional: true
less:
optional: true
sass:
optional: true
sass-embedded:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
tsx:
optional: true
yaml:
optional: true
vitest@4.1.5:
resolution: {integrity: sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==}
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@opentelemetry/api': ^1.9.0
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
'@vitest/browser-playwright': 4.1.5
'@vitest/browser-preview': 4.1.5
'@vitest/browser-webdriverio': 4.1.5
'@vitest/coverage-istanbul': 4.1.5
'@vitest/coverage-v8': 4.1.5
'@vitest/ui': 4.1.5
happy-dom: '*'
jsdom: '*'
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
'@opentelemetry/api':
optional: true
'@types/node':
optional: true
'@vitest/browser-playwright':
optional: true
'@vitest/browser-preview':
optional: true
'@vitest/browser-webdriverio':
optional: true
'@vitest/coverage-istanbul':
optional: true
'@vitest/coverage-v8':
optional: true
'@vitest/ui':
optional: true
happy-dom:
optional: true
jsdom:
optional: true
why-is-node-running@2.3.0:
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
engines: {node: '>=8'}
hasBin: true
yaml@2.8.3:
resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==}
engines: {node: '>= 14.6'}
hasBin: true
snapshots:
'@biomejs/biome@1.9.4':
@@ -630,6 +1032,22 @@ snapshots:
'@biomejs/cli-win32-x64@1.9.4':
optional: true
'@emnapi/core@1.9.2':
dependencies:
'@emnapi/wasi-threads': 1.2.1
tslib: 2.8.1
optional: true
'@emnapi/runtime@1.9.2':
dependencies:
tslib: 2.8.1
optional: true
'@emnapi/wasi-threads@1.2.1':
dependencies:
tslib: 2.8.1
optional: true
'@esbuild/aix-ppc64@0.27.7':
optional: true
@@ -722,6 +1140,66 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)':
dependencies:
'@emnapi/core': 1.9.2
'@emnapi/runtime': 1.9.2
'@tybys/wasm-util': 0.10.1
optional: true
'@oxc-project/types@0.126.0': {}
'@rolldown/binding-android-arm64@1.0.0-rc.16':
optional: true
'@rolldown/binding-darwin-arm64@1.0.0-rc.16':
optional: true
'@rolldown/binding-darwin-x64@1.0.0-rc.16':
optional: true
'@rolldown/binding-freebsd-x64@1.0.0-rc.16':
optional: true
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.16':
optional: true
'@rolldown/binding-linux-arm64-gnu@1.0.0-rc.16':
optional: true
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.16':
optional: true
'@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.16':
optional: true
'@rolldown/binding-linux-s390x-gnu@1.0.0-rc.16':
optional: true
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.16':
optional: true
'@rolldown/binding-linux-x64-musl@1.0.0-rc.16':
optional: true
'@rolldown/binding-openharmony-arm64@1.0.0-rc.16':
optional: true
'@rolldown/binding-wasm32-wasi@1.0.0-rc.16':
dependencies:
'@emnapi/core': 1.9.2
'@emnapi/runtime': 1.9.2
'@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
optional: true
'@rolldown/binding-win32-arm64-msvc@1.0.0-rc.16':
optional: true
'@rolldown/binding-win32-x64-msvc@1.0.0-rc.16':
optional: true
'@rolldown/pluginutils@1.0.0-rc.16': {}
'@rollup/rollup-android-arm-eabi@4.60.2':
optional: true
@@ -797,12 +1275,69 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.60.2':
optional: true
'@standard-schema/spec@1.1.0': {}
'@tybys/wasm-util@0.10.1':
dependencies:
tslib: 2.8.1
optional: true
'@types/chai@5.2.3':
dependencies:
'@types/deep-eql': 4.0.2
assertion-error: 2.0.1
'@types/deep-eql@4.0.2': {}
'@types/estree@1.0.8': {}
'@vitest/expect@4.1.5':
dependencies:
'@standard-schema/spec': 1.1.0
'@types/chai': 5.2.3
'@vitest/spy': 4.1.5
'@vitest/utils': 4.1.5
chai: 6.2.2
tinyrainbow: 3.1.0
'@vitest/mocker@4.1.5(vite@8.0.9(esbuild@0.27.7)(yaml@2.8.3))':
dependencies:
'@vitest/spy': 4.1.5
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 8.0.9(esbuild@0.27.7)(yaml@2.8.3)
'@vitest/pretty-format@4.1.5':
dependencies:
tinyrainbow: 3.1.0
'@vitest/runner@4.1.5':
dependencies:
'@vitest/utils': 4.1.5
pathe: 2.0.3
'@vitest/snapshot@4.1.5':
dependencies:
'@vitest/pretty-format': 4.1.5
'@vitest/utils': 4.1.5
magic-string: 0.30.21
pathe: 2.0.3
'@vitest/spy@4.1.5': {}
'@vitest/utils@4.1.5':
dependencies:
'@vitest/pretty-format': 4.1.5
convert-source-map: 2.0.0
tinyrainbow: 3.1.0
acorn@8.16.0: {}
any-promise@1.3.0: {}
assertion-error@2.0.1: {}
bundle-require@5.1.0(esbuild@0.27.7):
dependencies:
esbuild: 0.27.7
@@ -810,6 +1345,8 @@ snapshots:
cac@6.7.14: {}
chai@6.2.2: {}
chokidar@4.0.3:
dependencies:
readdirp: 4.1.2
@@ -820,10 +1357,16 @@ snapshots:
consola@3.4.2: {}
convert-source-map@2.0.0: {}
debug@4.4.3:
dependencies:
ms: 2.1.3
detect-libc@2.1.2: {}
es-module-lexer@2.0.0: {}
esbuild@0.27.7:
optionalDependencies:
'@esbuild/aix-ppc64': 0.27.7
@@ -853,6 +1396,12 @@ snapshots:
'@esbuild/win32-ia32': 0.27.7
'@esbuild/win32-x64': 0.27.7
estree-walker@3.0.3:
dependencies:
'@types/estree': 1.0.8
expect-type@1.3.0: {}
fdir@6.5.0(picomatch@4.0.4):
optionalDependencies:
picomatch: 4.0.4
@@ -868,6 +1417,55 @@ snapshots:
joycon@3.1.1: {}
lightningcss-android-arm64@1.32.0:
optional: true
lightningcss-darwin-arm64@1.32.0:
optional: true
lightningcss-darwin-x64@1.32.0:
optional: true
lightningcss-freebsd-x64@1.32.0:
optional: true
lightningcss-linux-arm-gnueabihf@1.32.0:
optional: true
lightningcss-linux-arm64-gnu@1.32.0:
optional: true
lightningcss-linux-arm64-musl@1.32.0:
optional: true
lightningcss-linux-x64-gnu@1.32.0:
optional: true
lightningcss-linux-x64-musl@1.32.0:
optional: true
lightningcss-win32-arm64-msvc@1.32.0:
optional: true
lightningcss-win32-x64-msvc@1.32.0:
optional: true
lightningcss@1.32.0:
dependencies:
detect-libc: 2.1.2
optionalDependencies:
lightningcss-android-arm64: 1.32.0
lightningcss-darwin-arm64: 1.32.0
lightningcss-darwin-x64: 1.32.0
lightningcss-freebsd-x64: 1.32.0
lightningcss-linux-arm-gnueabihf: 1.32.0
lightningcss-linux-arm64-gnu: 1.32.0
lightningcss-linux-arm64-musl: 1.32.0
lightningcss-linux-x64-gnu: 1.32.0
lightningcss-linux-x64-musl: 1.32.0
lightningcss-win32-arm64-msvc: 1.32.0
lightningcss-win32-x64-msvc: 1.32.0
lilconfig@3.1.3: {}
lines-and-columns@1.2.4: {}
@@ -893,8 +1491,12 @@ snapshots:
object-assign: 4.1.1
thenify-all: 1.6.0
nanoid@3.3.11: {}
object-assign@4.1.1: {}
obug@2.1.1: {}
pathe@2.0.3: {}
picocolors@1.1.1: {}
@@ -909,14 +1511,44 @@ snapshots:
mlly: 1.8.2
pathe: 2.0.3
postcss-load-config@6.0.1:
postcss-load-config@6.0.1(postcss@8.5.10)(yaml@2.8.3):
dependencies:
lilconfig: 3.1.3
optionalDependencies:
postcss: 8.5.10
yaml: 2.8.3
postcss@8.5.10:
dependencies:
nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.1
readdirp@4.1.2: {}
resolve-from@5.0.0: {}
rolldown@1.0.0-rc.16:
dependencies:
'@oxc-project/types': 0.126.0
'@rolldown/pluginutils': 1.0.0-rc.16
optionalDependencies:
'@rolldown/binding-android-arm64': 1.0.0-rc.16
'@rolldown/binding-darwin-arm64': 1.0.0-rc.16
'@rolldown/binding-darwin-x64': 1.0.0-rc.16
'@rolldown/binding-freebsd-x64': 1.0.0-rc.16
'@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.16
'@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.16
'@rolldown/binding-linux-arm64-musl': 1.0.0-rc.16
'@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.16
'@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.16
'@rolldown/binding-linux-x64-gnu': 1.0.0-rc.16
'@rolldown/binding-linux-x64-musl': 1.0.0-rc.16
'@rolldown/binding-openharmony-arm64': 1.0.0-rc.16
'@rolldown/binding-wasm32-wasi': 1.0.0-rc.16
'@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.16
'@rolldown/binding-win32-x64-msvc': 1.0.0-rc.16
rollup@4.60.2:
dependencies:
'@types/estree': 1.0.8
@@ -948,8 +1580,16 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.60.2
fsevents: 2.3.3
siginfo@2.0.0: {}
source-map-js@1.2.1: {}
source-map@0.7.6: {}
stackback@0.0.2: {}
std-env@4.1.0: {}
sucrase@3.35.1:
dependencies:
'@jridgewell/gen-mapping': 0.3.13
@@ -968,18 +1608,27 @@ snapshots:
dependencies:
any-promise: 1.3.0
tinybench@2.9.0: {}
tinyexec@0.3.2: {}
tinyexec@1.1.1: {}
tinyglobby@0.2.16:
dependencies:
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
tinyrainbow@3.1.0: {}
tree-kill@1.2.2: {}
ts-interface-checker@0.1.13: {}
tsup@8.5.1(typescript@5.9.3):
tslib@2.8.1:
optional: true
tsup@8.5.1(postcss@8.5.10)(typescript@5.9.3)(yaml@2.8.3):
dependencies:
bundle-require: 5.1.0(esbuild@0.27.7)
cac: 6.7.14
@@ -990,7 +1639,7 @@ snapshots:
fix-dts-default-cjs-exports: 1.0.1
joycon: 3.1.1
picocolors: 1.1.1
postcss-load-config: 6.0.1
postcss-load-config: 6.0.1(postcss@8.5.10)(yaml@2.8.3)
resolve-from: 5.0.0
rollup: 4.60.2
source-map: 0.7.6
@@ -999,6 +1648,7 @@ snapshots:
tinyglobby: 0.2.16
tree-kill: 1.2.2
optionalDependencies:
postcss: 8.5.10
typescript: 5.9.3
transitivePeerDependencies:
- jiti
@@ -1009,3 +1659,47 @@ snapshots:
typescript@5.9.3: {}
ufo@1.6.3: {}
vite@8.0.9(esbuild@0.27.7)(yaml@2.8.3):
dependencies:
lightningcss: 1.32.0
picomatch: 4.0.4
postcss: 8.5.10
rolldown: 1.0.0-rc.16
tinyglobby: 0.2.16
optionalDependencies:
esbuild: 0.27.7
fsevents: 2.3.3
yaml: 2.8.3
vitest@4.1.5(vite@8.0.9(esbuild@0.27.7)(yaml@2.8.3)):
dependencies:
'@vitest/expect': 4.1.5
'@vitest/mocker': 4.1.5(vite@8.0.9(esbuild@0.27.7)(yaml@2.8.3))
'@vitest/pretty-format': 4.1.5
'@vitest/runner': 4.1.5
'@vitest/snapshot': 4.1.5
'@vitest/spy': 4.1.5
'@vitest/utils': 4.1.5
es-module-lexer: 2.0.0
expect-type: 1.3.0
magic-string: 0.30.21
obug: 2.1.1
pathe: 2.0.3
picomatch: 4.0.4
std-env: 4.1.0
tinybench: 2.9.0
tinyexec: 1.1.1
tinyglobby: 0.2.16
tinyrainbow: 3.1.0
vite: 8.0.9(esbuild@0.27.7)(yaml@2.8.3)
why-is-node-running: 2.3.0
transitivePeerDependencies:
- msw
why-is-node-running@2.3.0:
dependencies:
siginfo: 2.0.0
stackback: 0.0.2
yaml@2.8.3: {}
+2
View File
@@ -1,9 +1,11 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"exactOptionalPropertyTypes": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,