@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,121 @@
1
+ // Per-kind Home-side mechanics for peer permissions.
2
+ //
3
+ // Each peer factory carries its kind + hooks as non-enumerable symbol
4
+ // properties (set by makePeerable / tagPeerable). The runtime registry is
5
+ // derived from a namespace import (`derivePeerRegistry(peers)`), not a
6
+ // mutable module-scoped table.
7
+
8
+ import type { z } from "zod";
9
+ import type { Network } from "./identifiers.ts";
10
+ import type { HomeApp, HomeService } from "./resolve.ts";
11
+ import type { Builder } from "./shared.ts";
12
+ import {
13
+ tagPermissionMeta,
14
+ type PermissionMeta,
15
+ type PermissionMetaTagged,
16
+ } from "./permission-meta.ts";
17
+
18
+ // Loose constraint used inside this module. The closed
19
+ // `PeerPermissionKind` is derived in permissions.ts — see
20
+ // host-registry.ts for the same pattern.
21
+ type PeerKindShape = `peers.${string}`;
22
+
23
+ export interface AllowedProvider {
24
+ appId: string;
25
+ serviceKey: string;
26
+ }
27
+
28
+ /**
29
+ * Per-kind Home-side mechanics. Each hook covers a different consumer of
30
+ * peer data:
31
+ *
32
+ * - extract / resolve: feed extractPeerEntries / resolvePeerValue.
33
+ * `resolve` receives the consumer's descriptor data; presence-only
34
+ * peers ignore the second arg.
35
+ * - extractNetworks / extractPeerVolumes / extractAllowedProvider:
36
+ * feed Home graph.ts when computing per-app allowed networks /
37
+ * volumes / providers
38
+ * - producerPredicate: feeds Home merge.ts when wiring depends_on edges
39
+ *
40
+ * Every peer permission requires an admin grant — there is no "ungated"
41
+ * shortcut. Consumers that haven't been granted resolve to an empty list.
42
+ */
43
+ export interface PeerHooks<TInput = unknown, TEntry = unknown, TValue = unknown> {
44
+ extract(appId: string, state: HomeApp): TEntry[];
45
+ resolve(entries: TEntry[], data: TInput): TValue;
46
+ extractNetworks?(entry: TEntry): Network | undefined;
47
+ extractPeerVolumes?(entry: TEntry): string | undefined;
48
+ extractAllowedProvider?(entry: TEntry): AllowedProvider | undefined;
49
+ producerPredicate?(svc: HomeService): boolean;
50
+ }
51
+
52
+ const PEER_KIND = Symbol("realm.peer.kind");
53
+ const PEER_HOOKS = Symbol("realm.peer.hooks");
54
+
55
+ export interface Peerable<K extends PeerKindShape = PeerKindShape, V = unknown> {
56
+ readonly [PEER_KIND]: K;
57
+ readonly [PEER_HOOKS]: PeerHooks<unknown, unknown, V>;
58
+ }
59
+
60
+ function isPeerable(value: unknown): value is Peerable {
61
+ return (
62
+ (typeof value === "function" || (typeof value === "object" && value !== null)) &&
63
+ PEER_KIND in (value as object) &&
64
+ PEER_HOOKS in (value as object)
65
+ );
66
+ }
67
+
68
+ /** Attach kind + hooks to a factory. The tagging half of makePeerable. */
69
+ function tagPeerable<T extends object, K extends PeerKindShape, V>(
70
+ target: T,
71
+ kind: K,
72
+ hooks: PeerHooks<unknown, unknown, V>,
73
+ meta: PermissionMeta,
74
+ ): T & Peerable<K, V> & PermissionMetaTagged<K> {
75
+ Object.defineProperty(target, PEER_KIND, { value: kind, enumerable: false });
76
+ Object.defineProperty(target, PEER_HOOKS, { value: hooks, enumerable: false });
77
+ return tagPermissionMeta(target, kind, meta) as T & Peerable<K, V> & PermissionMetaTagged<K>;
78
+ }
79
+
80
+ /**
81
+ * Bind an inner Builder (typically `basePermission`) and return a
82
+ * factory-builder that produces tagged peer-permission factories. Pure —
83
+ * no side effects on any registry. The runtime registry is derived from
84
+ * a namespace import via `derivePeerRegistry`.
85
+ *
86
+ * Composes with the factory mixins, mirroring `makeHostable`:
87
+ *
88
+ * const peerable = makePeerable(basePermission);
89
+ * export const routedApps = peerable("peers.routedApps", z.literal(true).default(true), {
90
+ * extract, resolve, ...
91
+ * });
92
+ */
93
+ export function makePeerable<BaseR extends object, KindUnion extends PeerKindShape>(
94
+ inner: Builder<BaseR, "perm", KindUnion>,
95
+ ) {
96
+ return <S extends z.ZodType, TEntry, TValue, K extends KindUnion>(
97
+ kind: K,
98
+ schema: S,
99
+ hooks: PeerHooks<z.input<S>, TEntry, TValue>,
100
+ meta: PermissionMeta,
101
+ ) => {
102
+ const factory = inner(kind, schema);
103
+ return tagPeerable(factory, kind, hooks as PeerHooks<unknown, unknown, TValue>, meta);
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Walk a namespace import (e.g. `import * as peers from "./schema/peer.ts"`)
109
+ * and build a kind→hooks map from every Peerable export. Non-peerable
110
+ * exports (types are erased; helpers like `isNetworkStrategy` are skipped)
111
+ * are filtered by `isPeerable`.
112
+ */
113
+ export function derivePeerRegistry(
114
+ mod: Record<string, unknown>,
115
+ ): ReadonlyMap<PeerKindShape, PeerHooks> {
116
+ const reg = new Map<PeerKindShape, PeerHooks>();
117
+ for (const value of Object.values(mod)) {
118
+ if (isPeerable(value)) reg.set(value[PEER_KIND], value[PEER_HOOKS]);
119
+ }
120
+ return reg;
121
+ }
@@ -0,0 +1,117 @@
1
+ // Cross-app world-state aggregation for peer permissions.
2
+ //
3
+ // Per-kind logic lives next to the factory in schema/peer.ts (each
4
+ // factory tagged with kind + hooks via makePeerable). This module
5
+ // derives a kind→hooks map from the namespace import and dispatches
6
+ // every extract / resolve / grant check through it.
7
+
8
+ import * as peers from "./schema/peer.ts";
9
+ import type { HomeApp } from "./resolve.ts";
10
+ import type { Grant } from "./grants.ts";
11
+ import { derivePeerRegistry, type Peerable, type PeerHooks } from "./peer-registry.ts";
12
+ import type { PeerPermissionKind, PermissionKind } from "./permissions.ts";
13
+
14
+ const REGISTRY = derivePeerRegistry(peers as Record<string, unknown>);
15
+
16
+ export function peerHooks(kind: string): PeerHooks | undefined {
17
+ return REGISTRY.get(kind as `peers.${string}`);
18
+ }
19
+
20
+ export function peerEntries(): ReadonlyArray<readonly [PeerPermissionKind, PeerHooks]> {
21
+ return [...REGISTRY.entries()] as unknown as ReadonlyArray<
22
+ readonly [PeerPermissionKind, PeerHooks]
23
+ >;
24
+ }
25
+
26
+ // Derived from `typeof peers`: each Peerable<K, V> contributes
27
+ // `{ kind: K, data: V[number] }` since every peer's `resolve` returns
28
+ // an array of world-entry values. Adding a peer kind = one declaration
29
+ // in schema/peer.ts; this union updates automatically. Constraint: V
30
+ // must be array-shaped (true for all current peers).
31
+ type PeerEntryDataMap = {
32
+ [N in keyof typeof peers as (typeof peers)[N] extends Peerable<infer K, unknown>
33
+ ? K
34
+ : never]: (typeof peers)[N] extends Peerable<PeerPermissionKind, infer V>
35
+ ? V extends readonly (infer E)[]
36
+ ? E
37
+ : never
38
+ : never;
39
+ };
40
+
41
+ export type WorldEntry = {
42
+ [K in keyof PeerEntryDataMap]: { readonly kind: K; readonly data: PeerEntryDataMap[K] };
43
+ }[keyof PeerEntryDataMap];
44
+
45
+ export type WorldState = ReadonlyArray<WorldEntry>;
46
+
47
+ /** One `peers.buildApps` world entry, narrowed out of the kind union. */
48
+ export type BuildAppWorldEntry = Extract<WorldEntry, { kind: "peers.buildApps" }>;
49
+
50
+ /**
51
+ * Narrow a world entry to `peers.buildApps`. This guard and the type above are
52
+ * the only places outside schema/peer.ts that name that kind, and they exist
53
+ * for one deliberate special case.
54
+ *
55
+ * Build-app entries are the only ones Home unions across WORKERS. Peer worlds
56
+ * are otherwise assembled per worker, which would confine every
57
+ * build-from-source app to whichever box the (singleton) builder happens to
58
+ * sit on. What licenses the exception is that `buildApps` declares `extract` +
59
+ * `resolve` and none of the four locality hooks — `extractNetworks`,
60
+ * `extractPeerVolumes`, `extractAllowedProvider`, `producerPredicate`. Each of
61
+ * those would turn an entry into something that exists on exactly one box: a
62
+ * docker network to join, a host path to mount, a `shared` ref to dereference,
63
+ * a `depends_on` edge. With none of them present, an entry from another worker
64
+ * is inert data — a recipe to read, nothing to wire. Adding any of those hooks
65
+ * to `buildApps` would break realm-wide scope; `peer-world.test.ts` guards it.
66
+ *
67
+ * Rename-safe by construction: `e.kind` is the closed `PeerPermissionKind`
68
+ * union, so a stale literal here is a TS2367 comparison error and the
69
+ * `Extract` above collapses to `never`.
70
+ */
71
+ export function isBuildAppEntry(e: WorldEntry): e is BuildAppWorldEntry {
72
+ return e.kind === "peers.buildApps";
73
+ }
74
+
75
+ /**
76
+ * Walk one app's resolved state and emit every peer-shaped contribution.
77
+ * Home aggregates results across all apps into a single WorldState before
78
+ * resolving any consumer's permission.
79
+ */
80
+ export function extractPeerEntries(appId: string, state: HomeApp): WorldEntry[] {
81
+ const out: WorldEntry[] = [];
82
+ for (const [kind, hooks] of REGISTRY) {
83
+ for (const data of hooks.extract(appId, state)) {
84
+ out.push({ kind, data } as WorldEntry);
85
+ }
86
+ }
87
+ return out;
88
+ }
89
+
90
+ /**
91
+ * Given a consumer's permission kind + descriptor data, return the
92
+ * filtered runtime value over the aggregated world.
93
+ */
94
+ export function resolvePeerValue(
95
+ kind: PeerPermissionKind,
96
+ data: unknown,
97
+ world: WorldState,
98
+ ): unknown {
99
+ const hooks = REGISTRY.get(kind);
100
+ if (!hooks) throw new Error(`unknown peer kind: ${kind}`);
101
+ const entries = world.filter((e) => e.kind === kind).map((e) => e.data);
102
+ return hooks.resolve(entries, data);
103
+ }
104
+
105
+ /**
106
+ * Did the admin grant this peer permission? Every peer kind requires an
107
+ * explicit grant entry at the consumer's path — there is no auto-grant.
108
+ */
109
+ export function isPeerGranted(
110
+ name: string,
111
+ kind: PermissionKind,
112
+ grants: Record<string, Grant> | undefined,
113
+ ): boolean {
114
+ if (!REGISTRY.has(kind as PeerPermissionKind)) return false;
115
+ const g = grants?.[name];
116
+ return g != null && g.kind === kind;
117
+ }
@@ -0,0 +1,116 @@
1
+ // Per-kind declaration metadata machinery. `PermissionMeta` is the shape
2
+ // every permission factory carries (per-field docs on the interface below).
3
+ // Mostly presentation, but `appKinds` is enforcement, and it lives here
4
+ // rather than in the per-family hooks (`HostHooks` / `PeerHooks` / …)
5
+ // because the rule is the same across all four namespaces.
6
+ //
7
+ // The runtime const `PERMISSION_META` is *derived* — see permissions.ts —
8
+ // by walking the schema/{host,peer,remote,home}.ts namespace exports and
9
+ // pulling the meta tag off each factory, which keeps the kind list and
10
+ // presentation strings in lockstep with their declaration site.
11
+
12
+ import type { IconName } from "./ui/components/icons.tsx";
13
+
14
+ /** Which factory produced an app: `createApp` or `createSystemApp`. */
15
+ export type AppKind = "single" | "system";
16
+
17
+ /**
18
+ * Which app kinds may declare a permission. `"both"` is the common case;
19
+ * the two narrow values are restrictions with a reason at the declaration
20
+ * site.
21
+ *
22
+ * Required rather than optional-defaulting-to-both, so a new kind has to
23
+ * answer the question instead of inheriting the loosest answer by
24
+ * omission. Note the asymmetry when picking one: widening later is
25
+ * additive, narrowing later breaks whatever came to rely on it (the same
26
+ * reasoning as `assertOneSilo` in sandbox.ts), so absent a real single-app
27
+ * or system-app consumer, the narrow value is the safer default.
28
+ */
29
+ export type AppKindScope = AppKind | "both";
30
+
31
+ export interface PermissionMeta {
32
+ /** Short Title-Case label for UI rows (e.g. "Port", "Directory"). */
33
+ name: string;
34
+ /** Lowercase phrase used inside violation messages (e.g. "bind a host port"). */
35
+ phrase: string;
36
+ /** Tooltip-length explanation shown next to the row. */
37
+ helpText: string;
38
+ /** Icon name from `icons.tsx` (e.g. "GraphicsCardIcon", "FolderOpenIcon"). */
39
+ icon: IconName;
40
+ /** Drives danger-tinted styling on the install panel. */
41
+ danger?: boolean;
42
+ /**
43
+ * App kinds allowed to declare this permission. Enforced twice: the
44
+ * factories walk `configSchema` and throw at author time, and Home's
45
+ * graph verification runs the same check authoritatively (see
46
+ * `checkPermissionAppKinds` in security.ts).
47
+ */
48
+ appKinds: AppKindScope;
49
+ }
50
+
51
+ /** Whether `appKind` may declare a permission scoped to `scope`. */
52
+ export function scopeAllows(scope: AppKindScope, appKind: AppKind): boolean {
53
+ return scope === "both" || scope === appKind;
54
+ }
55
+
56
+ // Unique symbol that brands every permission factory with its kind +
57
+ // meta. Module-private — only this file's `tagPermissionMeta` writes it,
58
+ // and the type-level helpers (`PermissionMetaTagged<K>`,
59
+ // `KindOfPermission<T>`) read it back via structural inference: TS resolves
60
+ // the property-key symbol type from this `unique symbol` declaration alone.
61
+ const PERMISSION_META_TAG: unique symbol = Symbol("realm.permission.meta");
62
+
63
+ export interface PermissionMetaTagged<K extends string = string> {
64
+ readonly [PERMISSION_META_TAG]: { readonly kind: K; readonly meta: PermissionMeta };
65
+ }
66
+
67
+ /**
68
+ * Type-level helper: extract the kind literal from a tagged factory.
69
+ * `KindOfPermission<typeof gpu>` resolves to `"host.gpu"`.
70
+ */
71
+ export type KindOfPermission<T> = T extends PermissionMetaTagged<infer K> ? K : never;
72
+
73
+ /**
74
+ * Attach kind + meta to a permission factory as a non-enumerable tag.
75
+ * Used by makeHostable / makePeerable / makeRemotable so every factory
76
+ * carries its meta at the declaration site, not in a hand-maintained
77
+ * lookup table.
78
+ */
79
+ export function tagPermissionMeta<T extends object, K extends string>(
80
+ target: T,
81
+ kind: K,
82
+ meta: PermissionMeta,
83
+ ): T & PermissionMetaTagged<K> {
84
+ Object.defineProperty(target, PERMISSION_META_TAG, {
85
+ value: { kind, meta },
86
+ enumerable: false,
87
+ });
88
+ return target as T & PermissionMetaTagged<K>;
89
+ }
90
+
91
+ function isMetaTagged(value: unknown): value is PermissionMetaTagged {
92
+ return (
93
+ (typeof value === "function" || (typeof value === "object" && value !== null)) &&
94
+ PERMISSION_META_TAG in (value as object)
95
+ );
96
+ }
97
+
98
+ /**
99
+ * Walk one or more namespace imports (e.g. `import * as host from
100
+ * "./schema/host.ts"`) and build a kind → meta map from every tagged
101
+ * factory. Non-tagged exports are skipped.
102
+ */
103
+ export function derivePermissionMeta(
104
+ ...modules: Array<Record<string, unknown>>
105
+ ): Record<string, PermissionMeta> {
106
+ const out: Record<string, PermissionMeta> = {};
107
+ for (const mod of modules) {
108
+ for (const value of Object.values(mod)) {
109
+ if (isMetaTagged(value)) {
110
+ const tag = value[PERMISSION_META_TAG];
111
+ out[tag.kind] = tag.meta;
112
+ }
113
+ }
114
+ }
115
+ return out;
116
+ }
@@ -0,0 +1,108 @@
1
+ // Permission kinds + presentation meta, all *derived* by walking the
2
+ // schema/{host,peer,remote,home}.ts namespace exports. Each factory is tagged
3
+ // at declaration via tagPermissionMeta — that tag carries kind + meta
4
+ // and is the single source of truth this file consumes.
5
+ //
6
+ // Living here (not in shared.ts) breaks what would otherwise be a
7
+ // type-level cycle: shared.ts is imported by factory.ts which is imported
8
+ // by the schemas, so shared.ts can't depend on the schemas.
9
+
10
+ import * as host from "./schema/host.ts";
11
+ import * as peers from "./schema/peer.ts";
12
+ import * as remote from "./schema/remote.ts";
13
+ import * as home from "./schema/home.ts";
14
+ import {
15
+ derivePermissionMeta,
16
+ scopeAllows,
17
+ type AppKind,
18
+ type PermissionMeta,
19
+ type KindOfPermission,
20
+ } from "./permission-meta.ts";
21
+
22
+ export type { AppKind, PermissionMeta };
23
+
24
+ // ----- Type-level derivation ----------------------------------------------
25
+
26
+ type KindsIn<M> = KindOfPermission<M[keyof M]>;
27
+
28
+ export type HostPermissionKind = Extract<KindsIn<typeof host>, `host.${string}`>;
29
+ export type PeerPermissionKind = Extract<KindsIn<typeof peers>, `peers.${string}`>;
30
+ export type RemotePermissionKind = Extract<KindsIn<typeof remote>, `remote.${string}`>;
31
+ // `home.*` is Home-resolved and never worker-advertised — like peers/remote
32
+ // it stays out of HOST_PERMISSION_KINDS (the worker-capability gate), but it
33
+ // carries data + a resolved value like host (see home-registry.ts).
34
+ export type HomePermissionKind = Extract<KindsIn<typeof home>, `home.${string}`>;
35
+ export type PermissionKind =
36
+ | HostPermissionKind
37
+ | PeerPermissionKind
38
+ | RemotePermissionKind
39
+ | HomePermissionKind;
40
+
41
+ // ----- Runtime derivation -------------------------------------------------
42
+
43
+ export const PERMISSION_META: Readonly<Record<PermissionKind, PermissionMeta>> =
44
+ derivePermissionMeta(
45
+ host as Record<string, unknown>,
46
+ peers as Record<string, unknown>,
47
+ remote as Record<string, unknown>,
48
+ home as Record<string, unknown>,
49
+ ) as Readonly<Record<PermissionKind, PermissionMeta>>;
50
+
51
+ /**
52
+ * Closed set of permission kinds known at runtime. Useful for wire-format
53
+ * Zod schemas (`z.enum([...PERMISSION_KINDS])`) that need to validate
54
+ * input strings against the actual registered kinds.
55
+ */
56
+ export const PERMISSION_KINDS: readonly PermissionKind[] = Object.keys(
57
+ PERMISSION_META,
58
+ ).sort() as PermissionKind[];
59
+
60
+ /**
61
+ * Host-only subset of `PERMISSION_KINDS` (peers + remote + home are
62
+ * Home-resolved at eval and never worker-advertised).
63
+ */
64
+ export const HOST_PERMISSION_KINDS: readonly HostPermissionKind[] = Object.keys(
65
+ derivePermissionMeta(host as Record<string, unknown>),
66
+ ).sort() as HostPermissionKind[];
67
+
68
+ /**
69
+ * Everything a worker can advertise on the register wire: the host
70
+ * permission kinds, plus `host.volumes` — a capability with no permission
71
+ * behind it. It says whether the worker can hold persistent bind mounts at
72
+ * all; a worker without it is an ephemeral runtime (sandbox VM,
73
+ * emptyDir-only k8s) and can't run any app that mounts a volume. Which
74
+ * absolute paths an app may mount is a separate question, answered by
75
+ * grants (Silos, broker sockets), not by this capability.
76
+ */
77
+ export type WorkerCapability = HostPermissionKind | "host.volumes";
78
+
79
+ export const WORKER_CAPABILITIES: readonly WorkerCapability[] = [
80
+ ...HOST_PERMISSION_KINDS,
81
+ "host.volumes",
82
+ ].sort() as WorkerCapability[];
83
+
84
+ /**
85
+ * Typed accessor — throws on unknown kinds. Use this instead of
86
+ * `PERMISSION_META[kind]` when you'd otherwise have to handle the
87
+ * `| undefined` that noUncheckedIndexedAccess forces on lookups.
88
+ */
89
+ export function permissionMeta(kind: PermissionKind): PermissionMeta {
90
+ const meta = PERMISSION_META[kind];
91
+ if (!meta) {
92
+ throw new Error(`unknown permission kind: ${kind}`);
93
+ }
94
+ return meta;
95
+ }
96
+
97
+ /**
98
+ * Whether an app of `appKind` may declare a permission of `kind`.
99
+ *
100
+ * Unknown kinds pass. A kind that isn't in the derived registry can't be
101
+ * declared by a real factory in the first place, so the only way to reach
102
+ * here with one is a hand-written packed bundle, and that's `checkAppGrants`'
103
+ * problem — failing closed here would just report the wrong reason for it.
104
+ */
105
+ export function permissionAllowsAppKind(kind: string, appKind: AppKind): boolean {
106
+ const meta = PERMISSION_META[kind as PermissionKind];
107
+ return meta ? scopeAllows(meta.appKinds, appKind) : true;
108
+ }
@@ -0,0 +1,34 @@
1
+ // The SDK package directory, found by walking up from the calling module to
2
+ // the nearest `package.json`. Source runs from `src/<...>` and the published
3
+ // package from `dist/<name>.js`, so a fixed `..` count would name a different
4
+ // directory in each layout; the walk names the same one in both.
5
+ //
6
+ // `pkgRoot` answers null rather than throwing: Home's production image stages
7
+ // the Fresh server bundle with no `package.json` anywhere above it, and the
8
+ // callers there only use the directory as one more place to look for a binary.
9
+
10
+ import { dirname, fromFileUrl, join } from "@std/path";
11
+
12
+ export function pkgRoot(moduleUrl: string): string | null {
13
+ if (!moduleUrl.startsWith("file:")) return null;
14
+ let dir = dirname(fromFileUrl(moduleUrl));
15
+ while (true) {
16
+ try {
17
+ Deno.statSync(join(dir, "package.json"));
18
+ return dir;
19
+ } catch {
20
+ // not here - keep walking
21
+ }
22
+ const parent = dirname(dir);
23
+ if (parent === dir) return null;
24
+ dir = parent;
25
+ }
26
+ }
27
+
28
+ // For the CLI builders, which read the ui-runtime sources out of the package
29
+ // and cannot run without it.
30
+ export function sdkDir(moduleUrl: string): string {
31
+ const dir = pkgRoot(moduleUrl);
32
+ if (dir === null) throw new Error(`the SDK package is not on disk above ${moduleUrl}`);
33
+ return dir;
34
+ }
@@ -0,0 +1,79 @@
1
+ import type { Ref } from "./ref.ts";
2
+ import { interpolate as i } from "./ref.ts";
3
+ import { Service } from "./service.ts";
4
+
5
+ /**
6
+ * Shared options that apply regardless of whether the caller picks the
7
+ * default image or overrides it.
8
+ */
9
+ interface PostgresCommonOptions {
10
+ /**
11
+ * Size of `/dev/shm` (tmpfs in RAM) inside the container, in MB.
12
+ * Defaults to `128` - up from Docker's 64MB default. Postgres uses
13
+ * POSIX shared memory for parallel workers and large hash joins; the
14
+ * value is a cap, not a pre-allocation, so idle containers cost nothing
15
+ * extra.
16
+ */
17
+ shmSizeMb?: number;
18
+ }
19
+
20
+ /**
21
+ * Either run the default `postgres:{majorVersion}` image (in which case
22
+ * `image` is omitted), or override `image` to run a Postgres flavor with
23
+ * bundled extensions. When `image` is set, `majorVersion` becomes
24
+ * required so the backup container can pick the matching `pg_dump`
25
+ * binary - a mismatched pair fails at backup time, not at evaluate time,
26
+ * so we lift the check up to the type system.
27
+ */
28
+ type PostgresOptions =
29
+ | (PostgresCommonOptions & { majorVersion?: number; image?: never })
30
+ | (PostgresCommonOptions & { majorVersion: number; image: string });
31
+
32
+ export class Postgres extends Service {
33
+ readonly image: string;
34
+ readonly port = 5432;
35
+ readonly user = "realm";
36
+ readonly database = "realm";
37
+ // Underscore-prefixed to keep it off the public surface - the backup
38
+ // strategy is the only consumer, and output doesn't carry it.
39
+ private readonly _majorVersion: number;
40
+ private readonly _shmSizeMb: number;
41
+
42
+ constructor(options?: PostgresOptions) {
43
+ super();
44
+ const majorVersion = options?.majorVersion ?? 18;
45
+ this.image = options?.image ?? `postgres:${majorVersion}`;
46
+ this._majorVersion = majorVersion;
47
+ this._shmSizeMb = options?.shmSizeMb ?? 128;
48
+ }
49
+
50
+ get password(): Ref<string> {
51
+ return this.derived("postgres-password");
52
+ }
53
+
54
+ get url(): Ref<string> {
55
+ return i`postgres://${this.user}:${this.password}@${this.host}:${this.port}/${this.database}`;
56
+ }
57
+
58
+ getServiceDefinition() {
59
+ return {
60
+ image: this.image,
61
+ volumes: [
62
+ {
63
+ hostPath: i`./data/${this.host}`,
64
+ containerPath: "/var/lib/postgresql",
65
+ backup: { kind: "pg-dump" as const, url: this.url, version: this._majorVersion },
66
+ },
67
+ ],
68
+ env: {
69
+ POSTGRES_USER: this.user,
70
+ POSTGRES_PASSWORD: this.password,
71
+ POSTGRES_DB: this.database,
72
+ },
73
+ healthcheck: {
74
+ test: ["CMD", "pg_isready", "-U", "realm", "-d", "realm"],
75
+ },
76
+ shmSizeMb: this._shmSizeMb,
77
+ };
78
+ }
79
+ }
@@ -0,0 +1,27 @@
1
+ // `@yourrealm/sdk/preact` — preact hooks and JSX types for realm.ts
2
+ // authors. realm.ts code imports from here instead of `preact/hooks`
3
+ // directly so the iframe framework stays an SDK implementation detail.
4
+ //
5
+ // Same dual-pattern as `@yourrealm/sdk/ui`: this file is the type
6
+ // contract used at authoring/typecheck time, and its values are stubs
7
+ // that throw if invoked outside the iframe. The real hooks ship from
8
+ // `ui-runtime/index.ts` (bundled into runtime.js), which the iframe
9
+ // importmap routes this specifier to.
10
+
11
+ import type * as _hooks from "preact/hooks";
12
+
13
+ export type { ComponentChildren, JSX, VNode } from "preact";
14
+
15
+ const stub = ((): never => {
16
+ throw new Error(
17
+ "@yourrealm/sdk/preact has no runtime in this environment. Hook calls are bundled by realm-sdk and execute inside the Home iframe.",
18
+ );
19
+ // deno-lint-ignore no-explicit-any
20
+ }) as any;
21
+
22
+ export const useCallback: typeof _hooks.useCallback = stub;
23
+ export const useEffect: typeof _hooks.useEffect = stub;
24
+ export const useMemo: typeof _hooks.useMemo = stub;
25
+ export const useReducer: typeof _hooks.useReducer = stub;
26
+ export const useRef: typeof _hooks.useRef = stub;
27
+ export const useState: typeof _hooks.useState = stub;
package/src/redis.ts ADDED
@@ -0,0 +1,43 @@
1
+ import type { Ref } from "./ref.ts";
2
+ import { interpolate as i } from "./ref.ts";
3
+ import { Service } from "./service.ts";
4
+
5
+ interface RedisOptions {
6
+ version?: string;
7
+ }
8
+
9
+ export class Redis extends Service {
10
+ readonly image: string;
11
+ readonly port = 6379;
12
+
13
+ constructor(options?: RedisOptions) {
14
+ super();
15
+ this.image = `redis:${options?.version ?? "8"}`;
16
+ }
17
+
18
+ get password(): Ref<string> {
19
+ return this.derived("redis-password");
20
+ }
21
+
22
+ get url(): Ref<string> {
23
+ return i`redis://default:${this.password}@${this.host}:${this.port}/0`;
24
+ }
25
+
26
+ getServiceDefinition() {
27
+ return {
28
+ image: this.image,
29
+ volumes: [
30
+ {
31
+ hostPath: i`./data/${this.host}`,
32
+ containerPath: "/data",
33
+ backup: { kind: "valkey-save" as const, url: this.url },
34
+ },
35
+ ],
36
+ command: ["--requirepass", this.password],
37
+ env: { REDISCLI_AUTH: this.password },
38
+ healthcheck: {
39
+ test: ["CMD", "redis-cli", "PING"],
40
+ },
41
+ };
42
+ }
43
+ }