@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
package/src/factory.ts ADDED
@@ -0,0 +1,481 @@
1
+ // Factory-level wrapper composition: two seeds (perm/input), shared
2
+ // Mode-generic wrappers, two composed bases (basePermission, baseInput). Each
3
+ // wrapper owns one meta slot and refuses double-stacks via GateNoSlot.
4
+
5
+ import type { z } from "zod";
6
+ import type {
7
+ AddOptionalSlot,
8
+ AddRequiredSlot,
9
+ Builder,
10
+ FactoryFor,
11
+ GateNoSlot,
12
+ InputKind,
13
+ Mode,
14
+ } from "./shared.ts";
15
+
16
+ // Open template literal shape used as basePermission's KindUnion. The closed
17
+ // `PermissionKind` (derived from schema/* in permissions.ts) can't be
18
+ // referenced here without forming a type-level cycle: schema/host.ts →
19
+ // host-registry.ts → factory.ts → ... → typeof schema/host.ts. Every prefix a
20
+ // registry narrows to must appear here, or `make*(basePermission)` won't
21
+ // typecheck for that prefix.
22
+ type AnyPermissionKindShape =
23
+ | `host.${string}`
24
+ | `peers.${string}`
25
+ | `remote.${string}`
26
+ | `home.${string}`;
27
+
28
+ // ----- Runtime helpers -----------------------------------------------------
29
+
30
+ export function defineMethod<T extends object>(
31
+ target: T,
32
+ name: string,
33
+ fn: (this: T, ...args: never[]) => unknown,
34
+ ): void {
35
+ Object.defineProperty(target, name, {
36
+ value: fn,
37
+ enumerable: false,
38
+ writable: false,
39
+ configurable: true,
40
+ });
41
+ }
42
+
43
+ export function cloneAnd<T extends object>(source: T, patch: object): T {
44
+ const c = Object.create(
45
+ Object.getPrototypeOf(source),
46
+ Object.getOwnPropertyDescriptors(source),
47
+ ) as T;
48
+ Object.assign(c, patch);
49
+ return c;
50
+ }
51
+
52
+ // ----- Seeds ---------------------------------------------------------------
53
+
54
+ const makePermRecord: Builder<{ readonly meta: object }, "perm", AnyPermissionKindShape> = (<
55
+ S extends z.ZodType,
56
+ K extends AnyPermissionKindShape,
57
+ >(
58
+ kind: K,
59
+ schema: S,
60
+ ) => {
61
+ return (input?: z.input<S>) => ({
62
+ kind,
63
+ data: schema.parse(input),
64
+ meta: {} as object,
65
+ });
66
+ }) as Builder<{ readonly meta: object }, "perm", AnyPermissionKindShape>;
67
+
68
+ const makeInputRecord: Builder<{ readonly meta: object }, "input", InputKind> = <
69
+ S extends z.ZodType,
70
+ K extends InputKind,
71
+ >(
72
+ kind: K,
73
+ schema: S,
74
+ ) => {
75
+ return () => ({
76
+ kind,
77
+ schema,
78
+ meta: {} as object,
79
+ });
80
+ };
81
+
82
+ // ----- Mode-agnostic wrappers ---------------------------------------------
83
+
84
+ interface WithOptional {
85
+ // The intersection narrows the existing `meta.isOptional: boolean` slot
86
+ // to the literal `true`, which lets type-level walkers (`GrantsOf`,
87
+ // `ConfigOf`'s optional-key handling) tell `r.x()` apart from
88
+ // `r.x().optional()`.
89
+ optional(this: this): this & { readonly meta: { readonly isOptional: true } };
90
+ }
91
+
92
+ function makeOptional<BaseR extends { meta: object }, M extends Mode, KindUnion extends string>(
93
+ inner: Builder<BaseR, M, KindUnion>,
94
+ ..._gate: GateNoSlot<BaseR["meta"], "isOptional">
95
+ ): Builder<
96
+ BaseR & {
97
+ readonly meta: AddRequiredSlot<BaseR["meta"], "isOptional", boolean>;
98
+ } & WithOptional,
99
+ M,
100
+ KindUnion
101
+ > {
102
+ return (<S extends z.ZodType, K extends KindUnion>(kind: K, schema: S) => {
103
+ const factory = inner<S, K>(kind, schema) as (...args: unknown[]) => { meta: object };
104
+ return ((...args: unknown[]) => {
105
+ const innerRecord = factory(...args);
106
+ // Use cloneAnd (not spread) so non-enumerable chain methods carry over.
107
+ const seeded = cloneAnd(innerRecord, {
108
+ meta: { ...(innerRecord.meta as object), isOptional: false },
109
+ });
110
+ defineMethod(seeded, "optional", function (this: typeof seeded) {
111
+ return cloneAnd(this, { meta: { ...(this.meta as object), isOptional: true } });
112
+ });
113
+ return seeded;
114
+ }) as unknown as FactoryFor<
115
+ S,
116
+ K,
117
+ BaseR & {
118
+ readonly meta: AddRequiredSlot<BaseR["meta"], "isOptional", boolean>;
119
+ } & WithOptional,
120
+ M
121
+ >;
122
+ }) as Builder<
123
+ BaseR & {
124
+ readonly meta: AddRequiredSlot<BaseR["meta"], "isOptional", boolean>;
125
+ } & WithOptional,
126
+ M,
127
+ KindUnion
128
+ >;
129
+ }
130
+
131
+ interface WithDescribe {
132
+ describe(this: this, text: string): this;
133
+ }
134
+
135
+ function makeDescribable<BaseR extends { meta: object }, M extends Mode, KindUnion extends string>(
136
+ inner: Builder<BaseR, M, KindUnion>,
137
+ ..._gate: GateNoSlot<BaseR["meta"], "description">
138
+ ): Builder<
139
+ BaseR & {
140
+ readonly meta: AddOptionalSlot<BaseR["meta"], "description", string>;
141
+ } & WithDescribe,
142
+ M,
143
+ KindUnion
144
+ > {
145
+ return (<S extends z.ZodType, K extends KindUnion>(kind: K, schema: S) => {
146
+ const factory = inner<S, K>(kind, schema) as (...args: unknown[]) => { meta: object };
147
+ return ((...args: unknown[]) => {
148
+ const record = factory(...args);
149
+ defineMethod(record, "describe", function (this: typeof record, text: string) {
150
+ return cloneAnd(this, { meta: { ...(this.meta as object), description: text } });
151
+ });
152
+ return record;
153
+ }) as unknown as FactoryFor<
154
+ S,
155
+ K,
156
+ BaseR & {
157
+ readonly meta: AddOptionalSlot<BaseR["meta"], "description", string>;
158
+ } & WithDescribe,
159
+ M
160
+ >;
161
+ }) as Builder<
162
+ BaseR & {
163
+ readonly meta: AddOptionalSlot<BaseR["meta"], "description", string>;
164
+ } & WithDescribe,
165
+ M,
166
+ KindUnion
167
+ >;
168
+ }
169
+
170
+ // ----- Permission-only wrappers -------------------------------------------
171
+
172
+ // `.reason()` widens meta.reason from optional to required, transitioning a
173
+ // Draft permission to Complete (assignable to the configSchema slot).
174
+ interface WithReason {
175
+ reason(this: this, text: string): this & { readonly meta: { readonly reason: string } };
176
+ }
177
+
178
+ // `.schema(z)` lands a live Zod schema as a non-enumerable `responseSchema`
179
+ // property — JSON.stringify drops it; the sandbox reads it back inside QuickJS.
180
+ // The method name (`schema`) and the property name (`responseSchema`) must
181
+ // differ; one identifier for both breaks polymorphic-`this` chaining for
182
+ // `.reason()` etc.
183
+ interface WithSchema {
184
+ schema<S extends z.ZodType>(this: this, s: S): this & { readonly responseSchema: S };
185
+ }
186
+
187
+ function makeReasonable<BaseR extends { meta: object }, KindUnion extends string>(
188
+ inner: Builder<BaseR, "perm", KindUnion>,
189
+ ..._gate: GateNoSlot<BaseR["meta"], "reason">
190
+ ): Builder<
191
+ BaseR & {
192
+ readonly meta: AddOptionalSlot<BaseR["meta"], "reason", string>;
193
+ } & WithReason,
194
+ "perm",
195
+ KindUnion
196
+ > {
197
+ return (<S extends z.ZodType, K extends KindUnion>(kind: K, schema: S) => {
198
+ const factory = inner<S, K>(kind, schema);
199
+ return ((input?: z.input<S>) => {
200
+ const record = (factory as unknown as (input?: z.input<S>) => { meta: object })(input);
201
+ defineMethod(record, "reason", function (this: typeof record, text: string) {
202
+ return cloneAnd(this, { meta: { ...(this.meta as object), reason: text } });
203
+ });
204
+ return record;
205
+ }) as unknown as FactoryFor<
206
+ S,
207
+ K,
208
+ BaseR & {
209
+ readonly meta: AddOptionalSlot<BaseR["meta"], "reason", string>;
210
+ } & WithReason,
211
+ "perm"
212
+ >;
213
+ }) as Builder<
214
+ BaseR & {
215
+ readonly meta: AddOptionalSlot<BaseR["meta"], "reason", string>;
216
+ } & WithReason,
217
+ "perm",
218
+ KindUnion
219
+ >;
220
+ }
221
+
222
+ // Opt-in mixin for factories that carry a live Zod object (today:
223
+ // remote.fetchUrl).
224
+ function makeSchemable<BaseR extends object, KindUnion extends string>(
225
+ inner: Builder<BaseR, "perm", KindUnion>,
226
+ ..._gate: GateNoSlot<BaseR, "responseSchema">
227
+ ): Builder<BaseR & WithSchema, "perm", KindUnion> {
228
+ return (<S extends z.ZodType, K extends KindUnion>(kind: K, schema: S) => {
229
+ const factory = inner<S, K>(kind, schema);
230
+ return ((input?: z.input<S>) => {
231
+ const record = (factory as unknown as (input?: z.input<S>) => object)(input);
232
+ defineMethod(record, "schema", function (this: object, s: z.ZodType) {
233
+ const cloned = cloneAnd(this, {});
234
+ Object.defineProperty(cloned, "responseSchema", {
235
+ value: s,
236
+ enumerable: false,
237
+ writable: false,
238
+ configurable: true,
239
+ });
240
+ return cloned;
241
+ });
242
+ return record;
243
+ }) as unknown as FactoryFor<S, K, BaseR & WithSchema, "perm">;
244
+ }) as Builder<BaseR & WithSchema, "perm", KindUnion>;
245
+ }
246
+
247
+ // ----- Input-only wrappers ------------------------------------------------
248
+
249
+ interface WithTitle {
250
+ title(this: this, text: string): this;
251
+ }
252
+
253
+ function makeTitleable<BaseR extends { meta: object }, KindUnion extends string>(
254
+ inner: Builder<BaseR, "input", KindUnion>,
255
+ ..._gate: GateNoSlot<BaseR["meta"], "title">
256
+ ): Builder<
257
+ BaseR & {
258
+ readonly meta: AddOptionalSlot<BaseR["meta"], "title", string>;
259
+ } & WithTitle,
260
+ "input",
261
+ KindUnion
262
+ > {
263
+ return (<S extends z.ZodType, K extends KindUnion>(kind: K, schema: S) => {
264
+ const factory = inner<S, K>(kind, schema);
265
+ return (() => {
266
+ const record = (factory as () => { meta: object })();
267
+ defineMethod(record, "title", function (this: typeof record, text: string) {
268
+ return cloneAnd(this, { meta: { ...(this.meta as object), title: text } });
269
+ });
270
+ return record;
271
+ }) as unknown as FactoryFor<
272
+ S,
273
+ K,
274
+ BaseR & {
275
+ readonly meta: AddOptionalSlot<BaseR["meta"], "title", string>;
276
+ } & WithTitle,
277
+ "input"
278
+ >;
279
+ }) as Builder<
280
+ BaseR & {
281
+ readonly meta: AddOptionalSlot<BaseR["meta"], "title", string>;
282
+ } & WithTitle,
283
+ "input",
284
+ KindUnion
285
+ >;
286
+ }
287
+
288
+ // `.prefill` infers I from `this.schema` so the value is constrained to the
289
+ // schema's I. Non-generic at the BaseR level so it sits cleanly alongside
290
+ // other mixins.
291
+ interface WithPrefill {
292
+ prefill<I>(
293
+ this: this & { readonly schema: z.ZodType<I> },
294
+ value: I,
295
+ ): this & { readonly meta: { readonly prefill: I } };
296
+ }
297
+
298
+ function makePrefillable<BaseR extends { meta: object }, KindUnion extends string>(
299
+ inner: Builder<BaseR, "input", KindUnion>,
300
+ ..._gate: GateNoSlot<BaseR["meta"], "prefill">
301
+ ): Builder<
302
+ BaseR & {
303
+ readonly meta: AddOptionalSlot<BaseR["meta"], "prefill", unknown>;
304
+ } & WithPrefill,
305
+ "input",
306
+ KindUnion
307
+ > {
308
+ return (<S extends z.ZodType, K extends KindUnion>(kind: K, schema: S) => {
309
+ const factory = inner<S, K>(kind, schema);
310
+ return (() => {
311
+ const record = (factory as () => { meta: object })();
312
+ defineMethod(record, "prefill", function (this: typeof record, value: unknown) {
313
+ return cloneAnd(this, { meta: { ...(this.meta as object), prefill: value } });
314
+ });
315
+ return record;
316
+ }) as unknown as FactoryFor<
317
+ S,
318
+ K,
319
+ BaseR & {
320
+ readonly meta: AddOptionalSlot<BaseR["meta"], "prefill", unknown>;
321
+ } & WithPrefill,
322
+ "input"
323
+ >;
324
+ }) as Builder<
325
+ BaseR & {
326
+ readonly meta: AddOptionalSlot<BaseR["meta"], "prefill", unknown>;
327
+ } & WithPrefill,
328
+ "input",
329
+ KindUnion
330
+ >;
331
+ }
332
+
333
+ interface WithPlaceholder {
334
+ placeholder(this: this, text: string): this;
335
+ }
336
+
337
+ function makePlaceholdable<BaseR extends { meta: object }, KindUnion extends string>(
338
+ inner: Builder<BaseR, "input", KindUnion>,
339
+ ..._gate: GateNoSlot<BaseR["meta"], "placeholder">
340
+ ): Builder<
341
+ BaseR & {
342
+ readonly meta: AddOptionalSlot<BaseR["meta"], "placeholder", string>;
343
+ } & WithPlaceholder,
344
+ "input",
345
+ KindUnion
346
+ > {
347
+ return (<S extends z.ZodType, K extends KindUnion>(kind: K, schema: S) => {
348
+ const factory = inner<S, K>(kind, schema);
349
+ return (() => {
350
+ const record = (factory as () => { meta: object })();
351
+ defineMethod(record, "placeholder", function (this: typeof record, text: string) {
352
+ return cloneAnd(this, { meta: { ...(this.meta as object), placeholder: text } });
353
+ });
354
+ return record;
355
+ }) as unknown as FactoryFor<
356
+ S,
357
+ K,
358
+ BaseR & {
359
+ readonly meta: AddOptionalSlot<BaseR["meta"], "placeholder", string>;
360
+ } & WithPlaceholder,
361
+ "input"
362
+ >;
363
+ }) as Builder<
364
+ BaseR & {
365
+ readonly meta: AddOptionalSlot<BaseR["meta"], "placeholder", string>;
366
+ } & WithPlaceholder,
367
+ "input",
368
+ KindUnion
369
+ >;
370
+ }
371
+
372
+ // ----- Number-input-only wrapper ------------------------------------------
373
+
374
+ // The three numeric slots a number input can carry, stacked onto BaseR's meta.
375
+ type WithNumericMeta<M> = AddOptionalSlot<
376
+ AddOptionalSlot<AddOptionalSlot<M, "int", boolean>, "min", number>,
377
+ "max",
378
+ number
379
+ >;
380
+
381
+ // `.int()` / `.min(n)` / `.max(n)` for `r.input.number()`. Each records the
382
+ // bound in `meta` (so the schema walker surfaces it to the Home UI as an
383
+ // `<input>` min/max/step without poking Zod internals) and tightens the
384
+ // record's live Zod schema, so the constraint holds wherever it is parsed.
385
+ // Composed onto `baseInput` for the number kind only, so `r.input.text().min()`
386
+ // stays a type error.
387
+ interface WithNumericConstraints {
388
+ int(this: this): this & { readonly meta: { readonly int: true } };
389
+ min(this: this, value: number): this & { readonly meta: { readonly min: number } };
390
+ max(this: this, value: number): this & { readonly meta: { readonly max: number } };
391
+ }
392
+
393
+ function makeNumericConstraints<BaseR extends { meta: object }, KindUnion extends string>(
394
+ inner: Builder<BaseR, "input", KindUnion>,
395
+ ): Builder<
396
+ BaseR & { readonly meta: WithNumericMeta<BaseR["meta"]> } & WithNumericConstraints,
397
+ "input",
398
+ KindUnion
399
+ > {
400
+ return (<S extends z.ZodType, K extends KindUnion>(kind: K, schema: S) => {
401
+ const factory = inner<S, K>(kind, schema);
402
+ return (() => {
403
+ // numberInput is only ever composed onto z.number(), so the record's
404
+ // schema is a ZodNumber at runtime; route through unknown since the
405
+ // generic S doesn't structurally overlap ZodNumber.
406
+ const record = (factory as unknown as () => { meta: object; schema: z.ZodNumber })();
407
+ defineMethod(record, "int", function (this: typeof record) {
408
+ return cloneAnd(this, {
409
+ meta: { ...(this.meta as object), int: true },
410
+ schema: this.schema.int(),
411
+ });
412
+ });
413
+ defineMethod(record, "min", function (this: typeof record, value: number) {
414
+ return cloneAnd(this, {
415
+ meta: { ...(this.meta as object), min: value },
416
+ schema: this.schema.min(value),
417
+ });
418
+ });
419
+ defineMethod(record, "max", function (this: typeof record, value: number) {
420
+ return cloneAnd(this, {
421
+ meta: { ...(this.meta as object), max: value },
422
+ schema: this.schema.max(value),
423
+ });
424
+ });
425
+ return record;
426
+ }) as unknown as FactoryFor<
427
+ S,
428
+ K,
429
+ BaseR & { readonly meta: WithNumericMeta<BaseR["meta"]> } & WithNumericConstraints,
430
+ "input"
431
+ >;
432
+ }) as Builder<
433
+ BaseR & { readonly meta: WithNumericMeta<BaseR["meta"]> } & WithNumericConstraints,
434
+ "input",
435
+ KindUnion
436
+ >;
437
+ }
438
+
439
+ // ----- Composed bases ------------------------------------------------------
440
+
441
+ export const basePermission = makeOptional(makeDescribable(makeReasonable(makePermRecord)));
442
+
443
+ // Permission base with `.schema(z)` chained in. Use this for kinds that
444
+ // carry a live Zod object on the record (today: remote.fetchUrl).
445
+ // The chain method is mandatory at the type level — see CompleteOf.
446
+ export const schemablePermission = makeSchemable(basePermission);
447
+
448
+ export const baseInput = makeOptional(
449
+ makeDescribable(makeTitleable(makePrefillable(makePlaceholdable(makeInputRecord)))),
450
+ );
451
+
452
+ // `r.input.number()` adds the numeric-constraint chain on top of baseInput; the
453
+ // other input kinds use baseInput directly, so `.int()/.min()/.max()` only
454
+ // appear on number fields.
455
+ export const numberInput = makeNumericConstraints(baseInput);
456
+
457
+ // ----- Boundary contracts — derived from the compose chain -----------------
458
+
459
+ // `R extends WithSchema` means the factory was composed via `makeSchemable`,
460
+ // so a complete record must carry `responseSchema: ZodType` as a value. The
461
+ // else branch ({ responseSchema?: never }) excludes records that carry the
462
+ // property without going through the WithSchema chain.
463
+ export type CompleteOf<B> =
464
+ B extends Builder<infer R, infer M, infer K>
465
+ ? M extends "perm"
466
+ ? (R extends WithReason ? R & { readonly meta: { readonly reason: string } } : R) &
467
+ (R extends WithSchema
468
+ ? { readonly responseSchema: z.ZodType<unknown> }
469
+ : { readonly responseSchema?: never }) & {
470
+ readonly kind: K;
471
+ readonly data: unknown;
472
+ }
473
+ : R & {
474
+ readonly kind: K;
475
+ readonly schema: z.ZodType<unknown>;
476
+ }
477
+ : never;
478
+
479
+ export type CompletePermission = CompleteOf<typeof basePermission>;
480
+ export type CompleteSchemablePermission = CompleteOf<typeof schemablePermission>;
481
+ export type CompleteInput = CompleteOf<typeof baseInput>;
package/src/garage.ts ADDED
@@ -0,0 +1,56 @@
1
+ import type { Ref } from "./ref.ts";
2
+ import { interpolate as i } from "./ref.ts";
3
+ import { Service } from "./service.ts";
4
+
5
+ interface GarageOptions {
6
+ version?: string;
7
+ }
8
+
9
+ export class Garage extends Service {
10
+ readonly image: string;
11
+ readonly port = 3900;
12
+ readonly adminPort = 3903;
13
+ readonly bucket = "data";
14
+
15
+ constructor(options?: GarageOptions) {
16
+ super();
17
+ this.image = `dxflrs/garage:${options?.version ?? "v1"}`;
18
+ }
19
+
20
+ get accessKey(): Ref<string> {
21
+ return this.derived("access-key");
22
+ }
23
+
24
+ get secretKey(): Ref<string> {
25
+ return this.derived("secret-key");
26
+ }
27
+
28
+ get url(): Ref<string> {
29
+ return i`http://${this.host}:${this.port}`;
30
+ }
31
+
32
+ get adminUrl(): Ref<string> {
33
+ return i`http://${this.host}:${this.adminPort}`;
34
+ }
35
+
36
+ getServiceDefinition() {
37
+ return {
38
+ image: this.image,
39
+ volumes: [
40
+ {
41
+ hostPath: i`./data/${this.host}`,
42
+ containerPath: "/var/lib/garage/data",
43
+ backup: { kind: "file-copy" as const },
44
+ },
45
+ ],
46
+ env: {
47
+ GARAGE_ACCESS_KEY: this.accessKey,
48
+ GARAGE_SECRET_KEY: this.secretKey,
49
+ GARAGE_DEFAULT_BUCKET: this.bucket,
50
+ },
51
+ healthcheck: {
52
+ test: ["CMD", "/garage", "status"],
53
+ },
54
+ };
55
+ }
56
+ }
@@ -0,0 +1,6 @@
1
+ export async function sha256Hex(input: string): Promise<string> {
2
+ const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
3
+ return Array.from(new Uint8Array(hash))
4
+ .map((b) => b.toString(16).padStart(2, "0"))
5
+ .join("");
6
+ }