@calcit/procs 0.13.18 → 0.13.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.yarn/install-state.gz +0 -0
- package/README.md +5 -4
- package/RFCs/07-28-git-module-store-rfc.md +11 -8
- package/RFCs/08-18-calcit-typed-js-ffi-boundary-rfc.md +993 -0
- package/RFCs/README.md +2 -1
- package/build.rs +24 -0
- package/editing-history/202608171118-project-module-runtime-resolution.md +6 -0
- package/editing-history/202608171133-visible-module-versions-and-cleanup.md +6 -0
- package/editing-history/202608171140-module-caches-root.md +5 -0
- package/editing-history/202608171200-module-cache-review-follow-up.md +5 -0
- package/editing-history/202608171205-release-0-13-19.md +4 -0
- package/editing-history/202608171215-module-path-resolution-docs.md +4 -0
- package/editing-history/202608171230-update-release-dependencies.md +4 -0
- package/editing-history/202608171409-module-cache-review-followup.md +5 -0
- package/editing-history/202608171427-module-cache-review-followup-locking.md +5 -0
- package/editing-history/202608171438-module-docs-home-lock.md +4 -0
- package/editing-history/202608181000-release-0-13-20.md +5 -0
- package/history/202608131340-agent-dependency-intent-audit.md +21 -0
- package/history/202608171611-dependency-audit-review.md +4 -0
- package/lib/package.json +1 -1
- package/package.json +1 -1
package/.yarn/install-state.gz
CHANGED
|
Binary file
|
package/README.md
CHANGED
|
@@ -135,9 +135,10 @@ Related examples and workflows:
|
|
|
135
135
|
```
|
|
136
136
|
|
|
137
137
|
Run `caps` to resolve the recursive dependency graph and install it. Immutable revisions are stored under
|
|
138
|
-
`~/.config/calcit/
|
|
138
|
+
`~/.config/calcit/module-caches/`, while the current project receives links under `.calcit/modules/`.
|
|
139
139
|
Different projects can therefore use different revisions without switching a shared checkout. Existing
|
|
140
|
-
|
|
140
|
+
project module links are the only runtime source for package-style module paths; explicit relative and
|
|
141
|
+
absolute paths retain their normal direct resolution.
|
|
141
142
|
|
|
142
143
|
Published SemVer tags are preferred. Branch refs remain supported for development, but `caps` warns with
|
|
143
144
|
the resolved commit. When a graph requests several SemVer tags for one repository, the highest requested
|
|
@@ -157,8 +158,8 @@ To load modules, use `:modules` configuration and the runtime snapshot file `cal
|
|
|
157
158
|
:modules $ [] |memof/calcit.cirru |lilac/
|
|
158
159
|
```
|
|
159
160
|
|
|
160
|
-
Paths defined in `:modules`
|
|
161
|
-
|
|
161
|
+
Paths defined in `:modules` load from the snapshot directory's `.calcit/modules/`, e.g.
|
|
162
|
+
`.calcit/modules/memof/calcit.cirru`. Run `caps` to materialize or refresh that project-local view.
|
|
162
163
|
|
|
163
164
|
Modules ending with `/` are automatically suffixed with `calcit.cirru`, and still fall back to `compact.cirru` for compatibility.
|
|
164
165
|
|
|
@@ -50,21 +50,24 @@ Calcit 继续使用 GitHub repository + Git ref 管理模块,不建设 registr
|
|
|
50
50
|
|
|
51
51
|
## 3. 目录模型
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
`~/.config/calcit/modules/` 保留给 legacy module root;新的不可变 store 使用它的
|
|
54
|
+
同级路径 `~/.config/calcit/module-caches/`。路径解析应集中到一个 helper,后续再
|
|
55
|
+
考虑 XDG 或其他平台差异。
|
|
55
56
|
|
|
56
57
|
```text
|
|
57
|
-
~/.config/calcit/
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
~/.config/calcit/
|
|
59
|
+
modules/
|
|
60
|
+
<repo>/ # legacy clone,迁移期保留
|
|
61
|
+
module-caches/
|
|
62
|
+
git/<owner>/<repo>/<commit>/source/
|
|
63
|
+
git/<owner>/<repo>/<commit>/realizations/<build-key>/
|
|
61
64
|
|
|
62
65
|
<project>/
|
|
63
66
|
deps.cirru
|
|
64
67
|
.calcit/
|
|
65
68
|
caps-state.cirru
|
|
66
69
|
modules/
|
|
67
|
-
<repo> -> ~/.config/calcit/
|
|
70
|
+
<repo> -> ~/.config/calcit/module-caches/.../source/
|
|
68
71
|
<native-repo> -> .../realizations/<build-key>/
|
|
69
72
|
tmp/
|
|
70
73
|
```
|
|
@@ -99,7 +102,7 @@ basename 建立项目链接。如果依赖图里出现 `owner-a/utils` 与 `owne
|
|
|
99
102
|
|
|
100
103
|
`caps` 按以下顺序修改本机状态:
|
|
101
104
|
|
|
102
|
-
1. 在 store 的
|
|
105
|
+
1. 在 store 的 `~/.config/calcit/module-caches/tmp/` 下创建临时目录,解析 ref、下载 source(与 store 同文件系统,保证 rename 原子);项目侧临时 project view 写入 `.calcit/tmp/`;
|
|
103
106
|
2. 完成递归依赖图和版本选择;
|
|
104
107
|
3. 完成需要的 native realization 与验证;
|
|
105
108
|
4. 写入临时 project view;
|
|
@@ -0,0 +1,993 @@
|
|
|
1
|
+
# RFC:以 Calcit 现有类型系统约束 JavaScript FFI 边界
|
|
2
|
+
|
|
3
|
+
状态:Draft
|
|
4
|
+
|
|
5
|
+
日期:2026-08-18
|
|
6
|
+
|
|
7
|
+
## 摘要
|
|
8
|
+
|
|
9
|
+
Calcit 现有的 `Struct`、`Enum`、trait、泛型函数 schema、`JsObject`、
|
|
10
|
+
`JsNullish<T>`、`:features #{:js-ffi}` 与 external-object trait 已经足以构成
|
|
11
|
+
JavaScript FFI 的主体模型。这里不建议引入一套 TypeScript 式的结构类型、联合类型、
|
|
12
|
+
重载和 DOM 继承树。
|
|
13
|
+
|
|
14
|
+
仍然需要进一步调整,重点不是增加更多 JS 专用类型,而是补齐以下能力:
|
|
15
|
+
|
|
16
|
+
1. 所有原始 JS 操作必须经过统一的 `:js-ffi` capability gate;当前实现只覆盖部分
|
|
17
|
+
JS syntax proc,裸 `js/*` 解析路径尚未统一检查。
|
|
18
|
+
2. `:features` 作为实现体 capability 元数据应在解析、查询和检查阶段保留,但不进入
|
|
19
|
+
普通函数类型匹配、泛型统一或 trait method candidate selection。
|
|
20
|
+
3. external-object trait 只在 definition 显式带有
|
|
21
|
+
`:ffi {:backend :js :kind :external-object}` 时启用宿主 lowering;普通 trait 的匹配和
|
|
22
|
+
推断保持不变。
|
|
23
|
+
4. 宿主字段写入可以使用可选的 writable lowering 元数据;默认 external field 只读,
|
|
24
|
+
但这不向核心 trait 系统增加 mutability 语义。
|
|
25
|
+
5. FFI wrapper 应尽快把宿主值规范化为 Calcit `Struct`、`Enum`、`Option`、`Result`
|
|
26
|
+
和 `Unit`,避免把 `Dynamic`、裸 Map 或 `JsObject` 扩散到业务代码。
|
|
27
|
+
|
|
28
|
+
本 RFC 是 Calcit 编译器的渐进改进方向,也同时规定 `js-ffi` 仓库应遵循的建模原则。
|
|
29
|
+
MVP 不增加 generic trait、associated type 或任何 FFI 专用的核心 type variant。
|
|
30
|
+
|
|
31
|
+
## 背景与现状
|
|
32
|
+
|
|
33
|
+
Calcit 当前已经具备:
|
|
34
|
+
|
|
35
|
+
- `JsObject`:不透明宿主值,不自动匹配 Calcit 数据类型;
|
|
36
|
+
- `JsNullish<T>`:只表示 JavaScript `null`/`undefined` 边界,与 Calcit `Option<T>`
|
|
37
|
+
分离;
|
|
38
|
+
- `:features $ #{} :js-ffi`:声明函数体允许使用 JavaScript interop;
|
|
39
|
+
- external-object trait:用 tag member 描述宿主字段,用 method member 描述宿主方法;
|
|
40
|
+
- `CodeEntry :ffi`:保存 backend、host name 和 lowering 信息,不污染普通类型 schema;
|
|
41
|
+
- `:names`:将 Calcit member name 映射到实际 JavaScript property name;
|
|
42
|
+
- generic `Fn`、generic `Struct`/`Enum`、trait `:where` bound;
|
|
43
|
+
- `js-get`/`js-set` 对 external field 的类型推断与写入类型检查;
|
|
44
|
+
- `--warn-dyn-method`:定位裸 `JsObject` 上未声明契约的字段或方法访问。
|
|
45
|
+
|
|
46
|
+
这些能力说明总体方向已经正确:普通数据使用 Calcit nominal data type,宿主对象使用
|
|
47
|
+
capability trait,原始宿主值保持 opaque。
|
|
48
|
+
|
|
49
|
+
当前仍有几个具体缺口。
|
|
50
|
+
|
|
51
|
+
### 原始 JS 能力门禁不完整
|
|
52
|
+
|
|
53
|
+
预处理器对 `is_js_syntax_procs` 中的调用检查 `:js-ffi`,但 `js/foo` 会在 namespace
|
|
54
|
+
qualified symbol 分支直接变成 `RawCode(Js, ...)`。因此门禁并未覆盖全部 raw JS 路径。
|
|
55
|
+
|
|
56
|
+
同样需要统一纳入检查的还有:
|
|
57
|
+
|
|
58
|
+
- `js/*` global/property/call;
|
|
59
|
+
- `.-`、`.!`、`.?-`、`.?!`;
|
|
60
|
+
- `aget`、`aset`、`js-get`、`js-set`、`js-delete`;
|
|
61
|
+
- `js-object`、`js-array`、`new`;
|
|
62
|
+
- `js-await`、`js-for-await`;
|
|
63
|
+
- JS `set!`;
|
|
64
|
+
- external-object trait 最终 lowering 成的 property/method 操作;
|
|
65
|
+
- `unsafe-coerce` 从 `JsObject`/`JsNullish<_>` 到 external trait 的可信断言。
|
|
66
|
+
|
|
67
|
+
门禁应基于“预处理后的 host operation kind”,而不是维护若干互相遗漏的字符串列表。
|
|
68
|
+
|
|
69
|
+
### `:features` 的保存与检查路径不完整
|
|
70
|
+
|
|
71
|
+
`CalcitFnTypeAnnotation` 已保存 `features`,但当前函数 signature matching 不比较
|
|
72
|
+
features,函数类型的 `Ord`/`Hash` 也未包含 features,部分从运行时函数重建 schema 的
|
|
73
|
+
路径会回到空 feature set。
|
|
74
|
+
|
|
75
|
+
MVP 不把这个差异直接升级为函数类型兼容规则。需要解决的是:函数实现体在 capability
|
|
76
|
+
validation 时能够找到它声明的 feature,且 query/analyze 不误报。普通函数签名匹配、
|
|
77
|
+
trait method matching、泛型统一和缓存中的核心类型身份不应因 FFI feature 改变。
|
|
78
|
+
|
|
79
|
+
### 宿主泛型关系不在本 RFC 中扩展
|
|
80
|
+
|
|
81
|
+
当前 trait 可组合、可作为 `:where` bound,但 trait definition 自身没有类型参数或
|
|
82
|
+
associated type。以下常见关系无法稳定表达:
|
|
83
|
+
|
|
84
|
+
- `Promise<T>` 的 resolve value;
|
|
85
|
+
- `Event<Target>` 的 target;
|
|
86
|
+
- `Iterator<T>` / `AsyncIterator<T>` 的 item;
|
|
87
|
+
- `MapLike<K, V>` 的 key/value;
|
|
88
|
+
- typed array 的 element type;
|
|
89
|
+
- stream 的 chunk type。
|
|
90
|
+
|
|
91
|
+
本 RFC 选择不为这些关系扩展 trait。adapter 应在边界内消费 Promise、iterator、event
|
|
92
|
+
或 map-like object,再使用现有 `Fn`、`List<T>`、`Map<K, V>`、Struct、Enum、Option 和
|
|
93
|
+
Result 表达公共 API。只有 generic trait 本身获得足够多非 FFI 用例时,才应在独立的
|
|
94
|
+
Calcit RFC 中讨论;不能由 JavaScript FFI 单独驱动核心 trait 表示和推断复杂化。
|
|
95
|
+
|
|
96
|
+
### 字段可写性没有进入契约
|
|
97
|
+
|
|
98
|
+
external trait field 已能声明字段类型,`js-set` 也能检查赋值类型,但契约尚未区分
|
|
99
|
+
read-only 与 writable。DOM、Node 和 npm API 中大量属性只读;仅检查 value type 仍可能
|
|
100
|
+
生成无效写入。
|
|
101
|
+
|
|
102
|
+
## 设计原则
|
|
103
|
+
|
|
104
|
+
### Calcit-owned data 与 host object 分离
|
|
105
|
+
|
|
106
|
+
使用如下映射:
|
|
107
|
+
|
|
108
|
+
| 语义 | Calcit 表示 |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| 已转换、由 Calcit 拥有的数据 | `Struct` |
|
|
111
|
+
| 有限状态、有限分支、错误类别 | `Enum` |
|
|
112
|
+
| 宿主对象可提供的字段/方法能力 | external-object trait |
|
|
113
|
+
| 未建立契约的宿主值 | `JsObject` |
|
|
114
|
+
| JavaScript 可空宿主值 | `JsNullish<T>` |
|
|
115
|
+
| 业务缺失 | `Option<T>` |
|
|
116
|
+
| 可恢复错误或异常 | `Result<T, E>` |
|
|
117
|
+
| 只有副作用、无业务返回值 | `Unit` |
|
|
118
|
+
|
|
119
|
+
不得把 JavaScript object 直接伪装成 Calcit `Struct`。Struct 表示已经转换并满足 Calcit
|
|
120
|
+
数据不变量的值;external trait 才表示“仍由宿主拥有,但我们信任它具有这些能力”。
|
|
121
|
+
|
|
122
|
+
### 不复制 TypeScript 类型系统
|
|
123
|
+
|
|
124
|
+
本 RFC 不引入:
|
|
125
|
+
|
|
126
|
+
- 通用 structural object type;
|
|
127
|
+
- union/intersection/conditional type;
|
|
128
|
+
- overload resolution;
|
|
129
|
+
- 完整 DOM interface inheritance;
|
|
130
|
+
- JavaScript prototype 或 Proxy 建模;
|
|
131
|
+
- 依赖任意字符串字面量的 dependent typing。
|
|
132
|
+
|
|
133
|
+
常见替代方式:
|
|
134
|
+
|
|
135
|
+
- union 用命名 Enum;
|
|
136
|
+
- overload 用多个明确 wrapper;
|
|
137
|
+
- 字符串常量集合用 Enum,并在 FFI adapter 中转换;
|
|
138
|
+
- 复杂宿主返回值由 decoder 转为 Struct/Enum;
|
|
139
|
+
- 只为项目实际使用的成员定义小型 capability trait。
|
|
140
|
+
|
|
141
|
+
### `:schema` 与 `:ffi` 元数据正交
|
|
142
|
+
|
|
143
|
+
`:schema` 只描述 Calcit 看见的类型。`:ffi` 只描述 backend lowering、host name、target
|
|
144
|
+
和 writable 等宿主事实。不要引入新的 `Ffi<T>` schema wrapper。
|
|
145
|
+
|
|
146
|
+
### 核心类型与推断的复杂度预算
|
|
147
|
+
|
|
148
|
+
本 RFC 的实现必须满足以下硬约束:
|
|
149
|
+
|
|
150
|
+
- 不新增 `JsPromise<T>`、`JsArray<T>`、`JsEvent<T>`、`JsIterator<T>` 等核心 type
|
|
151
|
+
variant;
|
|
152
|
+
- 不修改 `CalcitTypeAnnotation::matches_with_bindings` 来解释 `:ffi` 或 `:features`;
|
|
153
|
+
- 不为 FFI 增加普通 trait 的 structural satisfaction、自动 impl 或特殊 where-bound;
|
|
154
|
+
- 不改变普通 trait 的 method candidate、requires、impl-traits 与 generic unification;
|
|
155
|
+
- external-object 分支只在 trait definition 显式具有
|
|
156
|
+
`:ffi {:backend :js :kind :external-object}` 时启用;
|
|
157
|
+
- `:ffi` 始终是 lowering metadata,不参与 schema equality、subtyping 或 trait
|
|
158
|
+
satisfaction;
|
|
159
|
+
- 未带 `:ffi` 的程序在推断候选、诊断和 codegen 上保持原行为。
|
|
160
|
+
|
|
161
|
+
external-object trait 复用 trait 的 member type 描述,但不自动获得普通 runtime impl,
|
|
162
|
+
也不能因为字段或方法同名而结构化满足另一个 trait。FFI 检查应是核心类型检查之后的独立
|
|
163
|
+
capability/target validation;它可以拒绝一个已经类型正确但未授权或 target 不兼容的
|
|
164
|
+
程序,却不能制造新的类型匹配结果。
|
|
165
|
+
|
|
166
|
+
## `js-ffi` 仓库的类型层次
|
|
167
|
+
|
|
168
|
+
建议将定义分为三层。
|
|
169
|
+
|
|
170
|
+
### 第一层:raw adapters
|
|
171
|
+
|
|
172
|
+
raw adapter 是唯一允许出现原始 JS syntax、`unsafe-coerce` 和 host import 的层。每个
|
|
173
|
+
函数必须有完整 Fn schema,并标记:
|
|
174
|
+
|
|
175
|
+
```cirru.no-check
|
|
176
|
+
:features $ #{} :js-ffi
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
raw adapter 可以返回 `JsObject`、`JsNullish<Trait>` 或 external trait,但不得使用
|
|
180
|
+
`Dynamic` 隐藏已知关系。
|
|
181
|
+
|
|
182
|
+
### 第二层:typed host capabilities
|
|
183
|
+
|
|
184
|
+
以小型 external trait 表示宿主对象。示意:
|
|
185
|
+
|
|
186
|
+
```cirru
|
|
187
|
+
deftrait StorageHost
|
|
188
|
+
.get-item $ :: 'Fn
|
|
189
|
+
{}
|
|
190
|
+
:args $ [] 'js-ffi.browser/StorageHost 'String
|
|
191
|
+
:return $ :: 'JsNullish 'String
|
|
192
|
+
.set-item! $ :: 'Fn
|
|
193
|
+
{}
|
|
194
|
+
:args $ [] 'js-ffi.browser/StorageHost 'String 'String
|
|
195
|
+
:return 'Unit
|
|
196
|
+
.remove-item! $ :: 'Fn
|
|
197
|
+
{}
|
|
198
|
+
:args $ [] 'js-ffi.browser/StorageHost 'String
|
|
199
|
+
:return 'Unit
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
trait 的 `CodeEntry :ffi` 声明 `:backend :js`、`:kind :external-object` 和必要的
|
|
203
|
+
`:names`。业务代码不直接接收裸 `JsObject`。
|
|
204
|
+
|
|
205
|
+
### 第三层:normalized Calcit API
|
|
206
|
+
|
|
207
|
+
公共 API 优先返回 Calcit-owned data:
|
|
208
|
+
|
|
209
|
+
```cirru
|
|
210
|
+
defenum Runtime (:browser) (:node)
|
|
211
|
+
|
|
212
|
+
defstruct JsError
|
|
213
|
+
:name 'String
|
|
214
|
+
:message 'String
|
|
215
|
+
:stack $ :: 'Option 'String
|
|
216
|
+
|
|
217
|
+
defstruct BrowserProbe
|
|
218
|
+
:runtime 'js-ffi.types/Runtime
|
|
219
|
+
:document? 'Bool
|
|
220
|
+
:storage $ :: 'Result 'String 'js-ffi.types/JsError
|
|
221
|
+
|
|
222
|
+
defstruct NodeProbe
|
|
223
|
+
:runtime 'js-ffi.types/Runtime
|
|
224
|
+
:cwd 'String
|
|
225
|
+
:argv-count 'Number
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
当前 probe 使用 `Map<Tag, Dynamic>`,应迁移成命名 Struct。Storage mutation、console、
|
|
229
|
+
timer registration 等只有副作用的 wrapper 应显式以 `nil` 收尾并声明返回 `Unit`,不因
|
|
230
|
+
JavaScript 返回 `undefined` 就暴露 `Dynamic`。
|
|
231
|
+
|
|
232
|
+
## 类型表达案例目录
|
|
233
|
+
|
|
234
|
+
本节把常见 JavaScript API 分成三种视图:
|
|
235
|
+
|
|
236
|
+
| 层次 | 目的 | 可以出现的类型 |
|
|
237
|
+
| --- | --- | --- |
|
|
238
|
+
| raw boundary | 如实表示宿主不确定性 | `JsObject`、`JsNullish<T>`、external trait |
|
|
239
|
+
| typed host API | 保留宿主 identity,同时提供成员类型 | external trait、`Option<Trait>`、typed `Fn` |
|
|
240
|
+
| normalized API | 供普通 Calcit 业务代码使用 | Struct、Enum、List、Map、Option、Result、Unit |
|
|
241
|
+
|
|
242
|
+
不是每个 API 都必须同时提供三层。默认优先提供 normalized API;只有调用者确实需要继续
|
|
243
|
+
操作宿主对象时,才公开 typed host API。
|
|
244
|
+
|
|
245
|
+
### 案例 1:确定的 primitive 返回值
|
|
246
|
+
|
|
247
|
+
例如 `window.innerWidth` 的宿主契约明确是 number。raw adapter 在一个位置完成断言:
|
|
248
|
+
|
|
249
|
+
```cirru.no-check
|
|
250
|
+
defn viewport-width ()
|
|
251
|
+
unsafe-coerce js/window.innerWidth Number
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
对应 schema:
|
|
255
|
+
|
|
256
|
+
```cirru.no-check
|
|
257
|
+
:: 'Fn $ {}
|
|
258
|
+
:args $ []
|
|
259
|
+
:return 'Number
|
|
260
|
+
:features $ #{} :js-ffi
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
公共函数可以继续返回 `Number`。调用者不需要 `:js-ffi`,因为 raw property access 已封装
|
|
264
|
+
在 adapter 内。相同规则适用于可靠的 `Bool`、`String` 和 `Buffer` 返回值。
|
|
265
|
+
|
|
266
|
+
如果宿主值可能为 `null`/`undefined`,不能直接断言 primitive,应先使用
|
|
267
|
+
`JsNullish<Primitive>`,再转换为 Option 或 Result。
|
|
268
|
+
|
|
269
|
+
### 案例 2:JavaScript nullish 与业务缺失
|
|
270
|
+
|
|
271
|
+
`document.querySelector` 的原始结果是宿主 nullish:
|
|
272
|
+
|
|
273
|
+
```cirru.no-check
|
|
274
|
+
:: 'Fn $ {}
|
|
275
|
+
:args $ [] 'String
|
|
276
|
+
:return $ :: 'JsNullish 'js-ffi.browser/DomElement
|
|
277
|
+
:features $ #{} :js-ffi
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
低层 typed host wrapper 可以转换为:
|
|
281
|
+
|
|
282
|
+
```cirru.no-check
|
|
283
|
+
:: 'Fn $ {}
|
|
284
|
+
:args $ [] 'String
|
|
285
|
+
:return $ :: 'Option 'js-ffi.browser/DomElement
|
|
286
|
+
:features $ #{} :js-ffi
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
这里 Option 中仍然装着宿主对象。调用者若继续读取 external field 或调用 external method,
|
|
290
|
+
该访问所在函数仍需要 `:js-ffi`。
|
|
291
|
+
|
|
292
|
+
完全 normalized 的版本应复制所需数据:
|
|
293
|
+
|
|
294
|
+
```cirru
|
|
295
|
+
defstruct ElementSnapshot
|
|
296
|
+
:id 'String
|
|
297
|
+
:text 'String
|
|
298
|
+
:visible? 'Bool
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
```cirru.no-check
|
|
302
|
+
:: 'Fn $ {}
|
|
303
|
+
:args $ [] 'String
|
|
304
|
+
:return $ :: 'Option 'js-ffi.browser/ElementSnapshot
|
|
305
|
+
:features $ #{} :js-ffi
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
业务层拿到 `Option<ElementSnapshot>` 后只处理 Calcit 数据,不再依赖宿主 identity。
|
|
309
|
+
|
|
310
|
+
### 案例 3:错误、异常与 Result
|
|
311
|
+
|
|
312
|
+
不要把可能抛异常的 API 仅表示为返回值。例如 localStorage 在隐私模式或 quota 满时可能
|
|
313
|
+
抛出异常。先定义 normalized error:
|
|
314
|
+
|
|
315
|
+
```cirru
|
|
316
|
+
defenum JsErrorKind
|
|
317
|
+
:exception
|
|
318
|
+
:type-error
|
|
319
|
+
:range-error
|
|
320
|
+
:permission
|
|
321
|
+
:quota
|
|
322
|
+
:unknown
|
|
323
|
+
|
|
324
|
+
defstruct JsError
|
|
325
|
+
:kind 'js-ffi.types/JsErrorKind
|
|
326
|
+
:name 'String
|
|
327
|
+
:message 'String
|
|
328
|
+
:stack $ :: 'Option 'String
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Storage read 的公共类型应为:
|
|
332
|
+
|
|
333
|
+
```cirru.no-check
|
|
334
|
+
:: 'Fn $ {}
|
|
335
|
+
:args $ [] 'String
|
|
336
|
+
:return $ :: 'Result
|
|
337
|
+
:: 'Option 'String
|
|
338
|
+
, 'js-ffi.types/JsError
|
|
339
|
+
:features $ #{} :js-ffi
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
三层语义分别为:
|
|
343
|
+
|
|
344
|
+
- key 不存在:`Result.ok Option.none`;
|
|
345
|
+
- key 存在:`Result.ok (Option.some value)`;
|
|
346
|
+
- host exception:`Result.err JsError`。
|
|
347
|
+
|
|
348
|
+
不能用空字符串同时表示“不存在”和“读取失败”。
|
|
349
|
+
|
|
350
|
+
### 案例 4:只有副作用的 API
|
|
351
|
+
|
|
352
|
+
`console.log`、`localStorage.setItem`、`removeItem`、`focus` 和 `process.exit` 不应因为
|
|
353
|
+
JavaScript 返回 `undefined` 而声明为 `Dynamic`。wrapper 应显式以 `nil` 收尾并返回
|
|
354
|
+
`Unit`:
|
|
355
|
+
|
|
356
|
+
```cirru.no-check
|
|
357
|
+
defn console-log! (message)
|
|
358
|
+
js/console.log message
|
|
359
|
+
, nil
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
```cirru.no-check
|
|
363
|
+
:: 'Fn $ {}
|
|
364
|
+
:args $ [] 'String
|
|
365
|
+
:return 'Unit
|
|
366
|
+
:features $ #{} :js-ffi
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
如果 effect 可能失败,则使用 `Result<Unit, JsError>`。
|
|
370
|
+
|
|
371
|
+
### 案例 5:external field 与 method
|
|
372
|
+
|
|
373
|
+
DOM input 不建模为 Struct,因为对象仍由浏览器拥有。使用 capability trait:
|
|
374
|
+
|
|
375
|
+
```cirru.no-check
|
|
376
|
+
deftrait DomInput
|
|
377
|
+
:value 'String
|
|
378
|
+
:checked 'Bool
|
|
379
|
+
:disabled 'Bool
|
|
380
|
+
.focus! $ :: 'Fn
|
|
381
|
+
{}
|
|
382
|
+
:args $ [] 'js-ffi.browser/DomInput
|
|
383
|
+
:return 'Unit
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
对应 lowering metadata:
|
|
387
|
+
|
|
388
|
+
```cirru.no-check
|
|
389
|
+
:ffi $ {}
|
|
390
|
+
:backend :js
|
|
391
|
+
:kind :external-object
|
|
392
|
+
:names $ {}
|
|
393
|
+
:focus! |focus
|
|
394
|
+
:writable $ #{} :value :checked :disabled
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
类型行为:
|
|
398
|
+
|
|
399
|
+
```cirru.no-check
|
|
400
|
+
input :value
|
|
401
|
+
; => String
|
|
402
|
+
|
|
403
|
+
input .focus!
|
|
404
|
+
; => Unit
|
|
405
|
+
|
|
406
|
+
js-get input :value
|
|
407
|
+
; => JsNullish<String>, because raw property semantics still admit absence
|
|
408
|
+
|
|
409
|
+
js-set input :value |next
|
|
410
|
+
; => String or Unit according to the chosen set operation contract
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
`:value` 的普通 typed tag access 使用可信 external trait 契约;`js-get` 保留 raw
|
|
414
|
+
JavaScript nullish 语义。两者不应混成同一个推断规则。
|
|
415
|
+
|
|
416
|
+
### 案例 6:事件与 callback target
|
|
417
|
+
|
|
418
|
+
直接公开宿主事件时,事件和 target 都使用 external trait:
|
|
419
|
+
|
|
420
|
+
```cirru.no-check
|
|
421
|
+
deftrait ClickEvent
|
|
422
|
+
:target $ :: 'JsNullish 'js-ffi.browser/DomElement
|
|
423
|
+
:meta-key 'Bool
|
|
424
|
+
:ctrl-key 'Bool
|
|
425
|
+
:shift-key 'Bool
|
|
426
|
+
.prevent-default! $ :: 'Fn
|
|
427
|
+
{}
|
|
428
|
+
:args $ [] 'js-ffi.browser/ClickEvent
|
|
429
|
+
:return 'Unit
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
低层 listener schema:
|
|
433
|
+
|
|
434
|
+
```cirru.no-check
|
|
435
|
+
:: 'Fn $ {}
|
|
436
|
+
:args $ []
|
|
437
|
+
'js-ffi.browser/DomElement
|
|
438
|
+
:: 'Fn $ {}
|
|
439
|
+
:args $ [] 'js-ffi.browser/ClickEvent
|
|
440
|
+
:return 'Unit
|
|
441
|
+
:features $ #{} :js-ffi
|
|
442
|
+
:return 'Unit
|
|
443
|
+
:features $ #{} :js-ffi
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
如果业务 callback 不需要宿主操作,应先转换事件:
|
|
447
|
+
|
|
448
|
+
```cirru
|
|
449
|
+
defstruct ClickInfo
|
|
450
|
+
:target-id $ :: 'Option 'String
|
|
451
|
+
:meta? 'Bool
|
|
452
|
+
:ctrl? 'Bool
|
|
453
|
+
:shift? 'Bool
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
然后暴露纯 callback:
|
|
457
|
+
|
|
458
|
+
```cirru.no-check
|
|
459
|
+
:: 'Fn $ {}
|
|
460
|
+
:args $ []
|
|
461
|
+
'js-ffi.browser/DomElement
|
|
462
|
+
:: 'Fn $ {}
|
|
463
|
+
:args $ [] 'js-ffi.browser/ClickInfo
|
|
464
|
+
:return 'Unit
|
|
465
|
+
:return 'Unit
|
|
466
|
+
:features $ #{} :js-ffi
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
event name 与 event type 的关联不使用 dependent string type。分别提供 `on-click!`、
|
|
470
|
+
`on-input!`、`on-keydown!` 等 wrapper;共享配置使用 Enum。
|
|
471
|
+
|
|
472
|
+
上述 callback schema 中的 `:features` 只用于检查 callback 自己的函数体。高阶函数接收
|
|
473
|
+
callback 时仍按现有 Fn 参数与返回值匹配,不根据 feature set 改变泛型绑定或 trait
|
|
474
|
+
method candidate。纯 callback 和包含宿主操作的 callback 可以共用同一个业务签名,后者
|
|
475
|
+
只需在自身实现体通过 capability validation。
|
|
476
|
+
|
|
477
|
+
### 案例 7:字符串常量、状态与 union
|
|
478
|
+
|
|
479
|
+
JavaScript API 常返回有限字符串,例如 document ready state。使用 Enum:
|
|
480
|
+
|
|
481
|
+
```cirru
|
|
482
|
+
defenum DocumentReadyState
|
|
483
|
+
:loading
|
|
484
|
+
:interactive
|
|
485
|
+
:complete
|
|
486
|
+
:unknown 'String
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
raw adapter 先得到 String,decoder 再返回
|
|
490
|
+
`Result<DocumentReadyState, JsError>`,或在未来兼容未知值时使用 `:unknown String`。
|
|
491
|
+
|
|
492
|
+
`"GET" | "POST" | "PUT"` 同样定义 `HttpMethod` Enum。进入 fetch adapter 时转换为 JS
|
|
493
|
+
string;不要给业务函数暴露任意 String 后再依赖运行时约定。
|
|
494
|
+
|
|
495
|
+
### 案例 8:数组、iterable 与同质集合
|
|
496
|
+
|
|
497
|
+
原始 JavaScript Array 首先是 `JsObject`。若 adapter 会遍历并验证元素,应返回:
|
|
498
|
+
|
|
499
|
+
```cirru.no-check
|
|
500
|
+
:: 'Fn $ {}
|
|
501
|
+
:generics $ [] 'T
|
|
502
|
+
:args $ []
|
|
503
|
+
'JsObject
|
|
504
|
+
:: 'Fn $ {}
|
|
505
|
+
:args $ [] 'JsObject
|
|
506
|
+
:return $ :: 'Result 'T 'js-ffi.types/JsError
|
|
507
|
+
:return $ :: 'Result
|
|
508
|
+
:: 'List 'T
|
|
509
|
+
, 'js-ffi.types/JsError
|
|
510
|
+
:features $ #{} :js-ffi
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
这保留了 decoder input/output 的类型关系。不要用裸 `List` 或 `List<Dynamic>`。
|
|
514
|
+
|
|
515
|
+
需要 lazy/async host iteration 时,也不在本 RFC 中引入 `JsIterator<T>` 或
|
|
516
|
+
`JsAsyncIterator<T>`。adapter 应消费 iterable 后返回 `List<T>`,或者接受明确的 typed
|
|
517
|
+
callback,逐项推送已经验证的 item。若需要暂停、取消或错误状态,使用命名 Struct/Enum
|
|
518
|
+
表达 adapter 协议,而不是扩展核心 trait。
|
|
519
|
+
|
|
520
|
+
### 案例 9:JavaScript dictionary 与 Calcit Map
|
|
521
|
+
|
|
522
|
+
动态 key 的 host object 不能因为“看起来像 map”就声明为 Calcit Map。
|
|
523
|
+
|
|
524
|
+
- 仍由宿主持有、通过 `aget` 访问:`JsObject`;
|
|
525
|
+
- 读取时可能缺失:`JsNullish<T>`;
|
|
526
|
+
- 已复制并验证所有 key/value:`Map<K, V>`;
|
|
527
|
+
- 固定字段集合:Struct;
|
|
528
|
+
- 有方法或 identity:external trait。
|
|
529
|
+
|
|
530
|
+
例如 `process.env` 的公共读取 API:
|
|
531
|
+
|
|
532
|
+
```cirru.no-check
|
|
533
|
+
:: 'Fn $ {}
|
|
534
|
+
:args $ [] 'String
|
|
535
|
+
:return $ :: 'Option 'String
|
|
536
|
+
:features $ #{} :js-ffi
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
只有需要快照所有环境变量时才返回 `Map<String, String>`;不要暴露
|
|
540
|
+
`Map<String, JsNullish<JsObject>>` 给普通业务层。
|
|
541
|
+
|
|
542
|
+
### 案例 10:ES module function import
|
|
543
|
+
|
|
544
|
+
npm named export 在 import 点首先是 opaque host value。adapter 一次性断言完整 Fn
|
|
545
|
+
schema:
|
|
546
|
+
|
|
547
|
+
```cirru.no-check
|
|
548
|
+
defn make-id (size)
|
|
549
|
+
let
|
|
550
|
+
generate $ unsafe-coerce nanoid $ :: 'Fn
|
|
551
|
+
{}
|
|
552
|
+
:args $ [] 'Number
|
|
553
|
+
:return 'String
|
|
554
|
+
generate size
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
`make-id` 自身的 schema:
|
|
558
|
+
|
|
559
|
+
```cirru.no-check
|
|
560
|
+
:: 'Fn $ {}
|
|
561
|
+
:args $ [] 'Number
|
|
562
|
+
:return 'String
|
|
563
|
+
:features $ #{} :js-ffi
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
业务层只看到 `Number -> String`。如果 module export 是带 receiver 的对象方法,应定义
|
|
567
|
+
external trait method,不能把 method 取出来后当作普通 function,以免丢失 JavaScript
|
|
568
|
+
`this`。
|
|
569
|
+
|
|
570
|
+
### 案例 11:Promise 与 async
|
|
571
|
+
|
|
572
|
+
当前可实现的推荐 API是在 adapter 内 await 并捕获 rejection:
|
|
573
|
+
|
|
574
|
+
```cirru.no-check
|
|
575
|
+
:: 'Fn $ {}
|
|
576
|
+
:args $ [] 'String
|
|
577
|
+
:return $ :: 'Result 'String 'js-ffi.types/JsError
|
|
578
|
+
:features $ #{} :js-ffi :async
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
如果返回值在 JavaScript backend 上仍然是 Promise,`:async`/lowering metadata 负责调用
|
|
582
|
+
约定;逻辑返回类型描述 await 后的 Calcit value,不再暴露无类型 `JsObject`。
|
|
583
|
+
|
|
584
|
+
本 RFC 不公开未 await 的 Promise,也不引入 `JsPromise<T>`。需要组合 Promise 的 API
|
|
585
|
+
应在 JS adapter 内完成组合,并把最终逻辑结果返回为 `T`、`Option<T>` 或
|
|
586
|
+
`Result<T, E>`。如果将来 Calcit 因非 FFI 场景获得通用 async/task abstraction,它可以
|
|
587
|
+
由独立 RFC 统一承载;这里不预先复制 JavaScript Promise 类型。
|
|
588
|
+
|
|
589
|
+
### 案例 12:Node process 与 filesystem
|
|
590
|
+
|
|
591
|
+
Node API 经过 adapter 后应使用普通 Calcit 类型:
|
|
592
|
+
|
|
593
|
+
| JavaScript API | raw boundary | 公共类型 |
|
|
594
|
+
| --- | --- | --- |
|
|
595
|
+
| `process.cwd()` | trusted host String | `String` |
|
|
596
|
+
| `process.argv` | host Array | `List<String>` |
|
|
597
|
+
| `process.env[key]` | nullish host String | `Option<String>` |
|
|
598
|
+
| `fs.existsSync(path)` | trusted Bool | `Bool` |
|
|
599
|
+
| `fs.readFile` | callback/Promise + Buffer | `Result<Buffer, FsError>` |
|
|
600
|
+
| `process.exit(code)` | non-returning effect | `Unit`,未来可增加 `Never` |
|
|
601
|
+
|
|
602
|
+
Filesystem error 使用 Enum + Struct,而不是 String:
|
|
603
|
+
|
|
604
|
+
```cirru
|
|
605
|
+
defenum FsErrorKind
|
|
606
|
+
:not-found
|
|
607
|
+
:permission
|
|
608
|
+
:already-exists
|
|
609
|
+
:invalid-path
|
|
610
|
+
:io
|
|
611
|
+
:unknown 'String
|
|
612
|
+
|
|
613
|
+
defstruct FsError
|
|
614
|
+
:kind 'js-ffi.node/FsErrorKind
|
|
615
|
+
:message 'String
|
|
616
|
+
:path $ :: 'Option 'String
|
|
617
|
+
:code $ :: 'Option 'String
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### 案例 13:constructor 与有 identity 的实例
|
|
621
|
+
|
|
622
|
+
`new js/Date` 的结果不是 Calcit Struct。低层使用 external trait:
|
|
623
|
+
|
|
624
|
+
```cirru
|
|
625
|
+
deftrait DateHost
|
|
626
|
+
.timestamp $ :: 'Fn
|
|
627
|
+
{}
|
|
628
|
+
:args $ [] 'js-ffi.shared/DateHost
|
|
629
|
+
:return 'Number
|
|
630
|
+
.to-iso-string $ :: 'Fn
|
|
631
|
+
{}
|
|
632
|
+
:args $ [] 'js-ffi.shared/DateHost
|
|
633
|
+
:return 'String
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
若业务只需要不可变数据,转换为:
|
|
637
|
+
|
|
638
|
+
```cirru
|
|
639
|
+
defstruct DateSnapshot
|
|
640
|
+
:timestamp 'Number
|
|
641
|
+
:iso 'String
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
同样适用于 URL、Request、Response、AbortController 和 Node class instances。
|
|
645
|
+
|
|
646
|
+
### 案例 14:timer handle 的 target 差异
|
|
647
|
+
|
|
648
|
+
浏览器 `setTimeout` 常返回 number,Node 返回 object。不能为了共享 API 把二者都声明为
|
|
649
|
+
`Number` 或 `Dynamic`。
|
|
650
|
+
|
|
651
|
+
建议低层分开:
|
|
652
|
+
|
|
653
|
+
```cirru.no-check
|
|
654
|
+
js-ffi.browser/BrowserTimerId
|
|
655
|
+
js-ffi.node/NodeTimerHandle
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
browser 与 Node namespace 各自提供匹配的 `set-timeout!`/`clear-timeout!`。共享代码若不
|
|
659
|
+
需要取消 timer,就不返回 handle;若必须共享,使用 entry type slot 或未来 nominal opaque
|
|
660
|
+
newtype 绑定具体 handle,而不是暴露 backend representation。
|
|
661
|
+
|
|
662
|
+
### 案例 15:重载与配置对象
|
|
663
|
+
|
|
664
|
+
对于 JavaScript overload:
|
|
665
|
+
|
|
666
|
+
```text
|
|
667
|
+
fetch(url)
|
|
668
|
+
fetch(url, options)
|
|
669
|
+
fetch(request)
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
Calcit adapter 提供多个明确函数:
|
|
673
|
+
|
|
674
|
+
```cirru.no-check
|
|
675
|
+
fetch-text url
|
|
676
|
+
fetch-text-with url options
|
|
677
|
+
fetch-request request
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
配置使用 Struct,有限选项使用 Enum:
|
|
681
|
+
|
|
682
|
+
```cirru
|
|
683
|
+
defstruct FetchOptions
|
|
684
|
+
:method 'js-ffi.http/HttpMethod
|
|
685
|
+
:headers $ :: 'Map 'String 'String
|
|
686
|
+
:body $ :: 'Option 'String
|
|
687
|
+
:timeout-ms $ :: 'Option 'Number
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
adapter 负责把 FetchOptions 转成 JS object。不要直接把开放 `JsObject` options 传播到
|
|
691
|
+
业务层,也不需要在类型系统中实现 JavaScript overload resolution。
|
|
692
|
+
|
|
693
|
+
### 案例 16:动态 escape hatch
|
|
694
|
+
|
|
695
|
+
确实无法静态描述的 plugin/global object 可以保留:
|
|
696
|
+
|
|
697
|
+
```cirru.no-check
|
|
698
|
+
:: 'Fn $ {}
|
|
699
|
+
:args $ [] 'String
|
|
700
|
+
:return $ :: 'JsNullish 'JsObject
|
|
701
|
+
:features $ #{} :js-ffi
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
约束是:
|
|
705
|
+
|
|
706
|
+
- 只存在于 adapter namespace;
|
|
707
|
+
- 每次离开 adapter 前尽量 decode;
|
|
708
|
+
- literal key access 在 `--warn-dyn-method` 下可见;
|
|
709
|
+
- 不把 `Dynamic` 当作 `JsObject` 的别名;
|
|
710
|
+
- 不允许 raw host value 静默满足 Struct、Enum 或 external trait。
|
|
711
|
+
|
|
712
|
+
### 类型选择速查
|
|
713
|
+
|
|
714
|
+
| 遇到的宿主值 | 首选表达 |
|
|
715
|
+
| --- | --- |
|
|
716
|
+
| 明确 primitive | `String` / `Number` / `Bool` / `Buffer` |
|
|
717
|
+
| 可能 null/undefined 的宿主值 | `JsNullish<T>` |
|
|
718
|
+
| 已转换的业务缺失 | `Option<T>` |
|
|
719
|
+
| 可能异常/reject/decode 失败 | `Result<T, E>` |
|
|
720
|
+
| 固定数据字段 | Struct |
|
|
721
|
+
| 有限状态或分支 | Enum |
|
|
722
|
+
| 有 identity、字段和方法的宿主对象 | external-object trait |
|
|
723
|
+
| 同质 JS Array 已完成验证 | `List<T>` |
|
|
724
|
+
| 动态 dictionary 已完成复制验证 | `Map<K, V>` |
|
|
725
|
+
| 未知宿主对象 | `JsObject` |
|
|
726
|
+
| 回调 | 完整 `Fn` 参数与返回值;实现体 capability 另行检查 |
|
|
727
|
+
| Promise/Iterator/Event 类型关系 | 由 adapter 消费,转换为现有 Fn/List/Struct/Enum/Result |
|
|
728
|
+
| 纯副作用 | `Unit` |
|
|
729
|
+
| 跨 target handle | target-specific external trait 或 type slot |
|
|
730
|
+
|
|
731
|
+
## 编译器调整
|
|
732
|
+
|
|
733
|
+
### 统一 host operation 分类
|
|
734
|
+
|
|
735
|
+
预处理阶段增加统一分类,例如:
|
|
736
|
+
|
|
737
|
+
```text
|
|
738
|
+
HostOperation
|
|
739
|
+
JsGlobal
|
|
740
|
+
JsPropertyRead
|
|
741
|
+
JsPropertyWrite
|
|
742
|
+
JsMethodCall
|
|
743
|
+
JsConstruct
|
|
744
|
+
JsAwait
|
|
745
|
+
JsImport
|
|
746
|
+
ExternalFieldRead
|
|
747
|
+
ExternalFieldWrite
|
|
748
|
+
ExternalMethodCall
|
|
749
|
+
UnsafeHostAssertion
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
所有会 lowering 成 JavaScript host operation 的节点都调用同一个检查:
|
|
753
|
+
|
|
754
|
+
```text
|
|
755
|
+
require_feature(:js-ffi, operation, current_fn)
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
建议实现顺序固定为:
|
|
759
|
+
|
|
760
|
+
```text
|
|
761
|
+
parse/preprocess
|
|
762
|
+
-> existing schema inference and trait resolution
|
|
763
|
+
-> collect explicitly marked host operations
|
|
764
|
+
-> capability and target validation
|
|
765
|
+
-> backend lowering/codegen
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
`collect` 只能从 raw JS syntax 或显式 external-object `:ffi` metadata 产生 host
|
|
769
|
+
operation。它读取已经完成的类型结果,但不得回写 type binding、增加 trait candidate 或
|
|
770
|
+
让失败的普通类型匹配变为成功。这样新增 validation 可以独立开启、记录 warning,并在
|
|
771
|
+
成熟后升级为 error,而不改变 trait solver 的收敛行为。
|
|
772
|
+
|
|
773
|
+
第一阶段产生稳定 warning;strict mode 与下一次 breaking release 中升级为 error。
|
|
774
|
+
|
|
775
|
+
建议诊断码:
|
|
776
|
+
|
|
777
|
+
- `E_JS_FFI_FEATURE_REQUIRED`:raw/typed host operation 位于未授权函数体;
|
|
778
|
+
- `E_JS_FFI_TARGET_MISMATCH`:Node binding 用于 browser target,或反之;
|
|
779
|
+
- `E_JS_FFI_UNSAFE_ASSERTION`:host assertion 不在受信任 adapter;
|
|
780
|
+
- `E_JS_FFI_FIELD_READONLY`:写入未声明 writable 的 external field;
|
|
781
|
+
|
|
782
|
+
### lexical function scope
|
|
783
|
+
|
|
784
|
+
门禁以实际执行 host operation 的函数体为边界:
|
|
785
|
+
|
|
786
|
+
- 普通函数调用一个已经封装好的 typed FFI wrapper,不需要标记 `:js-ffi`;
|
|
787
|
+
- wrapper 自己必须标记;
|
|
788
|
+
- 匿名函数若包含 raw JS,也必须具有带 `:js-ffi` 的 `hint-fn` schema;
|
|
789
|
+
- 外层函数有 feature 不应自动授权一个可逃逸的匿名 closure;
|
|
790
|
+
- 宏不能把 raw JS 静默注入到无 feature 的调用者;检查应基于展开后的归属位置。
|
|
791
|
+
|
|
792
|
+
这样 `:js-ffi` 表示“这个实现体包含宿主操作”,而不是把能力沿普通调用链传播到整个
|
|
793
|
+
应用。
|
|
794
|
+
|
|
795
|
+
### 独立保存并验证函数 features
|
|
796
|
+
|
|
797
|
+
所有从 schema、函数实现体、import 或 callback hint 建立 capability context 的路径都应
|
|
798
|
+
保留 features,query/analyze 也应能报告它们。但 MVP 不把 feature set 加入
|
|
799
|
+
`CalcitTypeAnnotation::matches_with_bindings`、`matches_signature`、trait method selection
|
|
800
|
+
或 generic unification;也不要求因 FFI 修改核心类型的 `Eq`、`Ord` 和 `Hash` 语义。
|
|
801
|
+
|
|
802
|
+
编译器在普通类型检查完成后运行独立的 capability validation:遍历已经归属到 lexical
|
|
803
|
+
function body 的 `HostOperation`,检查该 body 的 feature set。这样 feature 丢失不会被
|
|
804
|
+
类型匹配“补救”,而是由保存实现体归属和 capability metadata 的专用路径解决。
|
|
805
|
+
|
|
806
|
+
callback 若自身包含 raw JS,它自己的实现体必须有 `:js-ffi`。把 callback 传入高阶函数
|
|
807
|
+
时,MVP 只按现有 Fn 参数与返回值规则进行类型匹配;不把 callback feature compatibility
|
|
808
|
+
加入 Fn subtype/unification。未来若确实需要限制 callback 可执行的 capability,应作为
|
|
809
|
+
类型检查后的单独 capability contract 提案,且不能改变 trait candidate 或泛型绑定结果。
|
|
810
|
+
|
|
811
|
+
普通 direct call 不要求 caller 拥有 callee 的 features;raw operation 只发生在 callee
|
|
812
|
+
已标记的实现体内。若未来需要表达传递副作用,应使用独立的 `:effects`,不要把
|
|
813
|
+
`:features` 同时解释成 effect row。
|
|
814
|
+
|
|
815
|
+
### strict mode 与 target policy
|
|
816
|
+
|
|
817
|
+
建议 entry 增加渐进配置:
|
|
818
|
+
|
|
819
|
+
```cirru.no-check
|
|
820
|
+
:feature-policy $ {}
|
|
821
|
+
:js-ffi :error
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
策略值可以是 `:allow`、`:warn`、`:error`。`js-ffi` 仓库自身和新项目直接使用
|
|
825
|
+
`:error`;旧项目可先使用 `:warn`。
|
|
826
|
+
|
|
827
|
+
`:ffi :backend` 或 definition target metadata 用于检查 browser/Node/native/WASM
|
|
828
|
+
availability。Backend 信息不参与普通 schema type matching,但调用不可用 binding 时必须
|
|
829
|
+
在 codegen 前失败。
|
|
830
|
+
|
|
831
|
+
当前实现把 entry target 独立保存,避免把 `:mode :js` 误当成 Node 或 browser:
|
|
832
|
+
|
|
833
|
+
```cirru.no-check
|
|
834
|
+
:browser $ {} (:mode :js) (:target :browser)
|
|
835
|
+
:node $ {} (:mode :native) (:target :node)
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
definition 的 `:ffi` metadata 可以声明 `(:target :browser)` 或
|
|
839
|
+
`(:target :node)`。typed external-object operation 和带该 metadata 的 raw host
|
|
840
|
+
wrapper 会在 codegen 前检查 selected entry;缺少 entry target 的旧项目保持兼容,暂不做
|
|
841
|
+
target-specific validation。`cr query def ns/name --json` 同时暴露 `ffi` metadata,包含
|
|
842
|
+
host name mapping,供 adapter 审计使用。
|
|
843
|
+
|
|
844
|
+
## external trait 的有限补充
|
|
845
|
+
|
|
846
|
+
### 不由 FFI 推动参数化 trait
|
|
847
|
+
|
|
848
|
+
本 RFC 不修改 `CalcitTrait` 的表示,不增加 applied-trait annotation,也不改变 `:where`、
|
|
849
|
+
TraitSet、member lookup、method selection 或 codegen hint 的泛型逻辑。`Promise<T>`、
|
|
850
|
+
`Iterator<T>`、`Event<Target>` 与 `MapLike<K, V>` 由 adapter 消费并规范化。
|
|
851
|
+
|
|
852
|
+
这是刻意的边界,而不是临时缺失:generic trait 或 associated type 会影响所有 Calcit
|
|
853
|
+
代码的 trait identity、impl coherence、method candidate 和泛型求解,不能作为 JS FFI
|
|
854
|
+
的附属功能引入。若以后独立 RFC 证明这类能力对普通 Calcit abstraction 同样必要,FFI
|
|
855
|
+
库可以随后复用,但本 RFC 的 gate、target validation 和 external lowering 不依赖它。
|
|
856
|
+
|
|
857
|
+
### writable fields
|
|
858
|
+
|
|
859
|
+
external trait field 默认 read-only。确实需要直接 mutation 时,在 `:ffi` 中声明:
|
|
860
|
+
|
|
861
|
+
```cirru.no-check
|
|
862
|
+
:ffi $ {}
|
|
863
|
+
:backend :js
|
|
864
|
+
:kind :external-object
|
|
865
|
+
:writable $ #{} :value :checked
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
`js-set` 或 typed set lowering 必须同时满足:
|
|
869
|
+
|
|
870
|
+
1. 字段存在;
|
|
871
|
+
2. 字段列在 `:writable`;
|
|
872
|
+
3. value type 匹配字段类型;
|
|
873
|
+
4. 当前函数有 `:js-ffi`。
|
|
874
|
+
|
|
875
|
+
如果 API 可以通过方法表达 mutation,优先定义 `.set-value!` wrapper,而不是开放字段写入。
|
|
876
|
+
`:writable` 只由 external-object lowering 和 capability validation 读取。普通 trait inference
|
|
877
|
+
仍然只看到 field type;不得为它增加 mutable field type、setter trait、variance 或新的
|
|
878
|
+
trait satisfaction 规则。
|
|
879
|
+
|
|
880
|
+
## 暂不扩展的类型
|
|
881
|
+
|
|
882
|
+
以下能力暂不增加专用 Calcit type:
|
|
883
|
+
|
|
884
|
+
- `JsArray<T>`:优先在边界转换成 `List<T>`;需要保留 identity 时使用非泛型的小型
|
|
885
|
+
external trait,并通过 typed adapter 读取元素;
|
|
886
|
+
- `JsPromise<T>`:在 adapter 中 await 后返回 `T` 或 `Result<T, E>`;
|
|
887
|
+
- `JsFunction`:使用完整 `Fn` schema;未知签名才使用裸 `Fn`;
|
|
888
|
+
- `null` 与 `undefined` 的类型级区分:默认继续使用 `JsNullish<T>`,只有确有业务语义时
|
|
889
|
+
通过显式 predicate 转换为 Calcit Enum;
|
|
890
|
+
- JavaScript number 的整数/浮点细分:除非 Calcit 自身增加 numeric type,否则由
|
|
891
|
+
decoder 做范围检查;
|
|
892
|
+
- TypeScript literal/union:使用 Enum 与 wrapper conversion。
|
|
893
|
+
|
|
894
|
+
## `unsafe-coerce` 与验证
|
|
895
|
+
|
|
896
|
+
`unsafe-coerce` 只提供 trusted evidence,不提供 runtime validation。规则如下:
|
|
897
|
+
|
|
898
|
+
- 从 `JsObject`/`JsNullish<_>` 到 primitive、Struct、Enum 或 external trait 的 coercion
|
|
899
|
+
只能出现在 `:js-ffi` adapter;
|
|
900
|
+
- coercion 到 Struct/Enum 仅用于已经由 decoder 验证过的值;否则 wrapper 应返回
|
|
901
|
+
`Result<T, JsError>`;
|
|
902
|
+
- query/analyze 应能报告 host coercion 的 source type、target type 和 location;
|
|
903
|
+
- 普通业务 namespace 不直接出现 host coercion。
|
|
904
|
+
|
|
905
|
+
未来如果增加 `:unsafe` feature,应要求 host coercion 同时具有 `:js-ffi` 与 `:unsafe`;
|
|
906
|
+
在此之前先由 `:js-ffi` 统一管理。
|
|
907
|
+
|
|
908
|
+
## 迁移方案
|
|
909
|
+
|
|
910
|
+
### Phase 0:仅使用现有类型整理 `js-ffi` 库
|
|
911
|
+
|
|
912
|
+
- 新增 `js-ffi.types`;
|
|
913
|
+
- 将 probe Map 改为 Struct;
|
|
914
|
+
- effect wrapper 从 `Dynamic` 改为 `Unit`;
|
|
915
|
+
- nullable host result 用 `JsNullish<T>`,对外转换为 `Option<T>`;
|
|
916
|
+
- exception/rejection 转为 `Result<T, JsError>`;
|
|
917
|
+
- 为 Storage、Document、Location、Process 等稳定能力声明非泛型 external trait;
|
|
918
|
+
- 开启 `--warn-dyn-method`,消除可建模的裸字段访问。
|
|
919
|
+
|
|
920
|
+
本阶段不修改核心 type annotation、trait identity、trait matching 或泛型推断。
|
|
921
|
+
|
|
922
|
+
### Phase 1:完整 capability gate
|
|
923
|
+
|
|
924
|
+
- 引入统一 `HostOperation` 分类;
|
|
925
|
+
- 覆盖裸 `js/*`、native member access、construct/await 和 external trait lowering;
|
|
926
|
+
- 增加 `:feature-policy`;
|
|
927
|
+
- 先 warning,再在 `js-ffi` 自身切到 error。
|
|
928
|
+
|
|
929
|
+
HostOperation 分类与 `:js-ffi` 检查是独立 validation,不进入
|
|
930
|
+
`matches_with_bindings`、trait candidate 或 generic unification。
|
|
931
|
+
|
|
932
|
+
### Phase 2:capability metadata 与 target 检查
|
|
933
|
+
|
|
934
|
+
- runtime function 与 anonymous callback 的实现体归属保留 features;
|
|
935
|
+
- query/analyze 报告 feature、host operation 与 target;
|
|
936
|
+
- browser/Node/native/WASM binding 在 codegen 前进行 availability validation;
|
|
937
|
+
- 如需 callback capability contract,单独设计 post-type-check 规则;
|
|
938
|
+
- effects graph 单独记录传递 effects,不改变 `:features` 的 body capability 语义。
|
|
939
|
+
|
|
940
|
+
### Phase 3:有限 lowering 完善
|
|
941
|
+
|
|
942
|
+
- writable field metadata;
|
|
943
|
+
- external member name 与 target availability 的查询输出;
|
|
944
|
+
- 保持 external-object 分支由显式 `:ffi` 元数据触发。
|
|
945
|
+
|
|
946
|
+
generic trait、associated type、effect row 和 FFI 专用 type constructor 都不属于本 RFC
|
|
947
|
+
的迁移阶段。
|
|
948
|
+
|
|
949
|
+
## 验收标准
|
|
950
|
+
|
|
951
|
+
1. 未标记函数中的任何 raw `js/*`、`.!`、`.-`、construct、await 或 external lowering
|
|
952
|
+
都产生 `E_JS_FFI_FEATURE_REQUIRED`。
|
|
953
|
+
2. `js-ffi` 公共 wrapper 不向业务代码暴露无理由的 `Dynamic`。
|
|
954
|
+
3. probe、错误和状态结果使用命名 Struct/Enum。
|
|
955
|
+
4. `JsNullish<T>` 不会静默匹配 `Option<T>`,存在性检查后仍保留 payload type。
|
|
956
|
+
5. external field/method 的类型和 host name 可由 `cr query` 查询。
|
|
957
|
+
6. 未声明 writable 的 external field 无法被 typed write。
|
|
958
|
+
7. 带 `:js-ffi` 的匿名 callback 能保留实现体归属,capability validation 能检查其 raw
|
|
959
|
+
operation;Fn 类型匹配结果不因 feature 改变。
|
|
960
|
+
8. browser binding 在 Node target、Node binding 在 browser target 时于 codegen 前失败。
|
|
961
|
+
9. 所有 RFC 示例可由 Markdown/Cirru 检查工具解析,Node 与浏览器 smoke test 继续通过。
|
|
962
|
+
10. 没有 `:ffi` metadata 的普通 trait 不进入 external-object lowering。
|
|
963
|
+
11. 增加 FFI metadata 不改变普通 trait satisfaction、method candidate 或泛型绑定结果。
|
|
964
|
+
12. 不使用 FFI 的现有 fixtures 在类型推断、诊断与 codegen 上保持行为一致。
|
|
965
|
+
|
|
966
|
+
## 决策
|
|
967
|
+
|
|
968
|
+
本 RFC 建议立即接受:
|
|
969
|
+
|
|
970
|
+
- 继续以 Struct/Enum/trait/Fn schema 为主,不新增 TypeScript 式结构类型;
|
|
971
|
+
- raw JS 统一受 `:js-ffi` gate 管控;
|
|
972
|
+
- external field 默认只读;
|
|
973
|
+
- FFI wrapper 对外优先返回 normalized Calcit data;
|
|
974
|
+
- `:features` 与未来 `:effects` 分离;
|
|
975
|
+
- capability/target validation 与核心类型匹配分离;
|
|
976
|
+
- generic trait、associated type 与 FFI 专用核心类型不属于本 RFC。
|
|
977
|
+
|
|
978
|
+
建议先验证真实用例再接受:
|
|
979
|
+
|
|
980
|
+
- target-specific opaque wrapper 或 type slot;
|
|
981
|
+
- `:unsafe` 独立 capability;
|
|
982
|
+
- callback capability contract。
|
|
983
|
+
|
|
984
|
+
generic trait 与 associated type 若要重新讨论,必须进入独立、面向 Calcit 通用类型系统
|
|
985
|
+
的 RFC,并提供非 FFI 用例与 trait inference 回归证明;不作为本 RFC 的后续 phase。
|
|
986
|
+
|
|
987
|
+
## 相关资料
|
|
988
|
+
|
|
989
|
+
- Calcit `RFCs/07-08-ffi-features-and-js-object-type-rfc.md`
|
|
990
|
+
- Calcit `RFCs/08-08-cross-backend-host-ffi-contracts-rfc.md`
|
|
991
|
+
- Calcit `RFCs/07-31-unsafe-coerce-driven-static-type-boundary-plan.md`
|
|
992
|
+
- Calcit `docs/features/js-interop.md`
|
|
993
|
+
- `js-ffi` README 与 `calcit.cirru`
|
package/RFCs/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# RFC 整理索引
|
|
2
2
|
|
|
3
|
-
更新时间:2026-08-
|
|
3
|
+
更新时间:2026-08-18
|
|
4
4
|
|
|
5
5
|
## 目录原则
|
|
6
6
|
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
| `08-04-strict-cirru-edn-decoding-rfc.md` | Implemented | Phase 1:`parse-cirru-edn-as` 严格类型化反序列化、无 Dynamic 的 `EdnDecoderGraph`、名义身份与 Native/JS 一致性。 |
|
|
45
45
|
| `08-05-systematic-nil-reduction-rfc.md` | Partial | 类型驱动减少 nil:先拆分可省略参数与 nullable 值,再迁移至 Option/Result 并逐步收紧 typed code。 |
|
|
46
46
|
| `08-08-cross-backend-host-ffi-contracts-rfc.md` | Draft | 统一 JS/native/WASM/WASI 的逻辑 FFI 契约与诊断,ABI transport 保持 backend-specific;首个完整 shape consumer 为 JS/DOM。 |
|
|
47
|
+
| `08-18-calcit-typed-js-ffi-boundary-rfc.md` | Draft | 在现有 Struct/Enum/Fn/trait 上补齐 JS capability gate 与 target validation;FFI metadata 不进入普通 trait 匹配和泛型推断。 |
|
|
47
48
|
|
|
48
49
|
## 已执行的清理
|
|
49
50
|
|
package/build.rs
CHANGED
|
@@ -57,6 +57,10 @@ pub struct SnapshotEntry {
|
|
|
57
57
|
pub modules: Vec<String>,
|
|
58
58
|
#[serde(default, rename = "type-slots")]
|
|
59
59
|
pub type_slots: HashMap<String, String>,
|
|
60
|
+
#[serde(default, rename = "feature-policy")]
|
|
61
|
+
pub feature_policy: HashMap<String, String>,
|
|
62
|
+
#[serde(default)]
|
|
63
|
+
pub target: Option<String>,
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
@@ -139,9 +143,27 @@ fn parse_snapshot_entry(data: Edn) -> Result<SnapshotEntry, String> {
|
|
|
139
143
|
description: from_edn(map.get_or_nil("description")).map_err(|e| format!("entry `:description`: {e}"))?,
|
|
140
144
|
modules: from_edn(map.get_or_nil("modules")).map_err(|e| format!("entry `:modules`: {e}"))?,
|
|
141
145
|
type_slots: from_edn(map.get_or_nil("type-slots")).map_err(|e| format!("entry `:type-slots`: {e}"))?,
|
|
146
|
+
feature_policy: match map.get_or_nil("feature-policy") {
|
|
147
|
+
Edn::Nil => HashMap::new(),
|
|
148
|
+
value => parse_feature_policy(value)?,
|
|
149
|
+
},
|
|
150
|
+
target: match map.get_or_nil("target") {
|
|
151
|
+
Edn::Nil => None,
|
|
152
|
+
value => Some(from_edn(value).map_err(|e| format!("entry `:target`: {e}"))?),
|
|
153
|
+
},
|
|
142
154
|
})
|
|
143
155
|
}
|
|
144
156
|
|
|
157
|
+
fn parse_feature_policy(data: Edn) -> Result<HashMap<String, String>, String> {
|
|
158
|
+
let map = data.view_map().map_err(|e| format!("entry `:feature-policy` must be a map: {e}"))?;
|
|
159
|
+
let text = |value: &Edn| match value {
|
|
160
|
+
Edn::Tag(tag) => Ok(tag.ref_str().to_owned()),
|
|
161
|
+
Edn::Str(value) | Edn::Symbol(value) => Ok(value.to_string().trim_start_matches(':').to_owned()),
|
|
162
|
+
other => Err(format!("entry `:feature-policy` expects tag/string/symbol values, got {other:?}")),
|
|
163
|
+
};
|
|
164
|
+
map.0.iter().map(|(key, value)| Ok((text(key)?, text(value)?))).collect()
|
|
165
|
+
}
|
|
166
|
+
|
|
145
167
|
fn parse_entries(data: Edn) -> Result<HashMap<String, SnapshotEntry>, String> {
|
|
146
168
|
let map = data.view_map().map_err(|e| format!("entries must be a map: {e}"))?;
|
|
147
169
|
let mut entries = HashMap::with_capacity(map.0.len());
|
|
@@ -576,6 +598,8 @@ fn main() {
|
|
|
576
598
|
description: String::new(),
|
|
577
599
|
modules: configs.modules.clone(),
|
|
578
600
|
type_slots: configs.type_slots.clone(),
|
|
601
|
+
feature_policy: HashMap::new(),
|
|
602
|
+
target: None,
|
|
579
603
|
},
|
|
580
604
|
);
|
|
581
605
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Project module runtime resolution
|
|
2
|
+
|
|
3
|
+
- `caps` already stores immutable dependency revisions in `~/.config/calcit/modules/.store/` and builds the project's `.calcit/modules/` link view.
|
|
4
|
+
- Runtime loaders must consume only that project view. Retaining a global module fallback can silently run a dependency revision that was not selected by the project's dependency graph.
|
|
5
|
+
- Centralize the project module directory calculation in `calcit::project_module_folder`, then use it for normal execution, WASM codegen, queries, configuration inspection, markdown checks, call-graph comparison, and the Cirru integration harness.
|
|
6
|
+
- Keep explicitly relative and absolute module paths working; only package-style module paths are constrained to the project view.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Visible module versions and cache cleanup
|
|
2
|
+
|
|
3
|
+
- Store immutable module revisions under `~/.config/calcit/modules/versions/` instead of a hidden `.store/` directory, retaining the owner, repository, and resolved commit in the path.
|
|
4
|
+
- Write `metadata.txt` beside each cached revision so the requested reference remains inspectable and global cleanup can select the highest SemVer release without a network request. When one commit is observed through multiple refs, retain the highest observed SemVer ref for cleanup ordering.
|
|
5
|
+
- Add `caps clean` as an explicit global cleanup command. It preserves one newest revision per module (SemVer takes precedence; materialization time breaks non-SemVer ties) and removes older revision directories.
|
|
6
|
+
- Rewrite `~/.config/calcit/modules/AGENTS.md` on each `caps` invocation. The guide treats `versions/` as immutable cache data and directs dependency changes through a repository commit, new tag, dependency update, and reinstall.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Module caches root
|
|
2
|
+
|
|
3
|
+
- Move the global immutable cache from the nested `modules/versions/` proposal to the clearer sibling path `~/.config/calcit/module-caches/`.
|
|
4
|
+
- Keep `~/.config/calcit/modules/` reserved for the legacy module root and per-project link vocabulary; `module-caches/AGENTS.md` is the authoritative generated guidance for cached revisions.
|
|
5
|
+
- Derive the cache root as a sibling of `CALCIT_MODULES_DIR`, so custom test and CI roots preserve the same `modules/` plus `module-caches/` layout.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Module cache review follow-up
|
|
2
|
+
|
|
3
|
+
- Scope `cr docs` module documentation and scope discovery to the current project's `.calcit/modules/` directory, removing the final global fallback.
|
|
4
|
+
- Register each successfully materialized project module view in `module-caches/projects/`; global cleanup preserves every cached revision that remains linked by one of those views.
|
|
5
|
+
- When a cached commit is later resolved through a SemVer tag, refresh its metadata with the highest observed SemVer ref so cleanup ordering stays correct.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# Module path resolution documentation
|
|
2
|
+
|
|
3
|
+
- Clarify that project-local `.calcit/modules/` links are mandatory only for package-style module paths.
|
|
4
|
+
- Preserve the documented behavior of explicit relative paths (resolved from the snapshot) and absolute paths (resolved directly).
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Module cache review follow-up
|
|
2
|
+
|
|
3
|
+
- Added a shared inter-process metadata lock for cache revision metadata updates and cleanup scans, preventing concurrent `caps` processes from losing the highest observed SemVer reference.
|
|
4
|
+
- Made project-view installation transactional across module links and `caps-state.cirru`; registration failures now restore the previous view and state before returning an error.
|
|
5
|
+
- Added concurrency and rollback regression tests, and routed module documentation tests through project-root-aware resolver helpers so the production path is covered.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Module cache locking follow-up
|
|
2
|
+
|
|
3
|
+
- Replaced removable lock files with OS-managed `fs4` locks, so ownership is released when the handle or process exits on every supported platform.
|
|
4
|
+
- Held the cache lock across revision installation and metadata publication, and added a project-scoped lock spanning module view activation, state replacement, registration, rollback, and cleanup.
|
|
5
|
+
- Isolated the documentation fallback regression test against the actual `$HOME/.config/calcit/modules` layout without leaking the test environment.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# 2026-08-17 14:38 — Serialize documentation HOME access
|
|
2
|
+
|
|
3
|
+
- Shared the documentation test HOME mutex between `TestHome` and tests that read the guidebook through the process environment.
|
|
4
|
+
- Guarded `collect_docs_for_query_uses_guidebook_without_module` so it cannot race with tests that temporarily replace `HOME`.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Release 0.13.20
|
|
2
|
+
|
|
3
|
+
- Publish the typed JavaScript FFI capability boundary and browser/Node target validation from PR #367.
|
|
4
|
+
- Reorganize and expand the JavaScript FFI documentation, including adapter design, diagnostics, and release checks.
|
|
5
|
+
- Keep Cargo and npm package versions synchronized at `0.13.20` for the direct PR-branch release.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Make dependency-boundary audits discoverable to agents
|
|
2
|
+
|
|
3
|
+
## Knowledge points
|
|
4
|
+
|
|
5
|
+
- `:dependencies`, `:dev-dependencies`, configured entry modules, and statically reachable source paths
|
|
6
|
+
answer different questions. An installed module must not be classified as runtime-only from its presence
|
|
7
|
+
under `.calcit/modules/`.
|
|
8
|
+
- Inspect root declaration intent in `deps.cirru`; use `caps tree` and `caps why <owner/repo>` to explain
|
|
9
|
+
the resolved recursive graph. The current display combines both root groups, so it cannot be the
|
|
10
|
+
authority for runtime versus development declaration intent.
|
|
11
|
+
- Use `cr config modules [--entry <name>]` to inspect each selected entry's module configuration, then
|
|
12
|
+
run `cr --check-only` for default and every release/CI entry. Named entries do not inherit default
|
|
13
|
+
modules.
|
|
14
|
+
- `docs check-md` defaults to modules from the default entry. Documentation-only dependencies require
|
|
15
|
+
appropriate explicit module paths through repeatable `--dep`; these checks, like entry preprocessing,
|
|
16
|
+
are static evidence and do not prove dynamic loading or external consumer use.
|
|
17
|
+
|
|
18
|
+
## Validation
|
|
19
|
+
|
|
20
|
+
- `cr docs check-md docs/run/load-deps.md --entry calcit/test.cirru --failures-only`
|
|
21
|
+
- `cr docs check-md docs/CalcitAgent.md --entry calcit/test.cirru --failures-only`
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# Address dependency audit documentation review
|
|
2
|
+
|
|
3
|
+
- Clarified that `docs check-md --entry` takes a snapshot filename, unlike named-entry selection in `config modules` and top-level `--check-only`.
|
|
4
|
+
- Recorded Markdown validation for both changed dependency-audit documents.
|
package/lib/package.json
CHANGED