@yourrealm/sdk 0.0.0-alpha.1378.ce03c8dc

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 (230) hide show
  1. package/LICENSE-EXCEPTION +26 -0
  2. package/README.md +34 -0
  3. package/dist/chunk-3R6KEFOI.js +22 -0
  4. package/dist/chunk-67BDAMYX.js +376 -0
  5. package/dist/chunk-AITGS27R.js +139 -0
  6. package/dist/chunk-CMAEHQ4F.js +6 -0
  7. package/dist/chunk-MLKGABMK.js +9 -0
  8. package/dist/chunk-O3UIK34B.js +1430 -0
  9. package/dist/chunk-VHTUSZUX.js +176 -0
  10. package/dist/cli.js +9006 -0
  11. package/dist/docker.js +54 -0
  12. package/dist/index.js +1053 -0
  13. package/dist/ingress.js +436 -0
  14. package/dist/jsx-runtime.js +2 -0
  15. package/dist/preact.js +22 -0
  16. package/dist/service-url.js +11 -0
  17. package/dist/services.js +213 -0
  18. package/dist/test.js +5069 -0
  19. package/dist/ui.js +254 -0
  20. package/dist/worker-protocol.js +89 -0
  21. package/package.json +112 -0
  22. package/src/app-icon.ts +74 -0
  23. package/src/apply.ts +52 -0
  24. package/src/bundle-ui.ts +211 -0
  25. package/src/bundle.ts +546 -0
  26. package/src/cli/build-dev-landing.ts +84 -0
  27. package/src/cli/build-install-panel.ts +90 -0
  28. package/src/cli/build-runtime.ts +133 -0
  29. package/src/cli/build.ts +63 -0
  30. package/src/cli/dev.ts +107 -0
  31. package/src/cli/down.ts +60 -0
  32. package/src/cli/main.ts +56 -0
  33. package/src/cli/test.ts +61 -0
  34. package/src/cli/validate.ts +37 -0
  35. package/src/compile.ts +100 -0
  36. package/src/create-app.ts +982 -0
  37. package/src/deep-merge.ts +33 -0
  38. package/src/dev/fixture.ts +48 -0
  39. package/src/dev/install-handler.ts +383 -0
  40. package/src/dev/server.ts +968 -0
  41. package/src/dev/stack.ts +108 -0
  42. package/src/dev/state.ts +159 -0
  43. package/src/docker.ts +96 -0
  44. package/src/errors.ts +61 -0
  45. package/src/factory.ts +481 -0
  46. package/src/garage.ts +56 -0
  47. package/src/generator/hash.ts +6 -0
  48. package/src/generator/resolve-refs.ts +207 -0
  49. package/src/grants-builder.ts +100 -0
  50. package/src/grants.ts +260 -0
  51. package/src/home-api.ts +172 -0
  52. package/src/home-cookie.ts +10 -0
  53. package/src/home-registry.ts +120 -0
  54. package/src/home.ts +187 -0
  55. package/src/host-registry.ts +143 -0
  56. package/src/http-header.ts +20 -0
  57. package/src/identifiers.ts +49 -0
  58. package/src/index.ts +66 -0
  59. package/src/info.ts +64 -0
  60. package/src/ini.ts +50 -0
  61. package/src/json.ts +14 -0
  62. package/src/jsx-runtime/index.ts +5 -0
  63. package/src/keys.ts +29 -0
  64. package/src/mysql.ts +56 -0
  65. package/src/pack-build.ts +108 -0
  66. package/src/pack.ts +115 -0
  67. package/src/peer-registry.ts +121 -0
  68. package/src/peer-world.ts +117 -0
  69. package/src/permission-meta.ts +116 -0
  70. package/src/permissions.ts +108 -0
  71. package/src/pkg-root.ts +34 -0
  72. package/src/postgres.ts +79 -0
  73. package/src/preact/index.ts +27 -0
  74. package/src/redis.ts +43 -0
  75. package/src/ref.ts +271 -0
  76. package/src/remote-registry.ts +33 -0
  77. package/src/required-host-kinds.ts +110 -0
  78. package/src/resolve-context.ts +111 -0
  79. package/src/resolve.ts +471 -0
  80. package/src/runtime/docker/compose/compose-schema.ts +118 -0
  81. package/src/runtime/docker/compose/convert-to-docker-compose.ts +278 -0
  82. package/src/runtime/docker/compose/default-hardening.ts +21 -0
  83. package/src/runtime/docker/compose/effects.ts +107 -0
  84. package/src/runtime/docker/compose/index.ts +17 -0
  85. package/src/runtime/docker/compose/labels.ts +39 -0
  86. package/src/runtime/docker/compose/types.ts +78 -0
  87. package/src/runtime/docker/home-proxy-server/index.ts +177 -0
  88. package/src/runtime/docker/home-proxy-server/protocol.ts +53 -0
  89. package/src/runtime/docker/ingress/build-config.ts +819 -0
  90. package/src/runtime/docker/ingress/index.ts +16 -0
  91. package/src/runtime/docker/ingress/types.ts +182 -0
  92. package/src/runtime/docker/log-frames.ts +121 -0
  93. package/src/runtime/docker/logs-server/index.ts +366 -0
  94. package/src/runtime/docker/spawn-server/context-key.ts +82 -0
  95. package/src/runtime/docker/spawn-server/index.ts +492 -0
  96. package/src/runtime/docker/spawn-server/protocol.ts +70 -0
  97. package/src/runtime/docker/spawn-server/spec.ts +122 -0
  98. package/src/runtime/docker/stats-server/disks.ts +161 -0
  99. package/src/runtime/docker/stats-server/index.ts +322 -0
  100. package/src/runtime/home-client/index.ts +71 -0
  101. package/src/runtime/log-line.ts +24 -0
  102. package/src/runtime/logs-client/index.ts +175 -0
  103. package/src/runtime/spawn-client/index.ts +93 -0
  104. package/src/runtime/spawn-client/kaniko.ts +79 -0
  105. package/src/runtime/stats-client/index.ts +65 -0
  106. package/src/runtime/stats-sample.ts +95 -0
  107. package/src/safe-json.ts +37 -0
  108. package/src/sandbox.ts +1209 -0
  109. package/src/schema/build-apps.ts +38 -0
  110. package/src/schema/home.ts +217 -0
  111. package/src/schema/host.ts +792 -0
  112. package/src/schema/inputs.ts +26 -0
  113. package/src/schema/nodes.ts +75 -0
  114. package/src/schema/peer.ts +400 -0
  115. package/src/schema/public-domain.ts +116 -0
  116. package/src/schema/remote.ts +74 -0
  117. package/src/security.ts +594 -0
  118. package/src/serialize.ts +21 -0
  119. package/src/service-definition.ts +284 -0
  120. package/src/service-url.ts +64 -0
  121. package/src/service.ts +39 -0
  122. package/src/services/index.ts +5 -0
  123. package/src/shared.ts +65 -0
  124. package/src/speculative-silo.ts +96 -0
  125. package/src/stable-secret.ts +14 -0
  126. package/src/surfaces.ts +264 -0
  127. package/src/tailwind-bin.ts +56 -0
  128. package/src/test/boot.ts +192 -0
  129. package/src/test/compile.ts +501 -0
  130. package/src/test/describe.ts +117 -0
  131. package/src/test/index.ts +11 -0
  132. package/src/test/ingress-fixture.ts +37 -0
  133. package/src/test/types.ts +318 -0
  134. package/src/trusted-headers.ts +79 -0
  135. package/src/types.ts +126 -0
  136. package/src/ui/components/aside.tsx +96 -0
  137. package/src/ui/components/badge.tsx +42 -0
  138. package/src/ui/components/button.tsx +59 -0
  139. package/src/ui/components/card.tsx +106 -0
  140. package/src/ui/components/checkbox.tsx +37 -0
  141. package/src/ui/components/code-block.tsx +29 -0
  142. package/src/ui/components/combobox.tsx +119 -0
  143. package/src/ui/components/config-field.tsx +284 -0
  144. package/src/ui/components/dialog.tsx +137 -0
  145. package/src/ui/components/domain-editor.tsx +317 -0
  146. package/src/ui/components/domain-list.ts +205 -0
  147. package/src/ui/components/dropdown-menu.tsx +73 -0
  148. package/src/ui/components/empty-state.tsx +51 -0
  149. package/src/ui/components/highlight.ts +59 -0
  150. package/src/ui/components/icons.tsx +101 -0
  151. package/src/ui/components/image-posture.tsx +85 -0
  152. package/src/ui/components/input.tsx +28 -0
  153. package/src/ui/components/label.tsx +16 -0
  154. package/src/ui/components/line-chart.tsx +113 -0
  155. package/src/ui/components/page.tsx +39 -0
  156. package/src/ui/components/permission-row.tsx +262 -0
  157. package/src/ui/components/permission-section.tsx +51 -0
  158. package/src/ui/components/pie-chart.tsx +245 -0
  159. package/src/ui/components/schema-block.tsx +60 -0
  160. package/src/ui/components/separator.tsx +42 -0
  161. package/src/ui/components/sheet.tsx +130 -0
  162. package/src/ui/components/sidebar.tsx +650 -0
  163. package/src/ui/components/silo-picker.tsx +510 -0
  164. package/src/ui/components/skeleton.tsx +13 -0
  165. package/src/ui/components/sonner.tsx +33 -0
  166. package/src/ui/components/switch.tsx +58 -0
  167. package/src/ui/components/table-view.ts +219 -0
  168. package/src/ui/components/table.tsx +506 -0
  169. package/src/ui/components/tabs.tsx +59 -0
  170. package/src/ui/components/timeago.tsx +84 -0
  171. package/src/ui/components/tooltip.tsx +56 -0
  172. package/src/ui/components.json +21 -0
  173. package/src/ui/dev/dev-shell.tsx +145 -0
  174. package/src/ui/home/home-grid.tsx +177 -0
  175. package/src/ui/hooks/use-mobile.ts +19 -0
  176. package/src/ui/index.ts +189 -0
  177. package/src/ui/install/adapter.ts +193 -0
  178. package/src/ui/install/deploy-targets-picker.tsx +185 -0
  179. package/src/ui/install/install-app-panel.tsx +912 -0
  180. package/src/ui/install/install-error.tsx +62 -0
  181. package/src/ui/install/perms-inactive.ts +50 -0
  182. package/src/ui/install/required-fields.ts +37 -0
  183. package/src/ui/install/tabs-form.tsx +557 -0
  184. package/src/ui/tokens.css +279 -0
  185. package/src/ui/utils.ts +78 -0
  186. package/src/ui-error.ts +139 -0
  187. package/src/ui-runtime/dev-landing-entry.tsx +258 -0
  188. package/src/ui-runtime/index.ts +83 -0
  189. package/src/ui-runtime/install-entry.tsx +119 -0
  190. package/src/ui-runtime/mount.ts +367 -0
  191. package/src/ui-runtime/runtime.css +56 -0
  192. package/src/ui-surface.ts +451 -0
  193. package/src/valkey.ts +43 -0
  194. package/src/worker/extract-tar.ts +125 -0
  195. package/src/worker/identity.ts +183 -0
  196. package/src/worker/index.ts +16 -0
  197. package/src/worker/public-ip.ts +92 -0
  198. package/src/worker/queue.ts +54 -0
  199. package/src/worker/reconcile.ts +127 -0
  200. package/src/worker/start-worker.ts +227 -0
  201. package/src/worker/ui-proxy.ts +335 -0
  202. package/src/worker/uplink.ts +523 -0
  203. package/src/worker/validate.ts +34 -0
  204. package/src/worker/verify-relay.ts +102 -0
  205. package/src/worker-protocol/app-dirs.ts +94 -0
  206. package/src/worker-protocol/home-proxy-paths.ts +76 -0
  207. package/src/worker-protocol/index.ts +55 -0
  208. package/src/worker-protocol/ingress.ts +166 -0
  209. package/src/worker-protocol/logs-socket-path.ts +50 -0
  210. package/src/worker-protocol/networks.ts +39 -0
  211. package/src/worker-protocol/node-version.ts +70 -0
  212. package/src/worker-protocol/silo-paths.ts +114 -0
  213. package/src/worker-protocol/spawn-paths.ts +165 -0
  214. package/src/worker-protocol/stats-socket-path.ts +40 -0
  215. package/src/worker-protocol/update-files.ts +77 -0
  216. package/src/worker-protocol/uplink/constants.ts +86 -0
  217. package/src/worker-protocol/uplink/errors.ts +61 -0
  218. package/src/worker-protocol/uplink/frame.ts +114 -0
  219. package/src/worker-protocol/uplink/identity.ts +40 -0
  220. package/src/worker-protocol/uplink/index.ts +63 -0
  221. package/src/worker-protocol/uplink/methods.ts +462 -0
  222. package/src/worker-protocol/uplink/mux.ts +646 -0
  223. package/src/worker-protocol/uplink/protocol.ts +11 -0
  224. package/src/worker-protocol/uplink/rpc.ts +309 -0
  225. package/src/worker-protocol/uplink/session.ts +540 -0
  226. package/src/worker-protocol/uplink/test-support.ts +188 -0
  227. package/src/worker-protocol/worker-mount.ts +27 -0
  228. package/src/worker-protocol/worker-state.ts +190 -0
  229. package/src/xml.ts +49 -0
  230. package/src/yaml.ts +15 -0
