sh3-core 0.1.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/dist/Shell.svelte +185 -0
- package/dist/Shell.svelte.d.ts +4 -0
- package/dist/api.d.ts +22 -0
- package/dist/api.js +45 -0
- package/dist/apps/lifecycle.d.ts +37 -0
- package/dist/apps/lifecycle.js +153 -0
- package/dist/apps/registry.svelte.d.ts +37 -0
- package/dist/apps/registry.svelte.js +60 -0
- package/dist/apps/types.d.ts +61 -0
- package/dist/apps/types.js +10 -0
- package/dist/assets/icons.svg +1119 -0
- package/dist/auth/auth.svelte.d.ts +44 -0
- package/dist/auth/auth.svelte.js +119 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.js +1 -0
- package/dist/build.d.ts +29 -0
- package/dist/build.js +85 -0
- package/dist/contract.d.ts +20 -0
- package/dist/contract.js +28 -0
- package/dist/documents/backends.d.ts +17 -0
- package/dist/documents/backends.js +156 -0
- package/dist/documents/config.d.ts +7 -0
- package/dist/documents/config.js +27 -0
- package/dist/documents/handle.d.ts +6 -0
- package/dist/documents/handle.js +154 -0
- package/dist/documents/http-backend.d.ts +22 -0
- package/dist/documents/http-backend.js +78 -0
- package/dist/documents/index.d.ts +6 -0
- package/dist/documents/index.js +8 -0
- package/dist/documents/notifications.d.ts +9 -0
- package/dist/documents/notifications.js +39 -0
- package/dist/documents/types.d.ts +97 -0
- package/dist/documents/types.js +12 -0
- package/dist/host-entry.d.ts +9 -0
- package/dist/host-entry.js +15 -0
- package/dist/host.d.ts +13 -0
- package/dist/host.js +73 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +13 -0
- package/dist/layout/DragPreview.svelte +63 -0
- package/dist/layout/DragPreview.svelte.d.ts +3 -0
- package/dist/layout/LayoutRenderer.svelte +260 -0
- package/dist/layout/LayoutRenderer.svelte.d.ts +6 -0
- package/dist/layout/SlotContainer.svelte +140 -0
- package/dist/layout/SlotContainer.svelte.d.ts +8 -0
- package/dist/layout/SlotDropZone.svelte +122 -0
- package/dist/layout/SlotDropZone.svelte.d.ts +8 -0
- package/dist/layout/drag.svelte.d.ts +45 -0
- package/dist/layout/drag.svelte.js +191 -0
- package/dist/layout/inspection.d.ts +52 -0
- package/dist/layout/inspection.js +157 -0
- package/dist/layout/ops.d.ts +78 -0
- package/dist/layout/ops.js +281 -0
- package/dist/layout/slotHostPool.svelte.d.ts +36 -0
- package/dist/layout/slotHostPool.svelte.js +229 -0
- package/dist/layout/store.svelte.d.ts +39 -0
- package/dist/layout/store.svelte.js +150 -0
- package/dist/layout/tree-walk.d.ts +15 -0
- package/dist/layout/tree-walk.js +33 -0
- package/dist/layout/types.d.ts +108 -0
- package/dist/layout/types.js +25 -0
- package/dist/overlays/ModalFrame.svelte +87 -0
- package/dist/overlays/ModalFrame.svelte.d.ts +10 -0
- package/dist/overlays/PopupFrame.svelte +85 -0
- package/dist/overlays/PopupFrame.svelte.d.ts +10 -0
- package/dist/overlays/ToastItem.svelte +77 -0
- package/dist/overlays/ToastItem.svelte.d.ts +9 -0
- package/dist/overlays/focusTrap.d.ts +1 -0
- package/dist/overlays/focusTrap.js +64 -0
- package/dist/overlays/modal.d.ts +9 -0
- package/dist/overlays/modal.js +141 -0
- package/dist/overlays/popup.d.ts +9 -0
- package/dist/overlays/popup.js +108 -0
- package/dist/overlays/roots.d.ts +4 -0
- package/dist/overlays/roots.js +31 -0
- package/dist/overlays/toast.d.ts +6 -0
- package/dist/overlays/toast.js +93 -0
- package/dist/overlays/types.d.ts +31 -0
- package/dist/overlays/types.js +15 -0
- package/dist/primitives/.gitkeep +0 -0
- package/dist/primitives/ResizableSplitter.svelte +333 -0
- package/dist/primitives/ResizableSplitter.svelte.d.ts +35 -0
- package/dist/primitives/TabbedPanel.svelte +305 -0
- package/dist/primitives/TabbedPanel.svelte.d.ts +50 -0
- package/dist/registry/client.d.ts +74 -0
- package/dist/registry/client.js +118 -0
- package/dist/registry/index.d.ts +13 -0
- package/dist/registry/index.js +14 -0
- package/dist/registry/installer.d.ts +53 -0
- package/dist/registry/installer.js +170 -0
- package/dist/registry/integrity.d.ts +32 -0
- package/dist/registry/integrity.js +92 -0
- package/dist/registry/loader.d.ts +50 -0
- package/dist/registry/loader.js +145 -0
- package/dist/registry/schema.d.ts +47 -0
- package/dist/registry/schema.js +180 -0
- package/dist/registry/storage.d.ts +37 -0
- package/dist/registry/storage.js +101 -0
- package/dist/registry/types.d.ts +245 -0
- package/dist/registry/types.js +14 -0
- package/dist/registry-shard/RegistryView.svelte +561 -0
- package/dist/registry-shard/RegistryView.svelte.d.ts +3 -0
- package/dist/registry-shard/registryApp.d.ts +10 -0
- package/dist/registry-shard/registryApp.js +24 -0
- package/dist/registry-shard/registryShard.svelte.d.ts +45 -0
- package/dist/registry-shard/registryShard.svelte.js +125 -0
- package/dist/shards/activate.svelte.d.ts +45 -0
- package/dist/shards/activate.svelte.js +124 -0
- package/dist/shards/registry.d.ts +4 -0
- package/dist/shards/registry.js +28 -0
- package/dist/shards/types.d.ts +155 -0
- package/dist/shards/types.js +20 -0
- package/dist/shell-shard/ShellHome.svelte +285 -0
- package/dist/shell-shard/ShellHome.svelte.d.ts +3 -0
- package/dist/shell-shard/shellShard.svelte.d.ts +2 -0
- package/dist/shell-shard/shellShard.svelte.js +47 -0
- package/dist/shellRuntime.svelte.d.ts +27 -0
- package/dist/shellRuntime.svelte.js +27 -0
- package/dist/state/backends.d.ts +26 -0
- package/dist/state/backends.js +99 -0
- package/dist/state/types.d.ts +38 -0
- package/dist/state/types.js +15 -0
- package/dist/state/zones.svelte.d.ts +52 -0
- package/dist/state/zones.svelte.js +141 -0
- package/dist/store/InstalledView.svelte +201 -0
- package/dist/store/InstalledView.svelte.d.ts +3 -0
- package/dist/store/StoreView.svelte +470 -0
- package/dist/store/StoreView.svelte.d.ts +3 -0
- package/dist/store/storeApp.d.ts +11 -0
- package/dist/store/storeApp.js +26 -0
- package/dist/store/storeShard.svelte.d.ts +29 -0
- package/dist/store/storeShard.svelte.js +99 -0
- package/dist/tokens.css +79 -0
- package/package.json +50 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side admin mode — API key elevation for SH3.
|
|
3
|
+
*
|
|
4
|
+
* Stores the key in the user state zone so it persists across sessions.
|
|
5
|
+
* Provides reactive `isAdmin` for gating admin apps. The key is verified
|
|
6
|
+
* against the server on elevation and on boot (via initAuth).
|
|
7
|
+
*
|
|
8
|
+
* Boot-time verification uses a short timeout and fails open — the shell
|
|
9
|
+
* remains usable without admin access when the server is slow or offline.
|
|
10
|
+
*
|
|
11
|
+
* OS analogy: sudo / elevated permissions, not web login.
|
|
12
|
+
*
|
|
13
|
+
* .svelte.ts because it uses $state for reactive admin status.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Initialize auth at boot. Call once from bootstrap().
|
|
17
|
+
*
|
|
18
|
+
* If a key is stored from a previous session, verifies it against the
|
|
19
|
+
* server with a 3-second timeout. If verification fails (key revoked,
|
|
20
|
+
* server down, timeout), the shell boots without admin access — the
|
|
21
|
+
* stored key is cleared only on explicit rejection (401), not on
|
|
22
|
+
* network failure (so a temporary outage doesn't force re-entry).
|
|
23
|
+
*
|
|
24
|
+
* @param url - Server base URL ('' for same-origin).
|
|
25
|
+
*/
|
|
26
|
+
export declare function initAuth(url?: string): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Elevate to admin mode with an API key. Verifies against the server
|
|
29
|
+
* before storing. Returns true on success, false if the key is invalid.
|
|
30
|
+
*/
|
|
31
|
+
export declare function elevate(key: string): Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* De-escalate from admin mode — clear the stored key and drop elevation.
|
|
34
|
+
*/
|
|
35
|
+
export declare function deescalate(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Reactive getter — true when the user has elevated to admin mode.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isAdmin(): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Build an Authorization header value for authenticated fetch calls.
|
|
42
|
+
* Returns null if not elevated.
|
|
43
|
+
*/
|
|
44
|
+
export declare function getAuthHeader(): string | null;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side admin mode — API key elevation for SH3.
|
|
3
|
+
*
|
|
4
|
+
* Stores the key in the user state zone so it persists across sessions.
|
|
5
|
+
* Provides reactive `isAdmin` for gating admin apps. The key is verified
|
|
6
|
+
* against the server on elevation and on boot (via initAuth).
|
|
7
|
+
*
|
|
8
|
+
* Boot-time verification uses a short timeout and fails open — the shell
|
|
9
|
+
* remains usable without admin access when the server is slow or offline.
|
|
10
|
+
*
|
|
11
|
+
* OS analogy: sudo / elevated permissions, not web login.
|
|
12
|
+
*
|
|
13
|
+
* .svelte.ts because it uses $state for reactive admin status.
|
|
14
|
+
*/
|
|
15
|
+
import { createStateZones } from '../state/zones.svelte';
|
|
16
|
+
const state = createStateZones('__shell__:auth', {
|
|
17
|
+
user: { apiKey: null },
|
|
18
|
+
});
|
|
19
|
+
/** Reactive admin status. */
|
|
20
|
+
let admin = $state(false);
|
|
21
|
+
/** Server base URL, set once during initAuth. */
|
|
22
|
+
let serverUrl = '';
|
|
23
|
+
/**
|
|
24
|
+
* Verify a key against the server. Returns true if valid.
|
|
25
|
+
* Accepts an optional AbortSignal for timeout control.
|
|
26
|
+
*/
|
|
27
|
+
async function verifyKey(key, signal) {
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch(`${serverUrl}/api/auth/verify`, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
32
|
+
signal,
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok)
|
|
35
|
+
return false;
|
|
36
|
+
const body = await response.json();
|
|
37
|
+
return body.valid === true;
|
|
38
|
+
}
|
|
39
|
+
catch (_a) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Initialize auth at boot. Call once from bootstrap().
|
|
45
|
+
*
|
|
46
|
+
* If a key is stored from a previous session, verifies it against the
|
|
47
|
+
* server with a 3-second timeout. If verification fails (key revoked,
|
|
48
|
+
* server down, timeout), the shell boots without admin access — the
|
|
49
|
+
* stored key is cleared only on explicit rejection (401), not on
|
|
50
|
+
* network failure (so a temporary outage doesn't force re-entry).
|
|
51
|
+
*
|
|
52
|
+
* @param url - Server base URL ('' for same-origin).
|
|
53
|
+
*/
|
|
54
|
+
export async function initAuth(url = '') {
|
|
55
|
+
serverUrl = url;
|
|
56
|
+
const stored = state.user.apiKey;
|
|
57
|
+
if (!stored)
|
|
58
|
+
return;
|
|
59
|
+
const controller = new AbortController();
|
|
60
|
+
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
61
|
+
try {
|
|
62
|
+
const response = await fetch(`${serverUrl}/api/auth/verify`, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: { Authorization: `Bearer ${stored}` },
|
|
65
|
+
signal: controller.signal,
|
|
66
|
+
});
|
|
67
|
+
clearTimeout(timeout);
|
|
68
|
+
if (response.ok) {
|
|
69
|
+
const body = await response.json();
|
|
70
|
+
if (body.valid === true) {
|
|
71
|
+
admin = true;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Server explicitly rejected the key — clear it.
|
|
76
|
+
if (response.status === 401 || response.status === 403) {
|
|
77
|
+
state.user.apiKey = null;
|
|
78
|
+
}
|
|
79
|
+
// Other errors (5xx, etc.): keep the key, boot unelevated.
|
|
80
|
+
}
|
|
81
|
+
catch (_a) {
|
|
82
|
+
clearTimeout(timeout);
|
|
83
|
+
// Network error or timeout: keep the key, boot unelevated.
|
|
84
|
+
// User can re-elevate manually once connectivity returns.
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Elevate to admin mode with an API key. Verifies against the server
|
|
89
|
+
* before storing. Returns true on success, false if the key is invalid.
|
|
90
|
+
*/
|
|
91
|
+
export async function elevate(key) {
|
|
92
|
+
const valid = await verifyKey(key);
|
|
93
|
+
if (valid) {
|
|
94
|
+
state.user.apiKey = key;
|
|
95
|
+
admin = true;
|
|
96
|
+
}
|
|
97
|
+
return valid;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* De-escalate from admin mode — clear the stored key and drop elevation.
|
|
101
|
+
*/
|
|
102
|
+
export function deescalate() {
|
|
103
|
+
state.user.apiKey = null;
|
|
104
|
+
admin = false;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Reactive getter — true when the user has elevated to admin mode.
|
|
108
|
+
*/
|
|
109
|
+
export function isAdmin() {
|
|
110
|
+
return admin;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Build an Authorization header value for authenticated fetch calls.
|
|
114
|
+
* Returns null if not elevated.
|
|
115
|
+
*/
|
|
116
|
+
export function getAuthHeader() {
|
|
117
|
+
const key = state.user.apiKey;
|
|
118
|
+
return key ? `Bearer ${key}` : null;
|
|
119
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { initAuth, elevate, deescalate, isAdmin, getAuthHeader } from './auth.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { initAuth, elevate, deescalate, isAdmin, getAuthHeader } from './auth.svelte';
|
package/dist/build.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build-time helpers for SH3 package authors.
|
|
3
|
+
*
|
|
4
|
+
* Usage in a downstream vite.config.ts:
|
|
5
|
+
*
|
|
6
|
+
* import { svelte } from '@sveltejs/vite-plugin-svelte';
|
|
7
|
+
* import { sh3CssInline } from 'sh3-core/build';
|
|
8
|
+
*
|
|
9
|
+
* export default defineConfig({
|
|
10
|
+
* plugins: [svelte(), sh3CssInline()],
|
|
11
|
+
* build: {
|
|
12
|
+
* lib: { entry: 'src/main.ts', formats: ['es'], fileName: 'bundle' },
|
|
13
|
+
* rollupOptions: {
|
|
14
|
+
* external: ['sh3', 'svelte', 'svelte/internal/client'],
|
|
15
|
+
* },
|
|
16
|
+
* },
|
|
17
|
+
* });
|
|
18
|
+
*/
|
|
19
|
+
import type { Plugin } from 'vite';
|
|
20
|
+
/**
|
|
21
|
+
* Vite plugin that inlines extracted CSS into the JS bundle.
|
|
22
|
+
*
|
|
23
|
+
* Vite's lib mode writes CSS to a separate file outside the Rollup
|
|
24
|
+
* bundle pipeline, so Rollup hooks like `generateBundle` never see it.
|
|
25
|
+
* This plugin uses `closeBundle` — which runs after all files are on
|
|
26
|
+
* disk — to find CSS files, inject their contents into the JS entry
|
|
27
|
+
* as a self-executing `<style>` injector, and delete the CSS files.
|
|
28
|
+
*/
|
|
29
|
+
export declare function sh3CssInline(): Plugin;
|
package/dist/build.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build-time helpers for SH3 package authors.
|
|
3
|
+
*
|
|
4
|
+
* Usage in a downstream vite.config.ts:
|
|
5
|
+
*
|
|
6
|
+
* import { svelte } from '@sveltejs/vite-plugin-svelte';
|
|
7
|
+
* import { sh3CssInline } from 'sh3-core/build';
|
|
8
|
+
*
|
|
9
|
+
* export default defineConfig({
|
|
10
|
+
* plugins: [svelte(), sh3CssInline()],
|
|
11
|
+
* build: {
|
|
12
|
+
* lib: { entry: 'src/main.ts', formats: ['es'], fileName: 'bundle' },
|
|
13
|
+
* rollupOptions: {
|
|
14
|
+
* external: ['sh3', 'svelte', 'svelte/internal/client'],
|
|
15
|
+
* },
|
|
16
|
+
* },
|
|
17
|
+
* });
|
|
18
|
+
*/
|
|
19
|
+
import { readFileSync, writeFileSync, unlinkSync, readdirSync } from 'node:fs';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
/**
|
|
22
|
+
* Vite plugin that inlines extracted CSS into the JS bundle.
|
|
23
|
+
*
|
|
24
|
+
* Vite's lib mode writes CSS to a separate file outside the Rollup
|
|
25
|
+
* bundle pipeline, so Rollup hooks like `generateBundle` never see it.
|
|
26
|
+
* This plugin uses `closeBundle` — which runs after all files are on
|
|
27
|
+
* disk — to find CSS files, inject their contents into the JS entry
|
|
28
|
+
* as a self-executing `<style>` injector, and delete the CSS files.
|
|
29
|
+
*/
|
|
30
|
+
export function sh3CssInline() {
|
|
31
|
+
let resolvedConfig;
|
|
32
|
+
return {
|
|
33
|
+
name: 'sh3-css-inline',
|
|
34
|
+
apply: 'build',
|
|
35
|
+
enforce: 'post',
|
|
36
|
+
configResolved(config) {
|
|
37
|
+
resolvedConfig = config;
|
|
38
|
+
},
|
|
39
|
+
closeBundle() {
|
|
40
|
+
const outDir = resolvedConfig.build.outDir;
|
|
41
|
+
// Find all .css and .js files in the output directory.
|
|
42
|
+
let files;
|
|
43
|
+
try {
|
|
44
|
+
files = readdirSync(outDir);
|
|
45
|
+
}
|
|
46
|
+
catch (_a) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const cssFiles = files.filter(f => f.endsWith('.css'));
|
|
50
|
+
const jsFiles = files.filter(f => f.endsWith('.js'));
|
|
51
|
+
if (cssFiles.length === 0 || jsFiles.length === 0)
|
|
52
|
+
return;
|
|
53
|
+
// Read and concatenate all CSS.
|
|
54
|
+
const cssChunks = [];
|
|
55
|
+
for (const cssFile of cssFiles) {
|
|
56
|
+
const content = readFileSync(join(outDir, cssFile), 'utf-8').trim();
|
|
57
|
+
if (content)
|
|
58
|
+
cssChunks.push(content);
|
|
59
|
+
}
|
|
60
|
+
if (cssChunks.length === 0)
|
|
61
|
+
return;
|
|
62
|
+
// Build a JS snippet that injects a <style> tag at load time.
|
|
63
|
+
const combined = cssChunks.join('\n');
|
|
64
|
+
const escaped = JSON.stringify(combined);
|
|
65
|
+
const injector = [
|
|
66
|
+
'/* sh3-css-inline: injected styles */',
|
|
67
|
+
'(function(){',
|
|
68
|
+
' const s=document.createElement("style");',
|
|
69
|
+
` s.textContent=${escaped};`,
|
|
70
|
+
' document.head.appendChild(s);',
|
|
71
|
+
'})();',
|
|
72
|
+
'',
|
|
73
|
+
].join('\n');
|
|
74
|
+
// Prepend the injector to the first JS file (the entry).
|
|
75
|
+
const jsEntry = jsFiles[0];
|
|
76
|
+
const jsPath = join(outDir, jsEntry);
|
|
77
|
+
const jsContent = readFileSync(jsPath, 'utf-8');
|
|
78
|
+
writeFileSync(jsPath, injector + jsContent);
|
|
79
|
+
// Delete the CSS files.
|
|
80
|
+
for (const cssFile of cssFiles) {
|
|
81
|
+
unlinkSync(join(outDir, cssFile));
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const contract: {
|
|
2
|
+
readonly version: 1;
|
|
3
|
+
/** Import specifiers any shard/app source file may use. */
|
|
4
|
+
readonly shardImports: readonly ["sh3-core", "sh3-core/tokens.css"];
|
|
5
|
+
/** Import specifiers restricted to the host entry point only. */
|
|
6
|
+
readonly hostImports: readonly ["sh3-core/host"];
|
|
7
|
+
/** Contract-level prefix. Any import starting with 'sh3-core' that is not
|
|
8
|
+
* listed in shardImports or hostImports is illegal. */
|
|
9
|
+
readonly packagePrefix: "sh3-core";
|
|
10
|
+
readonly shard: {
|
|
11
|
+
readonly requiredFields: readonly ["id", "label", "version", "views"];
|
|
12
|
+
readonly views: {
|
|
13
|
+
readonly requiredFields: readonly ["id", "label"];
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
readonly app: {
|
|
17
|
+
readonly requiredFields: readonly ["id", "label", "version", "requiredShards", "layoutVersion"];
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export type Contract = typeof contract;
|
package/dist/contract.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Machine-readable contract descriptor.
|
|
3
|
+
*
|
|
4
|
+
* Imported as `import { contract } from 'sh3-core/contract'`. The validator
|
|
5
|
+
* package reads this to know what import paths are legal, what fields
|
|
6
|
+
* manifests must have, and which contract version is in play.
|
|
7
|
+
*
|
|
8
|
+
* Pure data — no runtime logic, no dependencies.
|
|
9
|
+
*/
|
|
10
|
+
export const contract = {
|
|
11
|
+
version: 1,
|
|
12
|
+
/** Import specifiers any shard/app source file may use. */
|
|
13
|
+
shardImports: ['sh3-core', 'sh3-core/tokens.css'],
|
|
14
|
+
/** Import specifiers restricted to the host entry point only. */
|
|
15
|
+
hostImports: ['sh3-core/host'],
|
|
16
|
+
/** Contract-level prefix. Any import starting with 'sh3-core' that is not
|
|
17
|
+
* listed in shardImports or hostImports is illegal. */
|
|
18
|
+
packagePrefix: 'sh3-core',
|
|
19
|
+
shard: {
|
|
20
|
+
requiredFields: ['id', 'label', 'version', 'views'],
|
|
21
|
+
views: {
|
|
22
|
+
requiredFields: ['id', 'label'],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
app: {
|
|
26
|
+
requiredFields: ['id', 'label', 'version', 'requiredShards', 'layoutVersion'],
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DocumentBackend, DocumentMeta } from './types';
|
|
2
|
+
export declare class MemoryDocumentBackend implements DocumentBackend {
|
|
3
|
+
#private;
|
|
4
|
+
read(tenantId: string, shardId: string, path: string): Promise<string | ArrayBuffer | null>;
|
|
5
|
+
write(tenantId: string, shardId: string, path: string, content: string | ArrayBuffer): Promise<void>;
|
|
6
|
+
delete(tenantId: string, shardId: string, path: string): Promise<void>;
|
|
7
|
+
list(tenantId: string, shardId: string): Promise<DocumentMeta[]>;
|
|
8
|
+
exists(tenantId: string, shardId: string, path: string): Promise<boolean>;
|
|
9
|
+
}
|
|
10
|
+
export declare class IndexedDBDocumentBackend implements DocumentBackend {
|
|
11
|
+
#private;
|
|
12
|
+
read(tenantId: string, shardId: string, path: string): Promise<string | ArrayBuffer | null>;
|
|
13
|
+
write(tenantId: string, shardId: string, path: string, content: string | ArrayBuffer): Promise<void>;
|
|
14
|
+
delete(tenantId: string, shardId: string, path: string): Promise<void>;
|
|
15
|
+
list(tenantId: string, shardId: string): Promise<DocumentMeta[]>;
|
|
16
|
+
exists(tenantId: string, shardId: string, path: string): Promise<boolean>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Document zone backends — concrete storage implementations.
|
|
3
|
+
*
|
|
4
|
+
* MemoryDocumentBackend: Map-based, for tests and ephemeral use.
|
|
5
|
+
* IndexedDBDocumentBackend: The web default. Lazy-inits on first
|
|
6
|
+
* operation to avoid blocking bootstrap.
|
|
7
|
+
*/
|
|
8
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
+
};
|
|
13
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
14
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
15
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
16
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
17
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
18
|
+
};
|
|
19
|
+
var _MemoryDocumentBackend_store, _IndexedDBDocumentBackend_instances, _IndexedDBDocumentBackend_dbPromise, _IndexedDBDocumentBackend_db, _IndexedDBDocumentBackend_tx;
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Helpers
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
function compositeKey(tenantId, shardId, path) {
|
|
24
|
+
return `${tenantId}/${shardId}/${path}`;
|
|
25
|
+
}
|
|
26
|
+
function keyPrefix(tenantId, shardId) {
|
|
27
|
+
return `${tenantId}/${shardId}/`;
|
|
28
|
+
}
|
|
29
|
+
export class MemoryDocumentBackend {
|
|
30
|
+
constructor() {
|
|
31
|
+
_MemoryDocumentBackend_store.set(this, new Map());
|
|
32
|
+
}
|
|
33
|
+
async read(tenantId, shardId, path) {
|
|
34
|
+
const entry = __classPrivateFieldGet(this, _MemoryDocumentBackend_store, "f").get(compositeKey(tenantId, shardId, path));
|
|
35
|
+
return entry ? entry.content : null;
|
|
36
|
+
}
|
|
37
|
+
async write(tenantId, shardId, path, content) {
|
|
38
|
+
const size = typeof content === 'string' ? new Blob([content]).size : content.byteLength;
|
|
39
|
+
__classPrivateFieldGet(this, _MemoryDocumentBackend_store, "f").set(compositeKey(tenantId, shardId, path), {
|
|
40
|
+
content,
|
|
41
|
+
size,
|
|
42
|
+
lastModified: Date.now(),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async delete(tenantId, shardId, path) {
|
|
46
|
+
__classPrivateFieldGet(this, _MemoryDocumentBackend_store, "f").delete(compositeKey(tenantId, shardId, path));
|
|
47
|
+
}
|
|
48
|
+
async list(tenantId, shardId) {
|
|
49
|
+
const prefix = keyPrefix(tenantId, shardId);
|
|
50
|
+
const out = [];
|
|
51
|
+
for (const [key, entry] of __classPrivateFieldGet(this, _MemoryDocumentBackend_store, "f")) {
|
|
52
|
+
if (key.startsWith(prefix)) {
|
|
53
|
+
out.push({
|
|
54
|
+
path: key.slice(prefix.length),
|
|
55
|
+
size: entry.size,
|
|
56
|
+
lastModified: entry.lastModified,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
async exists(tenantId, shardId, path) {
|
|
63
|
+
return __classPrivateFieldGet(this, _MemoryDocumentBackend_store, "f").has(compositeKey(tenantId, shardId, path));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
_MemoryDocumentBackend_store = new WeakMap();
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// IndexedDBDocumentBackend
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
const IDB_NAME = 'sh3-documents';
|
|
71
|
+
const IDB_STORE = 'docs';
|
|
72
|
+
const IDB_VERSION = 2;
|
|
73
|
+
export class IndexedDBDocumentBackend {
|
|
74
|
+
constructor() {
|
|
75
|
+
_IndexedDBDocumentBackend_instances.add(this);
|
|
76
|
+
_IndexedDBDocumentBackend_dbPromise.set(this, null);
|
|
77
|
+
}
|
|
78
|
+
async read(tenantId, shardId, path) {
|
|
79
|
+
const key = compositeKey(tenantId, shardId, path);
|
|
80
|
+
const entry = await __classPrivateFieldGet(this, _IndexedDBDocumentBackend_instances, "m", _IndexedDBDocumentBackend_tx).call(this, 'readonly', (s) => s.get(key));
|
|
81
|
+
return entry ? entry.content : null;
|
|
82
|
+
}
|
|
83
|
+
async write(tenantId, shardId, path, content) {
|
|
84
|
+
const key = compositeKey(tenantId, shardId, path);
|
|
85
|
+
const size = typeof content === 'string' ? new Blob([content]).size : content.byteLength;
|
|
86
|
+
const entry = { content, size, lastModified: Date.now() };
|
|
87
|
+
await __classPrivateFieldGet(this, _IndexedDBDocumentBackend_instances, "m", _IndexedDBDocumentBackend_tx).call(this, 'readwrite', (s) => s.put(entry, key));
|
|
88
|
+
}
|
|
89
|
+
async delete(tenantId, shardId, path) {
|
|
90
|
+
const key = compositeKey(tenantId, shardId, path);
|
|
91
|
+
await __classPrivateFieldGet(this, _IndexedDBDocumentBackend_instances, "m", _IndexedDBDocumentBackend_tx).call(this, 'readwrite', (s) => s.delete(key));
|
|
92
|
+
}
|
|
93
|
+
async list(tenantId, shardId) {
|
|
94
|
+
const prefix = keyPrefix(tenantId, shardId);
|
|
95
|
+
const db = await __classPrivateFieldGet(this, _IndexedDBDocumentBackend_instances, "m", _IndexedDBDocumentBackend_db).call(this);
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
const tx = db.transaction(IDB_STORE, 'readonly');
|
|
98
|
+
const store = tx.objectStore(IDB_STORE);
|
|
99
|
+
// IDBKeyRange.bound selects all keys that start with the prefix.
|
|
100
|
+
// The upper bound appends a character beyond '/' to capture all
|
|
101
|
+
// sub-paths without over-selecting.
|
|
102
|
+
const range = IDBKeyRange.bound(prefix, prefix + '\uffff', false, false);
|
|
103
|
+
const req = store.openCursor(range);
|
|
104
|
+
const out = [];
|
|
105
|
+
req.onsuccess = () => {
|
|
106
|
+
const cursor = req.result;
|
|
107
|
+
if (cursor) {
|
|
108
|
+
const entry = cursor.value;
|
|
109
|
+
out.push({
|
|
110
|
+
path: cursor.key.slice(prefix.length),
|
|
111
|
+
size: entry.size,
|
|
112
|
+
lastModified: entry.lastModified,
|
|
113
|
+
});
|
|
114
|
+
cursor.continue();
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
resolve(out);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
req.onerror = () => reject(req.error);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
async exists(tenantId, shardId, path) {
|
|
124
|
+
const key = compositeKey(tenantId, shardId, path);
|
|
125
|
+
// getKey is cheaper than get — avoids deserializing the value.
|
|
126
|
+
const result = await __classPrivateFieldGet(this, _IndexedDBDocumentBackend_instances, "m", _IndexedDBDocumentBackend_tx).call(this, 'readonly', (s) => s.getKey(key));
|
|
127
|
+
return result !== undefined;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
_IndexedDBDocumentBackend_dbPromise = new WeakMap(), _IndexedDBDocumentBackend_instances = new WeakSet(), _IndexedDBDocumentBackend_db = function _IndexedDBDocumentBackend_db() {
|
|
131
|
+
if (!__classPrivateFieldGet(this, _IndexedDBDocumentBackend_dbPromise, "f")) {
|
|
132
|
+
__classPrivateFieldSet(this, _IndexedDBDocumentBackend_dbPromise, new Promise((resolve, reject) => {
|
|
133
|
+
const req = indexedDB.open(IDB_NAME, IDB_VERSION);
|
|
134
|
+
req.onupgradeneeded = () => {
|
|
135
|
+
const db = req.result;
|
|
136
|
+
if (!db.objectStoreNames.contains(IDB_STORE)) {
|
|
137
|
+
db.createObjectStore(IDB_STORE);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
req.onsuccess = () => resolve(req.result);
|
|
141
|
+
req.onerror = () => reject(req.error);
|
|
142
|
+
}), "f");
|
|
143
|
+
}
|
|
144
|
+
return __classPrivateFieldGet(this, _IndexedDBDocumentBackend_dbPromise, "f");
|
|
145
|
+
}, _IndexedDBDocumentBackend_tx =
|
|
146
|
+
/** Run a single-store transaction and return the request result. */
|
|
147
|
+
async function _IndexedDBDocumentBackend_tx(mode, fn) {
|
|
148
|
+
const db = await __classPrivateFieldGet(this, _IndexedDBDocumentBackend_instances, "m", _IndexedDBDocumentBackend_db).call(this);
|
|
149
|
+
return new Promise((resolve, reject) => {
|
|
150
|
+
const tx = db.transaction(IDB_STORE, mode);
|
|
151
|
+
const store = tx.objectStore(IDB_STORE);
|
|
152
|
+
const req = fn(store);
|
|
153
|
+
req.onsuccess = () => resolve(req.result);
|
|
154
|
+
req.onerror = () => reject(req.error);
|
|
155
|
+
});
|
|
156
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DocumentBackend } from './types';
|
|
2
|
+
export declare function getTenantId(): string;
|
|
3
|
+
export declare function getDocumentBackend(): DocumentBackend;
|
|
4
|
+
/** Host-only. Set the tenant id before bootstrap(). */
|
|
5
|
+
export declare function __setTenantId(id: string): void;
|
|
6
|
+
/** Host-only. Swap the document backend before bootstrap(). */
|
|
7
|
+
export declare function __setDocumentBackend(b: DocumentBackend): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Document zone configuration — module-level singletons.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the __setBackend pattern in state/zones.svelte.ts. The host
|
|
5
|
+
* calls __setTenantId and __setDocumentBackend before bootstrap() to
|
|
6
|
+
* configure multi-tenancy and swap backends (e.g. Tauri FS).
|
|
7
|
+
*
|
|
8
|
+
* Defaults: tenantId='local' (single-user self-hosted), backend=IndexedDB.
|
|
9
|
+
*/
|
|
10
|
+
import { IndexedDBDocumentBackend } from './backends';
|
|
11
|
+
const DEFAULT_TENANT = 'local';
|
|
12
|
+
let tenantId = DEFAULT_TENANT;
|
|
13
|
+
let backend = new IndexedDBDocumentBackend();
|
|
14
|
+
export function getTenantId() {
|
|
15
|
+
return tenantId;
|
|
16
|
+
}
|
|
17
|
+
export function getDocumentBackend() {
|
|
18
|
+
return backend;
|
|
19
|
+
}
|
|
20
|
+
/** Host-only. Set the tenant id before bootstrap(). */
|
|
21
|
+
export function __setTenantId(id) {
|
|
22
|
+
tenantId = id;
|
|
23
|
+
}
|
|
24
|
+
/** Host-only. Swap the document backend before bootstrap(). */
|
|
25
|
+
export function __setDocumentBackend(b) {
|
|
26
|
+
backend = b;
|
|
27
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DocumentBackend, DocumentHandle, DocumentHandleOptions } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Create a document handle scoped to a tenant, shard, and file filter.
|
|
4
|
+
* The framework calls this from `ShardContext.documents()`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createDocumentHandle(tenantId: string, shardId: string, backend: DocumentBackend, options: DocumentHandleOptions): DocumentHandle;
|