@fungi.computer/oyster 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/README.md +228 -176
  2. package/dist/agent-avatars.d.ts +103 -0
  3. package/dist/agent-avatars.js +66 -0
  4. package/dist/agents.d.ts +12 -2
  5. package/dist/agents.js +39 -2
  6. package/dist/app-builds.d.ts +76 -17
  7. package/dist/app-builds.js +207 -63
  8. package/dist/app-sources.d.ts +7 -22
  9. package/dist/app-sources.js +49 -14
  10. package/dist/apps/gate.d.ts +7 -7
  11. package/dist/apps/gate.js +6 -6
  12. package/dist/apps/installation.d.ts +35 -35
  13. package/dist/apps/installation.js +20 -19
  14. package/dist/build/artifact-writer.d.ts +40 -0
  15. package/dist/build/artifact-writer.js +181 -0
  16. package/dist/build/build-jobs.js +7 -1
  17. package/dist/build/build-policy.d.ts +10 -0
  18. package/dist/build/build-policy.js +10 -0
  19. package/dist/build/build-step.d.ts +12 -1
  20. package/dist/build/build-step.js +9 -1
  21. package/dist/build/candidate-builder.d.ts +68 -0
  22. package/dist/build/candidate-builder.js +91 -0
  23. package/dist/build/candidate-cli.d.ts +1 -0
  24. package/dist/build/candidate-cli.js +119 -0
  25. package/dist/build/candidate-cli.mjs +28220 -0
  26. package/dist/build/content-objects.d.ts +37 -0
  27. package/dist/build/content-objects.js +53 -0
  28. package/dist/build/effect.d.ts +18 -0
  29. package/dist/build/effect.js +52 -12
  30. package/dist/build/git-environment.d.ts +3 -0
  31. package/dist/build/git-environment.js +21 -9
  32. package/dist/build/local-build.d.ts +13 -3
  33. package/dist/build/local-build.js +149 -99
  34. package/dist/build/snapshot-package-store.d.ts +10 -0
  35. package/dist/build/snapshot-package-store.js +15 -0
  36. package/dist/build/snapshot-program-scripts.js +1 -1
  37. package/dist/build/snapshot-programs/snapshot-path.d.ts +2 -0
  38. package/dist/build/snapshot-programs/snapshot-path.js +6 -0
  39. package/dist/build/submission-contract.d.ts +461 -17
  40. package/dist/build/submission-contract.js +136 -26
  41. package/dist/build/workspace-build.d.ts +7 -2
  42. package/dist/build/workspace-build.js +55 -11
  43. package/dist/build-contract.d.ts +3 -0
  44. package/dist/build-contract.js +3 -0
  45. package/dist/build-egress.d.ts +62 -0
  46. package/dist/build-egress.js +148 -0
  47. package/dist/build.d.ts +1 -0
  48. package/dist/build.js +1 -0
  49. package/dist/cloudflare.js +4 -4
  50. package/dist/frame-binding-wire-script.js +1 -1
  51. package/dist/frame-binding-wire.d.ts +1 -1
  52. package/dist/frame-binding-wire.js +1 -1
  53. package/dist/git-journal.js +4 -4
  54. package/dist/index.d.ts +1 -0
  55. package/dist/index.js +1 -0
  56. package/dist/installation-consent.d.ts +3 -1
  57. package/dist/installation-consent.js +2 -0
  58. package/dist/mcp-owner.js +20 -8
  59. package/dist/pack.d.ts +6 -3
  60. package/dist/pack.js +28 -11
  61. package/dist/page/gill-observation.d.ts +4 -0
  62. package/dist/page/gill-observation.js +31 -0
  63. package/dist/page/mount.d.ts +4 -8
  64. package/dist/page/window-launch.d.ts +18 -0
  65. package/dist/page/window-launch.js +11 -0
  66. package/dist/page-script.js +4 -4
  67. package/dist/release.d.ts +11 -0
  68. package/dist/release.js +4 -1
  69. package/dist/shell/app-directory.d.ts +50 -45
  70. package/dist/shell/app-directory.js +19 -18
  71. package/dist/shell/shell-module.d.ts +26 -2
  72. package/dist/shell/shell-module.js +51 -2
  73. package/dist/shell/window-manager.d.ts +5 -1
  74. package/dist/shell/window-manager.js +21 -2
  75. package/dist/shell/workbench-directory.d.ts +332 -0
  76. package/dist/shell/workbench-directory.js +35 -0
  77. package/dist/shell/workbench-host-types.d.ts +1 -1
  78. package/dist/shell/workbench-host.js +5 -0
  79. package/dist/shell/workbench-model.d.ts +3 -1
  80. package/dist/source/index.d.ts +6 -7
  81. package/dist/source/index.js +6 -7
  82. package/dist/source/template-push.d.ts +33 -0
  83. package/dist/source/template-push.js +223 -0
  84. package/dist/source/template-repository.d.ts +2 -0
  85. package/dist/source/template-repository.js +13 -0
  86. package/dist/source/{template-write.d.ts → template-seed.d.ts} +19 -20
  87. package/dist/source/template-seed.js +80 -0
  88. package/dist/standalone/agent-directory.d.ts +4 -0
  89. package/dist/standalone/agent-directory.js +4 -0
  90. package/dist/standalone/agents.d.ts +227 -0
  91. package/dist/standalone/agents.js +834 -0
  92. package/dist/standalone/app-builds-binding.d.ts +36 -0
  93. package/dist/standalone/app-builds-binding.js +119 -0
  94. package/dist/standalone/app-set.d.ts +91 -0
  95. package/dist/standalone/app-set.js +395 -0
  96. package/dist/standalone/app-source.d.ts +41 -0
  97. package/dist/standalone/app-source.js +59 -0
  98. package/dist/standalone/builds.d.ts +18 -0
  99. package/dist/standalone/builds.js +101 -0
  100. package/dist/standalone/computer-egress.d.ts +37 -0
  101. package/dist/standalone/computer-egress.js +111 -0
  102. package/dist/standalone/computer.d.ts +122 -0
  103. package/dist/standalone/computer.js +714 -0
  104. package/dist/standalone/install-apps-cli.d.ts +2 -0
  105. package/dist/standalone/install-apps-cli.js +126 -0
  106. package/dist/standalone/instance.d.ts +35 -0
  107. package/dist/standalone/instance.js +74 -0
  108. package/dist/standalone/media.d.ts +76 -0
  109. package/dist/standalone/media.js +227 -0
  110. package/dist/standalone/preview.d.ts +51 -0
  111. package/dist/standalone/preview.js +167 -0
  112. package/dist/standalone/registry.d.ts +102 -0
  113. package/dist/standalone/registry.js +193 -0
  114. package/dist/standalone/tenancy.d.ts +42 -0
  115. package/dist/standalone/tenancy.js +241 -0
  116. package/dist/standalone/worker.d.ts +55 -0
  117. package/dist/standalone/worker.js +147 -0
  118. package/dist/standalone/workflow-authority-readers.d.ts +16 -0
  119. package/dist/standalone/workflow-authority-readers.js +35 -0
  120. package/dist/standalone/workflow-binding.d.ts +216 -0
  121. package/dist/standalone/workflow-binding.js +217 -0
  122. package/dist/standalone/workflow-events.d.ts +46 -0
  123. package/dist/standalone/workflow-events.js +39 -0
  124. package/dist/standalone/workflow-git-capture.d.ts +7 -0
  125. package/dist/standalone/workflow-git-capture.js +62 -0
  126. package/dist/standalone/workflow-host.d.ts +955 -0
  127. package/dist/standalone/workflow-host.js +778 -0
  128. package/dist/standalone/workflow-node-capture.d.ts +13 -0
  129. package/dist/standalone/workflow-node-capture.js +72 -0
  130. package/dist/standalone/workflow-root-authority.d.ts +27 -0
  131. package/dist/standalone/workflow-root-authority.js +87 -0
  132. package/dist/standalone/workflow-schedules.d.ts +52 -0
  133. package/dist/standalone/workflow-schedules.js +40 -0
  134. package/dist/standalone/workflow-trigger-host.d.ts +1716 -0
  135. package/dist/standalone/workflow-trigger-host.js +52 -0
  136. package/dist/standalone/workflows.d.ts +713 -0
  137. package/dist/standalone/workflows.js +554 -0
  138. package/dist/tenancy/auth0.d.ts +64 -0
  139. package/dist/tenancy/auth0.js +198 -0
  140. package/dist/tenancy/cloudflare/rate-limit.d.ts +8 -0
  141. package/dist/tenancy/cloudflare/rate-limit.js +19 -0
  142. package/dist/tenancy/cloudflare/registry.d.ts +50 -0
  143. package/dist/tenancy/cloudflare/registry.js +301 -0
  144. package/dist/tenancy/cloudflare.d.ts +2 -0
  145. package/dist/tenancy/cloudflare.js +2 -0
  146. package/dist/tenancy/cold-directory.d.ts +13 -0
  147. package/dist/tenancy/cold-directory.js +43 -0
  148. package/dist/tenancy/contracts.d.ts +195 -0
  149. package/dist/tenancy/contracts.js +1 -0
  150. package/dist/tenancy/index.d.ts +19 -0
  151. package/dist/tenancy/index.js +156 -0
  152. package/dist/tenancy/owner-key.d.ts +26 -0
  153. package/dist/tenancy/owner-key.js +63 -0
  154. package/dist/tenancy/oyster.d.ts +4 -0
  155. package/dist/tenancy/oyster.js +25 -0
  156. package/dist/tenancy/registry-record.d.ts +7 -0
  157. package/dist/tenancy/registry-record.js +44 -0
  158. package/dist/tenancy/registry.d.ts +27 -0
  159. package/dist/tenancy/registry.js +138 -0
  160. package/dist/tenancy/resolve.d.ts +7 -0
  161. package/dist/tenancy/resolve.js +64 -0
  162. package/dist/workers-git.js +1 -4
  163. package/dist/workers.d.ts +2 -2
  164. package/dist/workers.js +3 -1
  165. package/docs/api/build-contract.md +829 -32
  166. package/docs/api/build.md +253 -17
  167. package/docs/api/modules.md +40 -32
  168. package/docs/api/pack.md +6 -3
  169. package/docs/api/shell/app-directory.md +267 -0
  170. package/docs/api/shell/shell-module.md +64 -3
  171. package/docs/api/shell/workbench-directory.md +119 -0
  172. package/docs/api/shell/workbench.md +19 -0
  173. package/docs/api/shell.md +127 -0
  174. package/docs/api/tenancy/auth0.md +110 -0
  175. package/docs/api/tenancy/cloudflare.md +221 -0
  176. package/docs/api/tenancy/cold-directory.md +46 -0
  177. package/docs/api/tenancy/owner-key.md +63 -0
  178. package/docs/api/tenancy/registry.md +77 -0
  179. package/docs/api/tenancy/resolve.md +69 -0
  180. package/docs/api/tenancy.md +430 -0
  181. package/docs/api/workers-git.md +140 -21
  182. package/docs/api/workers.md +15 -2
  183. package/docs/site/agent-quickstart.mdx +52 -55
  184. package/examples/standalone/worker.ts +7 -14
  185. package/examples/standalone/wrangler.jsonc +67 -6
  186. package/package.json +78 -25
  187. package/dist/build/r2-artifact-writer.d.ts +0 -31
  188. package/dist/build/r2-artifact-writer.js +0 -179
  189. package/dist/source/template-write.js +0 -344
  190. package/docs/media/architecture.mmd +0 -32
  191. package/docs/media/architecture.png +0 -0
  192. package/docs/media/bonsai-network.png +0 -0
  193. package/docs/media/bonsai-repository.png +0 -0
  194. package/examples/git/worker.ts +0 -39
  195. package/examples/git/wrangler.jsonc +0 -32
