@lokvis/runtime 0.2.0-beta.0 → 0.2.0
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/LICENSE +21 -0
- package/dist/asset-store.d.ts +5 -2
- package/dist/asset-store.d.ts.map +1 -1
- package/dist/asset-store.js +93 -3
- package/dist/asset-store.js.map +1 -1
- package/dist/batch-processor.d.ts +200 -0
- package/dist/batch-processor.d.ts.map +1 -0
- package/dist/batch-processor.js +505 -0
- package/dist/batch-processor.js.map +1 -0
- package/dist/degradation.d.ts +109 -0
- package/dist/degradation.d.ts.map +1 -0
- package/dist/degradation.js +184 -0
- package/dist/degradation.js.map +1 -0
- package/dist/history-store.d.ts +76 -0
- package/dist/history-store.d.ts.map +1 -0
- package/dist/history-store.js +109 -0
- package/dist/history-store.js.map +1 -0
- package/dist/idb-asset-store.js +1 -1
- package/dist/idb-asset-store.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/memory-guard.d.ts +109 -0
- package/dist/memory-guard.d.ts.map +1 -0
- package/dist/memory-guard.js +161 -0
- package/dist/memory-guard.js.map +1 -0
- package/dist/opfs-asset-store.d.ts +35 -4
- package/dist/opfs-asset-store.d.ts.map +1 -1
- package/dist/opfs-asset-store.js +94 -10
- package/dist/opfs-asset-store.js.map +1 -1
- package/dist/runtime.d.ts +111 -3
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +311 -13
- package/dist/runtime.js.map +1 -1
- package/dist/types.d.ts +74 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/worker-host.d.ts +13 -0
- package/dist/worker-host.d.ts.map +1 -1
- package/dist/worker-host.js +47 -15
- package/dist/worker-host.js.map +1 -1
- package/dist/worker-protocol.d.ts +10 -1
- package/dist/worker-protocol.d.ts.map +1 -1
- package/dist/worker-protocol.js.map +1 -1
- package/dist/workflow-builder.d.ts +124 -0
- package/dist/workflow-builder.d.ts.map +1 -0
- package/dist/workflow-builder.js +240 -0
- package/dist/workflow-builder.js.map +1 -0
- package/package.json +3 -2
- package/src/asset-store.ts +103 -3
- package/src/batch-processor.ts +699 -0
- package/src/degradation.ts +241 -0
- package/src/history-store.ts +159 -0
- package/src/idb-asset-store.ts +1 -1
- package/src/index.ts +5 -0
- package/src/memory-guard.ts +202 -0
- package/src/opfs-asset-store.ts +118 -10
- package/src/runtime.ts +345 -9
- package/src/types.ts +70 -0
- package/src/worker-host.ts +56 -16
- package/src/worker-protocol.ts +11 -1
- package/src/workflow-builder.ts +303 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 能力降级阶梯(W3.4)
|
|
3
|
+
*
|
|
4
|
+
* 当 MemoryGuard 报告内存压力时,运行时需要按阶梯选择处理策略,
|
|
5
|
+
* 而非"要么全量要么崩溃"。本模块实现四级降级政策(whitepaper T1):
|
|
6
|
+
*
|
|
7
|
+
* L1-full 完整质量——压力低/中,正常处理。
|
|
8
|
+
* L2-tiled 分片处理——压力高,把输入按 tile 切分 + 中间结果溢出 OPFS,
|
|
9
|
+
* 内存里同时只持有一个 tile。
|
|
10
|
+
* L3-degraded 降级输出——压力 critical,先缩小到 maxEdge 再处理,
|
|
11
|
+
* 降低质量,避免 OOM;产出可用但非最佳。
|
|
12
|
+
* L4-reject 拒绝 + 引导——即使降级也无法安全处理(如单张图解码后即超
|
|
13
|
+
* critical 且无法再缩),抛 DegradationRejectedError 携带
|
|
14
|
+
* 用户可读的引导(换更小源图 / 关闭其他 tab / 升级浏览器)。
|
|
15
|
+
*
|
|
16
|
+
* 设计取舍:
|
|
17
|
+
* - 本模块只做"决策"(policy),不直接执行图像操作。执行由 plugin-image
|
|
18
|
+
* / executor 据 decision 调整参数后委托 engine-image。
|
|
19
|
+
* - 决策是纯函数(pickDegradation),便于单测与确定性回归。
|
|
20
|
+
* - L2 是否可走取决于操作是否支持 tiling(canTile);canvas 引擎对几何
|
|
21
|
+
* 变换可 tile,但对依赖全图的滤镜(模糊)与水印 tile 无意义 → 调用方
|
|
22
|
+
* 据操作语义传入 canTile。
|
|
23
|
+
*/
|
|
24
|
+
/** L3 降级输出时建议的最大边长(像素)——4K,兼顾可用性与内存 */
|
|
25
|
+
export const DEGRADED_MAX_EDGE = 4096;
|
|
26
|
+
/** L3 降级输出时建议的质量(0-100)——70 是视觉可接受的下限 */
|
|
27
|
+
export const DEGRADED_QUALITY = 70;
|
|
28
|
+
/**
|
|
29
|
+
* 根据内存压力与操作能力选择降级策略(纯函数)。
|
|
30
|
+
*
|
|
31
|
+
* 决策矩阵:
|
|
32
|
+
* pressure canTile canSpill → level
|
|
33
|
+
* low/elevated * * → L1-full
|
|
34
|
+
* high true true → L2-tiled(spill)
|
|
35
|
+
* high true false → L2-tiled(无 spill,仅靠 tile 控峰值)
|
|
36
|
+
* high false * → L3-degraded(降到 maxEdge+quality)
|
|
37
|
+
* critical * * → L3-degraded(若 decodedBytes 缩放后仍超
|
|
38
|
+
* critical → L4-reject)
|
|
39
|
+
*
|
|
40
|
+
* L4 触发条件:pressure=critical 且 decodedBytes 已按 maxEdge 缩放后
|
|
41
|
+
* 估算仍 >= budget 的 critical 阈值(0.95),即"再怎么缩也会撑爆"。
|
|
42
|
+
*
|
|
43
|
+
* **调用方注意**:当 pressure=critical 且 canSpill=true 时,若不提供
|
|
44
|
+
* decodedBytes,函数将跳过 L4 判定直接返回 L3-degraded。这意味着对于
|
|
45
|
+
* 超大单张图(无法通过 spill 分块),L3 缩放后仍可能 OOM。
|
|
46
|
+
* 建议:尽可能通过 estimateDecodedBytes(width, height) 提供 decodedBytes,
|
|
47
|
+
* 以提高 L4 判定精度,避免静默降级后崩溃。
|
|
48
|
+
*/
|
|
49
|
+
export function pickDegradation(ctx) {
|
|
50
|
+
const { pressure, canTile, canSpill, inputBytes, budget, decodedBytes } = ctx;
|
|
51
|
+
// L1:压力低/中,完整处理
|
|
52
|
+
if (pressure === 'low' || pressure === 'elevated') {
|
|
53
|
+
return {
|
|
54
|
+
level: 'L1-full',
|
|
55
|
+
spill: false,
|
|
56
|
+
reason: `pressure=${pressure}, full quality`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// L4 预判:输入本身已超预算且无法溢出 → 直接拒绝(避免无谓降级后仍 OOM)
|
|
60
|
+
// 仅当输入 blob 已 > budget 且不能溢出时,降级也救不了
|
|
61
|
+
if (pressure === 'critical' && inputBytes > budget && !canSpill) {
|
|
62
|
+
return rejectDecision(inputBytes, budget, 'input exceeds budget and OPFS spill unavailable');
|
|
63
|
+
}
|
|
64
|
+
if (pressure === 'high') {
|
|
65
|
+
// L2:可 tile 则分片处理(优先 spill 以降峰值)
|
|
66
|
+
if (canTile) {
|
|
67
|
+
return {
|
|
68
|
+
level: 'L2-tiled',
|
|
69
|
+
spill: canSpill,
|
|
70
|
+
reason: `pressure=high, tile-based${canSpill ? ' + OPFS spill' : ''}`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// 不可 tile(模糊/水印等全图操作)→ L3 降级
|
|
74
|
+
return degradedDecision(canSpill, 'pressure=high but operation not tileable, degrade output');
|
|
75
|
+
}
|
|
76
|
+
// pressure === 'critical'
|
|
77
|
+
// 若提供 decodedBytes 且缩放到 maxEdge 后估算仍超 critical(0.95)→ L4
|
|
78
|
+
if (decodedBytes !== undefined) {
|
|
79
|
+
const scaledEstimate = scaleDecodedEstimate(decodedBytes, DEGRADED_MAX_EDGE);
|
|
80
|
+
if (scaledEstimate >= budget * 0.95) {
|
|
81
|
+
return rejectDecision(inputBytes, budget, `decoded ~${scaledEstimate}B even after ${DEGRADED_MAX_EDGE}px cap exceeds critical`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// 否则 L3:降级输出
|
|
85
|
+
return degradedDecision(canSpill, 'pressure=critical, degrade output to fit budget');
|
|
86
|
+
}
|
|
87
|
+
/** L3 降级输出决策 */
|
|
88
|
+
function degradedDecision(spill, reason) {
|
|
89
|
+
return {
|
|
90
|
+
level: 'L3-degraded',
|
|
91
|
+
maxEdge: DEGRADED_MAX_EDGE,
|
|
92
|
+
quality: DEGRADED_QUALITY,
|
|
93
|
+
spill,
|
|
94
|
+
reason,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/** L4 拒绝决策 */
|
|
98
|
+
function rejectDecision(inputBytes, budget, reason) {
|
|
99
|
+
return {
|
|
100
|
+
level: 'L4-reject',
|
|
101
|
+
spill: false,
|
|
102
|
+
reason: `reject: ${reason} (input=${inputBytes}B, budget=${budget}B)`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 估算把位图缩放到 maxEdge 后的解码字节数(按面积比例缩放)。
|
|
107
|
+
* 用于 L4 判定:若缩放后仍超 critical 则无救。
|
|
108
|
+
*/
|
|
109
|
+
function scaleDecodedEstimate(decodedBytes, maxEdge) {
|
|
110
|
+
// decodedBytes ∝ width*height。缩放到 maxEdge 按长边,面积比 = (max/orig)^2。
|
|
111
|
+
// 但我们不知原始 width/height,只有 decodedBytes。保守起见:若 decodedBytes
|
|
112
|
+
// 已大于 maxEdge*maxEdge*4(即缩放后的占用),说明原图比 maxEdge 大,缩放有效;
|
|
113
|
+
// 否则原图已 <= maxEdge,缩放无变化,估算 = decodedBytes。
|
|
114
|
+
const scaledCap = maxEdge * maxEdge * 4;
|
|
115
|
+
return Math.min(decodedBytes, scaledCap);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* L4 拒绝时抛出的错误,携带用户可读的引导建议。
|
|
119
|
+
*
|
|
120
|
+
* 引导文案面向终端用户(经 UI 展示),而非开发者堆栈。
|
|
121
|
+
*/
|
|
122
|
+
export class DegradationRejectedError extends Error {
|
|
123
|
+
level = 'L4-reject';
|
|
124
|
+
inputBytes;
|
|
125
|
+
budget;
|
|
126
|
+
/** 用户可读的引导建议(UI 可直接渲染) */
|
|
127
|
+
guide;
|
|
128
|
+
constructor(inputBytes, budget, reason) {
|
|
129
|
+
super(`Operation rejected to avoid out-of-memory: ${reason}. ` +
|
|
130
|
+
`Input ${formatBytes(inputBytes)} exceeds memory budget ${formatBytes(budget)}.`);
|
|
131
|
+
this.name = 'DegradationRejectedError';
|
|
132
|
+
this.inputBytes = inputBytes;
|
|
133
|
+
this.budget = budget;
|
|
134
|
+
this.guide = buildGuide(inputBytes, budget);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/** 构造用户引导建议(按优先级) */
|
|
138
|
+
function buildGuide(inputBytes, budget) {
|
|
139
|
+
const guide = [];
|
|
140
|
+
if (inputBytes > budget) {
|
|
141
|
+
guide.push(`This image (${formatBytes(inputBytes)}) is larger than the in-browser memory budget ` +
|
|
142
|
+
`(${formatBytes(budget)}). Try a smaller source image.`);
|
|
143
|
+
}
|
|
144
|
+
guide.push('Close other browser tabs to free memory, then retry.');
|
|
145
|
+
guide.push('For very large images, use the desktop app or process in smaller batches.');
|
|
146
|
+
return guide;
|
|
147
|
+
}
|
|
148
|
+
/** 字节数 → 人类可读(MB/GB) */
|
|
149
|
+
export function formatBytes(bytes) {
|
|
150
|
+
if (!Number.isFinite(bytes) || bytes < 0)
|
|
151
|
+
return '0B';
|
|
152
|
+
if (bytes < 1024)
|
|
153
|
+
return `${bytes}B`;
|
|
154
|
+
if (bytes < 1024 * 1024)
|
|
155
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
156
|
+
if (bytes < 1024 * 1024 * 1024)
|
|
157
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
158
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)}GB`;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* 把降级决策应用到 resize 参数(若 L3):确保输出最大边不超过 maxEdge。
|
|
162
|
+
*
|
|
163
|
+
* 调用方在 L3 时据此调整 params.width/height,再交给 engine.resize。
|
|
164
|
+
* L1/L2/L4 时原样返回 params。
|
|
165
|
+
*/
|
|
166
|
+
export function applyDegradationToResizeParams(params, decision) {
|
|
167
|
+
if (decision.level !== 'L3-degraded' || decision.maxEdge === undefined) {
|
|
168
|
+
return params;
|
|
169
|
+
}
|
|
170
|
+
const next = { ...params };
|
|
171
|
+
// 只要任一已指定的边超过 maxEdge,就注入 maxEdge 上限保护内存。
|
|
172
|
+
// undefined 视为"未约束",不参与 veto;已指定且 ≤ maxEdge 的边才排除 maxEdge。
|
|
173
|
+
const w = next.width;
|
|
174
|
+
const h = next.height;
|
|
175
|
+
const wOver = w !== undefined && w > decision.maxEdge;
|
|
176
|
+
const hOver = h !== undefined && h > decision.maxEdge;
|
|
177
|
+
const wUnset = w === undefined;
|
|
178
|
+
const hUnset = h === undefined;
|
|
179
|
+
if (wOver || hOver || (wUnset && hUnset)) {
|
|
180
|
+
next.maxEdge = decision.maxEdge;
|
|
181
|
+
}
|
|
182
|
+
return next;
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=degradation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"degradation.js","sourceRoot":"","sources":["../src/degradation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAOH,uCAAuC;AACvC,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC;AACtC,yCAAyC;AACzC,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAsCnC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,eAAe,CAAC,GAAuB;IACrD,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,GAAG,CAAC;IAE9E,gBAAgB;IAChB,IAAI,QAAQ,KAAK,KAAK,IAAI,QAAQ,KAAK,UAAU,EAAE,CAAC;QAClD,OAAO;YACL,KAAK,EAAE,SAAS;YAChB,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE,YAAY,QAAQ,gBAAgB;SAC7C,CAAC;IACJ,CAAC;IAED,2CAA2C;IAC3C,qCAAqC;IACrC,IAAI,QAAQ,KAAK,UAAU,IAAI,UAAU,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChE,OAAO,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,iDAAiD,CAAC,CAAC;IAC/F,CAAC;IAED,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACxB,iCAAiC;QACjC,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO;gBACL,KAAK,EAAE,UAAU;gBACjB,KAAK,EAAE,QAAQ;gBACf,MAAM,EAAE,4BAA4B,QAAQ,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE;aACtE,CAAC;QACJ,CAAC;QACD,6BAA6B;QAC7B,OAAO,gBAAgB,CAAC,QAAQ,EAAE,0DAA0D,CAAC,CAAC;IAChG,CAAC;IAED,0BAA0B;IAC1B,yDAAyD;IACzD,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,cAAc,GAAG,oBAAoB,CAAC,YAAY,EAAE,iBAAiB,CAAC,CAAC;QAC7E,IAAI,cAAc,IAAI,MAAM,GAAG,IAAI,EAAE,CAAC;YACpC,OAAO,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,cAAc,gBAAgB,iBAAiB,yBAAyB,CAAC,CAAC;QAClI,CAAC;IACH,CAAC;IACD,aAAa;IACb,OAAO,gBAAgB,CAAC,QAAQ,EAAE,iDAAiD,CAAC,CAAC;AACvF,CAAC;AAED,gBAAgB;AAChB,SAAS,gBAAgB,CAAC,KAAc,EAAE,MAAc;IACtD,OAAO;QACL,KAAK,EAAE,aAAa;QACpB,OAAO,EAAE,iBAAiB;QAC1B,OAAO,EAAE,gBAAgB;QACzB,KAAK;QACL,MAAM;KACP,CAAC;AACJ,CAAC;AAED,cAAc;AACd,SAAS,cAAc,CAAC,UAAkB,EAAE,MAAc,EAAE,MAAc;IACxE,OAAO;QACL,KAAK,EAAE,WAAW;QAClB,KAAK,EAAE,KAAK;QACZ,MAAM,EAAE,WAAW,MAAM,WAAW,UAAU,aAAa,MAAM,IAAI;KACtE,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,oBAAoB,CAAC,YAAoB,EAAE,OAAe;IACjE,kEAAkE;IAClE,2DAA2D;IAC3D,uDAAuD;IACvD,4CAA4C;IAC5C,MAAM,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,CAAC,CAAC;IACxC,OAAO,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;AAC3C,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IACxC,KAAK,GAAG,WAAoB,CAAC;IAC7B,UAAU,CAAS;IACnB,MAAM,CAAS;IACxB,0BAA0B;IACjB,KAAK,CAAW;IAEzB,YAAY,UAAkB,EAAE,MAAc,EAAE,MAAc;QAC5D,KAAK,CACH,8CAA8C,MAAM,IAAI;YACtD,SAAS,WAAW,CAAC,UAAU,CAAC,0BAA0B,WAAW,CAAC,MAAM,CAAC,GAAG,CACnF,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;QACvC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC9C,CAAC;CACF;AAED,qBAAqB;AACrB,SAAS,UAAU,CAAC,UAAkB,EAAE,MAAc;IACpD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,UAAU,GAAG,MAAM,EAAE,CAAC;QACxB,KAAK,CAAC,IAAI,CACR,eAAe,WAAW,CAAC,UAAU,CAAC,gDAAgD;YACpF,IAAI,WAAW,CAAC,MAAM,CAAC,gCAAgC,CAC1D,CAAC;IACJ,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC;IACnE,KAAK,CAAC,IAAI,CAAC,2EAA2E,CAAC,CAAC;IACxF,OAAO,KAAK,CAAC;AACf,CAAC;AAED,wBAAwB;AACxB,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,GAAG,KAAK,GAAG,CAAC;IACrC,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IACjE,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IACjF,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,8BAA8B,CAC5C,MAA+B,EAC/B,QAA6B;IAE7B,IAAI,QAAQ,CAAC,KAAK,KAAK,aAAa,IAAI,QAAQ,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACvE,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,MAAM,IAAI,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;IAC3B,0CAA0C;IAC1C,2DAA2D;IAC3D,MAAM,CAAC,GAAG,IAAI,CAAC,KAA2B,CAAC;IAC3C,MAAM,CAAC,GAAG,IAAI,CAAC,MAA4B,CAAC;IAC5C,MAAM,KAAK,GAAG,CAAC,KAAK,SAAS,IAAI,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC;IACtD,MAAM,KAAK,GAAG,CAAC,KAAK,SAAS,IAAI,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC;IACtD,MAAM,MAAM,GAAG,CAAC,KAAK,SAAS,CAAC;IAC/B,MAAM,MAAM,GAAG,CAAC,KAAK,SAAS,CAAC;IAC/B,IAAI,KAAK,IAAI,KAAK,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;IAClC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History Store —— 历史栈 IndexedDB 持久化(W7.2)
|
|
3
|
+
*
|
|
4
|
+
* 把每个工作流的 HistoryStack 快照(entries + cursor)连同 initialInputs /
|
|
5
|
+
* currentOutputs 一起持久化到独立 Dexie 库 `lokvis-history`,实现跨会话保留:
|
|
6
|
+
* 刷新页面后,undo/redo 历史与"当前输出"游标均可恢复(前提:outputs 引用的
|
|
7
|
+
* 资产仍在 OPFS/IDB 中,由 W6.6 元数据持久化保证)。
|
|
8
|
+
*
|
|
9
|
+
* 与 OpfsMetadataDatabase 同样的降级策略:IndexedDB 不可用时返回 undefined,
|
|
10
|
+
* runtime 退化为仅内存历史(刷新后丢失,与 W2 行为一致)。
|
|
11
|
+
*
|
|
12
|
+
* 持久化时机(由 runtime 驱动):
|
|
13
|
+
* - 历史变更(append/undo/redo/jumpTo/clear/reset):HistoryStack.onChanged 回调
|
|
14
|
+
* → runtime.persistHistory(workflowId) → save
|
|
15
|
+
* - 条目清空(reset/clear):save 检测到 entries 为空时改为 delete,避免残留空记录
|
|
16
|
+
* - disposeWorkflow:stack.reset() 触发 onChanged → persistHistory → delete
|
|
17
|
+
*
|
|
18
|
+
* 恢复时机:
|
|
19
|
+
* - createRuntime 工厂构造完 impl 后调 loadPersistedHistory() 预加载所有快照
|
|
20
|
+
*/
|
|
21
|
+
import Dexie, { type Table } from 'dexie';
|
|
22
|
+
import type { AssetId, HistoryEntry } from '@lokvis/schema';
|
|
23
|
+
/** 持久化的历史记录(每个工作流一条) */
|
|
24
|
+
export interface HistoryRecord {
|
|
25
|
+
/** 主键 = 工作流 ID */
|
|
26
|
+
workflowId: string;
|
|
27
|
+
/** 历史条目(按时间顺序) */
|
|
28
|
+
entries: HistoryEntry[];
|
|
29
|
+
/** 游标:-1 表示无已应用条目;i 表示第 i 条已应用 */
|
|
30
|
+
cursor: number;
|
|
31
|
+
/** 工作流初始输入 AssetId(undo 回到初始时使用) */
|
|
32
|
+
initialInputs: AssetId[];
|
|
33
|
+
/** 工作流当前输出 AssetId(undo/redo 后的"当前"状态) */
|
|
34
|
+
currentOutputs: AssetId[];
|
|
35
|
+
/** 最后更新时间戳(用于排查与潜在 TTL 清理) */
|
|
36
|
+
updatedAt: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 历史持久化数据库。
|
|
40
|
+
*
|
|
41
|
+
* 独立于 OpfsMetadataDatabase('lokvis-opfs-metadata')与 IdbAssetStore
|
|
42
|
+
* ('lokvis-assets'),避免与资产元数据冲突。表以 workflowId 为主键,
|
|
43
|
+
* 额外索引 updatedAt 便于未来按时间清理陈旧记录。
|
|
44
|
+
*/
|
|
45
|
+
export declare class HistoryDatabase extends Dexie {
|
|
46
|
+
history: Table<HistoryRecord, string>;
|
|
47
|
+
constructor(name?: string);
|
|
48
|
+
}
|
|
49
|
+
/** HistoryStore 抽象接口(便于测试注入 mock) */
|
|
50
|
+
export interface HistoryStore {
|
|
51
|
+
/** 保存(或覆盖)一条历史记录 */
|
|
52
|
+
save(record: HistoryRecord): Promise<void>;
|
|
53
|
+
/** 读取指定工作流的历史记录 */
|
|
54
|
+
load(workflowId: string): Promise<HistoryRecord | undefined>;
|
|
55
|
+
/** 读取全部历史记录(用于启动时预加载) */
|
|
56
|
+
loadAll(): Promise<HistoryRecord[]>;
|
|
57
|
+
/** 删除指定工作流的历史记录 */
|
|
58
|
+
delete(workflowId: string): Promise<void>;
|
|
59
|
+
/** 清空所有历史记录 */
|
|
60
|
+
clear(): Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
/** HistoryStore 工厂选项 */
|
|
63
|
+
export interface HistoryStoreOptions {
|
|
64
|
+
/** 测试注入:自定义数据库实例 */
|
|
65
|
+
dbInstance?: HistoryDatabase;
|
|
66
|
+
/** 数据库名(默认 'lokvis-history';仅 dbInstance 未注入时生效) */
|
|
67
|
+
dbName?: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 创建 HistoryStore。
|
|
71
|
+
*
|
|
72
|
+
* IndexedDB 不可用时返回 undefined,调用方(runtime)据此跳过持久化,
|
|
73
|
+
* 退化为仅内存历史模式。
|
|
74
|
+
*/
|
|
75
|
+
export declare function createHistoryStore(options?: HistoryStoreOptions): HistoryStore | undefined;
|
|
76
|
+
//# sourceMappingURL=history-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history-store.d.ts","sourceRoot":"","sources":["../src/history-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,EAAE,KAAK,KAAK,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG5D,wBAAwB;AACxB,MAAM,WAAW,aAAa;IAC5B,kBAAkB;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB;IAClB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,aAAa,EAAE,OAAO,EAAE,CAAC;IACzB,0CAA0C;IAC1C,cAAc,EAAE,OAAO,EAAE,CAAC;IAC1B,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IACxC,OAAO,EAAG,KAAK,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;gBAE3B,IAAI,SAAmB;CAMpC;AAED,qCAAqC;AACrC,MAAM,WAAW,YAAY;IAC3B,oBAAoB;IACpB,IAAI,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,mBAAmB;IACnB,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAAC;IAC7D,yBAAyB;IACzB,OAAO,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IACpC,mBAAmB;IACnB,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,eAAe;IACf,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAwB;AACxB,MAAM,WAAW,mBAAmB;IAClC,oBAAoB;IACpB,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,mBAAwB,GAChC,YAAY,GAAG,SAAS,CAqD1B"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History Store —— 历史栈 IndexedDB 持久化(W7.2)
|
|
3
|
+
*
|
|
4
|
+
* 把每个工作流的 HistoryStack 快照(entries + cursor)连同 initialInputs /
|
|
5
|
+
* currentOutputs 一起持久化到独立 Dexie 库 `lokvis-history`,实现跨会话保留:
|
|
6
|
+
* 刷新页面后,undo/redo 历史与"当前输出"游标均可恢复(前提:outputs 引用的
|
|
7
|
+
* 资产仍在 OPFS/IDB 中,由 W6.6 元数据持久化保证)。
|
|
8
|
+
*
|
|
9
|
+
* 与 OpfsMetadataDatabase 同样的降级策略:IndexedDB 不可用时返回 undefined,
|
|
10
|
+
* runtime 退化为仅内存历史(刷新后丢失,与 W2 行为一致)。
|
|
11
|
+
*
|
|
12
|
+
* 持久化时机(由 runtime 驱动):
|
|
13
|
+
* - 历史变更(append/undo/redo/jumpTo/clear/reset):HistoryStack.onChanged 回调
|
|
14
|
+
* → runtime.persistHistory(workflowId) → save
|
|
15
|
+
* - 条目清空(reset/clear):save 检测到 entries 为空时改为 delete,避免残留空记录
|
|
16
|
+
* - disposeWorkflow:stack.reset() 触发 onChanged → persistHistory → delete
|
|
17
|
+
*
|
|
18
|
+
* 恢复时机:
|
|
19
|
+
* - createRuntime 工厂构造完 impl 后调 loadPersistedHistory() 预加载所有快照
|
|
20
|
+
*/
|
|
21
|
+
import Dexie, {} from 'dexie';
|
|
22
|
+
import { isIdbSupported } from './idb-asset-store.js';
|
|
23
|
+
/**
|
|
24
|
+
* 历史持久化数据库。
|
|
25
|
+
*
|
|
26
|
+
* 独立于 OpfsMetadataDatabase('lokvis-opfs-metadata')与 IdbAssetStore
|
|
27
|
+
* ('lokvis-assets'),避免与资产元数据冲突。表以 workflowId 为主键,
|
|
28
|
+
* 额外索引 updatedAt 便于未来按时间清理陈旧记录。
|
|
29
|
+
*/
|
|
30
|
+
export class HistoryDatabase extends Dexie {
|
|
31
|
+
history;
|
|
32
|
+
constructor(name = 'lokvis-history') {
|
|
33
|
+
super(name);
|
|
34
|
+
this.version(1).stores({
|
|
35
|
+
history: 'workflowId, updatedAt',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 创建 HistoryStore。
|
|
41
|
+
*
|
|
42
|
+
* IndexedDB 不可用时返回 undefined,调用方(runtime)据此跳过持久化,
|
|
43
|
+
* 退化为仅内存历史模式。
|
|
44
|
+
*/
|
|
45
|
+
export function createHistoryStore(options = {}) {
|
|
46
|
+
const db = resolveHistoryDb(options);
|
|
47
|
+
if (!db)
|
|
48
|
+
return undefined;
|
|
49
|
+
return {
|
|
50
|
+
async save(record) {
|
|
51
|
+
try {
|
|
52
|
+
await db.history.put(record);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
// 持久化失败不阻断历史操作,仅 warn 便于排查
|
|
56
|
+
console.warn(`[lokvis] history store save failed for ${record.workflowId}:`, err);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
async load(workflowId) {
|
|
60
|
+
try {
|
|
61
|
+
return await db.history.get(workflowId);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
console.warn(`[lokvis] history store load failed for ${workflowId}:`, err);
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
async loadAll() {
|
|
69
|
+
try {
|
|
70
|
+
return await db.history.toArray();
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
console.warn('[lokvis] history store loadAll failed:', err);
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
async delete(workflowId) {
|
|
78
|
+
try {
|
|
79
|
+
await db.history.delete(workflowId);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
console.warn(`[lokvis] history store delete failed for ${workflowId}:`, err);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
async clear() {
|
|
86
|
+
try {
|
|
87
|
+
await db.history.clear();
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
console.warn('[lokvis] history store clear failed:', err);
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/** 解析历史数据库实例(注入优先,否则在 IDB 可用时新建) */
|
|
96
|
+
function resolveHistoryDb(options) {
|
|
97
|
+
if (options.dbInstance)
|
|
98
|
+
return options.dbInstance;
|
|
99
|
+
if (!isIdbSupported())
|
|
100
|
+
return undefined;
|
|
101
|
+
try {
|
|
102
|
+
return new HistoryDatabase(options.dbName);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
console.warn('[lokvis] history database init failed, falling back to memory-only:', err);
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=history-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history-store.js","sourceRoot":"","sources":["../src/history-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,EAAc,MAAM,OAAO,CAAC;AAE1C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAkBtD;;;;;;GAMG;AACH,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACxC,OAAO,CAAgC;IAEvC,YAAY,IAAI,GAAG,gBAAgB;QACjC,KAAK,CAAC,IAAI,CAAC,CAAC;QACZ,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YACrB,OAAO,EAAE,uBAAuB;SACjC,CAAC,CAAC;IACL,CAAC;CACF;AAwBD;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,UAA+B,EAAE;IAEjC,MAAM,EAAE,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACrC,IAAI,CAAC,EAAE;QAAE,OAAO,SAAS,CAAC;IAE1B,OAAO;QACL,KAAK,CAAC,IAAI,CAAC,MAAM;YACf,IAAI,CAAC;gBACH,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAC/B,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,2BAA2B;gBAC3B,OAAO,CAAC,IAAI,CACV,0CAA0C,MAAM,CAAC,UAAU,GAAG,EAC9D,GAAG,CACJ,CAAC;YACJ,CAAC;QACH,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,UAAU;YACnB,IAAI,CAAC;gBACH,OAAO,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAC1C,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,IAAI,CACV,0CAA0C,UAAU,GAAG,EACvD,GAAG,CACJ,CAAC;gBACF,OAAO,SAAS,CAAC;YACnB,CAAC;QACH,CAAC;QACD,KAAK,CAAC,OAAO;YACX,IAAI,CAAC;gBACH,OAAO,MAAM,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACpC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,IAAI,CAAC,wCAAwC,EAAE,GAAG,CAAC,CAAC;gBAC5D,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,UAAU;YACrB,IAAI,CAAC;gBACH,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACtC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,IAAI,CACV,4CAA4C,UAAU,GAAG,EACzD,GAAG,CACJ,CAAC;YACJ,CAAC;QACH,CAAC;QACD,KAAK,CAAC,KAAK;YACT,IAAI,CAAC;gBACH,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YAC3B,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,IAAI,CAAC,sCAAsC,EAAE,GAAG,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,oCAAoC;AACpC,SAAS,gBAAgB,CACvB,OAA4B;IAE5B,IAAI,OAAO,CAAC,UAAU;QAAE,OAAO,OAAO,CAAC,UAAU,CAAC;IAClD,IAAI,CAAC,cAAc,EAAE;QAAE,OAAO,SAAS,CAAC;IACxC,IAAI,CAAC;QACH,OAAO,IAAI,eAAe,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CACV,qEAAqE,EACrE,GAAG,CACJ,CAAC;QACF,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
|
package/dist/idb-asset-store.js
CHANGED
|
@@ -41,7 +41,7 @@ export async function createIdbAssetStore(options = {}) {
|
|
|
41
41
|
const db = options.dbInstance ?? new AssetDatabase(options.dbName);
|
|
42
42
|
return {
|
|
43
43
|
async import(source) {
|
|
44
|
-
const { id, blob, metadata, type } = prepareImport(source);
|
|
44
|
+
const { id, blob, metadata, type } = await prepareImport(source);
|
|
45
45
|
const asset = buildAsset(id, blob, metadata, type, IDB_PATH_PREFIX);
|
|
46
46
|
await db.assets.put({ id, asset, blob });
|
|
47
47
|
return asset;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idb-asset-store.js","sourceRoot":"","sources":["../src/idb-asset-store.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,EAAc,MAAM,OAAO,CAAC;AAG1C,OAAO,EACL,UAAU,EACV,UAAU,EACV,aAAa,EACb,aAAa,GACd,MAAM,kBAAkB,CAAC;AAE1B,uBAAuB;AACvB,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAC5C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAED,0CAA0C;AAC1C,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC;AAuBrC,kBAAkB;AAClB,MAAM,OAAO,aAAc,SAAQ,KAAK;IACtC,MAAM,CAA8B;IAEpC,YAAY,IAAI,GAAG,eAAe;QAChC,KAAK,CAAC,IAAI,CAAC,CAAC;QACZ,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YACrB,yCAAyC;YACzC,MAAM,EAAE,IAAI;SACb,CAAC,CAAC;IACL,CAAC;CACF;AAED,2BAA2B;AAC3B,MAAM,UAAU,cAAc;IAC5B,uCAAuC;IACvC,OAAO,OAAO,SAAS,KAAK,WAAW,CAAC;AAC1C,CAAC;AAED,gCAAgC;AAChC,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,UAAgC,EAAE;IAElC,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;QAC7C,MAAM,IAAI,mBAAmB,CAC3B,gDAAgD,CACjD,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,IAAI,IAAI,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAEnE,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,MAAM;YACjB,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"idb-asset-store.js","sourceRoot":"","sources":["../src/idb-asset-store.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,11 @@ export * from './executor.js';
|
|
|
14
14
|
export * from './worker-protocol.js';
|
|
15
15
|
export * from './worker-host.js';
|
|
16
16
|
export * from './history.js';
|
|
17
|
+
export * from './history-store.js';
|
|
18
|
+
export * from './memory-guard.js';
|
|
19
|
+
export * from './degradation.js';
|
|
20
|
+
export * from './batch-processor.js';
|
|
21
|
+
export * from './workflow-builder.js';
|
|
17
22
|
export * from './runtime.js';
|
|
18
23
|
export { RUNTIME_VERSION } from './runtime.js';
|
|
19
24
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAE7B,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAE7B,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,11 @@ export * from './executor.js';
|
|
|
14
14
|
export * from './worker-protocol.js';
|
|
15
15
|
export * from './worker-host.js';
|
|
16
16
|
export * from './history.js';
|
|
17
|
+
export * from './history-store.js';
|
|
18
|
+
export * from './memory-guard.js';
|
|
19
|
+
export * from './degradation.js';
|
|
20
|
+
export * from './batch-processor.js';
|
|
21
|
+
export * from './workflow-builder.js';
|
|
17
22
|
export * from './runtime.js';
|
|
18
23
|
export { RUNTIME_VERSION } from './runtime.js';
|
|
19
24
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAE7B,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAE7B,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 内存阈值监测与 OPFS 溢出(W3.3)
|
|
3
|
+
*
|
|
4
|
+
* 目标(对应 whitepaper T1「浏览器内存/性能 ★★★★★」):
|
|
5
|
+
* 浏览器单 tab 内存有限(桌面典型 2-4GB,移动端更少),大图批量处理
|
|
6
|
+
* 极易触发 OOM 崩溃(整个 tab 被杀,丢失所有中间结果)。MemoryGuard 通过
|
|
7
|
+
* 显式追踪中间结果占用 + 周期性压力评估,在逼近预算时把中间 Blob 溢出
|
|
8
|
+
* 到 OPFS,把"OPFS 当虚拟内存用"——内存里只留当前活跃的一两个 Blob。
|
|
9
|
+
*
|
|
10
|
+
* 设计取舍:
|
|
11
|
+
* - tracked:显式登记的中间分配(decode 结果、canvas 输出等大对象)。
|
|
12
|
+
* 相比 `performance.memory`(Chrome-only、deprecated、MB 粒度、跨浏览器
|
|
13
|
+
* 不可用),tracked 是可控、可测、确定性的信号,适合做预算判定主依据。
|
|
14
|
+
* - budget:512MB(经验值——留出 JS runtime / DOM / 其他 tab 的余量,
|
|
15
|
+
* 实际由 RuntimeConfig.memoryBudget 传入,默认 512MB)。
|
|
16
|
+
* - pressure:low(<60%) / elevated(60-80%) / high(80-95%) / critical(≥95%)。
|
|
17
|
+
* - shouldSpill:pressure >= high 时建议把当前中间结果落 OPFS。
|
|
18
|
+
* - spill/restore/evict:经由 AssetStore(OPFS-backed when available;
|
|
19
|
+
* runtime 已用 wrapAssetStoreWithQuota 包裹,溢出受 storageQuota 约束,
|
|
20
|
+
* 避免无限溢出把磁盘填满)。
|
|
21
|
+
*
|
|
22
|
+
* 集成方式(由 W3.4 degradation 编排):
|
|
23
|
+
* const guard = new MemoryGuard({ budget, assetStore });
|
|
24
|
+
* const alloc = guard.track(blob.size); // 登记
|
|
25
|
+
* if (guard.shouldSpill()) { const a = await guard.spill(blob); blob = null; } // 溢出
|
|
26
|
+
* ...
|
|
27
|
+
* alloc.release(); // 回退
|
|
28
|
+
*/
|
|
29
|
+
import type { Asset, AssetType } from '@lokvis/schema';
|
|
30
|
+
import type { AssetStore } from './asset-store.js';
|
|
31
|
+
/** 默认内存预算:512MB(见上) */
|
|
32
|
+
export declare const DEFAULT_MEMORY_BUDGET: number;
|
|
33
|
+
/** 内存压力等级(降级阶梯 W3.4 据此选择策略) */
|
|
34
|
+
export type MemoryPressure = 'low' | 'elevated' | 'high' | 'critical';
|
|
35
|
+
export interface MemoryGuardOptions {
|
|
36
|
+
/** 总内存预算(字节),超出 high 阈值则建议溢出 */
|
|
37
|
+
budget?: number;
|
|
38
|
+
/** elevated 阈值占 budget 比例(默认 0.6) */
|
|
39
|
+
elevatedRatio?: number;
|
|
40
|
+
/** high 阈值(默认 0.8):达到则 shouldSpill=true */
|
|
41
|
+
highRatio?: number;
|
|
42
|
+
/** critical 阈值(默认 0.95):达到则降级阶梯走 L4 拒绝 */
|
|
43
|
+
criticalRatio?: number;
|
|
44
|
+
/** 溢出目标(通常为 OPFS-backed AssetStore);不提供则 shouldSpill 永远 false */
|
|
45
|
+
assetStore?: AssetStore;
|
|
46
|
+
}
|
|
47
|
+
/** track() 返回的分配句柄:丢弃引用时调用 release() 回退计数(幂等) */
|
|
48
|
+
export interface MemoryAllocation {
|
|
49
|
+
/** 该笔登记的字节数 */
|
|
50
|
+
readonly bytes: number;
|
|
51
|
+
/** 回退登记;重复调用幂等(仅首次生效) */
|
|
52
|
+
release(): void;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 估算解码后位图占用的内存(RGBA,4 字节/像素)。
|
|
56
|
+
*
|
|
57
|
+
* 一个 4000×3000 的 JPEG,blob 可能只有 2MB,但 decode 成 ImageBitmap 后
|
|
58
|
+
* 占 4000*3000*4 ≈ 46MB。追踪中间结果时应以此为准而非 blob.size,
|
|
59
|
+
* 否则严重低估内存压力。
|
|
60
|
+
*/
|
|
61
|
+
export declare function estimateDecodedBytes(width: number, height: number): number;
|
|
62
|
+
/** 估算 Blob 在内存中占用的字节数(对未解码的 Blob,以其 size 为准) */
|
|
63
|
+
export declare function estimateBlobBytes(blob: Blob): number;
|
|
64
|
+
/** 内存守卫:追踪中间结果占用 + 触发 OPFS 溢出 */
|
|
65
|
+
export declare class MemoryGuard {
|
|
66
|
+
private tracked;
|
|
67
|
+
private readonly budget;
|
|
68
|
+
private readonly elevatedRatio;
|
|
69
|
+
private readonly highRatio;
|
|
70
|
+
private readonly criticalRatio;
|
|
71
|
+
private readonly assetStore?;
|
|
72
|
+
constructor(opts?: MemoryGuardOptions);
|
|
73
|
+
/** 当前已登记的字节数 */
|
|
74
|
+
get currentUsage(): number;
|
|
75
|
+
/** 总预算(字节) */
|
|
76
|
+
get budgetBytes(): number;
|
|
77
|
+
/** 当前是否已配置可溢出的 AssetStore */
|
|
78
|
+
get canSpill(): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* 登记一笔内存占用。返回 release 句柄,丢弃引用时调用以回退计数。
|
|
81
|
+
* 用于追踪 decode 结果 / canvas 输出等大对象的生命周期。
|
|
82
|
+
*/
|
|
83
|
+
track(bytes: number): MemoryAllocation;
|
|
84
|
+
/** 当前压力等级(基于 tracked / budget) */
|
|
85
|
+
getPressure(): MemoryPressure;
|
|
86
|
+
/** 已用比例(0-1+) */
|
|
87
|
+
getUsageRatio(): number;
|
|
88
|
+
/**
|
|
89
|
+
* 是否应溢出(pressure >= high)。无 assetStore 时永远 false(无法溢出,
|
|
90
|
+
* 交由降级阶梯 L3/L4 处理)。
|
|
91
|
+
*/
|
|
92
|
+
shouldSpill(): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* 把一个中间 Blob 溢出到 OPFS(assetStore.create),返回 Asset 句柄。
|
|
95
|
+
* 调用方应在 spill 后丢弃内存中的 Blob 引用,以真正释放内存。
|
|
96
|
+
*
|
|
97
|
+
* @param blob 要溢出的中间结果
|
|
98
|
+
* @param mimeType Blob 的 MIME(用于元数据);默认取 blob.type
|
|
99
|
+
* @param assetType 资产类型(默认 'image');video/pdf 管线可传对应类型
|
|
100
|
+
*/
|
|
101
|
+
spill(blob: Blob, mimeType?: string, assetType?: AssetType): Promise<Asset>;
|
|
102
|
+
/** 从 OPFS 读回之前 spill 的中间结果 */
|
|
103
|
+
restore(asset: Asset): Promise<Blob>;
|
|
104
|
+
/** 丢弃已 spill 的中间结果(处理完成后回收 OPFS 空间) */
|
|
105
|
+
evict(asset: Asset): Promise<void>;
|
|
106
|
+
/** 重置所有登记(用于工作流结束 / 取消后的清理,tracked 归零) */
|
|
107
|
+
reset(): void;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=memory-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-guard.d.ts","sourceRoot":"","sources":["../src/memory-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAiB,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,uBAAuB;AACvB,eAAO,MAAM,qBAAqB,QAAoB,CAAC;AAEvD,+BAA+B;AAC/B,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,CAAC;AAEtE,MAAM,WAAW,kBAAkB;IACjC,gCAAgC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iEAAiE;IACjE,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,iDAAiD;AACjD,MAAM,WAAW,gBAAgB;IAC/B,eAAe;IACf,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yBAAyB;IACzB,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE1E;AAED,gDAAgD;AAChD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAEpD;AAED,iCAAiC;AACjC,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAa;gBAE7B,IAAI,GAAE,kBAAuB;IAgBzC,gBAAgB;IAChB,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,cAAc;IACd,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,6BAA6B;IAC7B,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB;IAgBtC,kCAAkC;IAClC,WAAW,IAAI,cAAc;IAQ7B,iBAAiB;IACjB,aAAa,IAAI,MAAM;IAIvB;;;OAGG;IACH,WAAW,IAAI,OAAO;IAMtB;;;;;;;OAOG;IACG,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,SAAS,GAAE,SAAmB,GAAG,OAAO,CAAC,KAAK,CAAC;IAgB1F,8BAA8B;IACxB,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IAO1C,uCAAuC;IACjC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IAKxC,0CAA0C;IAC1C,KAAK,IAAI,IAAI;CAGd"}
|