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,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Controller plugged in by a layout-aware parent to turn tab drags
|
|
3
|
+
* into layout mutations. The primitive itself is layout-agnostic:
|
|
4
|
+
* it just calls `onPointerDown` when a tab is grabbed, and asks the
|
|
5
|
+
* controller to hit-test the strip during a drag via `onStripHover`.
|
|
6
|
+
*/
|
|
7
|
+
export interface TabDragController {
|
|
8
|
+
onPointerDown(index: number, event: PointerEvent, element: HTMLElement): void;
|
|
9
|
+
onStripHover(stripRect: DOMRect, pointerX: number, pointerY: number, tabRects: DOMRect[]): number | null;
|
|
10
|
+
onStripLeave(): void;
|
|
11
|
+
readonly isDragging: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Pure helper: given an insert index and the tab button rects,
|
|
15
|
+
* compute the indicator's position in strip-local coordinates.
|
|
16
|
+
*/
|
|
17
|
+
export declare function computeIndicatorRect(insertIndex: number, tabEls: (HTMLButtonElement | undefined)[], stripEl: HTMLDivElement | undefined): {
|
|
18
|
+
left: number;
|
|
19
|
+
top: number;
|
|
20
|
+
height: number;
|
|
21
|
+
} | null;
|
|
22
|
+
import type { Snippet } from 'svelte';
|
|
23
|
+
type $$ComponentProps = {
|
|
24
|
+
labels: string[];
|
|
25
|
+
icons?: (string | undefined)[];
|
|
26
|
+
/** Snippet invoked once per tab with its index. */
|
|
27
|
+
body: Snippet<[number]>;
|
|
28
|
+
activeTab: number;
|
|
29
|
+
/** Called when the user picks a different tab. The parent is
|
|
30
|
+
* expected to write the new value back into whatever it is
|
|
31
|
+
* storing activeTab in. We use a callback rather than a
|
|
32
|
+
* $bindable prop because the parent's activeTab typically lives
|
|
33
|
+
* inside a larger $state object (a LayoutNode), and `bind:` on a
|
|
34
|
+
* sub-property trips Svelte 5's ownership warning. */
|
|
35
|
+
onActiveChange?: (index: number) => void;
|
|
36
|
+
dragController?: TabDragController;
|
|
37
|
+
/** Optional: called by the tab click handler; if it returns true,
|
|
38
|
+
* the click is ignored. Used to swallow the synthetic click that
|
|
39
|
+
* fires on the source tab after a drag commit. */
|
|
40
|
+
clickGuard?: () => boolean;
|
|
41
|
+
/** Per-tab closability. True if the tab can be closed. */
|
|
42
|
+
closable?: (boolean | undefined)[];
|
|
43
|
+
/** Per-tab dirty state. True if the tab has unsaved changes. */
|
|
44
|
+
dirty?: (boolean | undefined)[];
|
|
45
|
+
/** Called when the user clicks a tab's close button. */
|
|
46
|
+
onClose?: (index: number) => void;
|
|
47
|
+
};
|
|
48
|
+
declare const TabbedPanel: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
49
|
+
type TabbedPanel = ReturnType<typeof TabbedPanel>;
|
|
50
|
+
export default TabbedPanel;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry client -- fetches and merges registry indices, resolves
|
|
3
|
+
* packages, and downloads bundles with integrity verification.
|
|
4
|
+
*
|
|
5
|
+
* Typical usage:
|
|
6
|
+
* ```ts
|
|
7
|
+
* const packages = await fetchRegistries(['https://example.com/registry.json']);
|
|
8
|
+
* const pkg = packages.find(p => p.entry.id === 'my-shard');
|
|
9
|
+
* if (pkg) {
|
|
10
|
+
* const data = await fetchBundle(pkg.latest);
|
|
11
|
+
* const meta = buildPackageMeta(pkg, pkg.latest);
|
|
12
|
+
* }
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
import type { PackageEntry, PackageVersion, PackageMeta } from './types.js';
|
|
16
|
+
/**
|
|
17
|
+
* A `PackageEntry` annotated with its source registry URL and resolved latest version.
|
|
18
|
+
*
|
|
19
|
+
* Returned by `fetchRegistries` so callers can display package cards and
|
|
20
|
+
* initiate installs without needing to re-parse the raw index document.
|
|
21
|
+
*/
|
|
22
|
+
export interface ResolvedPackage {
|
|
23
|
+
/** The raw package entry from the registry index. */
|
|
24
|
+
entry: PackageEntry;
|
|
25
|
+
/**
|
|
26
|
+
* The latest version of this package.
|
|
27
|
+
* By convention this is `entry.versions[0]` — registries publish newest-first.
|
|
28
|
+
*/
|
|
29
|
+
latest: PackageVersion;
|
|
30
|
+
/** URL of the registry index this package was found in. */
|
|
31
|
+
sourceRegistry: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Fetch and merge multiple registry indices into a single package catalog.
|
|
35
|
+
*
|
|
36
|
+
* Registries are fetched in order. If the same package id appears in more
|
|
37
|
+
* than one registry, the first registry wins (left-to-right priority).
|
|
38
|
+
* Fetch or validation failures for individual registries are logged as
|
|
39
|
+
* warnings and skipped — the function always returns the packages that
|
|
40
|
+
* could be resolved successfully.
|
|
41
|
+
*
|
|
42
|
+
* @param urls - Ordered list of `registry.json` URLs to fetch.
|
|
43
|
+
* @returns Merged list of resolved packages, deduplicated by package id.
|
|
44
|
+
*/
|
|
45
|
+
export declare function fetchRegistries(urls: string[]): Promise<ResolvedPackage[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Download a bundle and verify its SRI integrity hash.
|
|
48
|
+
*
|
|
49
|
+
* Fetches `version.bundleUrl`, reads the response as an `ArrayBuffer`, then
|
|
50
|
+
* calls `verifyIntegrity` before returning. Throws on any network error,
|
|
51
|
+
* non-OK HTTP status, or integrity mismatch — the caller must not execute
|
|
52
|
+
* a bundle for which this function threw.
|
|
53
|
+
*
|
|
54
|
+
* If `bundleUrl` is relative (starts with `/`), it is resolved against
|
|
55
|
+
* `sourceRegistry` so cross-origin installs hit the correct server.
|
|
56
|
+
*
|
|
57
|
+
* @param version - The `PackageVersion` describing the bundle to download.
|
|
58
|
+
* @param sourceRegistry - The registry URL this package came from (used to resolve relative bundle paths).
|
|
59
|
+
* @returns Raw bundle bytes, verified against `version.integrity`.
|
|
60
|
+
* @throws If the fetch fails, the server returns a non-OK status, or the integrity check fails.
|
|
61
|
+
*/
|
|
62
|
+
export declare function fetchBundle(version: PackageVersion, sourceRegistry?: string): Promise<ArrayBuffer>;
|
|
63
|
+
/**
|
|
64
|
+
* Build a `PackageMeta` record from a resolved package and a chosen version.
|
|
65
|
+
*
|
|
66
|
+
* Combines identity fields from the `PackageEntry` with provenance and
|
|
67
|
+
* integrity fields from the selected `PackageVersion`. The result is passed
|
|
68
|
+
* to the framework install API alongside the raw bundle bytes.
|
|
69
|
+
*
|
|
70
|
+
* @param resolved - The resolved package, as returned by `fetchRegistries`.
|
|
71
|
+
* @param version - The specific version to install (e.g. `resolved.latest`).
|
|
72
|
+
* @returns A `PackageMeta` ready to be handed to the install API.
|
|
73
|
+
*/
|
|
74
|
+
export declare function buildPackageMeta(resolved: ResolvedPackage, version: PackageVersion): PackageMeta;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry client -- fetches and merges registry indices, resolves
|
|
3
|
+
* packages, and downloads bundles with integrity verification.
|
|
4
|
+
*
|
|
5
|
+
* Typical usage:
|
|
6
|
+
* ```ts
|
|
7
|
+
* const packages = await fetchRegistries(['https://example.com/registry.json']);
|
|
8
|
+
* const pkg = packages.find(p => p.entry.id === 'my-shard');
|
|
9
|
+
* if (pkg) {
|
|
10
|
+
* const data = await fetchBundle(pkg.latest);
|
|
11
|
+
* const meta = buildPackageMeta(pkg, pkg.latest);
|
|
12
|
+
* }
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
import { validateRegistryIndex } from './schema.js';
|
|
16
|
+
import { verifyIntegrity } from './integrity.js';
|
|
17
|
+
/**
|
|
18
|
+
* Fetch and merge multiple registry indices into a single package catalog.
|
|
19
|
+
*
|
|
20
|
+
* Registries are fetched in order. If the same package id appears in more
|
|
21
|
+
* than one registry, the first registry wins (left-to-right priority).
|
|
22
|
+
* Fetch or validation failures for individual registries are logged as
|
|
23
|
+
* warnings and skipped — the function always returns the packages that
|
|
24
|
+
* could be resolved successfully.
|
|
25
|
+
*
|
|
26
|
+
* @param urls - Ordered list of `registry.json` URLs to fetch.
|
|
27
|
+
* @returns Merged list of resolved packages, deduplicated by package id.
|
|
28
|
+
*/
|
|
29
|
+
export async function fetchRegistries(urls) {
|
|
30
|
+
const seen = new Set();
|
|
31
|
+
const results = [];
|
|
32
|
+
for (const url of urls) {
|
|
33
|
+
let raw;
|
|
34
|
+
try {
|
|
35
|
+
const response = await fetch(url);
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
console.warn(`[sh3] Registry fetch failed for ${url}: HTTP ${response.status}`);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
raw = await response.json();
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
console.warn(`[sh3] Registry ${url}: ${err instanceof Error ? err.message : String(err)}`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
let index;
|
|
47
|
+
try {
|
|
48
|
+
index = validateRegistryIndex(raw);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
console.warn(`[sh3] Registry ${url} failed validation: ${err instanceof Error ? err.message : String(err)}`);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
for (const entry of index.packages) {
|
|
55
|
+
if (seen.has(entry.id))
|
|
56
|
+
continue;
|
|
57
|
+
seen.add(entry.id);
|
|
58
|
+
results.push({
|
|
59
|
+
entry,
|
|
60
|
+
latest: entry.versions[0],
|
|
61
|
+
sourceRegistry: url,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return results;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Download a bundle and verify its SRI integrity hash.
|
|
69
|
+
*
|
|
70
|
+
* Fetches `version.bundleUrl`, reads the response as an `ArrayBuffer`, then
|
|
71
|
+
* calls `verifyIntegrity` before returning. Throws on any network error,
|
|
72
|
+
* non-OK HTTP status, or integrity mismatch — the caller must not execute
|
|
73
|
+
* a bundle for which this function threw.
|
|
74
|
+
*
|
|
75
|
+
* If `bundleUrl` is relative (starts with `/`), it is resolved against
|
|
76
|
+
* `sourceRegistry` so cross-origin installs hit the correct server.
|
|
77
|
+
*
|
|
78
|
+
* @param version - The `PackageVersion` describing the bundle to download.
|
|
79
|
+
* @param sourceRegistry - The registry URL this package came from (used to resolve relative bundle paths).
|
|
80
|
+
* @returns Raw bundle bytes, verified against `version.integrity`.
|
|
81
|
+
* @throws If the fetch fails, the server returns a non-OK status, or the integrity check fails.
|
|
82
|
+
*/
|
|
83
|
+
export async function fetchBundle(version, sourceRegistry) {
|
|
84
|
+
let url = version.bundleUrl;
|
|
85
|
+
if (sourceRegistry && url.startsWith('/')) {
|
|
86
|
+
const base = new URL(sourceRegistry);
|
|
87
|
+
url = `${base.origin}${url}`;
|
|
88
|
+
}
|
|
89
|
+
const response = await fetch(url);
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
throw new Error(`Bundle fetch failed: HTTP ${response.status} ${response.statusText} from ${url}`);
|
|
92
|
+
}
|
|
93
|
+
const data = await response.arrayBuffer();
|
|
94
|
+
await verifyIntegrity(data, version.integrity);
|
|
95
|
+
return data;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Build a `PackageMeta` record from a resolved package and a chosen version.
|
|
99
|
+
*
|
|
100
|
+
* Combines identity fields from the `PackageEntry` with provenance and
|
|
101
|
+
* integrity fields from the selected `PackageVersion`. The result is passed
|
|
102
|
+
* to the framework install API alongside the raw bundle bytes.
|
|
103
|
+
*
|
|
104
|
+
* @param resolved - The resolved package, as returned by `fetchRegistries`.
|
|
105
|
+
* @param version - The specific version to install (e.g. `resolved.latest`).
|
|
106
|
+
* @returns A `PackageMeta` ready to be handed to the install API.
|
|
107
|
+
*/
|
|
108
|
+
export function buildPackageMeta(resolved, version) {
|
|
109
|
+
return {
|
|
110
|
+
id: resolved.entry.id,
|
|
111
|
+
type: resolved.entry.type,
|
|
112
|
+
version: version.version,
|
|
113
|
+
contractVersion: version.contractVersion,
|
|
114
|
+
sourceRegistry: resolved.sourceRegistry,
|
|
115
|
+
integrity: version.integrity,
|
|
116
|
+
requires: version.requires,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry module barrel export.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the public surface of the registry subsystem: protocol types,
|
|
5
|
+
* schema validation, integrity verification, client functions, and the
|
|
6
|
+
* install API.
|
|
7
|
+
*/
|
|
8
|
+
export type { RegistryIndex, PackageEntry, PackageVersion, RequiredDependency, InstalledPackage, InstallResult, PackageMeta, } from './types';
|
|
9
|
+
export { RegistryValidationError, validateRegistryIndex } from './schema';
|
|
10
|
+
export { verifyIntegrity, computeIntegrity } from './integrity';
|
|
11
|
+
export type { ResolvedPackage } from './client';
|
|
12
|
+
export { fetchRegistries, fetchBundle, buildPackageMeta } from './client';
|
|
13
|
+
export { installPackage, uninstallPackage, listInstalledPackages, loadInstalledPackages, } from './installer';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry module barrel export.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the public surface of the registry subsystem: protocol types,
|
|
5
|
+
* schema validation, integrity verification, client functions, and the
|
|
6
|
+
* install API.
|
|
7
|
+
*/
|
|
8
|
+
// Schema validation.
|
|
9
|
+
export { RegistryValidationError, validateRegistryIndex } from './schema';
|
|
10
|
+
// Integrity verification.
|
|
11
|
+
export { verifyIntegrity, computeIntegrity } from './integrity';
|
|
12
|
+
export { fetchRegistries, fetchBundle, buildPackageMeta } from './client';
|
|
13
|
+
// Install API.
|
|
14
|
+
export { installPackage, uninstallPackage, listInstalledPackages, loadInstalledPackages, } from './installer';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package installer -- coordinates storing, loading, and registering packages.
|
|
3
|
+
*
|
|
4
|
+
* This is the bridge between the store shard (which fetches and verifies
|
|
5
|
+
* bundles) and the framework (which registers shards/apps). The install API
|
|
6
|
+
* is host-only; shards and apps must not import from here.
|
|
7
|
+
*
|
|
8
|
+
* Flow:
|
|
9
|
+
* 1. `installPackage(bundle, meta)` -- load module from bytes, verify
|
|
10
|
+
* declared type matches actual type, persist to IndexedDB, register
|
|
11
|
+
* with framework.
|
|
12
|
+
* 2. `uninstallPackage(id)` -- deactivate if active, remove from storage.
|
|
13
|
+
* 3. `loadInstalledPackages()` -- called at boot, re-loads all installed
|
|
14
|
+
* packages from IndexedDB and registers them.
|
|
15
|
+
*/
|
|
16
|
+
import type { InstalledPackage, InstallResult, PackageMeta } from './types';
|
|
17
|
+
/**
|
|
18
|
+
* Install a package from raw bundle bytes and metadata.
|
|
19
|
+
*
|
|
20
|
+
* Loads the ESM module, verifies the declared type matches the actual module
|
|
21
|
+
* shape, persists to IndexedDB, and registers with the framework. If
|
|
22
|
+
* registration fails (e.g. duplicate id from a shard that was already
|
|
23
|
+
* glob-discovered), the package is still persisted but `hotLoaded` is false.
|
|
24
|
+
*
|
|
25
|
+
* @param bundle - Raw verified ESM bundle bytes.
|
|
26
|
+
* @param meta - Provenance metadata for the install record.
|
|
27
|
+
* @returns Result object indicating success/failure and hot-load status.
|
|
28
|
+
*/
|
|
29
|
+
export declare function installPackage(bundle: ArrayBuffer, meta: PackageMeta): Promise<InstallResult>;
|
|
30
|
+
/**
|
|
31
|
+
* Uninstall a package by id.
|
|
32
|
+
*
|
|
33
|
+
* If the package is a shard and currently active, deactivates it first.
|
|
34
|
+
* Removes both the bundle and metadata from IndexedDB.
|
|
35
|
+
*
|
|
36
|
+
* @param id - The package id to uninstall.
|
|
37
|
+
*/
|
|
38
|
+
export declare function uninstallPackage(id: string): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* List all installed packages.
|
|
41
|
+
*
|
|
42
|
+
* Delegates directly to the storage layer. Returns metadata records only,
|
|
43
|
+
* not the bundle bytes.
|
|
44
|
+
*/
|
|
45
|
+
export declare function listInstalledPackages(): Promise<InstalledPackage[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Load all installed packages from IndexedDB and register them.
|
|
48
|
+
*
|
|
49
|
+
* Called once at boot by `bootstrap()`, before any glob-discovered shards
|
|
50
|
+
* or the shell shard are registered. Individual package failures are logged
|
|
51
|
+
* as warnings but do not prevent the shell from booting.
|
|
52
|
+
*/
|
|
53
|
+
export declare function loadInstalledPackages(): Promise<void>;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package installer -- coordinates storing, loading, and registering packages.
|
|
3
|
+
*
|
|
4
|
+
* This is the bridge between the store shard (which fetches and verifies
|
|
5
|
+
* bundles) and the framework (which registers shards/apps). The install API
|
|
6
|
+
* is host-only; shards and apps must not import from here.
|
|
7
|
+
*
|
|
8
|
+
* Flow:
|
|
9
|
+
* 1. `installPackage(bundle, meta)` -- load module from bytes, verify
|
|
10
|
+
* declared type matches actual type, persist to IndexedDB, register
|
|
11
|
+
* with framework.
|
|
12
|
+
* 2. `uninstallPackage(id)` -- deactivate if active, remove from storage.
|
|
13
|
+
* 3. `loadInstalledPackages()` -- called at boot, re-loads all installed
|
|
14
|
+
* packages from IndexedDB and registers them.
|
|
15
|
+
*/
|
|
16
|
+
import { loadBundleModule } from './loader';
|
|
17
|
+
import { savePackage, loadBundle, listInstalled, removePackage } from './storage';
|
|
18
|
+
import { verifyIntegrity } from './integrity';
|
|
19
|
+
import { registerShard, deactivateShard } from '../shards/activate.svelte';
|
|
20
|
+
import { registerApp } from '../apps/registry.svelte';
|
|
21
|
+
/**
|
|
22
|
+
* Install a package from raw bundle bytes and metadata.
|
|
23
|
+
*
|
|
24
|
+
* Loads the ESM module, verifies the declared type matches the actual module
|
|
25
|
+
* shape, persists to IndexedDB, and registers with the framework. If
|
|
26
|
+
* registration fails (e.g. duplicate id from a shard that was already
|
|
27
|
+
* glob-discovered), the package is still persisted but `hotLoaded` is false.
|
|
28
|
+
*
|
|
29
|
+
* @param bundle - Raw verified ESM bundle bytes.
|
|
30
|
+
* @param meta - Provenance metadata for the install record.
|
|
31
|
+
* @returns Result object indicating success/failure and hot-load status.
|
|
32
|
+
*/
|
|
33
|
+
export async function installPackage(bundle, meta) {
|
|
34
|
+
// 1. Verify bundle integrity before executing any code.
|
|
35
|
+
try {
|
|
36
|
+
await verifyIntegrity(bundle, meta.integrity);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
return {
|
|
40
|
+
success: false,
|
|
41
|
+
hotLoaded: false,
|
|
42
|
+
error: `Integrity check failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// 2. Load the module from verified bytes.
|
|
46
|
+
let loaded;
|
|
47
|
+
try {
|
|
48
|
+
loaded = await loadBundleModule(bundle);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
hotLoaded: false,
|
|
54
|
+
error: `Failed to load bundle: ${err instanceof Error ? err.message : String(err)}`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// 3. Verify the bundle contains the declared type.
|
|
58
|
+
if (meta.type === 'shard' && loaded.shards.length === 0) {
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
hotLoaded: false,
|
|
62
|
+
error: `Package "${meta.id}" declared type "shard" but bundle contains no valid shard`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (meta.type === 'app' && loaded.apps.length === 0) {
|
|
66
|
+
return {
|
|
67
|
+
success: false,
|
|
68
|
+
hotLoaded: false,
|
|
69
|
+
error: `Package "${meta.id}" declared type "app" but bundle contains no valid app`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// 4. Persist to IndexedDB.
|
|
73
|
+
const record = {
|
|
74
|
+
id: meta.id,
|
|
75
|
+
type: meta.type,
|
|
76
|
+
version: meta.version,
|
|
77
|
+
sourceRegistry: meta.sourceRegistry,
|
|
78
|
+
contractVersion: meta.contractVersion,
|
|
79
|
+
installedAt: new Date().toISOString(),
|
|
80
|
+
};
|
|
81
|
+
try {
|
|
82
|
+
await savePackage(meta.id, bundle, record);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
return {
|
|
86
|
+
success: false,
|
|
87
|
+
hotLoaded: false,
|
|
88
|
+
error: `Failed to persist package: ${err instanceof Error ? err.message : String(err)}`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// 5. Register all shards and apps from the bundle.
|
|
92
|
+
let hotLoaded = true;
|
|
93
|
+
try {
|
|
94
|
+
for (const shard of loaded.shards)
|
|
95
|
+
registerShard(shard);
|
|
96
|
+
for (const app of loaded.apps)
|
|
97
|
+
registerApp(app);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
// Registration failure (e.g. duplicate id) -- the package is persisted
|
|
101
|
+
// but not usable until reload. Log and continue.
|
|
102
|
+
console.warn(`[sh3] Package "${meta.id}" installed but registration failed (will retry on next boot):`, err instanceof Error ? err.message : err);
|
|
103
|
+
hotLoaded = false;
|
|
104
|
+
}
|
|
105
|
+
return { success: true, package: record, hotLoaded };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Uninstall a package by id.
|
|
109
|
+
*
|
|
110
|
+
* If the package is a shard and currently active, deactivates it first.
|
|
111
|
+
* Removes both the bundle and metadata from IndexedDB.
|
|
112
|
+
*
|
|
113
|
+
* @param id - The package id to uninstall.
|
|
114
|
+
*/
|
|
115
|
+
export async function uninstallPackage(id) {
|
|
116
|
+
// Attempt deactivation (no-op if not active or not a shard).
|
|
117
|
+
try {
|
|
118
|
+
deactivateShard(id);
|
|
119
|
+
}
|
|
120
|
+
catch (_a) {
|
|
121
|
+
// Ignore -- may not be a shard, or may not be active.
|
|
122
|
+
}
|
|
123
|
+
await removePackage(id);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* List all installed packages.
|
|
127
|
+
*
|
|
128
|
+
* Delegates directly to the storage layer. Returns metadata records only,
|
|
129
|
+
* not the bundle bytes.
|
|
130
|
+
*/
|
|
131
|
+
export async function listInstalledPackages() {
|
|
132
|
+
return listInstalled();
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Load all installed packages from IndexedDB and register them.
|
|
136
|
+
*
|
|
137
|
+
* Called once at boot by `bootstrap()`, before any glob-discovered shards
|
|
138
|
+
* or the shell shard are registered. Individual package failures are logged
|
|
139
|
+
* as warnings but do not prevent the shell from booting.
|
|
140
|
+
*/
|
|
141
|
+
export async function loadInstalledPackages() {
|
|
142
|
+
let packages;
|
|
143
|
+
try {
|
|
144
|
+
packages = await listInstalled();
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
console.warn('[sh3] Failed to read installed packages from storage:', err instanceof Error ? err.message : err);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
for (const pkg of packages) {
|
|
151
|
+
try {
|
|
152
|
+
const bytes = await loadBundle(pkg.id);
|
|
153
|
+
if (!bytes) {
|
|
154
|
+
console.warn(`[sh3] No bundle found for installed package "${pkg.id}", skipping`);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const loaded = await loadBundleModule(bytes);
|
|
158
|
+
for (const shard of loaded.shards)
|
|
159
|
+
registerShard(shard);
|
|
160
|
+
for (const app of loaded.apps)
|
|
161
|
+
registerApp(app);
|
|
162
|
+
if (loaded.shards.length === 0 && loaded.apps.length === 0) {
|
|
163
|
+
console.warn(`[sh3] Package "${pkg.id}" contains no valid shards or apps, skipping`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
console.warn(`[sh3] Failed to load installed package "${pkg.id}":`, err instanceof Error ? err.message : err);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SRI integrity verification for downloaded bundles.
|
|
3
|
+
*
|
|
4
|
+
* Uses the Web Crypto API (available in browsers and Tauri WebView).
|
|
5
|
+
* Accepts "sha384-<base64>" format matching the W3C SRI spec.
|
|
6
|
+
* See: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Verify that `data` matches the expected SRI integrity string.
|
|
10
|
+
*
|
|
11
|
+
* Uses the Web Crypto API to compute the digest and compares it against the
|
|
12
|
+
* base64 hash in `integrity`. Throws with a descriptive message on any
|
|
13
|
+
* mismatch, invalid format, or unsupported algorithm.
|
|
14
|
+
*
|
|
15
|
+
* @param data - Raw bytes of the downloaded bundle.
|
|
16
|
+
* @param integrity - SRI hash string, e.g. `"sha384-abc123..."`.
|
|
17
|
+
* @returns Resolves on success; throws on mismatch or bad format.
|
|
18
|
+
* @throws If the format is invalid, the algorithm is unsupported, or the hash does not match.
|
|
19
|
+
*/
|
|
20
|
+
export declare function verifyIntegrity(data: ArrayBuffer, integrity: string): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Compute an SRI hash for the given data.
|
|
23
|
+
*
|
|
24
|
+
* Useful for publishing tools that need to generate the `integrity` field
|
|
25
|
+
* for a `registry.json` entry. Defaults to `sha384`, which is the recommended
|
|
26
|
+
* algorithm for the SH3 registry protocol.
|
|
27
|
+
*
|
|
28
|
+
* @param data - Raw bytes of the bundle to hash.
|
|
29
|
+
* @param algorithm - Hash algorithm to use. Defaults to `"sha384"`.
|
|
30
|
+
* @returns SRI string in the form `"<algorithm>-<base64hash>"`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function computeIntegrity(data: ArrayBuffer, algorithm?: 'sha256' | 'sha384' | 'sha512'): Promise<string>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SRI integrity verification for downloaded bundles.
|
|
3
|
+
*
|
|
4
|
+
* Uses the Web Crypto API (available in browsers and Tauri WebView).
|
|
5
|
+
* Accepts "sha384-<base64>" format matching the W3C SRI spec.
|
|
6
|
+
* See: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
|
|
7
|
+
*/
|
|
8
|
+
const SUPPORTED_ALGORITHMS = ['sha256', 'sha384', 'sha512'];
|
|
9
|
+
/**
|
|
10
|
+
* Parse an SRI integrity string into its algorithm and base64 hash components.
|
|
11
|
+
*
|
|
12
|
+
* @param integrity - SRI string in the form `"<algorithm>-<base64hash>"`.
|
|
13
|
+
* @returns Object with `algorithm` and `hash` fields.
|
|
14
|
+
* @throws If the format is invalid or the algorithm is not supported.
|
|
15
|
+
*/
|
|
16
|
+
function parseSri(integrity) {
|
|
17
|
+
const dashIndex = integrity.indexOf('-');
|
|
18
|
+
if (dashIndex === -1) {
|
|
19
|
+
throw new Error(`Invalid SRI format: ${integrity}`);
|
|
20
|
+
}
|
|
21
|
+
const algorithm = integrity.slice(0, dashIndex);
|
|
22
|
+
const hash = integrity.slice(dashIndex + 1);
|
|
23
|
+
if (!SUPPORTED_ALGORITHMS.includes(algorithm)) {
|
|
24
|
+
throw new Error(`Unsupported SRI algorithm: ${algorithm}. Supported: ${SUPPORTED_ALGORITHMS.join(', ')}`);
|
|
25
|
+
}
|
|
26
|
+
if (!hash) {
|
|
27
|
+
throw new Error(`Empty hash in SRI: ${integrity}`);
|
|
28
|
+
}
|
|
29
|
+
return { algorithm, hash };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Map an SRI algorithm name to its Web Crypto API equivalent.
|
|
33
|
+
*
|
|
34
|
+
* @param alg - SRI algorithm name (`"sha256"`, `"sha384"`, `"sha512"`).
|
|
35
|
+
* @returns Web Crypto algorithm name (e.g. `"SHA-384"`).
|
|
36
|
+
*/
|
|
37
|
+
function algorithmToWebCrypto(alg) {
|
|
38
|
+
return `SHA-${alg.slice(3)}`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Encode an `ArrayBuffer` as a base64 string.
|
|
42
|
+
*
|
|
43
|
+
* @param buffer - Raw bytes to encode.
|
|
44
|
+
* @returns Base64 string representation of `buffer`.
|
|
45
|
+
*/
|
|
46
|
+
function bufferToBase64(buffer) {
|
|
47
|
+
const bytes = new Uint8Array(buffer);
|
|
48
|
+
let binary = '';
|
|
49
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
50
|
+
binary += String.fromCharCode(bytes[i]);
|
|
51
|
+
}
|
|
52
|
+
return btoa(binary);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Verify that `data` matches the expected SRI integrity string.
|
|
56
|
+
*
|
|
57
|
+
* Uses the Web Crypto API to compute the digest and compares it against the
|
|
58
|
+
* base64 hash in `integrity`. Throws with a descriptive message on any
|
|
59
|
+
* mismatch, invalid format, or unsupported algorithm.
|
|
60
|
+
*
|
|
61
|
+
* @param data - Raw bytes of the downloaded bundle.
|
|
62
|
+
* @param integrity - SRI hash string, e.g. `"sha384-abc123..."`.
|
|
63
|
+
* @returns Resolves on success; throws on mismatch or bad format.
|
|
64
|
+
* @throws If the format is invalid, the algorithm is unsupported, or the hash does not match.
|
|
65
|
+
*/
|
|
66
|
+
export async function verifyIntegrity(data, integrity) {
|
|
67
|
+
const { algorithm, hash: expectedHash } = parseSri(integrity);
|
|
68
|
+
const webCryptoAlg = algorithmToWebCrypto(algorithm);
|
|
69
|
+
const digest = await crypto.subtle.digest(webCryptoAlg, data);
|
|
70
|
+
const actualHash = bufferToBase64(digest);
|
|
71
|
+
if (actualHash !== expectedHash) {
|
|
72
|
+
throw new Error(`Integrity check failed for ${algorithm}. ` +
|
|
73
|
+
`Expected: ${expectedHash}, got: ${actualHash}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Compute an SRI hash for the given data.
|
|
78
|
+
*
|
|
79
|
+
* Useful for publishing tools that need to generate the `integrity` field
|
|
80
|
+
* for a `registry.json` entry. Defaults to `sha384`, which is the recommended
|
|
81
|
+
* algorithm for the SH3 registry protocol.
|
|
82
|
+
*
|
|
83
|
+
* @param data - Raw bytes of the bundle to hash.
|
|
84
|
+
* @param algorithm - Hash algorithm to use. Defaults to `"sha384"`.
|
|
85
|
+
* @returns SRI string in the form `"<algorithm>-<base64hash>"`.
|
|
86
|
+
*/
|
|
87
|
+
export async function computeIntegrity(data, algorithm = 'sha384') {
|
|
88
|
+
const webCryptoAlg = algorithmToWebCrypto(algorithm);
|
|
89
|
+
const digest = await crypto.subtle.digest(webCryptoAlg, data);
|
|
90
|
+
const hash = bufferToBase64(digest);
|
|
91
|
+
return `${algorithm}-${hash}`;
|
|
92
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime loader -- loads a pre-built ESM bundle from raw bytes via blob URL.
|
|
3
|
+
*
|
|
4
|
+
* The install API hands us an ArrayBuffer (the verified bundle), we wrap it
|
|
5
|
+
* in a blob URL, dynamic-import it, then immediately revoke the URL so the
|
|
6
|
+
* blob can be GC'd. The module's default export must carry a `manifest`
|
|
7
|
+
* field; we use duck-typing to distinguish shards from apps.
|
|
8
|
+
*
|
|
9
|
+
* Bare specifier rewriting: bundles are built with external dependencies
|
|
10
|
+
* (`sh3`, `svelte`, `svelte/internal/client`, etc.). Blob URLs can't
|
|
11
|
+
* resolve bare specifiers, so we expose each dependency's runtime exports
|
|
12
|
+
* at a stable blob URL (a "shim") and rewrite bare specifiers in the
|
|
13
|
+
* bundle source before loading.
|
|
14
|
+
*/
|
|
15
|
+
import type { Shard } from '../shards/types';
|
|
16
|
+
import type { App } from '../apps/types';
|
|
17
|
+
/**
|
|
18
|
+
* Result of loading a bundle — may contain a shard, an app, or both.
|
|
19
|
+
* Combo bundles (one shard + its companion app) are a supported pattern.
|
|
20
|
+
*/
|
|
21
|
+
export interface LoadedBundle {
|
|
22
|
+
shards: Shard[];
|
|
23
|
+
apps: App[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Load a pre-built ESM bundle from raw bytes.
|
|
27
|
+
*
|
|
28
|
+
* Scans all module exports (default + named) for objects with a `manifest`
|
|
29
|
+
* property and classifies each as a shard or app. Combo bundles that export
|
|
30
|
+
* both a shard and its companion app are supported.
|
|
31
|
+
*
|
|
32
|
+
* @param bytes - Raw bundle bytes (the verified ESM file contents).
|
|
33
|
+
* @returns All shards and apps found in the module's exports.
|
|
34
|
+
* @throws If the blob URL cannot be created or the import fails.
|
|
35
|
+
*/
|
|
36
|
+
export declare function loadBundleModule(bytes: ArrayBuffer): Promise<LoadedBundle>;
|
|
37
|
+
/**
|
|
38
|
+
* Type guard: returns true if the loaded module is a shard.
|
|
39
|
+
*
|
|
40
|
+
* A shard has an `activate` function and `manifest.views` array. An app
|
|
41
|
+
* has neither — it has `initialLayout` and `manifest.requiredShards`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function isShard(mod: Shard | App): mod is Shard;
|
|
44
|
+
/**
|
|
45
|
+
* Type guard: returns true if the loaded module is an app.
|
|
46
|
+
*
|
|
47
|
+
* An app has `initialLayout` and `manifest.requiredShards`. A shard has
|
|
48
|
+
* `activate` and `manifest.views` instead.
|
|
49
|
+
*/
|
|
50
|
+
export declare function isApp(mod: Shard | App): mod is App;
|