@@ -0,0 +1,258 @@
1
+ // Browser entry for `realm-sdk dev`'s landing page. Mounts the shared
2
+ // `DevShell` (Home-style sidebar) around one of three views, picked from
3
+ // the URL pathname the server rendered:
4
+ //
5
+ // - / → Home: router tiles + widget iframes in a mixed
6
+ // grid (mirrors platform/home/src/routes/(authed)/index.tsx).
7
+ // - /pages/<key> → page iframe for that surface.
8
+ //
9
+ // The island also owns two bridges:
10
+ //
11
+ // - postMessage bridge: forward `ui-call` from sandboxed iframes to
12
+ // `POST /loader/<kind>/<key>/<name>`. The iframes run in an opaque
13
+ // origin (no allow-same-origin) so they can't fetch directly.
14
+ //
15
+ // - SSE: subscribe to `/events` and bump an iframe `key` so any visible
16
+ // iframe remounts on realm.ts edits.
17
+ //
18
+ // Routing is server-driven: clicking a sidebar entry triggers a full
19
+ // navigation; the dev server returns this same HTML for / and
20
+ // /pages/<key>, just with a different `pathname` baked in.
21
+
22
+ import { render } from "preact";
23
+ import { useEffect, useMemo, useState } from "preact/hooks";
24
+ import type { WidgetManifest } from "../resolve.ts";
25
+ import { CubeIcon } from "../ui/components/icons.tsx";
26
+ import { DevShell, type DevShellState } from "../ui/dev/dev-shell.tsx";
27
+ import { HomeGrid, type RouterTile, type WidgetTile } from "../ui/home/home-grid.tsx";
28
+
29
+ // Wire shapes the dev server serializes into the page; the HomeGrid
30
+ // component takes its own (different) tile shape, so the landing maps
31
+ // these → tiles before rendering.
32
+ interface WireRouter {
33
+ serviceId: string;
34
+ name: string;
35
+ iconSrc?: string;
36
+ publicDomain?: string;
37
+ }
38
+
39
+ type WireWidget = Pick<WidgetManifest, "title" | "icon" | "cols" | "rows"> & { key: string };
40
+
41
+ interface LandingState extends DevShellState {
42
+ pathname: string;
43
+ routers: WireRouter[];
44
+ widgets: WireWidget[];
45
+ sidebarOpen: boolean;
46
+ }
47
+
48
+ function readState(): LandingState {
49
+ const el = document.getElementById("dev-state");
50
+ if (!el || el.tagName !== "SCRIPT") throw new Error("dev-state script tag missing");
51
+ return JSON.parse(el.textContent ?? "{}") as LandingState;
52
+ }
53
+
54
+ const KEY_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
55
+
56
+ function useUiCallBridge() {
57
+ useEffect(() => {
58
+ function findIframeWindow(source: EventTarget | null): HTMLIFrameElement | null {
59
+ if (!source) return null;
60
+ for (const f of document.querySelectorAll("iframe")) {
61
+ if (f.contentWindow === source) return f;
62
+ }
63
+ return null;
64
+ }
65
+ // `/iframe/<kind>/<key>` is where this landing page mounted the surface,
66
+ // so the src is the authentic record of which one it is.
67
+ function surfaceFromIframe(
68
+ iframe: HTMLIFrameElement,
69
+ ): { kind: "page" | "widget"; key: string } | null {
70
+ let path: string;
71
+ try {
72
+ path = new URL(iframe.src, globalThis.location.href).pathname;
73
+ } catch {
74
+ return null;
75
+ }
76
+ const m = path.match(/^\/iframe\/(page|widget)\/([^/]+)$/);
77
+ if (!m) return null;
78
+ let key: string;
79
+ try {
80
+ key = decodeURIComponent(m[2]!);
81
+ } catch {
82
+ return null;
83
+ }
84
+ if (!KEY_RE.test(key)) return null;
85
+ return { kind: m[1] as "page" | "widget", key };
86
+ }
87
+ function reply(
88
+ target: Window,
89
+ id: number,
90
+ payload: { data: unknown } | { error: string; code?: string },
91
+ ): void {
92
+ target.postMessage({ realm: "ui-response", id, ...payload }, "*");
93
+ }
94
+ async function onMessage(ev: MessageEvent): Promise<void> {
95
+ const v = ev.data as {
96
+ realm?: string;
97
+ id?: number;
98
+ endpoint?: string;
99
+ name?: string;
100
+ request?: unknown;
101
+ } | null;
102
+ if (!v || v.realm !== "ui-call" || typeof v.id !== "number") return;
103
+ if (typeof v.name !== "string" || !KEY_RE.test(v.name)) return;
104
+ if (v.endpoint !== "loader" && v.endpoint !== "action") return;
105
+ const source = ev.source as Window | null;
106
+ if (!source) return;
107
+ const iframe = findIframeWindow(source);
108
+ if (!iframe) return;
109
+ // The surface's identity comes off the element we mounted, never off
110
+ // the message — same rule Home's UiBridge enforces, so the dev loop
111
+ // can't accept a call shape the real parent would reject.
112
+ const surface = surfaceFromIframe(iframe);
113
+ if (!surface) return;
114
+ try {
115
+ const res = await fetch(
116
+ `/${v.endpoint}/${surface.kind}/${encodeURIComponent(surface.key)}/${encodeURIComponent(
117
+ v.name,
118
+ )}`,
119
+ {
120
+ method: "POST",
121
+ headers: { "content-type": "application/json" },
122
+ body: JSON.stringify({ request: v.request }),
123
+ },
124
+ );
125
+ const body = (await res.json().catch(() => null)) as unknown;
126
+ if (!res.ok) {
127
+ const errMsg =
128
+ body && typeof body === "object" && "error" in body
129
+ ? String((body as { error: unknown }).error)
130
+ : `HTTP ${res.status}`;
131
+ const code =
132
+ body && typeof body === "object" && "code" in body
133
+ ? String((body as { code: unknown }).code)
134
+ : undefined;
135
+ reply(source, v.id, { error: errMsg, code });
136
+ return;
137
+ }
138
+ reply(source, v.id, { data: body });
139
+ } catch (err) {
140
+ reply(source, v.id, { error: (err as Error)?.message ?? String(err) });
141
+ }
142
+ }
143
+ globalThis.addEventListener("message", onMessage);
144
+ return () => globalThis.removeEventListener("message", onMessage);
145
+ }, []);
146
+ }
147
+
148
+ function useSseReload(onReload: () => void) {
149
+ useEffect(() => {
150
+ const es = new EventSource("/events");
151
+ const handler = () => onReload();
152
+ es.addEventListener("reload", handler);
153
+ return () => {
154
+ es.removeEventListener("reload", handler);
155
+ es.close();
156
+ };
157
+ }, [onReload]);
158
+ }
159
+
160
+ function HomeView({
161
+ routers,
162
+ widgets,
163
+ bump,
164
+ }: {
165
+ routers: WireRouter[];
166
+ widgets: WireWidget[];
167
+ bump: number;
168
+ }) {
169
+ const routerTiles: RouterTile[] = routers.map((r) => ({
170
+ key: r.serviceId,
171
+ label: r.name,
172
+ ...(r.publicDomain ? { href: `https://${r.publicDomain}`, external: true } : {}),
173
+ ...(r.iconSrc ? { iconSrc: r.iconSrc } : {}),
174
+ }));
175
+ const widgetTiles: WidgetTile[] = widgets.map((w) => ({
176
+ key: w.key,
177
+ title: w.title,
178
+ icon: w.icon,
179
+ cols: w.cols,
180
+ rows: w.rows,
181
+ iframeSrc: `/iframe/widget/${encodeURIComponent(w.key)}`,
182
+ }));
183
+ return (
184
+ <HomeGrid
185
+ routers={routerTiles}
186
+ widgets={widgetTiles}
187
+ reloadNonce={bump}
188
+ emptyState={
189
+ <div class="rounded-2xl border border-dashed border-border bg-card/50 p-10 flex flex-col items-center gap-3 text-center">
190
+ <CubeIcon className="size-8 text-muted-foreground" />
191
+ <p class="text-sm text-muted-foreground max-w-sm">
192
+ This app declares no routed services or widgets. Add a{" "}
193
+ <code class="font-mono">router</code> to a service or a{" "}
194
+ <code class="font-mono">ui.widget</code> to see tiles here.
195
+ </p>
196
+ </div>
197
+ }
198
+ />
199
+ );
200
+ }
201
+
202
+ function PageView({ pageKey, bump }: { pageKey: string; bump: number }) {
203
+ return (
204
+ <iframe
205
+ key={`page:${pageKey}:${bump}`}
206
+ sandbox="allow-scripts allow-forms"
207
+ src={`/iframe/page/${encodeURIComponent(pageKey)}`}
208
+ class="flex-1 w-full border-0 bg-card"
209
+ />
210
+ );
211
+ }
212
+
213
+ function pageKeyFromPath(pathname: string): string | null {
214
+ const m = pathname.match(/^\/pages\/([^/]+)$/);
215
+ if (!m) return null;
216
+ try {
217
+ return decodeURIComponent(m[1]!);
218
+ } catch {
219
+ return null;
220
+ }
221
+ }
222
+
223
+ function DevLanding({ state }: { state: LandingState }) {
224
+ const [bump, setBump] = useState(0);
225
+ useUiCallBridge();
226
+ useSseReload(useMemo(() => () => setBump((n) => n + 1), []));
227
+
228
+ const pageKey = pageKeyFromPath(state.pathname);
229
+ const activePage = pageKey ? state.pages.find((p) => p.key === pageKey) : null;
230
+
231
+ const titleProps =
232
+ activePage !== null && activePage !== undefined
233
+ ? { title: activePage.title, titleIcon: activePage.icon }
234
+ : { title: "Home", titleIcon: "HouseIcon" };
235
+
236
+ return (
237
+ <DevShell
238
+ state={state}
239
+ pathname={state.pathname}
240
+ sidebarOpen={state.sidebarOpen}
241
+ {...titleProps}
242
+ >
243
+ {activePage ? (
244
+ <PageView pageKey={activePage.key} bump={bump} />
245
+ ) : pageKey ? (
246
+ <div class="p-6 text-xs text-muted-foreground">
247
+ Page <code class="font-mono">{pageKey}</code> not declared.
248
+ </div>
249
+ ) : (
250
+ <HomeView routers={state.routers} widgets={state.widgets} bump={bump} />
251
+ )}
252
+ </DevShell>
253
+ );
254
+ }
255
+
256
+ const root = document.getElementById("root");
257
+ if (!root) throw new Error("#root missing");
258
+ render(<DevLanding state={readState()} />, root);
@@ -0,0 +1,83 @@
1
+ // Iframe runtime barrel. esbuild bundles this into one file
2
+ // (runtime.js) and the iframe importmap maps every external the
3
+ // surface bundle declares — `@yourrealm/sdk`, `@yourrealm/sdk/ui`,
4
+ // `@yourrealm/sdk/preact`, `@yourrealm/sdk/jsx-runtime`,
5
+ // `@yourrealm/sdk/services`, `zod` — to that one URL. The browser
6
+ // dedupes by URL, so every import shares one runtime instance.
7
+
8
+ export { Fragment, h, render } from "preact";
9
+ export type { ComponentChildren, JSX, VNode } from "preact";
10
+
11
+ export { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "preact/hooks";
12
+
13
+ // JSX automatic runtime. Surface bundles compiled with
14
+ // --jsx=automatic --jsx-import-source=@yourrealm/sdk emit
15
+ // `import { jsx, jsxs, Fragment } from "@yourrealm/sdk/jsx-runtime"`
16
+ // for every JSX element, and the importmap routes that specifier here.
17
+ // `Fragment` is the same identity as preact's (exported above) so it's
18
+ // reachable through both specifiers without a duplicate binding.
19
+ export { jsx, jsxs, jsxDEV } from "preact/jsx-runtime";
20
+
21
+ // Identity helpers from @yourrealm/sdk/ui — they exist for TS inference
22
+ // at authoring time, but the bundled surface calls them at module load,
23
+ // so the importmap-resolved runtime needs them as real functions too.
24
+ // Re-exported from ui-surface so typed and runtime versions can't drift.
25
+ export { action, loader, page, widget } from "../ui-surface.ts";
26
+
27
+ // Components — must mirror the type-only contract in
28
+ // `packages/sdk/src/ui/index.ts` (the surface bundle resolves names
29
+ // from that type list against this module's exports).
30
+ export * from "../ui/components/badge.tsx";
31
+ export * from "../ui/components/button.tsx";
32
+ export * from "../ui/components/card.tsx";
33
+ export * from "../ui/components/checkbox.tsx";
34
+ export * from "../ui/components/combobox.tsx";
35
+ export * from "../ui/components/dialog.tsx";
36
+ export * from "../ui/components/empty-state.tsx";
37
+ export * from "../ui/components/icons.tsx";
38
+ export * from "../ui/components/input.tsx";
39
+ export * from "../ui/components/label.tsx";
40
+ export * from "../ui/components/line-chart.tsx";
41
+ export * from "../ui/components/page.tsx";
42
+ export * from "../ui/components/pie-chart.tsx";
43
+ export * from "../ui/components/separator.tsx";
44
+ export * from "../ui/components/sheet.tsx";
45
+ export * from "../ui/components/skeleton.tsx";
46
+ export * from "../ui/components/switch.tsx";
47
+ export * from "../ui/components/table.tsx";
48
+ export * from "../ui/components/timeago.tsx";
49
+ export * from "../ui/components/tooltip.tsx";
50
+
51
+ // SDK + zod re-exports. Surface bundles externalize `@yourrealm/sdk`
52
+ // (`createApp`, `r`, `z`, `apply`, `interpolate`, `ref`) and `zod`
53
+ // (`z`, schemas) so a per-surface bundle is single-digit KB instead
54
+ // of ~414 KB. Both modules are browser-safe: plain authoring JS, with
55
+ // nothing that touches Deno or node.
56
+ export * from "../index.ts";
57
+ export * from "zod";
58
+
59
+ // `@yourrealm/sdk/docker` (registry-gate helpers) is referenced by
60
+ // realm.ts files at module load — `verifyAnonymousRequest` closes over
61
+ // `checkDockerBasic` / `dockerChallenge` even though only Home's gate
62
+ // sandbox calls them. The iframe never runs the gate, but the surface
63
+ // bundle imports the names at module load, so the importmap-resolved
64
+ // runtime must provide them. They're pure JS (no Deno/atob/Web Crypto —
65
+ // see docker.ts), so re-export the real fns.
66
+ export { checkDockerBasic, dockerChallenge } from "../docker.ts";
67
+
68
+ // `@yourrealm/sdk/services` (Postgres, Valkey, etc.) is referenced by
69
+ // realm.ts files at module load — `getDesiredState` closes over the
70
+ // classes even though only Home eval calls it. The iframe never
71
+ // instantiates these, so the runtime needs *names* but not behavior:
72
+ // a single empty class re-exported as every Service satisfies ESM
73
+ // resolution, and constructing one yields a no-op object.
74
+ class _IframeServiceStub {}
75
+ export {
76
+ _IframeServiceStub as Postgres,
77
+ _IframeServiceStub as MySQL,
78
+ _IframeServiceStub as Valkey,
79
+ _IframeServiceStub as Redis,
80
+ _IframeServiceStub as Garage,
81
+ };
82
+
83
+ export { mount, mountNeedsConfig } from "./mount.ts";
@@ -0,0 +1,119 @@
1
+ // Browser entry for `realm-sdk dev`'s `/install` route. Mounts the shared
2
+ // `DevShell` (Home-style sidebar) around the install panel so the page
3
+ // matches the rest of the dev product chrome bit-for-bit. esbuild
4
+ // bundles this into `install-panel.js`; the dev server's HTML shell
5
+ // loads it and reads two JSON script tags (the dev shell state +
6
+ // the install config).
7
+ //
8
+ // The panel doesn't talk to the dev server directly — it goes through
9
+ // an InstallAdapter that POSTs to `/install/api/preview` and
10
+ // `/install/api/install`. On failure those endpoints respond with the
11
+ // same wire shape Home uses (`{error, violations?}`), so the same
12
+ // `parseInstallError` helper rehydrates an InstallErrorShape for the
13
+ // panel to render.
14
+
15
+ import { render } from "preact";
16
+ import InstallAppPanel from "../ui/install/install-app-panel.tsx";
17
+ import { parseInstallError } from "../ui/install/install-error.tsx";
18
+ import {
19
+ InstallAdapterError,
20
+ type InstallAdapter,
21
+ type InstallPayload,
22
+ type PreviewResult,
23
+ } from "../ui/install/adapter.ts";
24
+ import { WORKER_CAPABILITIES } from "../permissions.ts";
25
+ import { DevShell, type DevShellState } from "../ui/dev/dev-shell.tsx";
26
+
27
+ // realm-sdk dev runs every service via docker compose on the operator's
28
+ // host, so the local environment effectively satisfies every host
29
+ // capability. Advertise the full set under a synthetic worker so the
30
+ // install panel doesn't flag every `host.*` grant as unsupported.
31
+ const DEV_WORKER = {
32
+ id: "dev",
33
+ slug: "dev",
34
+ name: "realm-sdk dev (local)",
35
+ capabilities: [...WORKER_CAPABILITIES],
36
+ };
37
+
38
+ interface InstallConfig {
39
+ initialSource: string;
40
+ hideDeployTargets: boolean;
41
+ }
42
+
43
+ interface InstallShellState extends DevShellState {
44
+ pathname: string;
45
+ sidebarOpen: boolean;
46
+ }
47
+
48
+ function readConfig(): InstallConfig {
49
+ const el = document.getElementById("install-config");
50
+ if (!el || el.tagName !== "SCRIPT") throw new Error("install-config script tag missing");
51
+ const parsed = JSON.parse(el.textContent ?? "{}") as Partial<InstallConfig>;
52
+ return {
53
+ initialSource: parsed.initialSource ?? "",
54
+ hideDeployTargets: parsed.hideDeployTargets ?? true,
55
+ };
56
+ }
57
+
58
+ function readShellState(): InstallShellState {
59
+ const el = document.getElementById("dev-state");
60
+ if (!el || el.tagName !== "SCRIPT") throw new Error("dev-state script tag missing");
61
+ return JSON.parse(el.textContent ?? "{}") as InstallShellState;
62
+ }
63
+
64
+ async function postJson(path: string, body: unknown): Promise<unknown> {
65
+ const res = await fetch(path, {
66
+ method: "POST",
67
+ headers: { "content-type": "application/json" },
68
+ body: JSON.stringify(body),
69
+ });
70
+ const data = await res.json().catch(() => null);
71
+ if (!res.ok) {
72
+ throw new InstallAdapterError(parseInstallError(res.status, data));
73
+ }
74
+ return data;
75
+ }
76
+
77
+ const devInstallAdapter: InstallAdapter = {
78
+ async preview(source: string): Promise<PreviewResult> {
79
+ return (await postJson("/install/api/preview", { source })) as PreviewResult;
80
+ },
81
+ async install(payload: InstallPayload): Promise<{ id: string }> {
82
+ const data = await postJson("/install/api/install", payload);
83
+ return { id: (data as { id?: string } | null)?.id ?? "" };
84
+ },
85
+ // No listSilos / createSilo: a Silo is a Home record on a named worker and
86
+ // dev has neither. Leaving them off is how the panel knows to skip the picker
87
+ // and its install gate, so a silo app reaches the dev compiler's own "no Silo
88
+ // in the fixture" error instead of a permanently disabled button.
89
+ // Dev mounts the app's own UI in place, so the id the panel passes on install
90
+ // is ignored and the redirect lands on the dev home.
91
+ onInstalled(): void {
92
+ globalThis.location.href = "/";
93
+ },
94
+ };
95
+
96
+ function InstallPage() {
97
+ const config = readConfig();
98
+ const shell = readShellState();
99
+ return (
100
+ <DevShell
101
+ state={shell}
102
+ pathname={shell.pathname}
103
+ sidebarOpen={shell.sidebarOpen}
104
+ title="Install"
105
+ titleIcon="DownloadSimpleIcon"
106
+ >
107
+ <InstallAppPanel
108
+ workers={[DEV_WORKER]}
109
+ plumbing={devInstallAdapter}
110
+ initialSource={config.initialSource}
111
+ hideDeployTargets={config.hideDeployTargets}
112
+ />
113
+ </DevShell>
114
+ );
115
+ }
116
+
117
+ const root = document.getElementById("root");
118
+ if (!root) throw new Error("#root missing");
119
+ render(<InstallPage />, root);