@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,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static plugin assets served on demand (docs/issue/0022 P3).
|
|
3
|
+
*
|
|
4
|
+
* The Univer engine chunk, the two xlsx workers and the standalone workbook
|
|
5
|
+
* page are all classic scripts or worker entry points the browser fetches after
|
|
6
|
+
* boot, so they are served from here rather than inlined into the client bundle.
|
|
7
|
+
* `univer-api.json` is the catalogue the Agent reads through its tool.
|
|
8
|
+
*/
|
|
9
|
+
import { createHash } from 'node:crypto';
|
|
10
|
+
import { existsSync } from 'node:fs';
|
|
11
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
12
|
+
import { dirname, join } from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { guardDataSheetRequest } from "./routes.js";
|
|
15
|
+
/**
|
|
16
|
+
* The plugin directory, found by walking up to the `package.json` that owns
|
|
17
|
+
* this file.
|
|
18
|
+
*
|
|
19
|
+
* It used to be a pair of `new URL('../../lib/...', import.meta.url)` candidates
|
|
20
|
+
* because the path depth differs between `src/` and the emitted `lib/types/`,
|
|
21
|
+
* which meant every file move silently changed what those URLs pointed at. The
|
|
22
|
+
* walk is depth-independent, so moving this module cannot break it.
|
|
23
|
+
*/
|
|
24
|
+
function pluginRoot() {
|
|
25
|
+
let directory = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
27
|
+
if (existsSync(join(directory, 'package.json')))
|
|
28
|
+
return directory;
|
|
29
|
+
const parent = dirname(directory);
|
|
30
|
+
if (parent === directory)
|
|
31
|
+
break;
|
|
32
|
+
directory = parent;
|
|
33
|
+
}
|
|
34
|
+
return dirname(fileURLToPath(import.meta.url));
|
|
35
|
+
}
|
|
36
|
+
/** Absolute path of one build artifact under `lib/`. */
|
|
37
|
+
const artifact = (name) => join(pluginRoot(), 'lib', name);
|
|
38
|
+
/** Last read per artifact name. The engine chunk is 13MB and changes only on rebuild. */
|
|
39
|
+
const residentArtifacts = new Map();
|
|
40
|
+
/**
|
|
41
|
+
* Read one `lib/` artifact, reusing the last read while the file is unchanged.
|
|
42
|
+
*
|
|
43
|
+
* The engine chunk used to be re-read from disk on every request, which was the
|
|
44
|
+
* only way to notice a rebuild. The `mtimeMs`/`size` identity notices the same
|
|
45
|
+
* rebuild without the disk read, and it is what lets the route answer the
|
|
46
|
+
* content-hash check below without hashing 13MB per request.
|
|
47
|
+
*
|
|
48
|
+
* @param name - file name under `lib/`.
|
|
49
|
+
* @returns the current bytes and content hash.
|
|
50
|
+
*/
|
|
51
|
+
async function readArtifact(name) {
|
|
52
|
+
const path = artifact(name);
|
|
53
|
+
const info = await stat(path);
|
|
54
|
+
const identity = `${info.mtimeMs}:${info.size}`;
|
|
55
|
+
const resident = residentArtifacts.get(name);
|
|
56
|
+
if (resident !== undefined && resident.identity === identity)
|
|
57
|
+
return resident;
|
|
58
|
+
const bytes = await readFile(path);
|
|
59
|
+
const next = { identity, bytes, hash: createHash('sha256').update(bytes).digest('hex').slice(0, 12) };
|
|
60
|
+
residentArtifacts.set(name, next);
|
|
61
|
+
return next;
|
|
62
|
+
}
|
|
63
|
+
/** Every static route this plugin serves. */
|
|
64
|
+
export function assetRoutes() {
|
|
65
|
+
const routes = [];
|
|
66
|
+
routes.push({
|
|
67
|
+
kind: 'exact',
|
|
68
|
+
path: '/data-sheet/univer-client.js',
|
|
69
|
+
handler: async (req, res) => {
|
|
70
|
+
if (!guardDataSheetRequest(req, res))
|
|
71
|
+
return;
|
|
72
|
+
try {
|
|
73
|
+
const chunk = await readArtifact('univer-client.js');
|
|
74
|
+
// The loader asks for this file with its content hash in `?v=`. An exact
|
|
75
|
+
// match means the bytes cannot have changed, so the browser may keep them
|
|
76
|
+
// for a year and never ask again — which is the point: the chunk is 13MB
|
|
77
|
+
// and the sheet is opened in a fresh page constantly.
|
|
78
|
+
//
|
|
79
|
+
// Anything else — no `?v=` at all (the `/api/data-sheet?path=` transport mode
|
|
80
|
+
// drops the query), or a hash from a build that has since been replaced —
|
|
81
|
+
// is served `no-store`. A stale URL must never be able to pin different
|
|
82
|
+
// bytes, and that is exactly the failure the old unconditional `no-store`
|
|
83
|
+
// was there to prevent.
|
|
84
|
+
const requested = new URL(req.url ?? '/', 'http://x').searchParams.get('v');
|
|
85
|
+
const immutable = requested !== null && requested === chunk.hash;
|
|
86
|
+
res.writeHead(200, {
|
|
87
|
+
'content-type': 'text/javascript; charset=utf-8',
|
|
88
|
+
'cache-control': immutable ? 'public, max-age=31536000, immutable' : 'no-store, must-revalidate',
|
|
89
|
+
});
|
|
90
|
+
if (req.method === 'HEAD') {
|
|
91
|
+
res.end();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
res.end(chunk.bytes);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
res.writeHead(404);
|
|
98
|
+
res.end();
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
// XLSX/CSV encoding is loaded only when the user exports. Keeping it in a
|
|
103
|
+
// worker prevents SheetJS serialization from blocking the Univer canvas.
|
|
104
|
+
routes.push({
|
|
105
|
+
kind: 'exact',
|
|
106
|
+
path: '/data-sheet/xlsx-export-worker.js',
|
|
107
|
+
handler: async (_req, res) => {
|
|
108
|
+
if (!guardDataSheetRequest(_req, res))
|
|
109
|
+
return;
|
|
110
|
+
try {
|
|
111
|
+
const body = await readFile(artifact('xlsx-export-worker.js'));
|
|
112
|
+
res.writeHead(200, {
|
|
113
|
+
'content-type': 'text/javascript; charset=utf-8',
|
|
114
|
+
'cache-control': 'no-cache',
|
|
115
|
+
});
|
|
116
|
+
res.end(body);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
res.writeHead(404);
|
|
120
|
+
res.end();
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
routes.push({
|
|
125
|
+
kind: 'exact',
|
|
126
|
+
path: '/data-sheet/xlsx-import-worker.js',
|
|
127
|
+
handler: async (_req, res) => {
|
|
128
|
+
if (!guardDataSheetRequest(_req, res))
|
|
129
|
+
return;
|
|
130
|
+
try {
|
|
131
|
+
const body = await readFile(artifact('xlsx-import-worker.js'));
|
|
132
|
+
res.writeHead(200, {
|
|
133
|
+
'content-type': 'text/javascript; charset=utf-8',
|
|
134
|
+
'cache-control': 'no-cache',
|
|
135
|
+
});
|
|
136
|
+
res.end(body);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
res.writeHead(404);
|
|
140
|
+
res.end();
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
// Standalone client bundle for the optional full-browser workbook page.
|
|
145
|
+
routes.push({
|
|
146
|
+
kind: 'exact',
|
|
147
|
+
path: '/data-sheet/workbook-page.js',
|
|
148
|
+
handler: async (_req, res) => {
|
|
149
|
+
if (!guardDataSheetRequest(_req, res))
|
|
150
|
+
return;
|
|
151
|
+
try {
|
|
152
|
+
const body = await readFile(artifact('workbook-page.js'));
|
|
153
|
+
res.writeHead(200, {
|
|
154
|
+
'content-type': 'text/javascript; charset=utf-8',
|
|
155
|
+
'cache-control': 'no-cache',
|
|
156
|
+
});
|
|
157
|
+
res.end(body);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
res.writeHead(404);
|
|
161
|
+
res.end();
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
// Workbook catalog for the browser picker. Workbook content stays behind
|
|
166
|
+
// the per-workbook snapshot endpoint; this route returns names and sheet
|
|
167
|
+
// summaries and accepts the small lifecycle mutations used by the picker.
|
|
168
|
+
return routes;
|
|
169
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The live collaboration stream (docs/issue/0022 P3).
|
|
3
|
+
*
|
|
4
|
+
* A server-sent event stream plus the presence beacon. This is a subscription
|
|
5
|
+
* protocol rather than request/response — the response stays open until the
|
|
6
|
+
* client goes away — which is why it is a module of its own.
|
|
7
|
+
*/
|
|
8
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
9
|
+
import { type WorkbookRoute } from './routes.ts';
|
|
10
|
+
export declare function handleCollaborationRoute(route: WorkbookRoute, req: IncomingMessage, res: ServerResponse, url: URL): Promise<boolean>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { DataSheetError } from "../errors.js";
|
|
2
|
+
import { publishWorkbookPresence, subscribeWorkbookCollaboration, subscribeWorkbookPresence } from "../collaboration-hub.js";
|
|
3
|
+
import { readWorkbook } from "../workbook-manager.js";
|
|
4
|
+
import { parseRevision, readJsonBody, requireDataSheetSession, writeOperationError } from "./routes.js";
|
|
5
|
+
export async function handleCollaborationRoute(route, req, res, url) {
|
|
6
|
+
const id = route.id;
|
|
7
|
+
if (route.kind === 'presence' && req.method === 'POST') {
|
|
8
|
+
const sessionId = requireDataSheetSession(req, res, url);
|
|
9
|
+
if (sessionId === null)
|
|
10
|
+
return true;
|
|
11
|
+
let body;
|
|
12
|
+
try {
|
|
13
|
+
body = await readJsonBody(req);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
writeOperationError(res, error);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
const kind = body.kind;
|
|
20
|
+
if (kind !== 'selection' && kind !== 'editing' && kind !== 'active-sheet') {
|
|
21
|
+
writeOperationError(res, new DataSheetError('VALIDATION_ERROR', 'presence.kind 必须是 selection、editing 或 active-sheet'));
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
const payload = body.payload;
|
|
25
|
+
if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {
|
|
26
|
+
writeOperationError(res, new DataSheetError('VALIDATION_ERROR', 'presence.payload 必须是对象'));
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
if (JSON.stringify(payload).length > 8192) {
|
|
30
|
+
writeOperationError(res, new DataSheetError('PAYLOAD_TOO_LARGE', 'presence.payload 不能超过 8 KiB', 413));
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
publishWorkbookPresence({ workbookId: id, sessionId, actor: 'user', kind, payload: payload, timestamp: new Date().toISOString() });
|
|
34
|
+
res.writeHead(202, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' });
|
|
35
|
+
res.end(JSON.stringify({ ok: true, workbookId: id }));
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if (route.kind === 'collaboration' && req.method === 'GET') {
|
|
39
|
+
const sessionId = requireDataSheetSession(req, res, url);
|
|
40
|
+
if (sessionId === null)
|
|
41
|
+
return true;
|
|
42
|
+
const initialMeta = await readWorkbook(id);
|
|
43
|
+
if (initialMeta === null) {
|
|
44
|
+
res.writeHead(404);
|
|
45
|
+
res.end();
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
const after = parseRevision(url.searchParams.get('after'));
|
|
49
|
+
const operations = (initialMeta.operations ?? []).filter(operation => operation.revision > after);
|
|
50
|
+
const currentRevision = initialMeta.revision ?? 0;
|
|
51
|
+
const oldestRevision = operations[0]?.revision ?? currentRevision;
|
|
52
|
+
const requiresSnapshot = after < oldestRevision - 1;
|
|
53
|
+
const snapshot = requiresSnapshot
|
|
54
|
+
? initialMeta.snapshot
|
|
55
|
+
: undefined;
|
|
56
|
+
const pendingEvents = [];
|
|
57
|
+
const pendingPresence = [];
|
|
58
|
+
let ready = false;
|
|
59
|
+
let closed = false;
|
|
60
|
+
const pendingWrites = [];
|
|
61
|
+
const flushWrites = () => {
|
|
62
|
+
while (!closed && pendingWrites.length > 0) {
|
|
63
|
+
const frame = pendingWrites.shift();
|
|
64
|
+
if (!res.write(frame))
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const writeOperationEvent = (operation) => {
|
|
69
|
+
if (closed)
|
|
70
|
+
return;
|
|
71
|
+
const frame = `id: ${operation.revision}\nevent: operation\ndata: ${JSON.stringify({ workbookId: id, operation })}\n\n`;
|
|
72
|
+
if (pendingWrites.length > 0 || !res.write(frame)) {
|
|
73
|
+
if (pendingWrites.length >= 100) {
|
|
74
|
+
res.destroy();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
pendingWrites.push(frame);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const writePresenceEvent = (presence) => {
|
|
81
|
+
if (closed || presence.sessionId === sessionId)
|
|
82
|
+
return;
|
|
83
|
+
const frame = `id: ${Date.parse(presence.timestamp) || Date.now()}\nevent: presence\ndata: ${JSON.stringify({ workbookId: id, presence })}\n\n`;
|
|
84
|
+
if (pendingWrites.length > 0 || !res.write(frame)) {
|
|
85
|
+
if (pendingWrites.length >= 100) {
|
|
86
|
+
res.destroy();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
pendingWrites.push(frame);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
let unsubscribe;
|
|
93
|
+
let unsubscribePresence;
|
|
94
|
+
try {
|
|
95
|
+
unsubscribe = subscribeWorkbookCollaboration(id, event => {
|
|
96
|
+
if (closed)
|
|
97
|
+
return;
|
|
98
|
+
if (!ready) {
|
|
99
|
+
pendingEvents.push(event);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
writeOperationEvent(event.operation);
|
|
103
|
+
});
|
|
104
|
+
unsubscribePresence = subscribeWorkbookPresence(id, event => {
|
|
105
|
+
if (closed || event.presence.sessionId === sessionId)
|
|
106
|
+
return;
|
|
107
|
+
if (!ready)
|
|
108
|
+
pendingPresence.push(event);
|
|
109
|
+
else
|
|
110
|
+
writePresenceEvent(event.presence);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
res.writeHead(429, { 'content-type': 'application/json; charset=utf-8', 'retry-after': '5' });
|
|
115
|
+
res.end(JSON.stringify({ ok: false, error: error instanceof Error ? error.message : String(error) }));
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
let heartbeat;
|
|
119
|
+
res.on('drain', flushWrites);
|
|
120
|
+
res.once('close', () => {
|
|
121
|
+
closed = true;
|
|
122
|
+
if (heartbeat !== undefined)
|
|
123
|
+
clearInterval(heartbeat);
|
|
124
|
+
unsubscribe?.();
|
|
125
|
+
unsubscribePresence?.();
|
|
126
|
+
});
|
|
127
|
+
heartbeat = setInterval(() => {
|
|
128
|
+
if (!closed)
|
|
129
|
+
res.write(': heartbeat\n\n');
|
|
130
|
+
}, 15000);
|
|
131
|
+
res.writeHead(200, {
|
|
132
|
+
'content-type': 'text/event-stream; charset=utf-8',
|
|
133
|
+
'cache-control': 'no-cache, no-transform',
|
|
134
|
+
connection: 'keep-alive',
|
|
135
|
+
});
|
|
136
|
+
res.write(`event: ready\ndata: ${JSON.stringify({
|
|
137
|
+
workbookId: id,
|
|
138
|
+
revision: currentRevision,
|
|
139
|
+
operations: requiresSnapshot ? [] : operations,
|
|
140
|
+
requiresSnapshot,
|
|
141
|
+
...(snapshot === undefined ? {} : { snapshot }),
|
|
142
|
+
})}\n\n`);
|
|
143
|
+
ready = true;
|
|
144
|
+
for (const event of pendingEvents) {
|
|
145
|
+
if (event.operation.revision > currentRevision)
|
|
146
|
+
writeOperationEvent(event.operation);
|
|
147
|
+
}
|
|
148
|
+
for (const event of pendingPresence)
|
|
149
|
+
writePresenceEvent(event.presence);
|
|
150
|
+
await new Promise(resolve => res.once('close', resolve));
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version history over HTTP (docs/issue/0022 P3).
|
|
3
|
+
*
|
|
4
|
+
* The list, one historical snapshot or Step, and the diff between two revisions.
|
|
5
|
+
* The list is also where an abandoned editing session becomes observable:
|
|
6
|
+
* reading it is what settles it into a saved Version (PRD v3.3 D21).
|
|
7
|
+
*
|
|
8
|
+
* The Version record's wire shape comes from `version/records.ts`, which this
|
|
9
|
+
* endpoint answers with but does not define. It said the opposite — that the
|
|
10
|
+
* shape lived here and the model knew nothing about HTTP — while importing
|
|
11
|
+
* `toVersionRecords` from there on the next line.
|
|
12
|
+
*/
|
|
13
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
14
|
+
import { type WorkbookRoute } from './routes.ts';
|
|
15
|
+
export declare function handleHistoryRoute(route: WorkbookRoute, req: IncomingMessage, res: ServerResponse, url: URL): Promise<boolean>;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { DataSheetError } from "../errors.js";
|
|
2
|
+
import { diffReachedLimit, diffWorkbookSnapshots } from "../version/diff.js";
|
|
3
|
+
import { parseRevision } from "./routes.js";
|
|
4
|
+
import { historyVersionsFromOperations } from "../version/grouping.js";
|
|
5
|
+
import { activeHistoryRevisions, canReplayHistoryRevision, checkpointHasher, toVersionRecords } from "../version/records.js";
|
|
6
|
+
import { settleIdleUserVersions } from "../version/session.js";
|
|
7
|
+
import { readWorkbook, readWorkbookAtRevision, readWorkbookAtStep, readStepChanges } from "../workbook-manager.js";
|
|
8
|
+
import { writeOperationError } from "./routes.js";
|
|
9
|
+
import { operationSummary } from "../contract/operations.js";
|
|
10
|
+
export async function handleHistoryRoute(route, req, res, url) {
|
|
11
|
+
const id = route.id;
|
|
12
|
+
if (route.kind === 'history-diff' && req.method === 'GET') {
|
|
13
|
+
const fromRaw = url.searchParams.get('from');
|
|
14
|
+
const toRaw = url.searchParams.get('to');
|
|
15
|
+
// `parseRevision` rather than a bare `Number()`: the sibling endpoints use it
|
|
16
|
+
// for the same kind of cursor, and `Number()` also accepts `0x10`, `1e3` and
|
|
17
|
+
// `5.0` — a revision is a decimal integer. The defaults differ (`to` falls
|
|
18
|
+
// back to the current revision), so only the parsing is shared.
|
|
19
|
+
const from = parseRevision(fromRaw);
|
|
20
|
+
const currentMeta = await readWorkbook(id);
|
|
21
|
+
if (currentMeta === null) {
|
|
22
|
+
writeOperationError(res, new DataSheetError('WORKBOOK_NOT_FOUND', '工作簿不存在', 404));
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
const to = toRaw === null ? currentMeta.revision ?? 0 : parseRevision(toRaw);
|
|
26
|
+
if (!Number.isInteger(from) || from < 0 || !Number.isInteger(to) || to < 0 || from > to) {
|
|
27
|
+
writeOperationError(res, new DataSheetError('VALIDATION_ERROR', 'from/to 必须是非负整数,且 from 不得大于 to'));
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
const left = await readWorkbookAtRevision(id, from);
|
|
31
|
+
const right = await readWorkbookAtRevision(id, to);
|
|
32
|
+
if (left === null || right === null) {
|
|
33
|
+
// User-facing copy: this is the panel's notice text, so it says what
|
|
34
|
+
// happened and what still works, not which revision number failed.
|
|
35
|
+
writeOperationError(res, new DataSheetError('REVISION_UNAVAILABLE', '这两版之间的历史记录已被压缩,无法重建内容快照,因此不能比较。更早的版本仍然可以恢复。', 409, { from, to }));
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-cache' });
|
|
39
|
+
// `truncated` rather than nothing: the client renders these counts, and a
|
|
40
|
+
// capped list would otherwise read as the whole comparison.
|
|
41
|
+
const changes = diffWorkbookSnapshots(left.snapshot, right.snapshot);
|
|
42
|
+
res.end(JSON.stringify({ ok: true, workbookId: id, from, to, changes, truncated: diffReachedLimit(changes) }));
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
if (route.kind === 'history' && req.method === 'GET') {
|
|
46
|
+
// Reading the list is the moment "stopped typing ten minutes ago"
|
|
47
|
+
// becomes observable, so that is where an abandoned session is settled
|
|
48
|
+
// into a saved Version (PRD v3.3 D21). Idempotent and cheap.
|
|
49
|
+
await settleIdleUserVersions(id).catch(() => 0);
|
|
50
|
+
const meta = await readWorkbook(id, { includeCheckpoints: true });
|
|
51
|
+
if (meta === null) {
|
|
52
|
+
res.writeHead(404);
|
|
53
|
+
res.end();
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
const currentRevision = meta.revision ?? 0;
|
|
57
|
+
const requested = url.searchParams.get('revision');
|
|
58
|
+
if (requested !== null) {
|
|
59
|
+
const targetRevision = Number(requested);
|
|
60
|
+
if (!Number.isInteger(targetRevision) || targetRevision < 0) {
|
|
61
|
+
res.writeHead(400, { 'content-type': 'application/json; charset=utf-8' });
|
|
62
|
+
res.end(JSON.stringify({ ok: false, error: 'revision 必须是大于等于 0 的整数' }));
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
const stepRaw = url.searchParams.get('step');
|
|
66
|
+
if (stepRaw !== null) {
|
|
67
|
+
const step = Number(stepRaw);
|
|
68
|
+
if (!Number.isInteger(step) || step < 0) {
|
|
69
|
+
res.writeHead(400, { 'content-type': 'application/json; charset=utf-8' });
|
|
70
|
+
res.end(JSON.stringify({ ok: false, error: 'step 必须是大于等于 0 的整数' }));
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const historical = stepRaw === null
|
|
75
|
+
? await readWorkbookAtRevision(id, targetRevision)
|
|
76
|
+
: await readWorkbookAtStep(id, targetRevision, Number(stepRaw));
|
|
77
|
+
if (historical === null) {
|
|
78
|
+
res.writeHead(409, { 'content-type': 'application/json; charset=utf-8' });
|
|
79
|
+
res.end(JSON.stringify({ ok: false, error: '该版本的历史记录已被压缩,无法重建内容快照,因此不能预览。它仍然可以被恢复。', revision: currentRevision }));
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-cache' });
|
|
83
|
+
// A Step preview also returns what that Step changed, so the canvas
|
|
84
|
+
// can scroll to and tint the cells (PRD v3.3 §7.2).
|
|
85
|
+
const stepChanges = stepRaw === null ? null : await readStepChanges(id, targetRevision, Number(stepRaw));
|
|
86
|
+
const stepResult = stepRaw === null ? {} : {
|
|
87
|
+
step: historical?.step,
|
|
88
|
+
stepCount: historical?.stepCount,
|
|
89
|
+
changes: stepChanges ?? [],
|
|
90
|
+
// A Step can be a batch, so this list is bounded the same way the diff
|
|
91
|
+
// list is and has to say so when it stops.
|
|
92
|
+
truncated: stepChanges === null ? false : diffReachedLimit(stepChanges),
|
|
93
|
+
};
|
|
94
|
+
res.end(JSON.stringify({
|
|
95
|
+
ok: true,
|
|
96
|
+
workbookId: id,
|
|
97
|
+
revision: historical.revision,
|
|
98
|
+
snapshot: historical.snapshot,
|
|
99
|
+
...stepResult,
|
|
100
|
+
}));
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
const operations = (meta.operations ?? []).slice().sort((left, right) => left.revision - right.revision);
|
|
104
|
+
const rawCheckpoints = meta?.checkpoints ?? [];
|
|
105
|
+
const checkpoints = rawCheckpoints.map(checkpoint => checkpoint.revision).sort((left, right) => left - right);
|
|
106
|
+
const operationByRevision = new Map(operations.map(operation => [operation.revision, operation]));
|
|
107
|
+
const activeRevisions = activeHistoryRevisions(currentRevision, operationByRevision);
|
|
108
|
+
const canReplay = (targetRevision) => canReplayHistoryRevision(targetRevision, currentRevision, checkpoints, operationByRevision);
|
|
109
|
+
const entries = operations.map(operation => ({
|
|
110
|
+
opId: operation.opId,
|
|
111
|
+
revision: operation.revision,
|
|
112
|
+
parentRevision: operation.parentRevision ?? Math.max(0, operation.revision - 1),
|
|
113
|
+
actor: operation.actor,
|
|
114
|
+
type: operation.type,
|
|
115
|
+
timestamp: operation.timestamp,
|
|
116
|
+
summary: operationSummary(operation),
|
|
117
|
+
canPreview: canReplay(operation.revision),
|
|
118
|
+
}));
|
|
119
|
+
const versions = historyVersionsFromOperations(operations, canReplay, activeRevisions);
|
|
120
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-cache' });
|
|
121
|
+
res.end(JSON.stringify({
|
|
122
|
+
ok: true,
|
|
123
|
+
workbookId: id,
|
|
124
|
+
revision: currentRevision,
|
|
125
|
+
entries,
|
|
126
|
+
versions,
|
|
127
|
+
// Unified cross-plugin model (PRD §7.1); `versions` stays for compatibility.
|
|
128
|
+
versionRecords: toVersionRecords(versions, id, checkpointHasher(rawCheckpoints)),
|
|
129
|
+
checkpoints,
|
|
130
|
+
}));
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { type LegacyRoute } from './routes.ts';
|
|
3
|
+
/** One entry of the chooser's listing. */
|
|
4
|
+
export interface ProjectFileEntry {
|
|
5
|
+
/** Basename inside the listed directory. */
|
|
6
|
+
readonly name: string;
|
|
7
|
+
/** Path relative to `<project>/storages`, `/`-separated; empty at the root. */
|
|
8
|
+
readonly path: string;
|
|
9
|
+
readonly type: 'file' | 'directory';
|
|
10
|
+
/** Bytes, for a regular file. */
|
|
11
|
+
readonly size: number;
|
|
12
|
+
/** Filesystem creation time, ISO; empty when the platform does not report one. */
|
|
13
|
+
readonly createdAt: string;
|
|
14
|
+
/** Last modification time, ISO. */
|
|
15
|
+
readonly modifiedAt: string;
|
|
16
|
+
}
|
|
17
|
+
/** One directory of the project's storage, as the chooser renders it. */
|
|
18
|
+
export interface ProjectFileListing {
|
|
19
|
+
/** The listed directory, relative to `<project>/storages`; empty at the root. */
|
|
20
|
+
readonly directory: string;
|
|
21
|
+
/** How many entries were dropped by the cap. */
|
|
22
|
+
readonly truncated: number;
|
|
23
|
+
readonly entries: readonly ProjectFileEntry[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Normalize the requested directory to a relative path inside the storage root.
|
|
27
|
+
*
|
|
28
|
+
* @param raw - What the client sent: `''`, `data-table/`.
|
|
29
|
+
* @returns a `/`-separated relative path with no traversal.
|
|
30
|
+
* @throws when the path escapes the storage container.
|
|
31
|
+
*/
|
|
32
|
+
export declare function safeRelativeDirectory(raw: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* List one page of a storage container rooted at `root`.
|
|
35
|
+
*
|
|
36
|
+
* `root` is the project directory, and the container is `<root>/storages`; the
|
|
37
|
+
* parameter exists so tests can point at a temporary tree without a Session.
|
|
38
|
+
*/
|
|
39
|
+
export declare function listProjectFiles(root: string, relativeDirectory: string): Promise<ProjectFileListing>;
|
|
40
|
+
/** Largest file the chooser will hand back; matches the client's own import cap. */
|
|
41
|
+
export declare const MAX_PICKED_BYTES: number;
|
|
42
|
+
/**
|
|
43
|
+
* Read one file from the storage container.
|
|
44
|
+
*
|
|
45
|
+
* The browser cannot open a path itself, so a choice made in the chooser comes
|
|
46
|
+
* back through here. Contained exactly like the listing — the same root, the
|
|
47
|
+
* same refusal — and capped, because the import pipeline caps at the same size
|
|
48
|
+
* and a truncated workbook is worse than a clear refusal.
|
|
49
|
+
*
|
|
50
|
+
* @param root - Project directory.
|
|
51
|
+
* @param relativePath - File path relative to `<project>/storages`.
|
|
52
|
+
* @param maxBytes - Cap, defaulting to {@link MAX_PICKED_BYTES}.
|
|
53
|
+
* @returns the file's bytes and its basename.
|
|
54
|
+
* @throws when the path escapes the container, is a directory, or is too large.
|
|
55
|
+
*/
|
|
56
|
+
export declare function readProjectFile(root: string, relativePath: string, maxBytes?: number): Promise<{
|
|
57
|
+
name: string;
|
|
58
|
+
bytes: Buffer;
|
|
59
|
+
}>;
|
|
60
|
+
/**
|
|
61
|
+
* The chooser's listing for one request.
|
|
62
|
+
*
|
|
63
|
+
* @param project - Absolute project directory, or `undefined` when none was named.
|
|
64
|
+
* @param relativeDirectory - Directory relative to the storage container.
|
|
65
|
+
* @returns the listing; empty when there is no project or no storage yet.
|
|
66
|
+
* @throws when the path escapes the container.
|
|
67
|
+
*/
|
|
68
|
+
export declare function projectFiles(project: string | undefined, relativeDirectory: string): Promise<ProjectFileListing>;
|
|
69
|
+
/**
|
|
70
|
+
* The chooser's two routes: the listing, and one file's bytes.
|
|
71
|
+
*
|
|
72
|
+
* They live here rather than in the plugin body because the body has a 200-line
|
|
73
|
+
* budget — it assembles routes, it does not implement them (the same reason
|
|
74
|
+
* `assetRoutes()` is a module).
|
|
75
|
+
*
|
|
76
|
+
* @param ctx - Host context, read for the Session registry.
|
|
77
|
+
* @returns the routes to register.
|
|
78
|
+
*/
|
|
79
|
+
export declare function projectFileRoutes(ctx: Context): LegacyRoute[];
|