package/package.json CHANGED
@@ -1,17 +1,18 @@
1
1
  {
2
2
  "name": "@fungi.computer/oyster",
3
- "version": "0.0.1",
3
+ "version": "0.2.0",
4
4
  "author": "Levi <levi@technomancy.dev>",
5
5
  "description": "Run your own Apps on Cloudflare Workers or celld: installable App Releases, each with its own Durable Object, SQLite database and declared bindings",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
9
+ "bin": {
10
+ "oyster": "./dist/standalone/install-apps-cli.js"
11
+ },
9
12
  "files": [
10
13
  "dist",
11
14
  "docs/api",
12
- "docs/media",
13
15
  "examples/standalone",
14
- "examples/git",
15
16
  "docs/site/agent-quickstart.mdx",
16
17
  "README.md",
17
18
  "LICENSE"
@@ -63,6 +64,18 @@
63
64
  "types": "./dist/workers-git.d.ts",
64
65
  "import": "./dist/workers-git.js"
65
66
  },
67
+ "./standalone": {
68
+ "types": "./dist/standalone/worker.d.ts",
69
+ "import": "./dist/standalone/worker.js"
70
+ },
71
+ "./standalone/app-set": {
72
+ "types": "./dist/standalone/app-set.d.ts",
73
+ "import": "./dist/standalone/app-set.js"
74
+ },
75
+ "./standalone/media": {
76
+ "types": "./dist/standalone/media.d.ts",
77
+ "import": "./dist/standalone/media.js"
78
+ },
66
79
  "./event-recipients": {
67
80
  "types": "./dist/event-recipients.d.ts",
68
81
  "import": "./dist/event-recipients.js"
@@ -99,6 +112,10 @@
99
112
  "types": "./dist/shell/app-directory.d.ts",
100
113
  "import": "./dist/shell/app-directory.js"
101
114
  },
115
+ "./shell/workbench-directory": {
116
+ "types": "./dist/shell/workbench-directory.d.ts",
117
+ "import": "./dist/shell/workbench-directory.js"
118
+ },
102
119
  "./app-storage": {
103
120
  "types": "./dist/app-storage.d.ts",
104
121
  "import": "./dist/app-storage.js"
@@ -127,14 +144,14 @@
127
144
  "types": "./dist/app-builds.d.ts",
128
145
  "import": "./dist/app-builds.js"
129
146
  },
130
- "./r2-artifact-writer": {
131
- "types": "./dist/build/r2-artifact-writer.d.ts",
132
- "import": "./dist/build/r2-artifact-writer.js"
133
- },
134
147
  "./build-contract": {
135
148
  "types": "./dist/build-contract.d.ts",
136
149
  "import": "./dist/build-contract.js"
137
150
  },
151
+ "./build-egress": {
152
+ "types": "./dist/build-egress.d.ts",
153
+ "import": "./dist/build-egress.js"
154
+ },
138
155
  "./build-jobs": {
139
156
  "types": "./dist/build-jobs.d.ts",
140
157
  "import": "./dist/build-jobs.js"
@@ -143,9 +160,9 @@
143
160
  "types": "./dist/source/index.d.ts",
144
161
  "import": "./dist/source/index.js"
145
162
  },
146
- "./source/template-write": {
147
- "types": "./dist/source/template-write.d.ts",
148
- "import": "./dist/source/template-write.js"
163
+ "./source/template-push": {
164
+ "types": "./dist/source/template-push.d.ts",
165
+ "import": "./dist/source/template-push.js"
149
166
  },
150
167
  "./app-sources": {
151
168
  "types": "./dist/app-sources.d.ts",
@@ -171,6 +188,34 @@
171
188
  "types": "./dist/tenant.d.ts",
172
189
  "import": "./dist/tenant.js"
173
190
  },
191
+ "./tenancy": {
192
+ "types": "./dist/tenancy/index.d.ts",
193
+ "import": "./dist/tenancy/index.js"
194
+ },
195
+ "./tenancy/resolve": {
196
+ "types": "./dist/tenancy/resolve.d.ts",
197
+ "import": "./dist/tenancy/resolve.js"
198
+ },
199
+ "./tenancy/cold-directory": {
200
+ "types": "./dist/tenancy/cold-directory.d.ts",
201
+ "import": "./dist/tenancy/cold-directory.js"
202
+ },
203
+ "./tenancy/registry": {
204
+ "types": "./dist/tenancy/registry.d.ts",
205
+ "import": "./dist/tenancy/registry.js"
206
+ },
207
+ "./tenancy/owner-key": {
208
+ "types": "./dist/tenancy/owner-key.d.ts",
209
+ "import": "./dist/tenancy/owner-key.js"
210
+ },
211
+ "./tenancy/auth0": {
212
+ "types": "./dist/tenancy/auth0.d.ts",
213
+ "import": "./dist/tenancy/auth0.js"
214
+ },
215
+ "./tenancy/cloudflare": {
216
+ "types": "./dist/tenancy/cloudflare.d.ts",
217
+ "import": "./dist/tenancy/cloudflare.js"
218
+ },
174
219
  "./auth": {
175
220
  "types": "./dist/auth.d.ts",
176
221
  "import": "./dist/auth.js"
@@ -197,21 +242,22 @@
197
242
  }
198
243
  },
199
244
  "dependencies": {
200
- "@fungi.computer/cloudflare-worker-bundler": "0.2.3-fungi.0",
201
- "@fungi.computer/agar": "0.0.1",
202
- "@fungi.computer/burrow": "0.0.1",
245
+ "@fungi.computer/agar": "0.2.0",
246
+ "@fungi.computer/burrow": "0.0.2",
203
247
  "@fungi.computer/cairn": "0.0.1",
204
- "@fungi.computer/ear-tag": "0.0.1",
205
- "@fungi.computer/caps": "0.0.1",
248
+ "@fungi.computer/ear-tag": "0.0.2",
249
+ "@fungi.computer/caps": "0.1.0",
206
250
  "@fungi.computer/carrier": "0.0.1",
207
- "@fungi.computer/forage": "0.0.1",
208
- "@fungi.computer/gills": "0.0.1",
209
- "@fungi.computer/knapsack": "0.0.1",
210
- "@fungi.computer/mycelium": "0.0.1",
211
- "@fungi.computer/puffball": "0.0.1",
212
- "@fungi.computer/sigils": "0.0.1",
251
+ "@fungi.computer/forage": "0.1.0",
252
+ "@fungi.computer/gills": "0.1.0",
253
+ "@fungi.computer/knapsack": "0.1.0",
254
+ "@fungi.computer/mycelium": "0.1.0",
255
+ "@fungi.computer/puffball": "0.1.0",
256
+ "@fungi.computer/sigils": "0.1.0",
257
+ "@fungi.computer/sprite": "0.1.0",
258
+ "@fungi.computer/sprite-zdog": "0.1.0",
213
259
  "@fungi.computer/watchdog": "0.0.1",
214
- "@fungi.computer/whistle": "0.0.1",
260
+ "@fungi.computer/whistle": "0.0.2",
215
261
  "@modelcontextprotocol/server": "2.0.0",
216
262
  "@pnpm/lockfile.fs": "1100.2.13",
217
263
  "@pnpm/lockfile.pruner": "1100.0.27",
@@ -220,12 +266,19 @@
220
266
  "esbuild": "0.28.2",
221
267
  "fast-json-stable-stringify": "2.1.0",
222
268
  "hono": "4.13.5",
223
- "isomorphic-git": "1.42.2",
224
269
  "jose": "6.2.11",
225
- "memfs": "4.80.0",
226
270
  "mime": "4.1.0",
227
271
  "path-to-regexp": "8.4.2",
228
- "zod": "4.4.3"
272
+ "zod": "4.4.3",
273
+ "oauth4webapi": "3.8.8",
274
+ "@cfworker/json-schema": "4.1.1",
275
+ "@earendil-works/pi-ai": "0.99.2",
276
+ "@fungi.computer/braid": "0.1.0",
277
+ "@fungi.computer/computer": "0.1.0",
278
+ "@fungi.computer/darkroom": "0.1.0",
279
+ "@fungi.computer/shiitake": "0.2.0",
280
+ "@fungi.computer/veil": "0.1.0",
281
+ "@fungi.computer/workflows-wire": "0.1.0"
229
282
  },
230
283
  "license": "MIT",
231
284
  "peerDependencies": {
@@ -1,31 +0,0 @@
1
- import { type AssetBuildPolicy } from "@fungi.computer/puffball/assets";
2
- import { type AssetR2Object, type AssetR2Bucket } from "@fungi.computer/puffball/cloudflare-assets";
3
- import { type ArtifactRef, type AppReleaseCatalogOptions } from "@fungi.computer/puffball";
4
- import { type AppReleaseR2Bucket } from "@fungi.computer/puffball/cloudflare";
5
- import type { ArtifactWriter } from "./effect.js";
6
- /**
7
- * The native R2 writer for a built App: immutable, digest-addressed objects
8
- * keyed by artifact reference, read back and verified before the root is
9
- * returned. Hub's Publisher and Oyster's Computer builds both write Releases
10
- * through it.
11
- */
12
- export type ArtifactPolicy = NonNullable<AppReleaseCatalogOptions["artifactPolicy"]>;
13
- /** Native binding subset; conditional put never replaces a retained artifact. */
14
- export type PublisherR2Bucket = AppReleaseR2Bucket & AssetR2Bucket & {
15
- put(key: string, value: Uint8Array, options: {
16
- onlyIf: {
17
- etagDoesNotMatch: string;
18
- };
19
- sha256: string;
20
- httpMetadata: {
21
- contentType: string;
22
- };
23
- }): Promise<AssetR2Object | null>;
24
- };
25
- /** Private host pins a retry-stable reference; the actual build supplies its digest. */
26
- export declare function createR2ArtifactWriter(options: {
27
- bucket: PublisherR2Bucket;
28
- artifactRef: ArtifactRef;
29
- policy: ArtifactPolicy;
30
- assets?: AssetBuildPolicy;
31
- }): ArtifactWriter;
@@ -1,179 +0,0 @@
1
- import { buildAssetManifest } from "@fungi.computer/cloudflare-worker-bundler/assets";
2
- import { createHash } from "node:crypto";
3
- import * as Effect from "effect/Effect";
4
- import * as Schema from "effect/Schema";
5
- import { assetBuildMediaType, assetBuildJsonSchema, assetBuildPolicySchema, assetBaseSchema, decodeAssetBuild, assetPathSchema, } from "@fungi.computer/puffball/assets";
6
- import { appBuildJsonSchema, appMediaType, createAppBuild, } from "@fungi.computer/puffball/app";
7
- import { matchesAssetObject, } from "@fungi.computer/puffball/cloudflare-assets";
8
- import { parseSha256Digest, parseArtifactRef, } from "@fungi.computer/puffball";
9
- import { createR2ArtifactReader, } from "@fungi.computer/puffball/cloudflare";
10
- /** Private host pins a retry-stable reference; the actual build supplies its digest. */
11
- export function createR2ArtifactWriter(options) {
12
- const artifactRef = options.artifactRef;
13
- const assets = options.assets
14
- ? {
15
- bucket: options.bucket,
16
- policy: Object.freeze(Schema.decodeUnknownSync(assetBuildPolicySchema)({
17
- ...options.assets,
18
- })),
19
- }
20
- : undefined;
21
- const policy = { ...options.policy };
22
- const reader = createR2ArtifactReader(options.bucket, policy, assets);
23
- const writeImmutable = async (input) => {
24
- if (input.limit === undefined || input.bytes.byteLength > input.limit)
25
- throw new Error("Publisher artifact exceeds configured policy");
26
- const digest = createHash("sha256").update(input.bytes).digest("hex");
27
- const artifactDigest = parseSha256Digest(`sha256:${digest}`);
28
- const readStored = () => reader.read({
29
- artifactRef: input.artifactRef,
30
- expectedDigest: artifactDigest,
31
- verification: "publication",
32
- });
33
- let stored = await readStored();
34
- if (stored.kind === "missing") {
35
- await options.bucket.put(input.artifactRef, input.bytes, {
36
- onlyIf: { etagDoesNotMatch: "*" },
37
- sha256: digest,
38
- httpMetadata: { contentType: input.mediaType },
39
- });
40
- stored = await readStored();
41
- }
42
- const object = await options.bucket.head(input.artifactRef);
43
- if (stored.kind !== "verified" ||
44
- stored.mediaType !== input.mediaType ||
45
- object?.size !== input.bytes.byteLength)
46
- throw new Error("Publisher artifact readback failed");
47
- return {
48
- artifactRef: input.artifactRef,
49
- artifactDigest,
50
- version: object.version,
51
- byteLength: input.bytes.byteLength,
52
- mediaType: input.mediaType,
53
- };
54
- };
55
- const childArtifactRef = (digest) => parseArtifactRef(`artifact:sha256-${digest.slice("sha256:".length)}`);
56
- return {
57
- write: (output) => Effect.tryPromise(async () => {
58
- const uiOutput = output.kind === "backend" ? null : output.ui;
59
- const backendBytes = output.kind === "ui" ? null : output.backend;
60
- const uiBytes = uiOutput === null
61
- ? null
62
- : uiOutput.content.kind === "html"
63
- ? uiOutput.content.bytes.slice()
64
- : await storeAssetObjects(uiOutput.content.assets, uiOutput.content.base, options.bucket, assets?.policy);
65
- const uiMediaType = uiOutput?.mediaType ?? null;
66
- const ui = uiBytes === null || uiMediaType === null
67
- ? null
68
- : await writeImmutable({
69
- artifactRef: childArtifactRef(`sha256:${createHash("sha256")
70
- .update(uiBytes)
71
- .digest("hex")}`),
72
- bytes: uiBytes,
73
- mediaType: uiMediaType,
74
- limit: uiMediaType === assetBuildMediaType
75
- ? policy.maxManifestBytes
76
- : policy.maxBytes,
77
- });
78
- const backend = backendBytes === null
79
- ? null
80
- : await writeImmutable({
81
- artifactRef: childArtifactRef(`sha256:${createHash("sha256")
82
- .update(backendBytes)
83
- .digest("hex")}`),
84
- bytes: backendBytes,
85
- mediaType: "application/vnd.fungi.app-backend+json",
86
- limit: policy.maxBytes,
87
- });
88
- const rootInput = createAppBuild(ui === null || uiMediaType === null
89
- ? null
90
- : {
91
- artifactRef: ui.artifactRef,
92
- digest: ui.artifactDigest,
93
- version: ui.version,
94
- byteLength: ui.byteLength,
95
- mediaType: uiMediaType,
96
- }, backend === null
97
- ? null
98
- : {
99
- artifactRef: backend.artifactRef,
100
- digest: backend.artifactDigest,
101
- version: backend.version,
102
- byteLength: backend.byteLength,
103
- mediaType: "application/vnd.fungi.app-backend+json",
104
- }, output.declaration);
105
- const root = Schema.encodeUnknownSync(appBuildJsonSchema)(rootInput);
106
- const rootBytes = new TextEncoder().encode(root);
107
- const stored = await writeImmutable({
108
- artifactRef,
109
- bytes: rootBytes,
110
- mediaType: appMediaType,
111
- limit: policy.maxManifestBytes,
112
- });
113
- return {
114
- artifactRef: stored.artifactRef,
115
- artifactDigest: stored.artifactDigest,
116
- };
117
- }),
118
- };
119
- }
120
- async function storeAssetObjects(outputs, suppliedBase, bucket, policy) {
121
- const base = Schema.decodeUnknownSync(assetBaseSchema)(suppliedBase);
122
- if (!policy)
123
- throw new Error("Asset writer requires explicit policy and native metadata binding");
124
- if (outputs.length === 0 || outputs.length > policy.maxObjects)
125
- throw new Error("Asset count exceeds policy");
126
- const content = {};
127
- let total = 0;
128
- for (const output of outputs) {
129
- const path = Schema.decodeUnknownSync(assetPathSchema)(output.path);
130
- if (Object.hasOwn(content, path))
131
- throw new Error("Duplicate build path");
132
- total += output.bytes.byteLength;
133
- if (output.bytes.byteLength > policy.maxObjectBytes ||
134
- !Number.isSafeInteger(total) ||
135
- total > policy.maxTotalBytes)
136
- throw new Error("Asset bytes exceed policy");
137
- content[path] = new Uint8Array(output.bytes).buffer;
138
- }
139
- const metadata = await buildAssetManifest(content);
140
- const assets = await Effect.runPromise(Effect.forEach(Object.keys(content).sort(), (path) => Effect.tryPromise(async () => {
141
- const bytes = content[path];
142
- const type = metadata.get(path)?.contentType;
143
- if (!bytes || !type)
144
- throw new Error("Build asset has no maintained content type");
145
- const hash = createHash("sha256")
146
- .update(new Uint8Array(bytes))
147
- .digest("hex");
148
- const artifactRef = parseArtifactRef(`artifact:sha256-${hash}`);
149
- let object = await bucket.head(artifactRef);
150
- if (object === null) {
151
- object = await bucket.put(artifactRef, new Uint8Array(bytes), {
152
- onlyIf: { etagDoesNotMatch: "*" },
153
- sha256: hash,
154
- httpMetadata: { contentType: type },
155
- });
156
- object ??= await bucket.head(artifactRef);
157
- }
158
- if (!object)
159
- throw new Error("Asset readback missing");
160
- const asset = {
161
- path,
162
- artifactRef,
163
- digest: parseSha256Digest(`sha256:${hash}`),
164
- version: object.version,
165
- byteLength: bytes.byteLength,
166
- contentType: type,
167
- };
168
- if (!matchesAssetObject(asset, object))
169
- throw new Error("Immutable asset collision");
170
- return asset;
171
- }), { concurrency: 4 }));
172
- const root = new TextEncoder().encode(Schema.encodeUnknownSync(assetBuildJsonSchema)({
173
- format: "fungi-assets-v1",
174
- base,
175
- assets,
176
- }));
177
- decodeAssetBuild(root, policy);
178
- return root;
179
- }