@tita-agent/dsh-data-table 0.0.0-stage → 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/README.md +172 -2
- package/cordis.patch.yml +7 -0
- package/lib/client.js +16766 -0
- package/lib/contract-envelope.d.ts +91 -0
- package/lib/contract-envelope.js +106 -0
- package/lib/contract-layout.d.ts +130 -0
- package/lib/contract-layout.js +138 -0
- package/lib/contract-refs.d.ts +39 -0
- package/lib/contract-refs.js +9 -0
- package/lib/contract-version.d.ts +311 -0
- package/lib/contract-version.js +270 -0
- package/lib/contract-wire.d.ts +147 -0
- package/lib/contract-wire.js +99 -0
- package/lib/contracts.d.ts +12 -0
- package/lib/contracts.js +12 -0
- package/lib/types/audit.d.ts +42 -0
- package/lib/types/audit.js +39 -0
- package/lib/types/client/ProjectImportMenu.d.ts +30 -0
- package/lib/types/client/ProjectImportMenu.js +70 -0
- package/lib/types/client/SheetPanel.d.ts +33 -0
- package/lib/types/client/SheetPanel.js +1766 -0
- package/lib/types/client/SheetToggle.d.ts +18 -0
- package/lib/types/client/SheetToggle.js +26 -0
- package/lib/types/client/index.d.ts +15 -0
- package/lib/types/client/index.js +110 -0
- package/lib/types/client/persistence/changes.d.ts +15 -0
- package/lib/types/client/persistence/changes.js +51 -0
- package/lib/types/client/persistence/ids.d.ts +34 -0
- package/lib/types/client/persistence/ids.js +48 -0
- package/lib/types/client/persistence/merge.d.ts +35 -0
- package/lib/types/client/persistence/merge.js +362 -0
- package/lib/types/client/store.d.ts +61 -0
- package/lib/types/client/store.js +152 -0
- package/lib/types/client/surface-context.d.ts +27 -0
- package/lib/types/client/surface-context.js +104 -0
- package/lib/types/client/surface-control.d.ts +2 -0
- package/lib/types/client/surface-control.js +12 -0
- package/lib/types/client/transport/workbookApi.d.ts +159 -0
- package/lib/types/client/transport/workbookApi.js +246 -0
- package/lib/types/client/univer/StandaloneWorkbookPage.d.ts +5 -0
- package/lib/types/client/univer/StandaloneWorkbookPage.js +7 -0
- package/lib/types/client/univer/WorkbookCanvas.d.ts +40 -0
- package/lib/types/client/univer/WorkbookCanvas.js +87 -0
- package/lib/types/client/univer/WorkbookView.d.ts +3 -0
- package/lib/types/client/univer/WorkbookView.js +2 -0
- package/lib/types/client/univer/loader.d.ts +18 -0
- package/lib/types/client/univer/loader.js +81 -0
- package/lib/types/client/univer/standalone-entry.d.ts +1 -0
- package/lib/types/client/univer/standalone-entry.js +19 -0
- package/lib/types/client/versions/VersionChrome.d.ts +33 -0
- package/lib/types/client/versions/VersionChrome.js +21 -0
- package/lib/types/client/versions/inspect.d.ts +70 -0
- package/lib/types/client/versions/inspect.js +88 -0
- package/lib/types/client/versions/preview-decoration.d.ts +11 -0
- package/lib/types/client/versions/preview-decoration.js +54 -0
- package/lib/types/client/versions/records.d.ts +19 -0
- package/lib/types/client/versions/records.js +70 -0
- package/lib/types/client/workspace-host.d.ts +61 -0
- package/lib/types/client/workspace-host.js +124 -0
- package/lib/types/collaboration-hub.d.ts +31 -0
- package/lib/types/collaboration-hub.js +69 -0
- package/lib/types/contract/ids.d.ts +24 -0
- package/lib/types/contract/ids.js +46 -0
- package/lib/types/contract/mutations.d.ts +25 -0
- package/lib/types/contract/mutations.js +782 -0
- package/lib/types/contract/operations.d.ts +54 -0
- package/lib/types/contract/operations.js +203 -0
- package/lib/types/contract/snapshots.d.ts +176 -0
- package/lib/types/contract/snapshots.js +351 -0
- package/lib/types/contract/univer-api-catalog.d.ts +29 -0
- package/lib/types/contract/univer-api-catalog.js +264 -0
- package/lib/types/errors.d.ts +8 -0
- package/lib/types/errors.js +12 -0
- package/lib/types/http/assets-routes.d.ts +3 -0
- package/lib/types/http/assets-routes.js +169 -0
- package/lib/types/http/collaboration.d.ts +10 -0
- package/lib/types/http/collaboration.js +154 -0
- package/lib/types/http/history.d.ts +15 -0
- package/lib/types/http/history.js +134 -0
- package/lib/types/http/project-files.d.ts +79 -0
- package/lib/types/http/project-files.js +206 -0
- package/lib/types/http/project-scope.d.ts +50 -0
- package/lib/types/http/project-scope.js +52 -0
- package/lib/types/http/routes.d.ts +98 -0
- package/lib/types/http/routes.js +323 -0
- package/lib/types/http/standalone.d.ts +9 -0
- package/lib/types/http/standalone.js +31 -0
- package/lib/types/http/trunk.d.ts +10 -0
- package/lib/types/http/trunk.js +322 -0
- package/lib/types/http/worktree.d.ts +9 -0
- package/lib/types/http/worktree.js +145 -0
- package/lib/types/index.d.ts +18 -0
- package/lib/types/index.js +156 -0
- package/lib/types/invariant.d.ts +15 -0
- package/lib/types/invariant.js +15 -0
- package/lib/types/request-params.d.ts +21 -0
- package/lib/types/request-params.js +76 -0
- package/lib/types/store/paths.d.ts +68 -0
- package/lib/types/store/paths.js +148 -0
- package/lib/types/store/project.d.ts +88 -0
- package/lib/types/store/project.js +120 -0
- package/lib/types/store/read.d.ts +110 -0
- package/lib/types/store/read.js +404 -0
- package/lib/types/store/retention.d.ts +27 -0
- package/lib/types/store/retention.js +111 -0
- package/lib/types/store/write.d.ts +22 -0
- package/lib/types/store/write.js +210 -0
- package/lib/types/tools/arguments.d.ts +40 -0
- package/lib/types/tools/arguments.js +130 -0
- package/lib/types/tools/cells.d.ts +50 -0
- package/lib/types/tools/cells.js +298 -0
- package/lib/types/tools/index.d.ts +2 -0
- package/lib/types/tools/index.js +250 -0
- package/lib/types/tools/render.d.ts +47 -0
- package/lib/types/tools/render.js +290 -0
- package/lib/types/tools/univer-api.d.ts +18 -0
- package/lib/types/tools/univer-api.js +198 -0
- package/lib/types/tools/workbook-catalog.d.ts +3 -0
- package/lib/types/tools/workbook-catalog.js +83 -0
- package/lib/types/tools/workbook-edit.d.ts +192 -0
- package/lib/types/tools/workbook-edit.js +912 -0
- package/lib/types/tools/workbook-find.d.ts +11 -0
- package/lib/types/tools/workbook-find.js +89 -0
- package/lib/types/tools/workbook-history.d.ts +2 -0
- package/lib/types/tools/workbook-history.js +59 -0
- package/lib/types/tools/workbook-read.d.ts +21 -0
- package/lib/types/tools/workbook-read.js +204 -0
- package/lib/types/types.d.ts +377 -0
- package/lib/types/types.js +34 -0
- package/lib/types/univer/apply-operation.d.ts +14 -0
- package/lib/types/univer/apply-operation.js +398 -0
- package/lib/types/univer/chunk.d.ts +68 -0
- package/lib/types/univer/chunk.js +16 -0
- package/lib/types/univer/decoration.d.ts +34 -0
- package/lib/types/univer/decoration.js +88 -0
- package/lib/types/univer/diagnostics.d.ts +31 -0
- package/lib/types/univer/diagnostics.js +141 -0
- package/lib/types/univer/engine.d.ts +33 -0
- package/lib/types/univer/engine.js +338 -0
- package/lib/types/univer/entry.d.ts +8 -0
- package/lib/types/univer/entry.js +11 -0
- package/lib/types/univer/facade.d.ts +107 -0
- package/lib/types/univer/facade.js +1 -0
- package/lib/types/univer/fonts.d.ts +202 -0
- package/lib/types/univer/fonts.js +140 -0
- package/lib/types/univer/plugins/collaboration.d.ts +23 -0
- package/lib/types/univer/plugins/collaboration.js +47 -0
- package/lib/types/univer/plugins/export.d.ts +39 -0
- package/lib/types/univer/plugins/export.js +246 -0
- package/lib/types/univer/plugins/history.d.ts +24 -0
- package/lib/types/univer/plugins/history.js +50 -0
- package/lib/types/univer/plugins/version.d.ts +13 -0
- package/lib/types/univer/plugins/version.js +13 -0
- package/lib/types/version/diff.d.ts +36 -0
- package/lib/types/version/diff.js +156 -0
- package/lib/types/version/grouping.d.ts +7 -0
- package/lib/types/version/grouping.js +94 -0
- package/lib/types/version/records.d.ts +17 -0
- package/lib/types/version/records.js +102 -0
- package/lib/types/version/session.d.ts +36 -0
- package/lib/types/version/session.js +89 -0
- package/lib/types/version/summary.d.ts +24 -0
- package/lib/types/version/summary.js +111 -0
- package/lib/types/workbook-manager.d.ts +191 -0
- package/lib/types/workbook-manager.js +935 -0
- package/lib/types/xlsx-export-worker.d.ts +5 -0
- package/lib/types/xlsx-export-worker.js +24 -0
- package/lib/types/xlsx-export.d.ts +21 -0
- package/lib/types/xlsx-export.js +371 -0
- package/lib/types/xlsx-import-worker.d.ts +1 -0
- package/lib/types/xlsx-import-worker.js +19 -0
- package/lib/univer-client.js +690 -0
- package/lib/workbook-page.js +23826 -0
- package/lib/xlsx-export-worker.js +104 -0
- package/lib/xlsx-import-worker.js +46 -0
- package/package.json +106 -3
- package/src/audit.ts +58 -0
- package/src/client/ProjectImportMenu.tsx +100 -0
- package/src/client/SheetPanel.module.css +1578 -0
- package/src/client/SheetPanel.tsx +1934 -0
- package/src/client/SheetToggle.module.css +73 -0
- package/src/client/SheetToggle.tsx +65 -0
- package/src/client/contract.d.ts +19 -0
- package/src/client/index.ts +131 -0
- package/src/client/persistence/changes.ts +58 -0
- package/src/client/persistence/ids.ts +60 -0
- package/src/client/persistence/merge.ts +392 -0
- package/src/client/store.ts +188 -0
- package/src/client/surface-context.ts +123 -0
- package/src/client/surface-control.ts +13 -0
- package/src/client/transport/workbookApi.ts +355 -0
- package/src/client/univer/SheetView.module.css +110 -0
- package/src/client/univer/StandaloneWorkbookPage.module.css +8 -0
- package/src/client/univer/StandaloneWorkbookPage.tsx +15 -0
- package/src/client/univer/WorkbookCanvas.tsx +128 -0
- package/src/client/univer/WorkbookView.module.css +30 -0
- package/src/client/univer/WorkbookView.tsx +3 -0
- package/src/client/univer/loader.ts +90 -0
- package/src/client/univer/react-dom-client.d.ts +10 -0
- package/src/client/univer/standalone-entry.tsx +19 -0
- package/src/client/univer/standalone.css +12 -0
- package/src/client/univer/univer-locales.d.ts +89 -0
- package/src/client/univer/univer-modules.d.ts +26 -0
- package/src/client/versions/VersionChrome.tsx +114 -0
- package/src/client/versions/inspect.ts +138 -0
- package/src/client/versions/preview-decoration.ts +54 -0
- package/src/client/versions/records.ts +78 -0
- package/src/client/workspace-host.ts +147 -0
- package/src/collaboration-hub.ts +83 -0
- package/src/contract/ids.ts +50 -0
- package/src/contract/mutations.ts +791 -0
- package/src/contract/operations.ts +221 -0
- package/src/contract/snapshots.ts +383 -0
- package/src/contract/univer-api-catalog.ts +289 -0
- package/src/css-modules.d.ts +6 -0
- package/src/errors.ts +25 -0
- package/src/http/assets-routes.ts +177 -0
- package/src/http/collaboration.ts +144 -0
- package/src/http/history.ts +151 -0
- package/src/http/project-files.ts +227 -0
- package/src/http/project-scope.ts +77 -0
- package/src/http/routes.ts +364 -0
- package/src/http/standalone.ts +32 -0
- package/src/http/trunk.ts +322 -0
- package/src/http/worktree.ts +119 -0
- package/src/index.ts +165 -0
- package/src/invariant.ts +17 -0
- package/src/request-params.ts +92 -0
- package/src/store/paths.ts +165 -0
- package/src/store/project.ts +137 -0
- package/src/store/read.ts +454 -0
- package/src/store/retention.ts +106 -0
- package/src/store/write.ts +230 -0
- package/src/tools/arguments.ts +128 -0
- package/src/tools/cells.ts +298 -0
- package/src/tools/index.ts +274 -0
- package/src/tools/render.ts +301 -0
- package/src/tools/univer-api.ts +243 -0
- package/src/tools/workbook-catalog.ts +94 -0
- package/src/tools/workbook-edit.ts +987 -0
- package/src/tools/workbook-find.ts +80 -0
- package/src/tools/workbook-history.ts +63 -0
- package/src/tools/workbook-read.ts +210 -0
- package/src/types.ts +432 -0
- package/src/univer/apply-operation.ts +369 -0
- package/src/univer/chunk.ts +77 -0
- package/src/univer/decoration.ts +111 -0
- package/src/univer/diagnostics.ts +136 -0
- package/src/univer/engine.ts +370 -0
- package/src/univer/entry.ts +14 -0
- package/src/univer/facade.ts +91 -0
- package/src/univer/fonts.ts +148 -0
- package/src/univer/overrides.css +33 -0
- package/src/univer/plugins/collaboration.ts +47 -0
- package/src/univer/plugins/export.ts +236 -0
- package/src/univer/plugins/history.ts +49 -0
- package/src/univer/plugins/version.ts +13 -0
- package/src/version/diff.ts +169 -0
- package/src/version/grouping.ts +96 -0
- package/src/version/records.ts +113 -0
- package/src/version/session.ts +90 -0
- package/src/version/summary.ts +117 -0
- package/src/workbook-manager.ts +1111 -0
- package/src/xlsx-export-worker.ts +34 -0
- package/src/xlsx-export.ts +373 -0
- package/src/xlsx-import-worker.ts +30 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { DataSheetError } from "../errors.js";
|
|
2
|
+
import { lineageOfValue } from "../audit.js";
|
|
3
|
+
import { deleteWorkbook, readWorkbook, readWorkbookIndex, commitWorkbookProposal, rejectWorkbookProposal, rollbackWorkbook, mutateWorkbook, } from "../workbook-manager.js";
|
|
4
|
+
import { parseIndex, parseRange, isOperationType, asOperationPayload, optionalRevision } from "../request-params.js";
|
|
5
|
+
import { readWorkbookRange, workbookExists } from "../workbook-manager.js";
|
|
6
|
+
import { usedExtent } from "../contract/snapshots.js";
|
|
7
|
+
import { parseRevision } from "./routes.js";
|
|
8
|
+
import { standaloneWorkbookHtml } from "./standalone.js";
|
|
9
|
+
import { readJsonBody, requireDataSheetSession, writeOperationError } from "./routes.js";
|
|
10
|
+
export async function handleTrunkRoute(route, req, res, url) {
|
|
11
|
+
const id = route.id;
|
|
12
|
+
if (route.kind === 'view' && req.method === 'GET') {
|
|
13
|
+
const id = route.id;
|
|
14
|
+
const meta = await readWorkbook(id);
|
|
15
|
+
const title = meta?.name ?? id;
|
|
16
|
+
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-cache' });
|
|
17
|
+
res.end(standaloneWorkbookHtml(title));
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
if (route.kind === 'snapshot' && (req.method === 'DELETE' || req.method === 'POST' && url.searchParams.get('action') === 'delete')) {
|
|
21
|
+
let body = {};
|
|
22
|
+
try {
|
|
23
|
+
body = await readJsonBody(req);
|
|
24
|
+
}
|
|
25
|
+
catch { /* confirmation is checked below */ }
|
|
26
|
+
if (body.confirm !== true) {
|
|
27
|
+
res.writeHead(400, { 'content-type': 'application/json; charset=utf-8' });
|
|
28
|
+
res.end(JSON.stringify({ ok: false, error: '删除工作簿必须显式传入 confirm: true' }));
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
if (!await workbookExists(id)) {
|
|
33
|
+
res.writeHead(404, { 'content-type': 'application/json; charset=utf-8' });
|
|
34
|
+
res.end(JSON.stringify({ ok: false, error: `工作簿不存在:${id}` }));
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
await deleteWorkbook(id);
|
|
38
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' });
|
|
39
|
+
res.end(JSON.stringify({ ok: true, workbookId: id }));
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
writeOperationError(res, error);
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
if (route.kind === 'range') {
|
|
47
|
+
const sessionId = requireDataSheetSession(req, res, url);
|
|
48
|
+
if (sessionId === null)
|
|
49
|
+
return true;
|
|
50
|
+
if (req.method !== 'GET') {
|
|
51
|
+
res.writeHead(405);
|
|
52
|
+
res.end();
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
const id = route.id;
|
|
56
|
+
try {
|
|
57
|
+
const meta = await readWorkbook(id);
|
|
58
|
+
if (meta === null) {
|
|
59
|
+
writeOperationError(res, new DataSheetError('WORKBOOK_NOT_FOUND', '工作簿不存在', 404));
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
const sheetRef = url.searchParams.get('sheet') ?? '';
|
|
63
|
+
const rangeParam = url.searchParams.get('range') ?? '';
|
|
64
|
+
// A bounded, read-only snapshot of one range: this is how another
|
|
65
|
+
// plugin materializes a Sheet as its own data source (§8.6).
|
|
66
|
+
// No range asked for means *the sheet*, so the extent is measured rather
|
|
67
|
+
// than assumed. This used to default to `A1:Z1000` — rows 0–999 and columns
|
|
68
|
+
// A–Z — which silently handed back a prefix of any larger sheet, and the
|
|
69
|
+
// `truncated` flag below could not say so because it compared a value with
|
|
70
|
+
// itself (`values.length - 1 < endRow - startRow` is `n < n`). Data
|
|
71
|
+
// were being dropped before the loader ever saw them.
|
|
72
|
+
const requested = rangeParam.trim() === '' ? undefined : parseRange(rangeParam);
|
|
73
|
+
const extent = usedExtent(meta.snapshot, sheetRef);
|
|
74
|
+
const range = requested ?? { startRow: 0, startColumn: 0, endRow: extent.endRow, endColumn: extent.endColumn };
|
|
75
|
+
// this range twice — as a SQL relation (values only) and as the Sheet
|
|
76
|
+
// Block's read-only preview, which has to draw the range the way the sheet
|
|
77
|
+
// draws it. Making it opt-in keeps the relation path from paying for a
|
|
78
|
+
// matrix it never reads.
|
|
79
|
+
const withStyles = url.searchParams.get('styles') === '1';
|
|
80
|
+
const result = readWorkbookRange(meta.snapshot, sheetRef, range, withStyles ? { styles: true } : {});
|
|
81
|
+
const header = result.values[0] ?? [];
|
|
82
|
+
const body = result.values.slice(1);
|
|
83
|
+
// An empty sheet reads as no columns rather than one unnamed one.
|
|
84
|
+
const columns = header.every(value => value === null)
|
|
85
|
+
? []
|
|
86
|
+
: header.map((value, index) => typeof value === 'string' && value.trim() !== '' ? value.trim() : `column_${index + 1}`);
|
|
87
|
+
// 响应按 wire 契约标注,字段变化会在编译期被发现。
|
|
88
|
+
const payload = {
|
|
89
|
+
ok: true,
|
|
90
|
+
workbookId: id,
|
|
91
|
+
workbookName: meta.name,
|
|
92
|
+
revision: meta.revision ?? 0,
|
|
93
|
+
sheetId: result.sheetId,
|
|
94
|
+
sheetName: result.sheetName,
|
|
95
|
+
range: result.range,
|
|
96
|
+
columns,
|
|
97
|
+
rows: body,
|
|
98
|
+
// The style matrix spans the **whole** requested range, header row
|
|
99
|
+
// included, so it indexes exactly like `result.values` — the preview
|
|
100
|
+
// pairs `styles[row][column]` with the value it just drew rather than
|
|
101
|
+
// re-deriving which row is the header.
|
|
102
|
+
...(result.styles === undefined ? {} : { styles: result.styles }),
|
|
103
|
+
// Only a narrow request can be short of the sheet: that is the single
|
|
104
|
+
// case where more data exists than was returned, and saying so is what
|
|
105
|
+
// lets the reader ask for the rest.
|
|
106
|
+
truncated: requested !== undefined && (extent.endRow > requested.endRow || extent.endColumn > requested.endColumn),
|
|
107
|
+
};
|
|
108
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-cache' });
|
|
109
|
+
res.end(JSON.stringify(payload));
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
writeOperationError(res, error instanceof DataSheetError ? error : new DataSheetError('VALIDATION_ERROR', error instanceof Error ? error.message : String(error), 400));
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
if (route.kind === 'proposals' && req.method === 'GET') {
|
|
117
|
+
const index = await readWorkbookIndex(id);
|
|
118
|
+
const meta = index === null ? await readWorkbook(id) : null;
|
|
119
|
+
if (index === null && meta === null) {
|
|
120
|
+
res.writeHead(404);
|
|
121
|
+
res.end();
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
const proposals = (index?.proposals ?? meta?.proposals ?? []).filter(proposal => proposal.status === 'pending').map(proposal => ({
|
|
125
|
+
id: proposal.id,
|
|
126
|
+
summary: proposal.summary,
|
|
127
|
+
baseRevision: proposal.baseRevision,
|
|
128
|
+
createdAt: proposal.createdAt,
|
|
129
|
+
changes: proposal.changes,
|
|
130
|
+
}));
|
|
131
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-cache' });
|
|
132
|
+
res.end(JSON.stringify({ ok: true, workbookId: id, revision: index?.revision ?? meta?.revision ?? 0, proposals }));
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
if (route.kind === 'proposal-action' && req.method === 'POST') {
|
|
136
|
+
let body = {};
|
|
137
|
+
try {
|
|
138
|
+
body = await readJsonBody(req);
|
|
139
|
+
}
|
|
140
|
+
catch { /* reject does not need a body */ }
|
|
141
|
+
try {
|
|
142
|
+
const proposalId = route.proposalId;
|
|
143
|
+
if (route.action === 'commit') {
|
|
144
|
+
const result = await commitWorkbookProposal({ workbookId: id, proposalId, actor: 'user', expectedRevision: optionalRevision(body.baseRevision) });
|
|
145
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' });
|
|
146
|
+
res.end(JSON.stringify({ ok: true, revision: result.meta.revision ?? 0, operation: result.operation }));
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
const meta = await rejectWorkbookProposal(id, proposalId);
|
|
150
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' });
|
|
151
|
+
res.end(JSON.stringify({ ok: true, revision: meta.revision ?? 0, proposalId }));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
writeOperationError(res, error);
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
if (route.kind === 'rollback' && req.method === 'POST') {
|
|
160
|
+
let body;
|
|
161
|
+
try {
|
|
162
|
+
body = await readJsonBody(req);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
writeOperationError(res, new DataSheetError('VALIDATION_ERROR', 'bad json'));
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const result = await rollbackWorkbook({
|
|
170
|
+
workbookId: id,
|
|
171
|
+
targetRevision: parseIndex(body.targetRevision, 'targetRevision'),
|
|
172
|
+
expectedRevision: optionalRevision(body.baseRevision),
|
|
173
|
+
actor: 'user',
|
|
174
|
+
// The browser names the Version (`恢复到 V3`) because only it knows
|
|
175
|
+
// the display number; the manager falls back to the revision.
|
|
176
|
+
...(typeof body.summary === 'string' && body.summary.trim() !== '' ? { summary: body.summary.trim() } : {}),
|
|
177
|
+
lineage: lineageOfValue(body.lineage),
|
|
178
|
+
});
|
|
179
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' });
|
|
180
|
+
res.end(JSON.stringify({
|
|
181
|
+
ok: true,
|
|
182
|
+
revision: result.meta.revision ?? 0,
|
|
183
|
+
operation: result.operation,
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
writeOperationError(res, error);
|
|
188
|
+
}
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
if (route.kind === 'changes' && req.method === 'GET') {
|
|
192
|
+
const meta = await readWorkbook(id);
|
|
193
|
+
if (meta === null) {
|
|
194
|
+
res.writeHead(404);
|
|
195
|
+
res.end();
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
const after = parseRevision(url.searchParams.get('after'));
|
|
199
|
+
const operations = (meta.operations ?? []).filter(operation => operation.revision > after);
|
|
200
|
+
const currentRevision = meta.revision ?? 0;
|
|
201
|
+
const oldestRevision = operations[0]?.revision ?? currentRevision;
|
|
202
|
+
const requiresSnapshot = after < oldestRevision - 1;
|
|
203
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-cache' });
|
|
204
|
+
res.end(JSON.stringify({
|
|
205
|
+
ok: true,
|
|
206
|
+
workbookId: id,
|
|
207
|
+
revision: currentRevision,
|
|
208
|
+
operations,
|
|
209
|
+
requiresSnapshot,
|
|
210
|
+
snapshot: requiresSnapshot ? meta?.snapshot : undefined,
|
|
211
|
+
}));
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
if (route.kind === 'snapshot' && req.method === 'GET') {
|
|
215
|
+
const meta = await readWorkbook(id);
|
|
216
|
+
if (meta === null) {
|
|
217
|
+
res.writeHead(404);
|
|
218
|
+
res.end();
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-cache' });
|
|
222
|
+
res.end(JSON.stringify({
|
|
223
|
+
ok: true,
|
|
224
|
+
workbookId: meta.id,
|
|
225
|
+
revision: meta.revision ?? 0,
|
|
226
|
+
updatedAt: meta.updatedAt,
|
|
227
|
+
snapshot: meta.snapshot,
|
|
228
|
+
}));
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
if (route.kind === 'operation' && req.method === 'POST') {
|
|
232
|
+
let body;
|
|
233
|
+
try {
|
|
234
|
+
body = await readJsonBody(req);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
writeOperationError(res, new DataSheetError('VALIDATION_ERROR', 'bad json'));
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
const type = body.type;
|
|
241
|
+
if (!isOperationType(type)) {
|
|
242
|
+
writeOperationError(res, new DataSheetError('VALIDATION_ERROR', 'invalid operation type'));
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
const result = await mutateWorkbook({
|
|
247
|
+
workbookId: id,
|
|
248
|
+
// Browser HTTP routes are always user-originated. Agent writes use
|
|
249
|
+
// the tool host directly and cannot spoof this audit field.
|
|
250
|
+
actor: 'user',
|
|
251
|
+
type,
|
|
252
|
+
payload: asOperationPayload(body.payload),
|
|
253
|
+
expectedRevision: optionalRevision(body.baseRevision),
|
|
254
|
+
opId: typeof body.opId === 'string' ? body.opId : undefined,
|
|
255
|
+
versionId: typeof body.versionId === 'string' ? body.versionId : undefined,
|
|
256
|
+
// `temporary` remains accepted for browser bundles that have not
|
|
257
|
+
// been upgraded yet; the mutation authority normalizes it to
|
|
258
|
+
// the clearer durable `open` status.
|
|
259
|
+
versionStatus: body.versionStatus === 'open' || body.versionStatus === 'temporary' || body.versionStatus === 'final' ? body.versionStatus : undefined,
|
|
260
|
+
// Browser routes are always user-originated, and a user edit
|
|
261
|
+
// session is an auto checkpoint (PRD v3 §4).
|
|
262
|
+
versionKind: 'auto',
|
|
263
|
+
lineage: lineageOfValue(body.lineage),
|
|
264
|
+
});
|
|
265
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' });
|
|
266
|
+
res.end(JSON.stringify({
|
|
267
|
+
ok: true,
|
|
268
|
+
revision: result.meta.revision ?? 0,
|
|
269
|
+
operation: result.operation,
|
|
270
|
+
...(result.rebasedOperations === undefined ? {} : { rebasedOperations: result.rebasedOperations }),
|
|
271
|
+
...(result.transformed === undefined ? {} : { transformed: result.transformed }),
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
writeOperationError(res, error);
|
|
276
|
+
}
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
if (route.kind === 'snapshot' && req.method === 'POST') {
|
|
280
|
+
let body;
|
|
281
|
+
try {
|
|
282
|
+
body = await readJsonBody(req);
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
writeOperationError(res, new DataSheetError('VALIDATION_ERROR', 'bad json'));
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
if (!Object.prototype.hasOwnProperty.call(body, 'snapshot')) {
|
|
289
|
+
writeOperationError(res, new DataSheetError('VALIDATION_ERROR', 'snapshot is required'));
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
const snapshot = body.snapshot;
|
|
293
|
+
try {
|
|
294
|
+
const versionSummary = typeof body.versionSummary === 'string' && body.versionSummary.trim() !== '' ? body.versionSummary.trim() : undefined;
|
|
295
|
+
const versionId = typeof body.versionId === 'string' && body.versionId.trim() !== '' ? body.versionId.trim() : undefined;
|
|
296
|
+
const requestedVersionStatus = body.versionStatus === 'open' || body.versionStatus === 'final' ? body.versionStatus : undefined;
|
|
297
|
+
const result = await mutateWorkbook({
|
|
298
|
+
workbookId: id,
|
|
299
|
+
actor: 'user',
|
|
300
|
+
type: 'snapshot_replace',
|
|
301
|
+
payload: { snapshot },
|
|
302
|
+
expectedRevision: optionalRevision(body.baseRevision),
|
|
303
|
+
opId: typeof body.opId === 'string' ? body.opId : undefined,
|
|
304
|
+
lineage: lineageOfValue(body.lineage),
|
|
305
|
+
...(versionId === undefined ? {} : { versionId }),
|
|
306
|
+
// A named checkpoint is an explicit, self-contained Version even
|
|
307
|
+
// when the content did not change (PRD §8); a plain snapshot flush
|
|
308
|
+
// is one Step of the editing session instead (PRD v3.1 §4).
|
|
309
|
+
versionKind: versionSummary === undefined ? 'auto' : 'named',
|
|
310
|
+
...(requestedVersionStatus === undefined ? {} : { versionStatus: requestedVersionStatus }),
|
|
311
|
+
...(versionSummary === undefined ? {} : { versionSummary, versionStatus: requestedVersionStatus ?? 'final' }),
|
|
312
|
+
});
|
|
313
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' });
|
|
314
|
+
res.end(JSON.stringify({ ok: true, revision: result.meta.revision ?? 0, operation: result.operation }));
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
writeOperationError(res, error);
|
|
318
|
+
}
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Isolated review worktrees over HTTP (docs/issue/0022 P3).
|
|
3
|
+
*
|
|
4
|
+
* Draft copies are session-scoped, which is why every handler here resolves the
|
|
5
|
+
* session first and checks the owner before touching a worktree.
|
|
6
|
+
*/
|
|
7
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
8
|
+
import { type WorkbookRoute } from './routes.ts';
|
|
9
|
+
export declare function handleWorktreeRoute(route: WorkbookRoute, req: IncomingMessage, res: ServerResponse, url: URL): Promise<boolean>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { DataSheetError } from "../errors.js";
|
|
2
|
+
import { diffReachedLimit, diffWorkbookSnapshots } from "../version/diff.js";
|
|
3
|
+
import { createWorkbookWorktree, listWorkbookWorktrees, mergeWorkbookWorktree, mutateWorkbookWorktree, readWorkbookAtRevision, readWorkbookWorktree, setWorkbookWorktreeStatus, } from "../workbook-manager.js";
|
|
4
|
+
import { isOperationType, asOperationPayload, optionalRevision } from "../request-params.js";
|
|
5
|
+
import { readJsonBody, requireDataSheetSession, writeOperationError } from "./routes.js";
|
|
6
|
+
export async function handleWorktreeRoute(route, req, res, url) {
|
|
7
|
+
const id = route.id;
|
|
8
|
+
if (route.kind === 'worktrees') {
|
|
9
|
+
const sessionId = requireDataSheetSession(req, res, url);
|
|
10
|
+
if (sessionId === null)
|
|
11
|
+
return true;
|
|
12
|
+
if (req.method === 'GET') {
|
|
13
|
+
const worktrees = await listWorkbookWorktrees(id, sessionId);
|
|
14
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-cache' });
|
|
15
|
+
res.end(JSON.stringify({ ok: true, workbookId: id, worktrees }));
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
if (req.method === 'POST') {
|
|
19
|
+
let body;
|
|
20
|
+
try {
|
|
21
|
+
body = await readJsonBody(req);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
writeOperationError(res, error);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const worktree = await createWorkbookWorktree({ workbookId: id, ownerSessionId: sessionId, name: typeof body.name === 'string' ? body.name : undefined, worktreeId: typeof body.worktreeId === 'string' ? body.worktreeId : undefined });
|
|
29
|
+
res.writeHead(201, { 'content-type': 'application/json; charset=utf-8' });
|
|
30
|
+
res.end(JSON.stringify({ ok: true, workbookId: id, worktree }));
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
writeOperationError(res, error);
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
res.writeHead(405);
|
|
38
|
+
res.end();
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
if (route.kind === 'worktree' && req.method === 'GET') {
|
|
42
|
+
const sessionId = requireDataSheetSession(req, res, url);
|
|
43
|
+
if (sessionId === null)
|
|
44
|
+
return true;
|
|
45
|
+
const worktree = await readWorkbookWorktree(id, route.worktreeId);
|
|
46
|
+
if (worktree === null) {
|
|
47
|
+
writeOperationError(res, new DataSheetError('WORKTREE_NOT_FOUND', 'worktree 不存在', 404));
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
if (worktree.ownerSessionId !== sessionId) {
|
|
51
|
+
writeOperationError(res, new DataSheetError('SESSION_SCOPE_DENIED', '当前 session 无权访问该 worktree', 403));
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-cache' });
|
|
55
|
+
res.end(JSON.stringify({ ok: true, workbookId: id, worktree }));
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
if (route.kind === 'worktree-operations' && req.method === 'POST') {
|
|
59
|
+
const sessionId = requireDataSheetSession(req, res, url);
|
|
60
|
+
if (sessionId === null)
|
|
61
|
+
return true;
|
|
62
|
+
let body;
|
|
63
|
+
try {
|
|
64
|
+
body = await readJsonBody(req);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
writeOperationError(res, error);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
if (!isOperationType(body.type)) {
|
|
71
|
+
writeOperationError(res, new DataSheetError('VALIDATION_ERROR', 'invalid operation type'));
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const result = await mutateWorkbookWorktree({
|
|
76
|
+
workbookId: id,
|
|
77
|
+
worktreeId: route.worktreeId,
|
|
78
|
+
ownerSessionId: sessionId,
|
|
79
|
+
actor: 'user',
|
|
80
|
+
type: body.type,
|
|
81
|
+
payload: asOperationPayload(body.payload),
|
|
82
|
+
expectedRevision: optionalRevision(body.baseRevision),
|
|
83
|
+
opId: typeof body.opId === 'string' ? body.opId : undefined,
|
|
84
|
+
});
|
|
85
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' });
|
|
86
|
+
res.end(JSON.stringify({ ok: true, workbookId: id, worktree: result.worktree, revision: result.worktree.revision, operation: result.operation }));
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
writeOperationError(res, error);
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
if (route.kind === 'worktree-action' && req.method === 'POST') {
|
|
94
|
+
const sessionId = requireDataSheetSession(req, res, url);
|
|
95
|
+
if (sessionId === null)
|
|
96
|
+
return true;
|
|
97
|
+
let body = {};
|
|
98
|
+
try {
|
|
99
|
+
body = await readJsonBody(req);
|
|
100
|
+
}
|
|
101
|
+
catch { /* empty action body is valid */ }
|
|
102
|
+
const worktreeId = route.worktreeId;
|
|
103
|
+
const action = route.action;
|
|
104
|
+
try {
|
|
105
|
+
if (action === 'merge') {
|
|
106
|
+
const result = await mergeWorkbookWorktree({ workbookId: id, worktreeId, ownerSessionId: sessionId, actor: 'user', expectedRevision: optionalRevision(body.baseRevision) });
|
|
107
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' });
|
|
108
|
+
res.end(JSON.stringify({ ok: true, workbookId: id, worktree: result.worktree, revision: result.mutation.meta.revision ?? 0, operation: result.mutation.operation }));
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const worktree = await setWorkbookWorktreeStatus({ workbookId: id, worktreeId, ownerSessionId: sessionId, status: action === 'discard' ? 'discarded' : action });
|
|
112
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' });
|
|
113
|
+
res.end(JSON.stringify({ ok: true, workbookId: id, worktree }));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
writeOperationError(res, error);
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
if (route.kind === 'worktree-diff' && req.method === 'GET') {
|
|
122
|
+
const sessionId = requireDataSheetSession(req, res, url);
|
|
123
|
+
if (sessionId === null)
|
|
124
|
+
return true;
|
|
125
|
+
const worktree = await readWorkbookWorktree(id, route.worktreeId);
|
|
126
|
+
if (worktree === null) {
|
|
127
|
+
writeOperationError(res, new DataSheetError('WORKTREE_NOT_FOUND', 'worktree 不存在', 404));
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
if (worktree.ownerSessionId !== sessionId) {
|
|
131
|
+
writeOperationError(res, new DataSheetError('SESSION_SCOPE_DENIED', '当前 session 无权访问该 worktree', 403));
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
const base = await readWorkbookAtRevision(id, worktree.baseRevision);
|
|
135
|
+
if (base === null) {
|
|
136
|
+
writeOperationError(res, new DataSheetError('REVISION_UNAVAILABLE', 'worktree 基线 revision 已被压缩或无法重放', 409));
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-cache' });
|
|
140
|
+
const changes = diffWorkbookSnapshots(base.snapshot, worktree.snapshot);
|
|
141
|
+
res.end(JSON.stringify({ ok: true, workbookId: id, worktreeId: worktree.id, from: worktree.baseRevision, to: worktree.revision, changes, truncated: diffReachedLimit(changes) }));
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node half of dsh-data-table: the plugin body.
|
|
3
|
+
*
|
|
4
|
+
* It does two things: assemble the HTTP routes (the asset routes plus one
|
|
5
|
+
* prefix route whose sub-paths are matched in `http/routes.ts`) and register the
|
|
6
|
+
* Agent tool surface. Everything it hands to either transport lives in a module
|
|
7
|
+
* beside it, so this file is the list of what the plugin offers — not how any of
|
|
8
|
+
* it works (docs/issue/0022 P3b).
|
|
9
|
+
* @module dsh-data-table
|
|
10
|
+
*/
|
|
11
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
12
|
+
export declare const name = "dsh-data-table";
|
|
13
|
+
export declare const inject: readonly ["tools", "connection", "sessions"];
|
|
14
|
+
/**
|
|
15
|
+
* Plugin body: chunk route + workbook/sheet tools.
|
|
16
|
+
* @param ctx - registrant context.
|
|
17
|
+
*/
|
|
18
|
+
export declare function apply(ctx: Context): void;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { setLineageScope } from "./audit.js";
|
|
2
|
+
import { UNIVER_API_CATALOG, UNIVER_API_VERSION } from "./contract/univer-api-catalog.js";
|
|
3
|
+
import { listWorkbookSummaries } from "./workbook-manager.js";
|
|
4
|
+
import { assetRoutes } from "./http/assets-routes.js";
|
|
5
|
+
import { handleCollaborationRoute } from "./http/collaboration.js";
|
|
6
|
+
import { handleHistoryRoute } from "./http/history.js";
|
|
7
|
+
import { handleTrunkRoute } from "./http/trunk.js";
|
|
8
|
+
import { handleWorktreeRoute } from "./http/worktree.js";
|
|
9
|
+
import { guardDataSheetRequest, matchWorkbookRoute, readJsonBody, registerConnectionRoutes, writeOperationError, } from "./http/routes.js";
|
|
10
|
+
import { projectFor, runForProject, sessionIdOf } from "./http/project-scope.js";
|
|
11
|
+
import { projectFileRoutes } from "./http/project-files.js";
|
|
12
|
+
import { registerWorkbookTools } from "./tools/index.js";
|
|
13
|
+
import { executeWorkbookCatalog } from "./tools/workbook-catalog.js";
|
|
14
|
+
export const name = 'dsh-data-table';
|
|
15
|
+
// `sessions` is read to turn a Session id into that Session's project directory,
|
|
16
|
+
// in both transports: a browser request names its id in a header, and a tool call
|
|
17
|
+
// names the same id through its Agent. Cordis throws on undeclared service access,
|
|
18
|
+
// and the throw lands inside a request handler whose exception the web server
|
|
19
|
+
// reports as a bodyless 400 — so a missing declaration looked exactly like a
|
|
20
|
+
// malformed request (2026-09-25).
|
|
21
|
+
//
|
|
22
|
+
// `sandboxPolicy` is deliberately *not* declared: it is not reachable from a plugin context in
|
|
23
|
+
// the web deployment (`cannot get property "sandboxPolicy" without inject` in
|
|
24
|
+
// `~/.dsh/logs/dsh-web-3080.log`, 77 times), so reading it as a fallback turned every workbook
|
|
25
|
+
// list into a 400. A Session's own header is the answer, and a caller that names no Session
|
|
26
|
+
// keeps the pre-project store instead of a project guessed from a policy.
|
|
27
|
+
export const inject = ['tools', 'connection', 'sessions'];
|
|
28
|
+
/**
|
|
29
|
+
* Plugin body: chunk route + workbook/sheet tools.
|
|
30
|
+
* @param ctx - registrant context.
|
|
31
|
+
*/
|
|
32
|
+
export function apply(ctx) {
|
|
33
|
+
setLineageScope(ctx);
|
|
34
|
+
const routes = [...assetRoutes(), {
|
|
35
|
+
// A prefix route: everything under `/data-sheet/workbook/` is one entry
|
|
36
|
+
// point, and `matchWorkbookRoute` decides which domain handles it.
|
|
37
|
+
kind: 'prefix',
|
|
38
|
+
// Workbook projection and operation endpoints.
|
|
39
|
+
// Paths:
|
|
40
|
+
// GET /data-sheet/workbook/<id>.json
|
|
41
|
+
// POST /data-sheet/workbook/<id>.json (legacy snapshot checkpoint)
|
|
42
|
+
// GET /data-sheet/workbook/<id>/changes?after=N
|
|
43
|
+
// GET /data-sheet/workbook/<id>/collaboration (live SSE session)
|
|
44
|
+
// GET /data-sheet/workbook/<id>/history[?revision=N]
|
|
45
|
+
// POST /data-sheet/workbook/<id>/operations (fine-grained mutation)
|
|
46
|
+
// NOTE: prefix route paths must NOT end with a trailing slash — the host
|
|
47
|
+
// matcher appends its own "/" (startsWith(`${prefix}/`)), so a trailing
|
|
48
|
+
// slash makes subpaths unmatchable.
|
|
49
|
+
path: '/data-sheet/workbook',
|
|
50
|
+
handler: async (req, res) => {
|
|
51
|
+
if (!guardDataSheetRequest(req, res))
|
|
52
|
+
return;
|
|
53
|
+
// Writes must be JSON: a form-encoded body would be parsed as an empty
|
|
54
|
+
// object and silently drop the payload.
|
|
55
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
56
|
+
const contentType = String(req.headers['content-type'] ?? '').toLowerCase();
|
|
57
|
+
if (!contentType.startsWith('application/json')) {
|
|
58
|
+
res.writeHead(415, { 'content-type': 'application/json; charset=utf-8' });
|
|
59
|
+
res.end(JSON.stringify({ ok: false, code: 'VALIDATION_ERROR', error: '写请求必须使用 application/json' }));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const url = new URL(req.url ?? '/', 'http://x');
|
|
64
|
+
// `decodeURIComponent` throws on a malformed escape (`/x/%E0%A4%A`), and
|
|
65
|
+
// that would escape the handler as an unhandled URIError. A path we cannot
|
|
66
|
+
// decode is simply not one of our routes.
|
|
67
|
+
let pathname;
|
|
68
|
+
try {
|
|
69
|
+
pathname = decodeURIComponent(url.pathname);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
res.writeHead(400);
|
|
73
|
+
res.end();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const route = matchWorkbookRoute(pathname);
|
|
77
|
+
if (route === null) {
|
|
78
|
+
res.writeHead(404);
|
|
79
|
+
res.end();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// Worktrees are session-scoped draft copies and are handled first, so a
|
|
83
|
+
// draft can never be mistaken for a normal workbook mutation. The live
|
|
84
|
+
// stream is next because it holds the response open.
|
|
85
|
+
// Every workbook path resolves inside the requesting Session's project, so
|
|
86
|
+
// the store root it writes to is that project's `storages/data-table`.
|
|
87
|
+
const project = projectFor(ctx, sessionIdOf(req.headers, url));
|
|
88
|
+
const dispatched = await runForProject(project, async () => {
|
|
89
|
+
if (await handleWorktreeRoute(route, req, res, url))
|
|
90
|
+
return true;
|
|
91
|
+
if (await handleCollaborationRoute(route, req, res, url))
|
|
92
|
+
return true;
|
|
93
|
+
if (await handleHistoryRoute(route, req, res, url))
|
|
94
|
+
return true;
|
|
95
|
+
if (await handleTrunkRoute(route, req, res, url))
|
|
96
|
+
return true;
|
|
97
|
+
return false;
|
|
98
|
+
});
|
|
99
|
+
if (dispatched)
|
|
100
|
+
return;
|
|
101
|
+
res.writeHead(405);
|
|
102
|
+
res.end();
|
|
103
|
+
},
|
|
104
|
+
}, {
|
|
105
|
+
kind: 'exact',
|
|
106
|
+
path: '/data-sheet/workbooks.json',
|
|
107
|
+
handler: async (_req, res) => {
|
|
108
|
+
if (!guardDataSheetRequest(_req, res))
|
|
109
|
+
return;
|
|
110
|
+
const project = projectFor(ctx, sessionIdOf(_req.headers, new URL(_req.url ?? '/', 'http://x')));
|
|
111
|
+
// POST routes to the Agent's catalog implementation so the browser and the
|
|
112
|
+
// model share one create/rename/delete path.
|
|
113
|
+
if (_req.method === 'POST') {
|
|
114
|
+
let body;
|
|
115
|
+
try {
|
|
116
|
+
body = await readJsonBody(_req);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
writeOperationError(res, error);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const result = await runForProject(project, async () => executeWorkbookCatalog({ ...body, action: body.action }));
|
|
124
|
+
res.writeHead(body.action === 'create' ? 201 : 200, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-cache' });
|
|
125
|
+
res.end(JSON.stringify(result));
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
writeOperationError(res, error);
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (_req.method !== 'GET' && _req.method !== 'HEAD') {
|
|
133
|
+
res.writeHead(405, { 'content-type': 'application/json; charset=utf-8' });
|
|
134
|
+
res.end(JSON.stringify({ ok: false, error: 'method not allowed' }));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const workbooks = await runForProject(project, async () => listWorkbookSummaries());
|
|
138
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-cache' });
|
|
139
|
+
res.end(JSON.stringify({ ok: true, workbooks }));
|
|
140
|
+
},
|
|
141
|
+
}, {
|
|
142
|
+
// Agent-facing metadata endpoint. It is deliberately separate from the
|
|
143
|
+
// mutation routes so a client can discover the safe Univer surface without
|
|
144
|
+
// receiving a browser-only Injector or executing arbitrary JavaScript.
|
|
145
|
+
kind: 'exact',
|
|
146
|
+
path: '/data-sheet/univer-api.json',
|
|
147
|
+
handler: async (_req, res) => {
|
|
148
|
+
if (!guardDataSheetRequest(_req, res))
|
|
149
|
+
return;
|
|
150
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-cache' });
|
|
151
|
+
res.end(JSON.stringify({ ok: true, version: UNIVER_API_VERSION, catalog: UNIVER_API_CATALOG }));
|
|
152
|
+
},
|
|
153
|
+
}, ...projectFileRoutes(ctx)];
|
|
154
|
+
registerConnectionRoutes(ctx, routes, '/data-sheet');
|
|
155
|
+
registerWorkbookTools(ctx);
|
|
156
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for dsh-data-table.
|
|
3
|
+
*
|
|
4
|
+
* Runtime invariant: the browser half can only render a sheet that the host
|
|
5
|
+
* tool admitted, because the renderer parses the durable tool-call arguments
|
|
6
|
+
* through {@link isSheetData} — the same shape the tool validated. A renderer
|
|
7
|
+
* reaching an unvalidated sheet is a build-level type violation, not a runtime
|
|
8
|
+
* state, so the companion registers the manifest name only.
|
|
9
|
+
* @module dsh-data-table/invariant
|
|
10
|
+
*/
|
|
11
|
+
export declare const manifest = "dsh-data-table";
|
|
12
|
+
export declare const invariant: {
|
|
13
|
+
manifest: string;
|
|
14
|
+
reason: string;
|
|
15
|
+
};
|