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.
Files changed (134) hide show
  1. package/dist/Shell.svelte +185 -0
  2. package/dist/Shell.svelte.d.ts +4 -0
  3. package/dist/api.d.ts +22 -0
  4. package/dist/api.js +45 -0
  5. package/dist/apps/lifecycle.d.ts +37 -0
  6. package/dist/apps/lifecycle.js +153 -0
  7. package/dist/apps/registry.svelte.d.ts +37 -0
  8. package/dist/apps/registry.svelte.js +60 -0
  9. package/dist/apps/types.d.ts +61 -0
  10. package/dist/apps/types.js +10 -0
  11. package/dist/assets/icons.svg +1119 -0
  12. package/dist/auth/auth.svelte.d.ts +44 -0
  13. package/dist/auth/auth.svelte.js +119 -0
  14. package/dist/auth/index.d.ts +1 -0
  15. package/dist/auth/index.js +1 -0
  16. package/dist/build.d.ts +29 -0
  17. package/dist/build.js +85 -0
  18. package/dist/contract.d.ts +20 -0
  19. package/dist/contract.js +28 -0
  20. package/dist/documents/backends.d.ts +17 -0
  21. package/dist/documents/backends.js +156 -0
  22. package/dist/documents/config.d.ts +7 -0
  23. package/dist/documents/config.js +27 -0
  24. package/dist/documents/handle.d.ts +6 -0
  25. package/dist/documents/handle.js +154 -0
  26. package/dist/documents/http-backend.d.ts +22 -0
  27. package/dist/documents/http-backend.js +78 -0
  28. package/dist/documents/index.d.ts +6 -0
  29. package/dist/documents/index.js +8 -0
  30. package/dist/documents/notifications.d.ts +9 -0
  31. package/dist/documents/notifications.js +39 -0
  32. package/dist/documents/types.d.ts +97 -0
  33. package/dist/documents/types.js +12 -0
  34. package/dist/host-entry.d.ts +9 -0
  35. package/dist/host-entry.js +15 -0
  36. package/dist/host.d.ts +13 -0
  37. package/dist/host.js +73 -0
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.js +13 -0
  40. package/dist/layout/DragPreview.svelte +63 -0
  41. package/dist/layout/DragPreview.svelte.d.ts +3 -0
  42. package/dist/layout/LayoutRenderer.svelte +260 -0
  43. package/dist/layout/LayoutRenderer.svelte.d.ts +6 -0
  44. package/dist/layout/SlotContainer.svelte +140 -0
  45. package/dist/layout/SlotContainer.svelte.d.ts +8 -0
  46. package/dist/layout/SlotDropZone.svelte +122 -0
  47. package/dist/layout/SlotDropZone.svelte.d.ts +8 -0
  48. package/dist/layout/drag.svelte.d.ts +45 -0
  49. package/dist/layout/drag.svelte.js +191 -0
  50. package/dist/layout/inspection.d.ts +52 -0
  51. package/dist/layout/inspection.js +157 -0
  52. package/dist/layout/ops.d.ts +78 -0
  53. package/dist/layout/ops.js +281 -0
  54. package/dist/layout/slotHostPool.svelte.d.ts +36 -0
  55. package/dist/layout/slotHostPool.svelte.js +229 -0
  56. package/dist/layout/store.svelte.d.ts +39 -0
  57. package/dist/layout/store.svelte.js +150 -0
  58. package/dist/layout/tree-walk.d.ts +15 -0
  59. package/dist/layout/tree-walk.js +33 -0
  60. package/dist/layout/types.d.ts +108 -0
  61. package/dist/layout/types.js +25 -0
  62. package/dist/overlays/ModalFrame.svelte +87 -0
  63. package/dist/overlays/ModalFrame.svelte.d.ts +10 -0
  64. package/dist/overlays/PopupFrame.svelte +85 -0
  65. package/dist/overlays/PopupFrame.svelte.d.ts +10 -0
  66. package/dist/overlays/ToastItem.svelte +77 -0
  67. package/dist/overlays/ToastItem.svelte.d.ts +9 -0
  68. package/dist/overlays/focusTrap.d.ts +1 -0
  69. package/dist/overlays/focusTrap.js +64 -0
  70. package/dist/overlays/modal.d.ts +9 -0
  71. package/dist/overlays/modal.js +141 -0
  72. package/dist/overlays/popup.d.ts +9 -0
  73. package/dist/overlays/popup.js +108 -0
  74. package/dist/overlays/roots.d.ts +4 -0
  75. package/dist/overlays/roots.js +31 -0
  76. package/dist/overlays/toast.d.ts +6 -0
  77. package/dist/overlays/toast.js +93 -0
  78. package/dist/overlays/types.d.ts +31 -0
  79. package/dist/overlays/types.js +15 -0
  80. package/dist/primitives/.gitkeep +0 -0
  81. package/dist/primitives/ResizableSplitter.svelte +333 -0
  82. package/dist/primitives/ResizableSplitter.svelte.d.ts +35 -0
  83. package/dist/primitives/TabbedPanel.svelte +305 -0
  84. package/dist/primitives/TabbedPanel.svelte.d.ts +50 -0
  85. package/dist/registry/client.d.ts +74 -0
  86. package/dist/registry/client.js +118 -0
  87. package/dist/registry/index.d.ts +13 -0
  88. package/dist/registry/index.js +14 -0
  89. package/dist/registry/installer.d.ts +53 -0
  90. package/dist/registry/installer.js +170 -0
  91. package/dist/registry/integrity.d.ts +32 -0
  92. package/dist/registry/integrity.js +92 -0
  93. package/dist/registry/loader.d.ts +50 -0
  94. package/dist/registry/loader.js +145 -0
  95. package/dist/registry/schema.d.ts +47 -0
  96. package/dist/registry/schema.js +180 -0
  97. package/dist/registry/storage.d.ts +37 -0
  98. package/dist/registry/storage.js +101 -0
  99. package/dist/registry/types.d.ts +245 -0
  100. package/dist/registry/types.js +14 -0
  101. package/dist/registry-shard/RegistryView.svelte +561 -0
  102. package/dist/registry-shard/RegistryView.svelte.d.ts +3 -0
  103. package/dist/registry-shard/registryApp.d.ts +10 -0
  104. package/dist/registry-shard/registryApp.js +24 -0
  105. package/dist/registry-shard/registryShard.svelte.d.ts +45 -0
  106. package/dist/registry-shard/registryShard.svelte.js +125 -0
  107. package/dist/shards/activate.svelte.d.ts +45 -0
  108. package/dist/shards/activate.svelte.js +124 -0
  109. package/dist/shards/registry.d.ts +4 -0
  110. package/dist/shards/registry.js +28 -0
  111. package/dist/shards/types.d.ts +155 -0
  112. package/dist/shards/types.js +20 -0
  113. package/dist/shell-shard/ShellHome.svelte +285 -0
  114. package/dist/shell-shard/ShellHome.svelte.d.ts +3 -0
  115. package/dist/shell-shard/shellShard.svelte.d.ts +2 -0
  116. package/dist/shell-shard/shellShard.svelte.js +47 -0
  117. package/dist/shellRuntime.svelte.d.ts +27 -0
  118. package/dist/shellRuntime.svelte.js +27 -0
  119. package/dist/state/backends.d.ts +26 -0
  120. package/dist/state/backends.js +99 -0
  121. package/dist/state/types.d.ts +38 -0
  122. package/dist/state/types.js +15 -0
  123. package/dist/state/zones.svelte.d.ts +52 -0
  124. package/dist/state/zones.svelte.js +141 -0
  125. package/dist/store/InstalledView.svelte +201 -0
  126. package/dist/store/InstalledView.svelte.d.ts +3 -0
  127. package/dist/store/StoreView.svelte +470 -0
  128. package/dist/store/StoreView.svelte.d.ts +3 -0
  129. package/dist/store/storeApp.d.ts +11 -0
  130. package/dist/store/storeApp.js +26 -0
  131. package/dist/store/storeShard.svelte.d.ts +29 -0
  132. package/dist/store/storeShard.svelte.js +99 -0
  133. package/dist/tokens.css +79 -0
  134. 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;