RFC-31 Phase 1: Variable model refactor with (name, schema) composite key #33
Reference in New Issue
Block a user
Delete Branch "fix/32-variable-model-refactor"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Implements RFC-31 Phase 1 by refactoring the Variable model to use a
(name, schema)composite primary key, replacing the previous ULID-basedid+scopedesign.What Changed
Core Model
id(ULID) andscopeproperties(name, schema)composite primary keyvalue,createdAt,updatedAttimestampsDatabase Schema
variableswith columnsname,schema,value,created_at,updated_at(name, schema)compositeidx_variables_nameidx_variables_schemaidx_variables_updated_atschemareferencescas_nodes(hash)with ON DELETE CASCADEAPI Changes
createVariable(name, schema, value)- Create with composite keygetVariable(name, schema)- Retrieve by composite keyupdateVariable(name, schema, value)- Update value onlydeleteVariable(name, schema)- Delete by composite keylistVariables({ name?, schema?, before?, limit? })- List with filtersGC Integration
listSchemaHashes()provides schema refs to GCWhy
The new design:
(name, schema)naturally identifies a typed variableTest Coverage
✅ All 169 tests passing (354 assertions)
Verification
✅ Build: TypeScript compiles cleanly
✅ Tests: 169/169 passing
✅ Lint: Biome check passes
✅ Strict mode: All TypeScript strict checks pass
Ref
Fixes #32
Implements RFC-31 Phase 1 - refactors the Variable model to use a composite primary key of (name, schema) instead of the previous ULID id + scope approach. Key changes: 1. **Type Model**: - Removed `id: VariableId` and `scope: string` fields - Added `name: string` as part of composite key with `schema: Hash` - Variables with same name but different schemas are now distinct entities 2. **Database Schema**: - Changed primary key from `id` to `(name, schema)` - Updated foreign keys in `variable_tags` and `variable_labels` tables - Replaced scope-based indexes with name-based indexes - Enabled foreign key constraints for proper cascade deletes 3. **CRUD Operations** - all methods updated to use `(name, schema)`: - `create(name, value, options)` - validates unique (name, schema) - `get(name, schema)` - retrieves by composite key - `update(name, schema, value)` - updates with schema validation - `delete(name, schema)` - deletes with cascade to tags/labels - `list({ namePrefix?, schema?, tags?, labels? })` - filters by name prefix and schema - `tag(name, schema, operations)` - manages tags/labels by composite key 4. **Error Types**: - New: `VariableDuplicateError` for duplicate `(name, schema)` pairs - New: `InvalidVariableNameError` for empty names - Removed: `InvalidScopeError` (no longer needed) - Updated: `VariableNotFoundError` to reference `(name, schema)` 5. **GC Adaptation**: - Garbage collection works correctly with refactored model - Preserves nodes referenced by variables across all schemas - Global collection across all variable names and schemas 6. **Tests**: - Added comprehensive test suite covering all new functionality - Database schema validation tests - CRUD operation tests with composite keys - Multi-schema scenarios (same name, different schemas) - Tag/label management tests - GC integration tests - End-to-end workflow tests 7. **Breaking Changes**: - This is a backward-incompatible change - CLI commands will need updates in a future phase (out of scope for Phase 1) - Data migration is out of scope for Phase 1 Fixes #32 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>Code Review — RFC-31 Phase 1
Verdict: Changes Requested
🔴 Critical
缺少
set()upsert 方法 — RFC #31 明确要求 PUT 语义:set(name, value)存在则更新,不存在则创建。当前仍然是create()+update()分离。应该:set(name: string, value: Hash, options?: { tags?, labels? }): Variable方法set()内部检查(name, extractSchema(value))是否存在,存在则更新 value + updated,不存在则插入create()和update()可以保留作为内部方法或删除,但公开 API 必须有set()get()的 schema 应为可选参数 — RFC 要求:get(name: string)— 无 schema 时,单 schema 变体返回Variable,多 schema 变体返回Variable[]get(name: string, schema: Hash)— 精确匹配,返回Variable | nullget(name: string, schema: Hash)schema 是必填的,不符合设计delete()的 schema 也应可选 — 同理:remove(name)删除所有 schema 变体remove(name, schema)精确删除⚠️ Warnings
[a-zA-Z0-9._-]+/做分隔符a//b)/(/a/b或a/b/)validateName()私有方法InvalidVariableNameError的 message 应说明具体违规原因✅ Looks Good
(name, schema)复合主键 ✅any已消除 ✅— 小橘 🍊(NEKO Team)
补充:get() 不做多态返回
经讨论,
get()多态返回Variable | Variable[]违背 CAS 固定 schema 原则。改为:get(name, schema)— 必填 schema,返回Variable | nulllist(prefix?)— 覆盖"查同名所有 schema 变体"场景,list("workflow/config/agent")即可不要实现
get(name)返回数组的多态行为。— 小橘 🍊(NEKO Team)
Code Review Round 2
Verdict: Changes Requested
🔴 Critical
create()方法 — RFC 设计只要set()作为唯一创建/更新入口。create()是 60 行重复逻辑,且VariableDuplicateError也变成死代码。一并删除。⚠️ Warning
update()和remove()里有死代码 —Array.isArray(existing)检查不可能为 true(get()现在返回Variable | null),删除这些分支。残留 JSDoc —
list()方法前有一行孤立注释/** Delete a variable (deprecated: use remove() instead) */,是delete()的残留,删掉。set()update 路径缺 tag/label 冲突检查 — create 路径有TagLabelConflictError检查,update 路径没有。补上。gc.test.ts用了create()不是set()— 删掉create()后 GC 测试也要改用set()。✅ Good
— 小橘 🍊(NEKO Team)