sh3-server 0.8.2 → 0.9.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/app/assets/index-DKuJNK2S.js +17 -0
- package/app/assets/index-DKuJNK2S.js.map +1 -0
- package/app/assets/index-DkC3EpjJ.css +1 -0
- package/app/index.html +2 -2
- package/dist/caller.d.ts +2 -1
- package/dist/caller.js +2 -1
- package/dist/doc-store/conflicts.d.ts +19 -0
- package/dist/doc-store/conflicts.js +79 -0
- package/dist/doc-store/index.d.ts +11 -0
- package/dist/doc-store/index.js +22 -0
- package/dist/doc-store/meta.d.ts +11 -0
- package/dist/doc-store/meta.js +37 -0
- package/dist/doc-store/policy.d.ts +15 -0
- package/dist/doc-store/policy.js +85 -0
- package/dist/doc-store/reserved.d.ts +7 -0
- package/dist/doc-store/reserved.js +26 -0
- package/dist/doc-store/roles.d.ts +12 -0
- package/dist/doc-store/roles.js +15 -0
- package/dist/doc-store/store.d.ts +71 -0
- package/dist/doc-store/store.js +336 -0
- package/dist/doc-store/tick.d.ts +13 -0
- package/dist/doc-store/tick.js +52 -0
- package/dist/fs-backend.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +30 -11
- package/dist/keys.d.ts +4 -2
- package/dist/keys.js +18 -3
- package/dist/migrations/sync-grants.d.ts +7 -0
- package/dist/migrations/sync-grants.js +27 -0
- package/dist/packages.d.ts +3 -4
- package/dist/packages.js +5 -5
- package/dist/routes/docs.d.ts +11 -7
- package/dist/routes/docs.js +88 -122
- package/dist/routes/keys.js +4 -2
- package/dist/scope.d.ts +2 -0
- package/dist/scope.js +20 -0
- package/dist/shard-router.d.ts +8 -9
- package/dist/shard-router.js +114 -62
- package/package.json +1 -1
- package/app/assets/index-Cb-zoqb1.js +0 -17
- package/app/assets/index-Cb-zoqb1.js.map +0 -1
- package/app/assets/index-DPcN5Lor.css +0 -1
package/dist/packages.js
CHANGED
|
@@ -18,7 +18,7 @@ function isValidId(id) {
|
|
|
18
18
|
* For each valid package, mount server routes (if server.js exists) and
|
|
19
19
|
* return the full list of discovered packages.
|
|
20
20
|
*/
|
|
21
|
-
export async function loadPackages(shardRouter, dataDir, keys, settings, wsRegister,
|
|
21
|
+
export async function loadPackages(shardRouter, dataDir, keys, settings, wsRegister, docStore) {
|
|
22
22
|
const packagesDir = join(dataDir, 'packages');
|
|
23
23
|
if (!existsSync(packagesDir)) {
|
|
24
24
|
mkdirSync(packagesDir, { recursive: true });
|
|
@@ -68,7 +68,7 @@ export async function loadPackages(shardRouter, dataDir, keys, settings, wsRegis
|
|
|
68
68
|
};
|
|
69
69
|
if (hasServer) {
|
|
70
70
|
try {
|
|
71
|
-
await shardRouter.mount(manifest.id, serverJs, { pkgDir, keys, settings, wsRegister,
|
|
71
|
+
await shardRouter.mount(manifest.id, serverJs, { pkgDir, keys, settings, wsRegister, docStore });
|
|
72
72
|
}
|
|
73
73
|
catch (err) {
|
|
74
74
|
console.warn(`[sh3] ${manifest.id}/server.js failed to load:`, err);
|
|
@@ -193,7 +193,7 @@ export function validateRequiredShards(manifest, knownShards) {
|
|
|
193
193
|
* Returns a Hono router with POST /install and POST /uninstall.
|
|
194
194
|
* Protected by the blanket `/api/*` auth middleware already applied upstream.
|
|
195
195
|
*/
|
|
196
|
-
export function createPackageManagementRoutes(shardRouter, dataDir, keys, settings, wsRegister,
|
|
196
|
+
export function createPackageManagementRoutes(shardRouter, dataDir, keys, settings, wsRegister, docStore, frameworkShardIds = []) {
|
|
197
197
|
const router = new Hono();
|
|
198
198
|
router.post('/install', async (c) => {
|
|
199
199
|
const form = await c.req.formData();
|
|
@@ -267,7 +267,7 @@ export function createPackageManagementRoutes(shardRouter, dataDir, keys, settin
|
|
|
267
267
|
writeFileSync(join(pkgDir, 'server.js'), buf);
|
|
268
268
|
// Hot-mount server routes
|
|
269
269
|
try {
|
|
270
|
-
await shardRouter.mount(id, join(pkgDir, 'server.js'), { pkgDir, keys, settings, wsRegister,
|
|
270
|
+
await shardRouter.mount(id, join(pkgDir, 'server.js'), { pkgDir, keys, settings, wsRegister, docStore });
|
|
271
271
|
}
|
|
272
272
|
catch (err) {
|
|
273
273
|
// Roll back entire install — broken server bundle must not be half-installed
|
|
@@ -297,7 +297,7 @@ export function createPackageManagementRoutes(shardRouter, dataDir, keys, settin
|
|
|
297
297
|
return c.json({ error: `Package "${id}" not found` }, 404);
|
|
298
298
|
}
|
|
299
299
|
// Unmount server routes immediately
|
|
300
|
-
shardRouter.unmount(id);
|
|
300
|
+
await shardRouter.unmount(id);
|
|
301
301
|
// Remove code files
|
|
302
302
|
rmSync(manifestPath, { force: true });
|
|
303
303
|
const clientPath = join(pkgDir, 'client.js');
|
package/dist/routes/docs.d.ts
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Document backend API routes.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Delegates every read/write/list/delete to the TenantDocStore, which owns
|
|
5
|
+
* the metadata-sidecar write pipeline (version bump, syncMode cache, tick
|
|
6
|
+
* advance, conflict bucket). This router is intentionally thin: it validates
|
|
7
|
+
* auth and path shape, then calls the store.
|
|
6
8
|
*
|
|
7
|
-
* GET /api/docs/:tenant/_shards →
|
|
8
|
-
* GET /api/docs/:tenant/_all →
|
|
9
|
+
* GET /api/docs/:tenant/_shards → listAll (shard ids)
|
|
10
|
+
* GET /api/docs/:tenant/_all → listAll
|
|
9
11
|
* GET /api/docs/:tenant/:shard → list
|
|
10
12
|
* GET /api/docs/:tenant/:shard/*path → read
|
|
11
13
|
* HEAD /api/docs/:tenant/:shard/*path → exists
|
|
12
|
-
* PUT /api/docs/:tenant/:shard/*path → write
|
|
13
|
-
* DELETE /api/docs/:tenant/:shard/*path → delete
|
|
14
|
+
* PUT /api/docs/:tenant/:shard/*path → write
|
|
15
|
+
* DELETE /api/docs/:tenant/:shard/*path → delete
|
|
14
16
|
*/
|
|
15
17
|
import { Hono } from 'hono';
|
|
16
|
-
|
|
18
|
+
import type { SettingsStore } from '../settings.js';
|
|
19
|
+
import type { TenantDocStore } from '../doc-store/index.js';
|
|
20
|
+
export declare function createDocsRouter(store: TenantDocStore, settings?: SettingsStore): Hono;
|
package/dist/routes/docs.js
CHANGED
|
@@ -1,162 +1,128 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Document backend API routes.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Delegates every read/write/list/delete to the TenantDocStore, which owns
|
|
5
|
+
* the metadata-sidecar write pipeline (version bump, syncMode cache, tick
|
|
6
|
+
* advance, conflict bucket). This router is intentionally thin: it validates
|
|
7
|
+
* auth and path shape, then calls the store.
|
|
6
8
|
*
|
|
7
|
-
* GET /api/docs/:tenant/_shards →
|
|
8
|
-
* GET /api/docs/:tenant/_all →
|
|
9
|
+
* GET /api/docs/:tenant/_shards → listAll (shard ids)
|
|
10
|
+
* GET /api/docs/:tenant/_all → listAll
|
|
9
11
|
* GET /api/docs/:tenant/:shard → list
|
|
10
12
|
* GET /api/docs/:tenant/:shard/*path → read
|
|
11
13
|
* HEAD /api/docs/:tenant/:shard/*path → exists
|
|
12
|
-
* PUT /api/docs/:tenant/:shard/*path → write
|
|
13
|
-
* DELETE /api/docs/:tenant/:shard/*path → delete
|
|
14
|
+
* PUT /api/docs/:tenant/:shard/*path → write
|
|
15
|
+
* DELETE /api/docs/:tenant/:shard/*path → delete
|
|
14
16
|
*/
|
|
15
17
|
import { Hono } from 'hono';
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
export function createDocsRouter(dataDir) {
|
|
18
|
+
import { tenantParamMatch } from '../scope.js';
|
|
19
|
+
export function createDocsRouter(store, settings) {
|
|
19
20
|
const router = new Hono();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (!resolved.startsWith(join(docsDir, tenant, shard))) {
|
|
25
|
-
throw new Error('Path traversal detected');
|
|
26
|
-
}
|
|
27
|
-
return resolved;
|
|
21
|
+
router.use('/:tenant/*', tenantParamMatch('tenant', settings));
|
|
22
|
+
router.use('/:tenant', tenantParamMatch('tenant', settings));
|
|
23
|
+
function isReservedShardId(shard) {
|
|
24
|
+
return shard.startsWith('__');
|
|
28
25
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (!existsSync(dir))
|
|
32
|
-
return results;
|
|
33
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
34
|
-
for (const entry of entries) {
|
|
35
|
-
const full = join(dir, entry.name);
|
|
36
|
-
if (entry.isDirectory()) {
|
|
37
|
-
results.push(...collectFiles(full, base));
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
const stat = statSync(full);
|
|
41
|
-
results.push({
|
|
42
|
-
path: relative(base, full).replace(/\\/g, '/'),
|
|
43
|
-
size: stat.size,
|
|
44
|
-
lastModified: stat.mtimeMs,
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return results;
|
|
49
|
-
}
|
|
50
|
-
// List all shard ids that have content for a tenant.
|
|
51
|
-
router.get('/:tenant/_shards', (c) => {
|
|
26
|
+
// Shards list
|
|
27
|
+
router.get('/:tenant/_shards', async (c) => {
|
|
52
28
|
const { tenant } = c.req.param();
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return c.json(shards);
|
|
29
|
+
const all = await store.listAll(tenant);
|
|
30
|
+
const seen = new Set();
|
|
31
|
+
for (const e of all)
|
|
32
|
+
seen.add(e.shardId);
|
|
33
|
+
return c.json([...seen].filter((id) => !isReservedShardId(id)));
|
|
59
34
|
});
|
|
60
|
-
//
|
|
61
|
-
router.get('/:tenant/_all', (c) => {
|
|
35
|
+
// All docs
|
|
36
|
+
router.get('/:tenant/_all', async (c) => {
|
|
62
37
|
const { tenant } = c.req.param();
|
|
63
|
-
|
|
64
|
-
if (!existsSync(tenantDir))
|
|
65
|
-
return c.json([]);
|
|
66
|
-
const entries = readdirSync(tenantDir, { withFileTypes: true });
|
|
67
|
-
const out = [];
|
|
68
|
-
for (const entry of entries) {
|
|
69
|
-
if (!entry.isDirectory())
|
|
70
|
-
continue;
|
|
71
|
-
const shardDir = join(tenantDir, entry.name);
|
|
72
|
-
for (const f of collectFiles(shardDir, shardDir)) {
|
|
73
|
-
out.push({ ...f, shardId: entry.name });
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return c.json(out);
|
|
38
|
+
return c.json(await store.listAll(tenant));
|
|
77
39
|
});
|
|
78
|
-
//
|
|
79
|
-
router.get('/:tenant/:shard', (c) => {
|
|
40
|
+
// Per-shard list
|
|
41
|
+
router.get('/:tenant/:shard', async (c) => {
|
|
80
42
|
const { tenant, shard } = c.req.param();
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return c.json(
|
|
43
|
+
if (isReservedShardId(shard))
|
|
44
|
+
return c.notFound();
|
|
45
|
+
return c.json(await store.list(tenant, shard));
|
|
84
46
|
});
|
|
85
|
-
// Read
|
|
86
|
-
router.get('/:tenant/:shard/*', (c) => {
|
|
47
|
+
// Read
|
|
48
|
+
router.get('/:tenant/:shard/*', async (c) => {
|
|
87
49
|
const { tenant, shard } = c.req.param();
|
|
50
|
+
if (isReservedShardId(shard))
|
|
51
|
+
return c.notFound();
|
|
88
52
|
const filePath = c.req.path.replace(`/api/docs/${tenant}/${shard}/`, '');
|
|
89
53
|
if (!filePath)
|
|
90
54
|
return c.json({ error: 'Missing file path' }, 400);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
55
|
+
if (c.req.query('meta') === '1') {
|
|
56
|
+
const meta = await store.readMeta(tenant, shard, filePath);
|
|
57
|
+
if (!meta)
|
|
58
|
+
return c.json({ exists: false });
|
|
59
|
+
const payload = { exists: true, ...meta };
|
|
60
|
+
if (meta.syncState === 'conflict') {
|
|
61
|
+
const cf = await store.readConflict(tenant, shard, filePath);
|
|
62
|
+
if (cf) {
|
|
63
|
+
payload.branches = cf.branches.map((b) => ({
|
|
64
|
+
origin: b.origin,
|
|
65
|
+
version: b.version,
|
|
66
|
+
at: b.at,
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return c.json(payload);
|
|
97
71
|
}
|
|
98
|
-
|
|
72
|
+
const content = await store.read(tenant, shard, filePath);
|
|
73
|
+
if (content === null)
|
|
99
74
|
return c.notFound();
|
|
100
|
-
|
|
101
|
-
const content = readFileSync(resolved);
|
|
102
|
-
// Detect binary vs text heuristically: if the file can be decoded as
|
|
103
|
-
// UTF-8 without replacement characters, treat as text.
|
|
104
|
-
const text = new TextDecoder('utf-8', { fatal: true });
|
|
105
|
-
try {
|
|
106
|
-
const str = text.decode(content);
|
|
107
|
-
return c.text(str);
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
return new Response(content, {
|
|
111
|
-
headers: { 'Content-Type': 'application/octet-stream' },
|
|
112
|
-
});
|
|
113
|
-
}
|
|
75
|
+
return c.text(content);
|
|
114
76
|
});
|
|
115
|
-
//
|
|
116
|
-
router.on('HEAD', '/:tenant/:shard/*', (c) => {
|
|
77
|
+
// Exists
|
|
78
|
+
router.on('HEAD', '/:tenant/:shard/*', async (c) => {
|
|
117
79
|
const { tenant, shard } = c.req.param();
|
|
80
|
+
if (isReservedShardId(shard))
|
|
81
|
+
return c.notFound();
|
|
118
82
|
const filePath = c.req.path.replace(`/api/docs/${tenant}/${shard}/`, '');
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
83
|
+
return new Response(null, {
|
|
84
|
+
status: (await store.exists(tenant, shard, filePath)) ? 200 : 404,
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
// Conflict resolve — must match before the generic PUT so the `/resolve`
|
|
88
|
+
// suffix isn't captured as part of the file path.
|
|
89
|
+
router.post('/:tenant/:shard/*', async (c) => {
|
|
90
|
+
const { tenant, shard } = c.req.param();
|
|
91
|
+
if (isReservedShardId(shard))
|
|
92
|
+
return c.json({ error: 'Reserved shard id' }, 400);
|
|
93
|
+
const rawPath = c.req.path.replace(`/api/docs/${tenant}/${shard}/`, '');
|
|
94
|
+
if (!rawPath.endsWith('/resolve')) {
|
|
95
|
+
return c.json({ error: 'Unknown docs POST endpoint' }, 404);
|
|
122
96
|
}
|
|
123
|
-
|
|
124
|
-
|
|
97
|
+
const filePath = rawPath.replace(/\/resolve$/, '');
|
|
98
|
+
if (!filePath)
|
|
99
|
+
return c.json({ error: 'Missing file path' }, 400);
|
|
100
|
+
const body = await c.req.json().catch(() => null);
|
|
101
|
+
if (!body || typeof body.choice === 'undefined') {
|
|
102
|
+
return c.json({ error: 'Body must include { choice }' }, 400);
|
|
125
103
|
}
|
|
126
|
-
|
|
104
|
+
await store.resolveConflict(tenant, shard, filePath, body.choice);
|
|
105
|
+
return c.json({ ok: true });
|
|
127
106
|
});
|
|
128
|
-
// Write
|
|
107
|
+
// Write
|
|
129
108
|
router.put('/:tenant/:shard/*', async (c) => {
|
|
130
109
|
const { tenant, shard } = c.req.param();
|
|
110
|
+
if (isReservedShardId(shard))
|
|
111
|
+
return c.json({ error: 'Reserved shard id' }, 400);
|
|
131
112
|
const filePath = c.req.path.replace(`/api/docs/${tenant}/${shard}/`, '');
|
|
132
113
|
if (!filePath)
|
|
133
114
|
return c.json({ error: 'Missing file path' }, 400);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
return c.json({ error: 'Invalid path' }, 400);
|
|
140
|
-
}
|
|
141
|
-
mkdirSync(dirname(resolved), { recursive: true });
|
|
142
|
-
const body = await c.req.arrayBuffer();
|
|
143
|
-
writeFileSync(resolved, Buffer.from(body));
|
|
144
|
-
return c.json({ ok: true });
|
|
115
|
+
const body = await c.req.text();
|
|
116
|
+
const result = await store.write(tenant, shard, filePath, body);
|
|
117
|
+
return c.json({ ok: true, ...result });
|
|
145
118
|
});
|
|
146
|
-
// Delete
|
|
147
|
-
router.delete('/:tenant/:shard/*', (c) => {
|
|
119
|
+
// Delete
|
|
120
|
+
router.delete('/:tenant/:shard/*', async (c) => {
|
|
148
121
|
const { tenant, shard } = c.req.param();
|
|
122
|
+
if (isReservedShardId(shard))
|
|
123
|
+
return c.json({ error: 'Reserved shard id' }, 400);
|
|
149
124
|
const filePath = c.req.path.replace(`/api/docs/${tenant}/${shard}/`, '');
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
resolved = resolvePath(tenant, shard, filePath);
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
155
|
-
return c.json({ error: 'Invalid path' }, 400);
|
|
156
|
-
}
|
|
157
|
-
if (existsSync(resolved)) {
|
|
158
|
-
unlinkSync(resolved);
|
|
159
|
-
}
|
|
125
|
+
await store.delete(tenant, shard, filePath);
|
|
160
126
|
return c.json({ ok: true });
|
|
161
127
|
});
|
|
162
128
|
return router;
|
package/dist/routes/keys.js
CHANGED
|
@@ -62,7 +62,8 @@ export function createKeysRouter(keys, onRevoke) {
|
|
|
62
62
|
shardId: body.shardId,
|
|
63
63
|
label: body.label,
|
|
64
64
|
scopes: body.scopes,
|
|
65
|
-
|
|
65
|
+
peerRole: body.peerRole,
|
|
66
|
+
peerId: body.peerId,
|
|
66
67
|
expiresIn: body.expiresIn,
|
|
67
68
|
tenantId: caller.tenantId,
|
|
68
69
|
userId: caller.userId,
|
|
@@ -102,7 +103,8 @@ export function createKeysRouter(keys, onRevoke) {
|
|
|
102
103
|
ownerUserId: entry.userId,
|
|
103
104
|
mintedByShardId: entry.shardId,
|
|
104
105
|
scopes: entry.scopes,
|
|
105
|
-
|
|
106
|
+
peerRole: entry.peerRole,
|
|
107
|
+
peerId: entry.peerId,
|
|
106
108
|
expiresAt,
|
|
107
109
|
});
|
|
108
110
|
return c.json({ id: row.id, key: row.key });
|
package/dist/scope.d.ts
CHANGED
|
@@ -5,5 +5,7 @@
|
|
|
5
5
|
* tenantRequired — 401 unless caller.tenantId is non-null.
|
|
6
6
|
*/
|
|
7
7
|
import type { MiddlewareHandler } from 'hono';
|
|
8
|
+
import type { SettingsStore } from './settings.js';
|
|
8
9
|
export declare function scopeRequired(scope: string): MiddlewareHandler;
|
|
9
10
|
export declare const tenantRequired: MiddlewareHandler;
|
|
11
|
+
export declare function tenantParamMatch(paramName: string, settings?: SettingsStore): MiddlewareHandler;
|
package/dist/scope.js
CHANGED
|
@@ -23,3 +23,23 @@ export const tenantRequired = async (c, next) => {
|
|
|
23
23
|
return c.json({ error: 'Tenant-scoped credentials required' }, 401);
|
|
24
24
|
return next();
|
|
25
25
|
};
|
|
26
|
+
export function tenantParamMatch(paramName, settings) {
|
|
27
|
+
return async (c, next) => {
|
|
28
|
+
// Open / no-auth mode: admin has explicitly disabled tenant isolation.
|
|
29
|
+
// Mirrors sessionAuth's open-mode bypass; required for Tauri sidecar mode.
|
|
30
|
+
if (settings && !settings.get().auth.required)
|
|
31
|
+
return next();
|
|
32
|
+
const caller = c.get('caller');
|
|
33
|
+
if (!caller)
|
|
34
|
+
return c.json({ error: 'Caller not resolved' }, 500);
|
|
35
|
+
if (caller.scopes.includes('admin:*'))
|
|
36
|
+
return next();
|
|
37
|
+
if (!caller.tenantId)
|
|
38
|
+
return c.json({ error: 'Tenant-scoped credentials required' }, 401);
|
|
39
|
+
const requested = c.req.param(paramName);
|
|
40
|
+
if (caller.tenantId !== requested) {
|
|
41
|
+
return c.json({ error: `Caller tenant does not match :${paramName}` }, 403);
|
|
42
|
+
}
|
|
43
|
+
return next();
|
|
44
|
+
};
|
|
45
|
+
}
|
package/dist/shard-router.d.ts
CHANGED
|
@@ -2,12 +2,12 @@ import { Hono } from 'hono';
|
|
|
2
2
|
import type { MiddlewareHandler } from 'hono';
|
|
3
3
|
import type { KeyStore } from './keys.js';
|
|
4
4
|
import type { SettingsStore } from './settings.js';
|
|
5
|
+
import type { TenantDocStore } from './doc-store/index.js';
|
|
5
6
|
export interface MountContext {
|
|
6
7
|
pkgDir: string;
|
|
7
8
|
keys: KeyStore;
|
|
8
9
|
settings: SettingsStore;
|
|
9
|
-
|
|
10
|
-
documentBackend: import('sh3-core/server-sync').DocumentBackend;
|
|
10
|
+
docStore: TenantDocStore;
|
|
11
11
|
/**
|
|
12
12
|
* Register a WebSocket upgrade handler on a path under this shard's
|
|
13
13
|
* route prefix. The returned value is a Hono middleware handler that
|
|
@@ -49,16 +49,12 @@ export interface MountContext {
|
|
|
49
49
|
}
|
|
50
50
|
/** Middleware requiring the caller's scope set to include admin:*. */
|
|
51
51
|
export declare function adminOnly(_keys: KeyStore, settings: SettingsStore): MiddlewareHandler;
|
|
52
|
-
/**
|
|
53
|
-
* Build a ServerShardContext object for the given shard, wiring
|
|
54
|
-
* sync/syncRegistry based on the declared permissions.
|
|
55
|
-
*/
|
|
56
|
-
export declare function buildShardCtx(shardId: string, dataDir: string, permissions: string[], keys: KeyStore, settings: SettingsStore, wsRegister: MountContext['wsRegister'], documentBackend: MountContext['documentBackend']): Record<string, unknown>;
|
|
57
52
|
/**
|
|
58
53
|
* Dynamic shard route manager. Holds a Map of shard Hono sub-apps
|
|
59
54
|
* and delegates requests from a single wildcard route.
|
|
60
55
|
*/
|
|
61
56
|
export declare class ShardRouter {
|
|
57
|
+
#private;
|
|
62
58
|
private shards;
|
|
63
59
|
/**
|
|
64
60
|
* Import a server.js bundle and mount its routes.
|
|
@@ -73,9 +69,12 @@ export declare class ShardRouter {
|
|
|
73
69
|
mountStatic(shardId: string, mod: {
|
|
74
70
|
id: string;
|
|
75
71
|
routes: (router: Hono, ctx: any) => void | Promise<void>;
|
|
72
|
+
teardown?: () => void | Promise<void>;
|
|
76
73
|
}, ctx: MountContext): Promise<void>;
|
|
77
|
-
/** Remove a shard's routes. */
|
|
78
|
-
unmount(shardId: string): boolean
|
|
74
|
+
/** Remove a shard's routes. Calls `teardown()` if the shard defined one. */
|
|
75
|
+
unmount(shardId: string): Promise<boolean>;
|
|
76
|
+
/** Call teardown on every mounted shard without clearing the registry. */
|
|
77
|
+
unmountAll(): Promise<void>;
|
|
79
78
|
/**
|
|
80
79
|
* Hono handler for the wildcard route.
|
|
81
80
|
* Looks up the shard sub-app and delegates with path stripping.
|