@dsh-sparrow/dsh-file-manage 0.1.0 → 0.1.2-rc.1.1
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/CHANGELOG.md +13 -0
- package/CHANGELOG.zh-CN.md +13 -0
- package/LICENSE +21 -0
- package/README.md +4 -2
- package/README.zh-CN.md +4 -2
- package/lib/client.js +123 -53
- package/lib/compat.js +34 -0
- package/lib/host.js +40 -10
- package/lib/types/client/api.d.ts +11 -0
- package/lib/types/client/paging.d.ts +13 -0
- package/lib/types/client/styles.d.ts +8 -3
- package/lib/types/compat.d.ts +34 -0
- package/lib/types/host.d.ts +1 -1
- package/package.json +13 -13
- package/lib/client/FileManageDock.js +0 -154
- package/lib/client/api.js +0 -50
- package/lib/client/index.js +0 -81
- package/lib/client/quota.js +0 -23
- package/lib/client/styles.js +0 -326
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
English | [简体中文](CHANGELOG.zh-CN.md)
|
|
4
4
|
|
|
5
|
+
## 0.1.2-rc.1.1 (2026-09-09)
|
|
6
|
+
|
|
7
|
+
- If dsh is upgraded to a version this plugin does not support yet, the plugin now disables itself instead of running against an unknown contract (dsh and other plugins are unaffected).
|
|
8
|
+
|
|
9
|
+
## 0.1.2-rc.1 (2026-09-05)
|
|
10
|
+
|
|
11
|
+
- Version-line alignment with official dsh 0.1.2-rc.1 (stability line).
|
|
12
|
+
- Pagination and delete race fixes: after quickly reopening the panel or hitting Retry, stale pages no longer merge into the fresh list, "Load more" no longer sticks disabled, and a late delete callback no longer dismisses a newly opened confirmation dialog.
|
|
13
|
+
- Requests that outlive the panel now stop by themselves instead of hanging (15s cap for list/delete, 60s for the count pass), and the count pass stops paging as soon as the client connection drops, so an abandoned count no longer keeps burning API quota.
|
|
14
|
+
- The sidebar "Cloud Files" button now aligns its width with Settings (same as the Archive entry).
|
|
15
|
+
- "Load more" now lives at the end of the file list and the list auto-scrolls to the bottom after each page loads (the button no longer sits in a fixed panel footer).
|
|
16
|
+
- A subtle 🐦 dsh-sparrow brand line closes the panel.
|
|
17
|
+
|
|
5
18
|
## 0.1.0 (2026-09-02)
|
|
6
19
|
|
|
7
20
|
- Promoted 0.1.0 (identical to 0.1.0-alpha.3).
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
简体中文 | [English](CHANGELOG.md)
|
|
4
4
|
|
|
5
|
+
## 0.1.2-rc.1.1(2026-09-09)
|
|
6
|
+
|
|
7
|
+
- dsh 升级到本插件尚不支持的版本时,插件会自动停用(dsh 与其它插件不受影响),不再带病运行。
|
|
8
|
+
|
|
9
|
+
## 0.1.2-rc.1(2026-09-05)
|
|
10
|
+
|
|
11
|
+
- 版本对齐官方 dsh 0.1.2-rc.1(稳定线)。
|
|
12
|
+
- 翻页与删除的并发修复:快速关开面板或点击重试后,陈旧页不再混入新列表、「加载更多」不再卡死,迟到的删除回调不再误关新弹出的确认框。
|
|
13
|
+
- 请求超时后自行停止不再无限挂起(列表/删除 15s、计数 60s 上限),计数翻页在客户端连接断开(超时/导航)时立即停下,被放弃的计数不再持续消耗 API 配额。
|
|
14
|
+
- 侧边栏「云端文件」按钮与 Settings 同宽对齐(与归档入口一致)。
|
|
15
|
+
- 「加载更多」移到文件列表末尾,每页加载后自动滚到列表底部(按钮不再固定在面板底部)。
|
|
16
|
+
- 面板底部新增 🐦 dsh-sparrow 品牌行。
|
|
17
|
+
|
|
5
18
|
## 0.1.0 (2026-09-02)
|
|
6
19
|
|
|
7
20
|
- 转正 0.1.0(与 0.1.0-alpha.3 一致)。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 peiyucn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -12,14 +12,14 @@ Large images pasted into DSH are auto-uploaded to the DeepSeek Files API, but th
|
|
|
12
12
|
dsh plugin --profile web add @dsh-sparrow/dsh-file-manage
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
Requires dsh
|
|
15
|
+
Requires dsh 0.1.2-rc.1 — the exact official version line this release is built and verified against (newer lines, pre-releases in particular, are not covered) — and a working `pnpm` (`dsh plugin` forwards installation to pnpm).
|
|
16
16
|
|
|
17
17
|
> Do **not** run `npm install @dsh-sparrow/dsh-file-manage` directly: that only downloads the package into some `node_modules` and does not register it with the DSH web profile. Use the `dsh plugin` command above, then restart DSH.
|
|
18
18
|
|
|
19
19
|
## Usage
|
|
20
20
|
|
|
21
21
|
* **Entry**: the "Cloud Files" button at the sidebar footer
|
|
22
|
-
* **List**:
|
|
22
|
+
* **List**: newest first with "Load more" at the bottom (100 more files per click, official after cursor); each row shows filename / size / upload time / expiry time (when present)
|
|
23
23
|
* **Quota**: the panel header shows the total count and a netdisk-style quota bar (used storage / the official 25 GiB limit)
|
|
24
24
|
* **Delete**: per-row delete with confirmation; "DSH auto-uploaded" files (`dsh-` prefix) get an extra note — sessions referencing them will transparently re-upload on next use (may be slower)
|
|
25
25
|
* **Copy file_id**: one-click copy per row
|
|
@@ -40,3 +40,5 @@ Requires dsh ≥ 0.1.2-alpha.4 and a working `pnpm` (`dsh plugin` forwards insta
|
|
|
40
40
|
## Uninstall & leftovers
|
|
41
41
|
|
|
42
42
|
* The plugin keeps no local state; uninstalling leaves the cloud files untouched and DSH behavior unchanged
|
|
43
|
+
|
|
44
|
+
**Changelog**: [CHANGELOG.md](https://github.com/peiyucn/dsh-sparrow/blob/main/plugins/dsh-file-manage/CHANGELOG.md)
|
package/README.zh-CN.md
CHANGED
|
@@ -12,14 +12,14 @@ DSH 贴图的大图会自动上传到 DeepSeek Files API(云端文件库),
|
|
|
12
12
|
dsh plugin --profile web add @dsh-sparrow/dsh-file-manage
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
适配 dsh
|
|
15
|
+
适配 dsh 0.1.2-rc.1(本版构建与验证所对齐的确切官方版本线;更新的版本线、尤其是预发布版本不在承诺范围内),并需要可用的 `pnpm`(`dsh plugin` 会把安装操作转发给 pnpm)。
|
|
16
16
|
|
|
17
17
|
> **不要**直接执行 `npm install @dsh-sparrow/dsh-file-manage`:那只会把包下载到某个 `node_modules`,不会注册进 DSH 的 web profile。请使用上面的 `dsh plugin` 命令安装,并在安装后重启 DSH。
|
|
18
18
|
|
|
19
19
|
## 使用
|
|
20
20
|
|
|
21
21
|
* **入口**:侧边栏底部「云端文件」按钮
|
|
22
|
-
*
|
|
22
|
+
* **列表**:最新在前,底部「加载更多」每次多显示 100 条(官方 after 游标翻页);行内展示文件名 / 大小 / 上传时间 / 到期时间(有则显)
|
|
23
23
|
* **配额**:列表顶部显示总数与网盘式配额进度条(已用存储 / 25 GiB 官方上限)
|
|
24
24
|
* **删除**:行内删除按钮二次确认;「DSH 自动上传」角标(`dsh-` 前缀)的文件有额外提示——删除后旧会话再次引用时官方会自动重新上传(可能稍慢)
|
|
25
25
|
* **复制 file_id**:行内按钮一键复制
|
|
@@ -40,3 +40,5 @@ dsh plugin --profile web add @dsh-sparrow/dsh-file-manage
|
|
|
40
40
|
## 卸载与残留
|
|
41
41
|
|
|
42
42
|
* 插件无任何本地持久化状态;卸载后云端文件原样保留,DSH 行为不受影响
|
|
43
|
+
|
|
44
|
+
**更新日志**:[CHANGELOG.zh-CN.md](https://github.com/peiyucn/dsh-sparrow/blob/main/plugins/dsh-file-manage/CHANGELOG.zh-CN.md)
|
package/lib/client.js
CHANGED
|
@@ -32,19 +32,56 @@ __export(index_exports, {
|
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(index_exports);
|
|
34
34
|
|
|
35
|
+
// src/client/paging.ts
|
|
36
|
+
var RENDER_PAGE_SIZE = 100;
|
|
37
|
+
var FILE_PAGE_SIZE = 200;
|
|
38
|
+
function renderedRowCount(totalRows, renderLimit) {
|
|
39
|
+
const total = Math.max(0, Math.trunc(totalRows));
|
|
40
|
+
const limit = Math.max(0, Math.trunc(renderLimit));
|
|
41
|
+
return Math.min(total, limit);
|
|
42
|
+
}
|
|
43
|
+
function hasLoadMore(totalRows, renderLimit, dataHasMore) {
|
|
44
|
+
return renderedRowCount(totalRows, renderLimit) < totalRows || dataHasMore;
|
|
45
|
+
}
|
|
46
|
+
|
|
35
47
|
// src/client/api.ts
|
|
48
|
+
var COUNT_CACHE_TTL_MS = 3e4;
|
|
49
|
+
var countCache = null;
|
|
50
|
+
function readCountCache(entry, nowMs) {
|
|
51
|
+
return entry !== null && nowMs < entry.expiresAt ? entry.value : null;
|
|
52
|
+
}
|
|
53
|
+
function invalidateCountCache() {
|
|
54
|
+
countCache = null;
|
|
55
|
+
}
|
|
36
56
|
var REQUEST_TIMEOUT_MS = 15e3;
|
|
37
57
|
var COUNT_TIMEOUT_MS = 6e4;
|
|
58
|
+
async function fetchWithTimeout(path, init, timeoutMs) {
|
|
59
|
+
try {
|
|
60
|
+
return await fetch(path, { ...init, signal: AbortSignal.timeout(timeoutMs) });
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error instanceof DOMException && (error.name === "AbortError" || error.name === "TimeoutError")) {
|
|
63
|
+
throw new Error(`\u8BF7\u6C42\u8D85\u65F6\uFF08${Math.round(timeoutMs / 1e3)}s\uFF09\uFF0C\u8BF7\u91CD\u8BD5`);
|
|
64
|
+
}
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function readJson(response) {
|
|
69
|
+
try {
|
|
70
|
+
return await response.json();
|
|
71
|
+
} catch {
|
|
72
|
+
return {};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function readErrorMessage(response) {
|
|
76
|
+
const payload = await readJson(response);
|
|
77
|
+
return payload.error?.message ?? `\u8BF7\u6C42\u5931\u8D25\uFF08HTTP ${response.status}\uFF09`;
|
|
78
|
+
}
|
|
38
79
|
async function listApi(after) {
|
|
39
|
-
const params = new URLSearchParams();
|
|
80
|
+
const params = new URLSearchParams({ limit: String(FILE_PAGE_SIZE) });
|
|
40
81
|
if (after !== void 0) params.set("after", after);
|
|
41
|
-
const response = await
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const payload = await response.json();
|
|
45
|
-
if (!response.ok) {
|
|
46
|
-
throw new Error(payload.error?.message ?? `\u8BF7\u6C42\u5931\u8D25\uFF08HTTP ${response.status}\uFF09`);
|
|
47
|
-
}
|
|
82
|
+
const response = await fetchWithTimeout(`/api/file-manage/list?${params.toString()}`, {}, REQUEST_TIMEOUT_MS);
|
|
83
|
+
if (!response.ok) throw new Error(await readErrorMessage(response));
|
|
84
|
+
const payload = await readJson(response);
|
|
48
85
|
return {
|
|
49
86
|
rows: payload.items ?? [],
|
|
50
87
|
hasMore: payload.hasMore ?? false,
|
|
@@ -52,12 +89,12 @@ async function listApi(after) {
|
|
|
52
89
|
};
|
|
53
90
|
}
|
|
54
91
|
async function countApi() {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
92
|
+
const cached = readCountCache(countCache, Date.now());
|
|
93
|
+
if (cached !== null) return cached;
|
|
94
|
+
const response = await fetchWithTimeout("/api/file-manage/count", {}, COUNT_TIMEOUT_MS);
|
|
95
|
+
if (!response.ok) throw new Error(await readErrorMessage(response));
|
|
96
|
+
const payload = await readJson(response);
|
|
97
|
+
const summary = {
|
|
61
98
|
count: payload.count ?? 0,
|
|
62
99
|
totalBytes: payload.totalBytes ?? 0,
|
|
63
100
|
totalBytesLabel: payload.totalBytesLabel ?? "0 B",
|
|
@@ -65,17 +102,14 @@ async function countApi() {
|
|
|
65
102
|
quotaBytesLabel: payload.quotaBytesLabel ?? "0 B",
|
|
66
103
|
quotaCount: payload.quotaCount ?? 0
|
|
67
104
|
};
|
|
105
|
+
countCache = { value: summary, expiresAt: Date.now() + COUNT_CACHE_TTL_MS };
|
|
106
|
+
return summary;
|
|
68
107
|
}
|
|
69
108
|
async function deleteApi(id) {
|
|
70
109
|
const params = new URLSearchParams({ id });
|
|
71
|
-
const response = await
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
});
|
|
75
|
-
const payload = await response.json();
|
|
76
|
-
if (!response.ok) {
|
|
77
|
-
throw new Error(payload.error?.message ?? `\u8BF7\u6C42\u5931\u8D25\uFF08HTTP ${response.status}\uFF09`);
|
|
78
|
-
}
|
|
110
|
+
const response = await fetchWithTimeout(`/api/file-manage/files?${params.toString()}`, { method: "DELETE" }, REQUEST_TIMEOUT_MS);
|
|
111
|
+
if (!response.ok) throw new Error(await readErrorMessage(response));
|
|
112
|
+
invalidateCountCache();
|
|
79
113
|
}
|
|
80
114
|
|
|
81
115
|
// src/client/FileManageDock.tsx
|
|
@@ -411,17 +445,22 @@ var styles = {
|
|
|
411
445
|
fontSize: 12,
|
|
412
446
|
lineHeight: "18px"
|
|
413
447
|
},
|
|
414
|
-
|
|
448
|
+
/** 合集品牌 footer:固定面板底部(滚动区之外),顶部分割线与内容区隔开;紧凑规格。 */
|
|
449
|
+
footer: {
|
|
415
450
|
flex: "none",
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
451
|
+
boxSizing: "border-box",
|
|
452
|
+
padding: "6px 24px 8px",
|
|
453
|
+
borderTop: "1px solid var(--dsw-alias-border-l1, #e2e5ea)",
|
|
454
|
+
textAlign: "center",
|
|
455
|
+
fontSize: 11,
|
|
456
|
+
lineHeight: "16px",
|
|
457
|
+
color: "var(--dsw-alias-label-tertiary, #8a919f)"
|
|
420
458
|
}
|
|
421
459
|
};
|
|
422
460
|
|
|
423
461
|
// src/client/FileManageDock.tsx
|
|
424
462
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
463
|
+
var COPIED_RESET_MS = 2e3;
|
|
425
464
|
function FileManageDock({ wide, listFiles, deleteFile, countFiles, t }) {
|
|
426
465
|
const [open, setOpen] = (0, import_react.useState)(false);
|
|
427
466
|
const [rows, setRows] = (0, import_react.useState)([]);
|
|
@@ -437,13 +476,20 @@ function FileManageDock({ wide, listFiles, deleteFile, countFiles, t }) {
|
|
|
437
476
|
const [summaryPending, setSummaryPending] = (0, import_react.useState)(false);
|
|
438
477
|
const closeButtonRef = (0, import_react.useRef)(null);
|
|
439
478
|
const refreshSeqRef = (0, import_react.useRef)(0);
|
|
479
|
+
const bodyRef = (0, import_react.useRef)(null);
|
|
480
|
+
const scrollPendingRef = (0, import_react.useRef)(false);
|
|
440
481
|
const copiedTimerRef = (0, import_react.useRef)(null);
|
|
482
|
+
const [renderLimit, setRenderLimit] = (0, import_react.useState)(RENDER_PAGE_SIZE);
|
|
441
483
|
const reload = (0, import_react.useCallback)(() => {
|
|
442
484
|
const seq = ++refreshSeqRef.current;
|
|
443
485
|
setLoading(true);
|
|
444
486
|
setSummaryPending(true);
|
|
445
487
|
setSummary(null);
|
|
446
488
|
setError(null);
|
|
489
|
+
setLoadingMore(false);
|
|
490
|
+
setBusyDelete(false);
|
|
491
|
+
scrollPendingRef.current = false;
|
|
492
|
+
setRenderLimit(RENDER_PAGE_SIZE);
|
|
447
493
|
listFiles().then((page) => {
|
|
448
494
|
if (seq !== refreshSeqRef.current) return;
|
|
449
495
|
setRows(page.rows);
|
|
@@ -486,18 +532,29 @@ function FileManageDock({ wide, listFiles, deleteFile, countFiles, t }) {
|
|
|
486
532
|
if (copiedTimerRef.current !== null) window.clearTimeout(copiedTimerRef.current);
|
|
487
533
|
}, []);
|
|
488
534
|
const loadMore = (0, import_react.useCallback)(() => {
|
|
489
|
-
if (
|
|
535
|
+
if (loadingMore) return;
|
|
536
|
+
if (renderLimit < rows.length) {
|
|
537
|
+
setRenderLimit((limit) => limit + RENDER_PAGE_SIZE);
|
|
538
|
+
scrollPendingRef.current = true;
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (lastId === void 0) return;
|
|
542
|
+
const seq = refreshSeqRef.current;
|
|
490
543
|
setLoadingMore(true);
|
|
544
|
+
scrollPendingRef.current = true;
|
|
491
545
|
listFiles(lastId).then((page) => {
|
|
546
|
+
if (seq !== refreshSeqRef.current) return;
|
|
492
547
|
setRows((current) => [...current, ...page.rows]);
|
|
493
548
|
setHasMore(page.hasMore);
|
|
494
549
|
setLastId(page.lastId);
|
|
550
|
+
setRenderLimit((limit) => limit + RENDER_PAGE_SIZE);
|
|
495
551
|
}).catch((reason) => {
|
|
552
|
+
if (seq !== refreshSeqRef.current) return;
|
|
496
553
|
setError(reason instanceof Error ? reason.message : String(reason));
|
|
497
554
|
}).finally(() => {
|
|
498
|
-
setLoadingMore(false);
|
|
555
|
+
if (seq === refreshSeqRef.current) setLoadingMore(false);
|
|
499
556
|
});
|
|
500
|
-
}, [lastId, loadingMore, listFiles]);
|
|
557
|
+
}, [renderLimit, rows.length, lastId, loadingMore, listFiles]);
|
|
501
558
|
const copyId = (0, import_react.useCallback)(async (row) => {
|
|
502
559
|
try {
|
|
503
560
|
await navigator.clipboard.writeText(row.id);
|
|
@@ -505,27 +562,39 @@ function FileManageDock({ wide, listFiles, deleteFile, countFiles, t }) {
|
|
|
505
562
|
if (copiedTimerRef.current !== null) window.clearTimeout(copiedTimerRef.current);
|
|
506
563
|
copiedTimerRef.current = window.setTimeout(() => {
|
|
507
564
|
setCopied(null);
|
|
508
|
-
},
|
|
565
|
+
}, COPIED_RESET_MS);
|
|
509
566
|
} catch {
|
|
510
567
|
setCopied(null);
|
|
511
568
|
setError(t("copyFailed"));
|
|
512
569
|
}
|
|
513
570
|
}, [t]);
|
|
571
|
+
(0, import_react.useEffect)(() => {
|
|
572
|
+
if (!scrollPendingRef.current) return;
|
|
573
|
+
scrollPendingRef.current = false;
|
|
574
|
+
const el = bodyRef.current;
|
|
575
|
+
if (el !== null) el.scrollTop = el.scrollHeight;
|
|
576
|
+
}, [rows, renderLimit]);
|
|
514
577
|
const initialLoading = loading || summaryPending;
|
|
515
578
|
const quotaRatio = summary === null ? 0 : storageUsageRatio(summary.totalBytes, summary.quotaBytes);
|
|
579
|
+
const visibleCount = renderedRowCount(rows.length, renderLimit);
|
|
516
580
|
const submitDelete = (0, import_react.useCallback)(() => {
|
|
517
581
|
if (confirming === null || busyDelete) return;
|
|
582
|
+
const seq = refreshSeqRef.current;
|
|
583
|
+
const deleteId = confirming.id;
|
|
518
584
|
setBusyDelete(true);
|
|
519
|
-
deleteFile(
|
|
520
|
-
setRows((current) => current.filter((row) => row.id !==
|
|
521
|
-
setConfirming(null);
|
|
522
|
-
void countFiles().then(
|
|
585
|
+
deleteFile(deleteId).then(() => {
|
|
586
|
+
setRows((current) => current.filter((row) => row.id !== deleteId));
|
|
587
|
+
setConfirming((current) => current !== null && current.id === deleteId ? null : current);
|
|
588
|
+
void countFiles().then((next) => {
|
|
589
|
+
if (seq === refreshSeqRef.current) setSummary(next);
|
|
590
|
+
}).catch(() => {
|
|
523
591
|
});
|
|
524
592
|
}).catch((reason) => {
|
|
593
|
+
if (seq !== refreshSeqRef.current) return;
|
|
525
594
|
setError(reason instanceof Error ? reason.message : String(reason));
|
|
526
|
-
setConfirming(null);
|
|
595
|
+
setConfirming((current) => current !== null && current.id === deleteId ? null : current);
|
|
527
596
|
}).finally(() => {
|
|
528
|
-
setBusyDelete(false);
|
|
597
|
+
if (seq === refreshSeqRef.current) setBusyDelete(false);
|
|
529
598
|
});
|
|
530
599
|
}, [busyDelete, confirming, deleteFile, countFiles]);
|
|
531
600
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
@@ -560,9 +629,9 @@ function FileManageDock({ wide, listFiles, deleteFile, countFiles, t }) {
|
|
|
560
629
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-file-manage-quota-text", children: `${summary.totalBytesLabel} / ${summary.quotaBytesLabel} \xB7 ${t("quota.used", { percent: formatUsagePercent(quotaRatio) })}` }),
|
|
561
630
|
quotaRatio > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-file-manage-quota-fill", style: { width: `${Math.round(quotaRatio * 100)}%` } }) : null
|
|
562
631
|
] }),
|
|
563
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-file-manage-count", children:
|
|
632
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-file-manage-count", children: visibleCount < summary.count ? t("summary.loaded", { loaded: visibleCount, count: summary.count }) : t("summary.count", { count: summary.count }) })
|
|
564
633
|
] }) : null,
|
|
565
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...styles.body, padding: summary !== null ? "0 24px 24px" : "12px 24px 24px" }, className: "dsh-file-manage-body", children: initialLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-file-manage-loading", role: "status", children: [
|
|
634
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: bodyRef, style: { ...styles.body, padding: summary !== null ? "0 24px 24px" : "12px 24px 24px" }, className: "dsh-file-manage-body", children: initialLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-file-manage-loading", role: "status", children: [
|
|
566
635
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-file-manage-spinner", "aria-hidden": true }),
|
|
567
636
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("loading") })
|
|
568
637
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
@@ -574,7 +643,7 @@ function FileManageDock({ wide, listFiles, deleteFile, countFiles, t }) {
|
|
|
574
643
|
}, children: t("retry") })
|
|
575
644
|
] }) : null,
|
|
576
645
|
error === null && rows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.secondarySmall, children: t("empty") }) : null,
|
|
577
|
-
rows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-file-manage-card", children: rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.row, children: [
|
|
646
|
+
rows.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-file-manage-card", children: rows.slice(0, renderLimit).map((row) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.row, children: [
|
|
578
647
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { minWidth: 0 }, children: [
|
|
579
648
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6, minWidth: 0 }, children: [
|
|
580
649
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.title, title: row.filename, children: row.filename }),
|
|
@@ -611,20 +680,21 @@ function FileManageDock({ wide, listFiles, deleteFile, countFiles, t }) {
|
|
|
611
680
|
}
|
|
612
681
|
)
|
|
613
682
|
] })
|
|
614
|
-
] }, row.id)) }) : null
|
|
683
|
+
] }, row.id)) }) : null,
|
|
684
|
+
hasLoadMore(rows.length, renderLimit, hasMore) && !initialLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", justifyContent: "center", paddingTop: 12 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
685
|
+
"button",
|
|
686
|
+
{
|
|
687
|
+
type: "button",
|
|
688
|
+
className: "dsh-file-manage-btn",
|
|
689
|
+
disabled: loadingMore,
|
|
690
|
+
onClick: () => {
|
|
691
|
+
void loadMore();
|
|
692
|
+
},
|
|
693
|
+
children: loadingMore ? t("loading") : t("loadMore")
|
|
694
|
+
}
|
|
695
|
+
) }) : null
|
|
615
696
|
] }) }),
|
|
616
|
-
|
|
617
|
-
"button",
|
|
618
|
-
{
|
|
619
|
-
type: "button",
|
|
620
|
-
className: "dsh-file-manage-btn",
|
|
621
|
-
disabled: loadingMore,
|
|
622
|
-
onClick: () => {
|
|
623
|
-
void loadMore();
|
|
624
|
-
},
|
|
625
|
-
children: loadingMore ? t("loading") : t("loadMore")
|
|
626
|
-
}
|
|
627
|
-
) }) : null
|
|
697
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.footer, children: "\u{1F426} dsh-sparrow" })
|
|
628
698
|
] }) }) : null,
|
|
629
699
|
confirming !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-file-manage-confirm-overlay", role: "presentation", onMouseDown: (event) => {
|
|
630
700
|
if (event.target === event.currentTarget && !busyDelete) setConfirming(null);
|
package/lib/compat.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 宿主兼容自检(根 AGENTS《插件与宿主兼容》)——纯能力版。
|
|
3
|
+
*
|
|
4
|
+
* 本插件不读 dsh 会话数据,因此没有会话格式门;守卫点是**宿主能力面**
|
|
5
|
+
* (依赖的服务/方法/导出是否存在,以及运行环境特性)。不满足即抛错自停用,
|
|
6
|
+
* 绝不带病运行。判定与文案是纯逻辑,供单测。
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* 缺失的能力名(纯逻辑,供单测)。
|
|
10
|
+
* @param capabilities - 本插件声明的能力。
|
|
11
|
+
* @returns 未满足的能力名,按声明顺序。
|
|
12
|
+
*/
|
|
13
|
+
export function missingCapabilities(capabilities) {
|
|
14
|
+
return capabilities.filter(capability => !capability.ok).map(capability => capability.name);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 能力门:任一必需能力缺失即抛错(自停用)。
|
|
18
|
+
*
|
|
19
|
+
* cordis 逐插件捕获 `apply` 异常并把该插件标为 inactive,dsh 与其余插件不受影响
|
|
20
|
+
* (已查证 cordis `lib/index.js:1350-1362`)。抛错前先记一条面向用户的告警,
|
|
21
|
+
* 说明「为什么停用、怎么恢复」。
|
|
22
|
+
* @param ctx - 插件上下文(只用到 logger)。
|
|
23
|
+
* @param pluginName - 插件名(日志与错误前缀)。
|
|
24
|
+
* @param capabilities - 本插件声明的能力。
|
|
25
|
+
*/
|
|
26
|
+
export function assertCapabilities(ctx, pluginName, capabilities) {
|
|
27
|
+
const missing = missingCapabilities(capabilities);
|
|
28
|
+
if (missing.length === 0)
|
|
29
|
+
return;
|
|
30
|
+
const reason = `缺少本插件依赖的 ${missing.join('、')}`;
|
|
31
|
+
ctx.logger.warn(`${pluginName}: ${reason};已停用插件以免影响 dsh(升级本插件或运行环境后自动恢复)`);
|
|
32
|
+
throw new Error(`${pluginName}: ${reason}`);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=compat.js.map
|
package/lib/host.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** dsh-file-manage host half:DeepSeek Files API 云端文件清单 REST 路由(复用官方 DeepSeekFilesClient)。 */
|
|
2
2
|
import { credentialRef } from '@deepseek-ai/dsh-credentials';
|
|
3
|
-
import {
|
|
3
|
+
import { assertCapabilities } from './compat.js';
|
|
4
4
|
import { classifyUpstreamError, COUNT_PAGE_LIMIT, COUNT_PAGE_TIMEOUT_MS, decodeFileIdParam, formatBytes, MAX_COUNT_PAGES, normalizePageQuery, toFileRow } from './files.js';
|
|
5
5
|
export const name = 'dsh-file-manage';
|
|
6
6
|
export const inject = ['webServer', 'credentials', 'settings'];
|
|
@@ -9,6 +9,11 @@ const PREFIX = '/api/file-manage';
|
|
|
9
9
|
const DEFAULT_BASE_URL = 'https://api.deepseek.com';
|
|
10
10
|
const DEFAULT_API_KEY_ENV = 'DEEPSEEK_API_KEY';
|
|
11
11
|
const LLM_DEEPSEEK_NS = 'llm-deepseek';
|
|
12
|
+
/**
|
|
13
|
+
* 单页 list / 单条 delete 的上游超时:官方 client 的 fetch 本身无默认超时,客户端断开
|
|
14
|
+
* (关面板 / 15s 超时)后残留的上游请求必须自限,否则会无限挂起并占用官方 API 配额。
|
|
15
|
+
*/
|
|
16
|
+
const UPSTREAM_TIMEOUT_MS = 15_000;
|
|
12
17
|
/** 插件自身错误(面板可读的 code / message + HTTP 状态)。 */
|
|
13
18
|
class FileManageError extends Error {
|
|
14
19
|
code;
|
|
@@ -68,11 +73,31 @@ function sendUpstreamError(res, error) {
|
|
|
68
73
|
const info = classifyUpstreamError(error);
|
|
69
74
|
sendJson(res, info.status, { error: { code: info.code, message: info.message } });
|
|
70
75
|
}
|
|
76
|
+
let officialSurface;
|
|
77
|
+
/** 读取(并缓存)官方导出面;缺任何一项即抛可读错误(启动期抛错 = 自停用)。 */
|
|
78
|
+
async function deepSeekSurface() {
|
|
79
|
+
if (officialSurface !== undefined)
|
|
80
|
+
return officialSurface;
|
|
81
|
+
const module = await import('@deepseek-ai/dsh-llm-deepseek');
|
|
82
|
+
const missing = ['DeepSeekFileId', 'DeepSeekFilesClient', 'MAX_STORED_FILE_BYTES', 'MAX_STORED_FILE_COUNT']
|
|
83
|
+
.filter(key => module[key] === undefined);
|
|
84
|
+
if (missing.length > 0) {
|
|
85
|
+
throw new FileManageError('UNSUPPORTED_HOST', `当前 dsh 的 dsh-llm-deepseek 不再导出 ${missing.join('、')};本插件已停用(升级本插件后自动恢复)`, 501);
|
|
86
|
+
}
|
|
87
|
+
officialSurface = module;
|
|
88
|
+
return officialSurface;
|
|
89
|
+
}
|
|
71
90
|
/**
|
|
72
91
|
* host half 入口:注册 prefix 路由(list / 单条删除),全部副作用挂在 apply 的 effect 上。
|
|
73
92
|
* @param ctx - DSH 插件上下文。
|
|
74
93
|
*/
|
|
75
|
-
export function apply(ctx) {
|
|
94
|
+
export async function apply(ctx) {
|
|
95
|
+
// 宿主兼容自检(根 AGENTS《插件与宿主兼容》,先于一切注册):能力面或官方面缺失即自停用。
|
|
96
|
+
assertCapabilities(ctx, name, [
|
|
97
|
+
{ name: 'webServer.register', ok: typeof ctx.webServer?.register === 'function' },
|
|
98
|
+
{ name: 'credentials.resolve', ok: typeof ctx.credentials?.resolve === 'function' },
|
|
99
|
+
]);
|
|
100
|
+
await deepSeekSurface();
|
|
76
101
|
ctx.effect(() => ctx.webServer.register({
|
|
77
102
|
kind: 'prefix',
|
|
78
103
|
path: PREFIX,
|
|
@@ -80,6 +105,7 @@ export function apply(ctx) {
|
|
|
80
105
|
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
81
106
|
const pathname = url.pathname;
|
|
82
107
|
try {
|
|
108
|
+
const surface = await deepSeekSurface();
|
|
83
109
|
if (req.method === 'GET' && pathname === `${PREFIX}/list`) {
|
|
84
110
|
const query = normalizePageQuery({
|
|
85
111
|
after: url.searchParams.get('after') ?? undefined,
|
|
@@ -87,11 +113,12 @@ export function apply(ctx) {
|
|
|
87
113
|
order: url.searchParams.get('order') ?? undefined,
|
|
88
114
|
});
|
|
89
115
|
const connection = await resolveConnection(ctx);
|
|
90
|
-
const client = new DeepSeekFilesClient({ baseURL: connection.baseURL, apiKey: connection.apiKey });
|
|
116
|
+
const client = new surface.DeepSeekFilesClient({ baseURL: connection.baseURL, apiKey: connection.apiKey });
|
|
91
117
|
const page = await client.list({
|
|
92
|
-
...query.after === undefined ? {} : { after: DeepSeekFileId(query.after) },
|
|
118
|
+
...query.after === undefined ? {} : { after: surface.DeepSeekFileId(query.after) },
|
|
93
119
|
limit: query.limit,
|
|
94
120
|
order: query.order,
|
|
121
|
+
signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS),
|
|
95
122
|
});
|
|
96
123
|
sendJson(res, 200, {
|
|
97
124
|
items: page.data.map(toFileRow),
|
|
@@ -103,11 +130,14 @@ export function apply(ctx) {
|
|
|
103
130
|
if (req.method === 'GET' && pathname === `${PREFIX}/count`) {
|
|
104
131
|
// 官方 list 无总数字段:翻到底累计(每页 1000,配额内最多 10 页,MAX_COUNT_PAGES 兜底)。
|
|
105
132
|
const connection = await resolveConnection(ctx);
|
|
106
|
-
const client = new DeepSeekFilesClient({ baseURL: connection.baseURL, apiKey: connection.apiKey });
|
|
133
|
+
const client = new surface.DeepSeekFilesClient({ baseURL: connection.baseURL, apiKey: connection.apiKey });
|
|
107
134
|
let count = 0;
|
|
108
135
|
let totalBytes = 0;
|
|
109
136
|
let after;
|
|
110
137
|
for (let page = 0; page < MAX_COUNT_PAGES; page++) {
|
|
138
|
+
// 客户端已断开(超时 / 关面板)时停止翻页:剩余页数没有接收方,白烧官方 API 配额。
|
|
139
|
+
if (res.destroyed)
|
|
140
|
+
break;
|
|
111
141
|
const result = await client.list({
|
|
112
142
|
...after === undefined ? {} : { after },
|
|
113
143
|
limit: COUNT_PAGE_LIMIT,
|
|
@@ -125,9 +155,9 @@ export function apply(ctx) {
|
|
|
125
155
|
count,
|
|
126
156
|
totalBytes,
|
|
127
157
|
totalBytesLabel: formatBytes(totalBytes),
|
|
128
|
-
quotaBytes: MAX_STORED_FILE_BYTES,
|
|
129
|
-
quotaBytesLabel: formatBytes(MAX_STORED_FILE_BYTES),
|
|
130
|
-
quotaCount: MAX_STORED_FILE_COUNT,
|
|
158
|
+
quotaBytes: surface.MAX_STORED_FILE_BYTES,
|
|
159
|
+
quotaBytesLabel: formatBytes(surface.MAX_STORED_FILE_BYTES),
|
|
160
|
+
quotaCount: surface.MAX_STORED_FILE_COUNT,
|
|
131
161
|
});
|
|
132
162
|
return;
|
|
133
163
|
}
|
|
@@ -138,8 +168,8 @@ export function apply(ctx) {
|
|
|
138
168
|
return;
|
|
139
169
|
}
|
|
140
170
|
const connection = await resolveConnection(ctx);
|
|
141
|
-
const client = new DeepSeekFilesClient({ baseURL: connection.baseURL, apiKey: connection.apiKey });
|
|
142
|
-
await client.delete(DeepSeekFileId(id));
|
|
171
|
+
const client = new surface.DeepSeekFilesClient({ baseURL: connection.baseURL, apiKey: connection.apiKey });
|
|
172
|
+
await client.delete(surface.DeepSeekFileId(id), AbortSignal.timeout(UPSTREAM_TIMEOUT_MS));
|
|
143
173
|
sendJson(res, 200, { deleted: true, id });
|
|
144
174
|
return;
|
|
145
175
|
}
|
|
@@ -9,6 +9,17 @@ export interface FileCountSummary {
|
|
|
9
9
|
quotaBytesLabel: string;
|
|
10
10
|
quotaCount: number;
|
|
11
11
|
}
|
|
12
|
+
/** 总数统计 TTL 缓存时长:面板快速重开/重试不重复 count 全量翻页(count 要翻到底,配额内最多 10 页)。 */
|
|
13
|
+
export declare const COUNT_CACHE_TTL_MS = 30000;
|
|
14
|
+
/** TTL 缓存条目(api.ts 模块级单例:面板单实例,随 client bundle 生命周期,有界一条)。 */
|
|
15
|
+
export interface CountCacheEntry {
|
|
16
|
+
readonly value: FileCountSummary;
|
|
17
|
+
readonly expiresAt: number;
|
|
18
|
+
}
|
|
19
|
+
/** TTL 读缓存(时钟注入的纯逻辑,供单测):未过期返回快照,过期或缺条返回 null。 */
|
|
20
|
+
export declare function readCountCache(entry: CountCacheEntry | null, nowMs: number): FileCountSummary | null;
|
|
21
|
+
/** 删除成功后失效计数缓存:下一次 count 拉取重新翻页,包含删除结果。 */
|
|
22
|
+
export declare function invalidateCountCache(): void;
|
|
12
23
|
export declare function listApi(after?: string): Promise<{
|
|
13
24
|
rows: FileRow[];
|
|
14
25
|
hasMore: boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** 文件面板渲染窗口纯逻辑(对齐 archive-manage spec 09):数据分页与 DOM 渲染窗口分离。 @module dsh-file-manage/client/paging */
|
|
2
|
+
/** 渲染窗口大小:每次渲染的行数上限,「加载更多」按此递增(archive 同款 100)。 */
|
|
3
|
+
export declare const RENDER_PAGE_SIZE = 100;
|
|
4
|
+
/**
|
|
5
|
+
* 数据分页大小:每次 list 拉取的行数。必须大于渲染窗口(200 > 100),
|
|
6
|
+
* 窗口才会真正裁剪 DOM——否则窗口永远跑在数据前面,退化回全量渲染。
|
|
7
|
+
*/
|
|
8
|
+
export declare const FILE_PAGE_SIZE = 200;
|
|
9
|
+
/** 窗口内可见行数:min(已加载行数, 窗口上限);负数/小数钳到安全值(不抛)。 */
|
|
10
|
+
export declare function renderedRowCount(totalRows: number, renderLimit: number): number;
|
|
11
|
+
/** 「加载更多」是否可见:窗口未盖满已加载行(纯延伸窗口),或服务端还有更多数据(延伸窗口 + 拉页)。 */
|
|
12
|
+
export declare function hasLoadMore(totalRows: number, renderLimit: number, dataHasMore: boolean): boolean;
|
|
13
|
+
//# sourceMappingURL=paging.d.ts.map
|
|
@@ -56,11 +56,16 @@ export declare const styles: {
|
|
|
56
56
|
fontSize: number;
|
|
57
57
|
lineHeight: string;
|
|
58
58
|
};
|
|
59
|
-
|
|
59
|
+
/** 合集品牌 footer:固定面板底部(滚动区之外),顶部分割线与内容区隔开;紧凑规格。 */
|
|
60
|
+
readonly footer: {
|
|
60
61
|
flex: string;
|
|
61
|
-
|
|
62
|
-
justifyContent: "center";
|
|
62
|
+
boxSizing: "border-box";
|
|
63
63
|
padding: string;
|
|
64
|
+
borderTop: string;
|
|
65
|
+
textAlign: "center";
|
|
66
|
+
fontSize: number;
|
|
67
|
+
lineHeight: string;
|
|
68
|
+
color: "var(--dsw-alias-label-tertiary, #8a919f)";
|
|
64
69
|
};
|
|
65
70
|
};
|
|
66
71
|
//# sourceMappingURL=styles.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 宿主兼容自检(根 AGENTS《插件与宿主兼容》)——纯能力版。
|
|
3
|
+
*
|
|
4
|
+
* 本插件不读 dsh 会话数据,因此没有会话格式门;守卫点是**宿主能力面**
|
|
5
|
+
* (依赖的服务/方法/导出是否存在,以及运行环境特性)。不满足即抛错自停用,
|
|
6
|
+
* 绝不带病运行。判定与文案是纯逻辑,供单测。
|
|
7
|
+
*/
|
|
8
|
+
/** 一项能力:`name` 进日志,`ok` 由调用方探测(服务/方法/导出/浏览器特性是否存在)。 */
|
|
9
|
+
export interface HostCapability {
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly ok: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 缺失的能力名(纯逻辑,供单测)。
|
|
15
|
+
* @param capabilities - 本插件声明的能力。
|
|
16
|
+
* @returns 未满足的能力名,按声明顺序。
|
|
17
|
+
*/
|
|
18
|
+
export declare function missingCapabilities(capabilities: readonly HostCapability[]): string[];
|
|
19
|
+
/**
|
|
20
|
+
* 能力门:任一必需能力缺失即抛错(自停用)。
|
|
21
|
+
*
|
|
22
|
+
* cordis 逐插件捕获 `apply` 异常并把该插件标为 inactive,dsh 与其余插件不受影响
|
|
23
|
+
* (已查证 cordis `lib/index.js:1350-1362`)。抛错前先记一条面向用户的告警,
|
|
24
|
+
* 说明「为什么停用、怎么恢复」。
|
|
25
|
+
* @param ctx - 插件上下文(只用到 logger)。
|
|
26
|
+
* @param pluginName - 插件名(日志与错误前缀)。
|
|
27
|
+
* @param capabilities - 本插件声明的能力。
|
|
28
|
+
*/
|
|
29
|
+
export declare function assertCapabilities(ctx: {
|
|
30
|
+
logger: {
|
|
31
|
+
warn(message: string): void;
|
|
32
|
+
};
|
|
33
|
+
}, pluginName: string, capabilities: readonly HostCapability[]): void;
|
|
34
|
+
//# sourceMappingURL=compat.d.ts.map
|
package/lib/types/host.d.ts
CHANGED
|
@@ -6,5 +6,5 @@ export declare const inject: string[];
|
|
|
6
6
|
* host half 入口:注册 prefix 路由(list / 单条删除),全部副作用挂在 apply 的 effect 上。
|
|
7
7
|
* @param ctx - DSH 插件上下文。
|
|
8
8
|
*/
|
|
9
|
-
export declare function apply(ctx: Context): void
|
|
9
|
+
export declare function apply(ctx: Context): Promise<void>;
|
|
10
10
|
//# sourceMappingURL=host.d.ts.map
|