sage-mem 2.1.25 → 2.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/README.md +1 -1
- package/README.zh.md +1 -1
- package/lib/autodream/tools.js +2 -2
- package/lib/memory/archive.js +24 -24
- package/lib/memory/lock.js +2 -2
- package/lib/memory/reader.js +1 -1
- package/lib/memory/scan.js +5 -5
- package/lib/memory/util.js +2 -2
- package/lib/memory/watch.js +1 -1
- package/lib/shared/codes.js +71 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
**Cross-session memory for DeepSeek Harness — plain markdown files, no database, no worker.**
|
|
14
14
|
|
|
15
|
-
**Current release: `2.
|
|
15
|
+
**Current release: `2.2.0`** — the npm badge above is the dynamic source of truth. **Contract baseline: `1.0.0`** — the first stable release, whose public commitments, the capability boundaries and the list of things it explicitly cannot do are frozen in the
|
|
16
16
|
[frozen contract](https://github.com/gezi-wen/sage-mem/blob/main/docs/1.0.0-contract.md).
|
|
17
17
|
|
|
18
18
|
If it is useful to you, a Star is the most direct feedback.
|
package/README.zh.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
> File-based cross-session memory for DeepSeek Harness — every memory is a plain markdown file you can open, edit and delete. No database, no worker, no port.
|
|
16
16
|
|
|
17
|
-
**当前发布版本:`2.
|
|
17
|
+
**当前发布版本:`2.2.0`** —— 顶部 npm 徽章是动态权威。**契约基线:`1.0.0`** —— 第一个稳定版,公开承诺、能力边界与「明确做不到」的清单冻结在
|
|
18
18
|
[契约冻结](https://github.com/gezi-wen/sage-mem/blob/main/docs/1.0.0-contract.md)。
|
|
19
19
|
|
|
20
20
|
如果它对你有用,给个 Star 是最直接的反馈。
|
package/lib/autodream/tools.js
CHANGED
|
@@ -764,7 +764,7 @@ export function createToolRunner(opts) {
|
|
|
764
764
|
if (guard.refuse) {
|
|
765
765
|
// **不知道就不写。** 以前这条路会退化成「用当前版本当守卫」,等于替调用方担保一份它没读过的
|
|
766
766
|
// 内容 —— 那比「什么都不做」危险得多。转成一条待人工处理的冲突,让人来判。
|
|
767
|
-
await recordStaleConflict(fullPath, content, { code: '
|
|
767
|
+
await recordStaleConflict(fullPath, content, { code: 'unverified_overwrite', message: guard.why }, 'external_change')
|
|
768
768
|
throw Object.assign(new Error(guard.why), { code: 'SAGE_MEM_UNVERIFIED_OVERWRITE' })
|
|
769
769
|
}
|
|
770
770
|
try {
|
|
@@ -959,7 +959,7 @@ export function createToolRunner(opts) {
|
|
|
959
959
|
const before = await fileFingerprint(full)
|
|
960
960
|
const res = await archiveMemory(memoryDir, basename(full), reason)
|
|
961
961
|
if (!res.ok) {
|
|
962
|
-
if (res.code === '
|
|
962
|
+
if (res.code === 'not_found') return `错误:没有这个文件:${basename(full)}`
|
|
963
963
|
if (res.code === 'exists') return `错误:archive/ 里已有同名文件(${basename(full)}),请先人工处理,避免覆盖。`
|
|
964
964
|
return `错误:${String(res.error).replace(/^sage-mem: /, '')}`
|
|
965
965
|
}
|
package/lib/memory/archive.js
CHANGED
|
@@ -34,7 +34,7 @@ function checkName(name, verb) {
|
|
|
34
34
|
return { ok: false, code: 'reserved', error: `sage-mem: reserved file, refusing to ${verb}: ${nameKey(name)}` }
|
|
35
35
|
}
|
|
36
36
|
const safe = safeName(name)
|
|
37
|
-
if (!safe) return { ok: false, code: '
|
|
37
|
+
if (!safe) return { ok: false, code: 'invalid_name', error: `sage-mem: invalid file name: ${String(name)}` }
|
|
38
38
|
return { ok: true, safe }
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -63,7 +63,7 @@ export async function archiveMemory(memoryDir, name, reason, at = localStamp())
|
|
|
63
63
|
if (lock.status === 'unknown') {
|
|
64
64
|
return {
|
|
65
65
|
ok: false,
|
|
66
|
-
code: '
|
|
66
|
+
code: 'lock_unknown',
|
|
67
67
|
error: `sage-mem: 锁表不可读(${lock.reason}),无法确认这条记忆是否受保护,拒绝归档:${safe}`,
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -83,7 +83,7 @@ export async function archiveMemory(memoryDir, name, reason, at = localStamp())
|
|
|
83
83
|
if (!srcGate.ok) {
|
|
84
84
|
return {
|
|
85
85
|
ok: false,
|
|
86
|
-
code: srcGate.code === 'missing' ? '
|
|
86
|
+
code: srcGate.code === 'missing' ? 'not_found' : srcGate.code,
|
|
87
87
|
error: `sage-mem: file not found: ${safe}(${srcGate.error})`,
|
|
88
88
|
}
|
|
89
89
|
}
|
|
@@ -162,14 +162,14 @@ export async function archiveMemory(memoryDir, name, reason, at = localStamp())
|
|
|
162
162
|
* 「错误:没有这个文件」—— 诊断是错的,人会去别处找。按真实错误码分流。
|
|
163
163
|
*/
|
|
164
164
|
await rm(dst, { force: true }).catch(() => {})
|
|
165
|
-
if (takenRes.code === '
|
|
166
|
-
return { ok: false, code: '
|
|
165
|
+
if (takenRes.code === 'not_found') {
|
|
166
|
+
return { ok: false, code: 'not_found', error: `sage-mem: file not found: ${safe}(归档途中源文件消失了)` }
|
|
167
167
|
}
|
|
168
|
-
if (takenRes.code === '
|
|
168
|
+
if (takenRes.code === 'move_failed') {
|
|
169
169
|
const moveErr = takenRes.cause
|
|
170
170
|
return {
|
|
171
171
|
ok: false,
|
|
172
|
-
code: '
|
|
172
|
+
code: 'move_failed',
|
|
173
173
|
error: `sage-mem: 归档 ${safe} 时移不动它(${moveErr?.code ?? moveErr}:${moveErr?.message ?? moveErr})。文件仍在原处、一个字节没动,本次归档未完成 —— 可稍后重试(若是被其它程序占用,关掉它再来)。`,
|
|
174
174
|
}
|
|
175
175
|
}
|
|
@@ -241,16 +241,16 @@ export async function restoreMemory(memoryDir, name) {
|
|
|
241
241
|
if (!dirGate.ok) {
|
|
242
242
|
return {
|
|
243
243
|
ok: false,
|
|
244
|
-
code: dirGate.code === 'missing' ? '
|
|
244
|
+
code: dirGate.code === 'missing' ? 'not_archived' : dirGate.code,
|
|
245
245
|
error: `sage-mem: not archived: ${safe}(${dirGate.error})`,
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
248
|
const srcGate = await inspectMemoryEntry(src, { limitBytes: null })
|
|
249
|
-
if (!srcGate.ok) return { ok: false, code: '
|
|
249
|
+
if (!srcGate.ok) return { ok: false, code: 'not_archived', error: `sage-mem: not archived: ${safe}(${srcGate.error})` }
|
|
250
250
|
// 顶层已有同名(且不是链接)时拒绝:根目录那份是正在用的记忆,归档这份只是退役版。
|
|
251
251
|
const dstGate = await inspectMemoryEntry(dst, { limitBytes: null })
|
|
252
252
|
if (dstGate.ok) {
|
|
253
|
-
return { ok: false, code: '
|
|
253
|
+
return { ok: false, code: 'target_exists', error: `sage-mem: already exists, refusing to overwrite: ${safe}` }
|
|
254
254
|
}
|
|
255
255
|
if (dstGate.code !== 'missing') {
|
|
256
256
|
return { ok: false, code: dstGate.code, error: `sage-mem: refusing to restore over ${safe}: ${dstGate.error}` }
|
|
@@ -278,14 +278,14 @@ export async function restoreMemory(memoryDir, name) {
|
|
|
278
278
|
*/
|
|
279
279
|
const takenRes = await takeOverSource(src, sourceBytes, 'restoring')
|
|
280
280
|
if (!takenRes.ok) {
|
|
281
|
-
if (takenRes.code === '
|
|
282
|
-
return { ok: false, code: '
|
|
281
|
+
if (takenRes.code === 'not_found') {
|
|
282
|
+
return { ok: false, code: 'not_archived', error: `sage-mem: not archived: ${safe}(恢复途中源文件消失了)` }
|
|
283
283
|
}
|
|
284
|
-
if (takenRes.code === '
|
|
284
|
+
if (takenRes.code === 'move_failed') {
|
|
285
285
|
const moveErr = takenRes.cause
|
|
286
286
|
return {
|
|
287
287
|
ok: false,
|
|
288
|
-
code: '
|
|
288
|
+
code: 'move_failed',
|
|
289
289
|
error: `sage-mem: 恢复 ${safe} 时移不动它(${moveErr?.code ?? moveErr}:${moveErr?.message ?? moveErr})。文件仍在 archive/ 里、一个字节没动 —— 可稍后重试(若是被其它程序占用,关掉它再来)。`,
|
|
290
290
|
}
|
|
291
291
|
}
|
|
@@ -318,12 +318,12 @@ export async function restoreMemory(memoryDir, name) {
|
|
|
318
318
|
if (!back.ok) {
|
|
319
319
|
return {
|
|
320
320
|
ok: false,
|
|
321
|
-
code: '
|
|
321
|
+
code: 'restore_failed',
|
|
322
322
|
error: `sage-mem: 恢复 ${safe} 落位失败(${err?.code ?? err}),且归还归档区原名时被占用 —— **内容仍保留在 archive/${basename(back.keptAt)}**,请手工改回 ${safe} 再试。`,
|
|
323
323
|
}
|
|
324
324
|
}
|
|
325
325
|
if (err?.code === 'EEXIST') {
|
|
326
|
-
return { ok: false, code: '
|
|
326
|
+
return { ok: false, code: 'target_exists', error: `sage-mem: already exists, refusing to overwrite: ${safe}` }
|
|
327
327
|
}
|
|
328
328
|
throw err
|
|
329
329
|
}
|
|
@@ -421,7 +421,7 @@ export async function deleteArchivedMemory(memoryDir, name, expectedArchivedAt =
|
|
|
421
421
|
if (!dirGate.ok) {
|
|
422
422
|
return {
|
|
423
423
|
ok: false,
|
|
424
|
-
code: dirGate.code === 'missing' ? '
|
|
424
|
+
code: dirGate.code === 'missing' ? 'not_archived' : dirGate.code,
|
|
425
425
|
error: `sage-mem: not archived: ${safe}(${dirGate.error})`,
|
|
426
426
|
}
|
|
427
427
|
}
|
|
@@ -430,7 +430,7 @@ export async function deleteArchivedMemory(memoryDir, name, expectedArchivedAt =
|
|
|
430
430
|
if (!srcGate.ok) {
|
|
431
431
|
return {
|
|
432
432
|
ok: false,
|
|
433
|
-
code: srcGate.code === 'missing' ? '
|
|
433
|
+
code: srcGate.code === 'missing' ? 'not_archived' : srcGate.code,
|
|
434
434
|
error: `sage-mem: not archived: ${safe}(${srcGate.error})`,
|
|
435
435
|
}
|
|
436
436
|
}
|
|
@@ -441,14 +441,14 @@ export async function deleteArchivedMemory(memoryDir, name, expectedArchivedAt =
|
|
|
441
441
|
const sourceBytes = await readFile(full)
|
|
442
442
|
const takenRes = await takeOverSource(full, sourceBytes, 'deleting')
|
|
443
443
|
if (!takenRes.ok) {
|
|
444
|
-
if (takenRes.code === '
|
|
445
|
-
return { ok: false, code: '
|
|
444
|
+
if (takenRes.code === 'not_found') {
|
|
445
|
+
return { ok: false, code: 'not_archived', error: `sage-mem: not archived: ${safe}(删除途中它从归档区消失了)` }
|
|
446
446
|
}
|
|
447
|
-
if (takenRes.code === '
|
|
447
|
+
if (takenRes.code === 'move_failed') {
|
|
448
448
|
const moveErr = takenRes.cause
|
|
449
449
|
return {
|
|
450
450
|
ok: false,
|
|
451
|
-
code: '
|
|
451
|
+
code: 'move_failed',
|
|
452
452
|
error: `sage-mem: 删除 ${safe} 时移不动它(${moveErr?.code ?? moveErr}:${moveErr?.message ?? moveErr})。文件仍在 archive/ 里、一个字节没动 —— 可稍后重试(若是被其它程序占用,关掉它再来)。`,
|
|
453
453
|
}
|
|
454
454
|
}
|
|
@@ -513,7 +513,7 @@ export async function deleteArchivedMemory(memoryDir, name, expectedArchivedAt =
|
|
|
513
513
|
if (!back.ok) {
|
|
514
514
|
return {
|
|
515
515
|
ok: false,
|
|
516
|
-
code: '
|
|
516
|
+
code: 'delete_failed',
|
|
517
517
|
error:
|
|
518
518
|
`sage-mem: 删除 ${safe} 失败(${delErr?.code ?? delErr}),且归还原名时被占用 —— ` +
|
|
519
519
|
`**内容仍保留在 archive/${basename(back.keptAt)}**,请手工改回 ${safe} 再试。`,
|
|
@@ -521,7 +521,7 @@ export async function deleteArchivedMemory(memoryDir, name, expectedArchivedAt =
|
|
|
521
521
|
}
|
|
522
522
|
return {
|
|
523
523
|
ok: false,
|
|
524
|
-
code: '
|
|
524
|
+
code: 'delete_failed',
|
|
525
525
|
error: `sage-mem: 删除 ${safe} 失败(${delErr?.code ?? delErr}:${delErr?.message ?? delErr}),文件已还回 archive/${safe},没有丢。`,
|
|
526
526
|
}
|
|
527
527
|
}
|
package/lib/memory/lock.js
CHANGED
|
@@ -125,7 +125,7 @@ export async function isLocked(memoryDir, name) {
|
|
|
125
125
|
* RMW 全程在**按锁表路径分键的进程内队列**里:读最新表 → 改一项 → 原子写回。
|
|
126
126
|
* 并发的不同文件因此逐条合并,不再互相覆盖。
|
|
127
127
|
*
|
|
128
|
-
* 坏表上**拒绝写入**(`{ok:false, code:'
|
|
128
|
+
* 坏表上**拒绝写入**(`{ok:false, code:'lock_table_unknown'}`),而不是把表重置成空表 ——
|
|
129
129
|
* 后者会静默抹掉用户的全部保护状态,比「这次没锁上」严重得多。
|
|
130
130
|
*/
|
|
131
131
|
export async function setLocked(memoryDir, name, locked) {
|
|
@@ -136,7 +136,7 @@ export async function setLocked(memoryDir, name, locked) {
|
|
|
136
136
|
if (read.status === 'unknown') {
|
|
137
137
|
return {
|
|
138
138
|
ok: false,
|
|
139
|
-
code: '
|
|
139
|
+
code: 'lock_table_unknown',
|
|
140
140
|
error: `sage-mem: 锁表不可读(${read.reason}),本次没有写入,也不会把它重置成空表:${name}`,
|
|
141
141
|
}
|
|
142
142
|
}
|
package/lib/memory/reader.js
CHANGED
|
@@ -226,7 +226,7 @@ export async function readSlice(req = {}) {
|
|
|
226
226
|
if (!gate.ok) {
|
|
227
227
|
if (gate.code === 'missing') return { ok: false, code: 'not_found', error: `没有这份记忆:${norm.name}` }
|
|
228
228
|
// stat-failed 不是「形状不对」,是读不到属性(权限 / 盘掉线)→ 归 io_error 更好排查
|
|
229
|
-
if (gate.code === '
|
|
229
|
+
if (gate.code === 'stat_failed') return { ok: false, code: 'io_error', error: `读不到文件属性:${gate.error}` }
|
|
230
230
|
return { ok: false, code: 'denied', error: `拒绝读取 ${norm.name}:${gate.error}` }
|
|
231
231
|
}
|
|
232
232
|
|
package/lib/memory/scan.js
CHANGED
|
@@ -158,17 +158,17 @@ export async function inspectMemoryEntry(full, opts = {}) {
|
|
|
158
158
|
} catch (err) {
|
|
159
159
|
return {
|
|
160
160
|
ok: false,
|
|
161
|
-
code: err?.code === 'ENOENT' ? 'missing' : '
|
|
161
|
+
code: err?.code === 'ENOENT' ? 'missing' : 'stat_failed',
|
|
162
162
|
error: `读不到文件属性(${err?.code ?? err})`,
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
if (info.isSymbolicLink()) return { ok: false, code: 'symlink', error: '是指向别处的符号链接 / 目录联接' }
|
|
166
|
-
if (!info.isFile()) return { ok: false, code: '
|
|
166
|
+
if (!info.isFile()) return { ok: false, code: 'not_file', error: '不是普通文件' }
|
|
167
167
|
// null = 明确「不判体积」;其余一律按数值上限处理(0 / NaN / 字符串都会被当成超限)。
|
|
168
168
|
if (limitBytes !== null && (!Number.isFinite(limitBytes) || info.size > limitBytes)) {
|
|
169
169
|
return {
|
|
170
170
|
ok: false,
|
|
171
|
-
code: '
|
|
171
|
+
code: 'too_big',
|
|
172
172
|
error: Number.isFinite(limitBytes)
|
|
173
173
|
? `体积 ${info.size} 字节,超过单文件上限 ${limitBytes} 字节`
|
|
174
174
|
: `体积 ${info.size} 字节,上限参数不合法(${String(limitBytes)})`,
|
|
@@ -287,12 +287,12 @@ export async function inspectDir(dir) {
|
|
|
287
287
|
} catch (err) {
|
|
288
288
|
return {
|
|
289
289
|
ok: false,
|
|
290
|
-
code: err?.code === 'ENOENT' ? 'missing' : '
|
|
290
|
+
code: err?.code === 'ENOENT' ? 'missing' : 'stat_failed',
|
|
291
291
|
error: `读不到目录属性(${err?.code ?? err})`,
|
|
292
292
|
}
|
|
293
293
|
}
|
|
294
294
|
if (info.isSymbolicLink()) return { ok: false, code: 'symlink', error: '是指向别处的符号链接 / 目录联接' }
|
|
295
|
-
if (!info.isDirectory()) return { ok: false, code: '
|
|
295
|
+
if (!info.isDirectory()) return { ok: false, code: 'not_dir', error: '不是目录' }
|
|
296
296
|
return { ok: true }
|
|
297
297
|
}
|
|
298
298
|
|
package/lib/memory/util.js
CHANGED
|
@@ -249,7 +249,7 @@ async function copyMoveExclusive(from, to) {
|
|
|
249
249
|
* @param {string} tag — 私有名后缀(`archiving` / `restoring`);体检按它认崩溃残留
|
|
250
250
|
* @returns {Promise<
|
|
251
251
|
* {ok:true, taken:string} |
|
|
252
|
-
* {ok:false, code:'
|
|
252
|
+
* {ok:false, code:'not_found'|'move_failed'|'changed', cause?:Error, keptAt?:string}
|
|
253
253
|
* >}
|
|
254
254
|
*/
|
|
255
255
|
export async function takeOverSource(from, expected, tag) {
|
|
@@ -259,7 +259,7 @@ export async function takeOverSource(from, expected, tag) {
|
|
|
259
259
|
(err) => err,
|
|
260
260
|
)
|
|
261
261
|
if (moveErr) {
|
|
262
|
-
return { ok: false, code: moveErr?.code === 'ENOENT' ? '
|
|
262
|
+
return { ok: false, code: moveErr?.code === 'ENOENT' ? 'not_found' : 'move_failed', cause: moveErr }
|
|
263
263
|
}
|
|
264
264
|
const current = await readFile(taken).catch(() => null)
|
|
265
265
|
if (current !== null && Buffer.compare(current, expected) === 0) return { ok: true, taken }
|
package/lib/memory/watch.js
CHANGED
|
@@ -107,7 +107,7 @@ export function createDirWatcher(dir, onChange) {
|
|
|
107
107
|
floodCount++
|
|
108
108
|
if (floodCount > WATCH_FLOOD_MAX_EVENTS) {
|
|
109
109
|
// 句柄已坏(多半是目录被删 / 被同名重建):报错这条路会顺手 close 掉,空转立刻停。
|
|
110
|
-
onError({ code: '
|
|
110
|
+
onError({ code: 'flood', message: `${WATCH_FLOOD_WINDOW_MS}ms 内 ${floodCount} 个变更事件,句柄已不可信` })
|
|
111
111
|
return
|
|
112
112
|
}
|
|
113
113
|
if (!burstOpen) {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sage-mem 业务错误码的**唯一清单**(2026-11-03 归一,2.2.0)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么要有它:`code` 曾经同时存在三套命名法 ——
|
|
5
|
+
* - snake_case:`io_error` / `not_found` / `stale_version`
|
|
6
|
+
* - kebab-case:`not-found` / `move-failed` / `lock-table-unknown`
|
|
7
|
+
* - UPPER:`UNVERIFIED_OVERWRITE` / `FLOOD`
|
|
8
|
+
* 更糟的是**同一概念两个值并存**:`not_found`(reader)与 `not-found`(archive)、
|
|
9
|
+
* `too_big` 与 `too-big`。消费方只能两边都匹配,漏一边就是静默的错误分支
|
|
10
|
+
* (`client/50-shared-ui.js` 的 code → i18n 映射表就踩过)。
|
|
11
|
+
*
|
|
12
|
+
* 现在:**业务码一律 snake_case,且必须在本文件登记**。
|
|
13
|
+
* `test/code-registry-test.mjs` 盯着两件事:
|
|
14
|
+
* 1. `lib/**` 里出现的 `code:` 字面量必须在 `CODES` 里;
|
|
15
|
+
* 2. 清单里不许出现 kebab-case。
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ **不在本清单的三类码,不要往这里加:**
|
|
18
|
+
* - **Node errno**:`ENOENT` / `EEXIST` / `EIO` / `EACCES` …——不是我们的命名空间,
|
|
19
|
+
* 它们来自 `node:fs`,我们只是判读。
|
|
20
|
+
* - **宿主的码**:`FS_SANDBOX_DENIED` / `FS_STALE_VERSION`——我们在解读 DSH 的文件沙箱
|
|
21
|
+
* 与版本闸,改名等于假装自己拥有它。
|
|
22
|
+
* - **`Error.code`**:`SAGE_MEM_UNVERIFIED_OVERWRITE` / `LOCK_TABLE_UNKNOWN`——
|
|
23
|
+
* 挂在**异常对象**上而非返回值里,保留 UPPER 前缀是为了与 errno 区分(`error.code`
|
|
24
|
+
* 和 `result.code` 是两个命名空间,别合并)。
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** 全部业务码。值的唯一合法拼写就是键名。 */
|
|
28
|
+
export const CODES = Object.freeze({
|
|
29
|
+
// ── 读 / 扫描 ──
|
|
30
|
+
missing: 'missing',
|
|
31
|
+
not_found: 'not_found',
|
|
32
|
+
denied: 'denied',
|
|
33
|
+
symlink: 'symlink',
|
|
34
|
+
stat_failed: 'stat_failed',
|
|
35
|
+
not_file: 'not_file',
|
|
36
|
+
not_dir: 'not_dir',
|
|
37
|
+
too_big: 'too_big',
|
|
38
|
+
malformed: 'malformed',
|
|
39
|
+
// ── 写 ──
|
|
40
|
+
invalid: 'invalid',
|
|
41
|
+
invalid_transition: 'invalid_transition',
|
|
42
|
+
io_error: 'io_error',
|
|
43
|
+
changed: 'changed',
|
|
44
|
+
stale_version: 'stale_version',
|
|
45
|
+
revision_changed: 'revision_changed',
|
|
46
|
+
unsupported: 'unsupported',
|
|
47
|
+
exists: 'exists',
|
|
48
|
+
// ── 归档 ──
|
|
49
|
+
not_archived: 'not_archived',
|
|
50
|
+
move_failed: 'move_failed',
|
|
51
|
+
target_exists: 'target_exists',
|
|
52
|
+
delete_failed: 'delete_failed',
|
|
53
|
+
restore_failed: 'restore_failed',
|
|
54
|
+
invalid_name: 'invalid_name',
|
|
55
|
+
reserved: 'reserved',
|
|
56
|
+
locked: 'locked',
|
|
57
|
+
lock_unknown: 'lock_unknown',
|
|
58
|
+
lock_table_unknown: 'lock_table_unknown',
|
|
59
|
+
unverified_overwrite: 'unverified_overwrite',
|
|
60
|
+
// ── 其它 ──
|
|
61
|
+
memory_unavailable: 'memory_unavailable',
|
|
62
|
+
flood: 'flood',
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
/** 值集合(判据用)。 */
|
|
66
|
+
export const CODE_VALUES = new Set(Object.keys(CODES))
|
|
67
|
+
|
|
68
|
+
/** 这个 code 是不是登记过的业务码。 */
|
|
69
|
+
export function isKnownCode(value) {
|
|
70
|
+
return typeof value === 'string' && CODE_VALUES.has(value)
|
|
71
|
+
}
|
package/package.json
CHANGED