@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.
- package/README.md +228 -176
- package/dist/agent-avatars.d.ts +103 -0
- package/dist/agent-avatars.js +66 -0
- package/dist/agents.d.ts +12 -2
- package/dist/agents.js +39 -2
- package/dist/app-builds.d.ts +76 -17
- package/dist/app-builds.js +207 -63
- package/dist/app-sources.d.ts +7 -22
- package/dist/app-sources.js +49 -14
- package/dist/apps/gate.d.ts +7 -7
- package/dist/apps/gate.js +6 -6
- package/dist/apps/installation.d.ts +35 -35
- package/dist/apps/installation.js +20 -19
- package/dist/build/artifact-writer.d.ts +40 -0
- package/dist/build/artifact-writer.js +181 -0
- package/dist/build/build-jobs.js +7 -1
- package/dist/build/build-policy.d.ts +10 -0
- package/dist/build/build-policy.js +10 -0
- package/dist/build/build-step.d.ts +12 -1
- package/dist/build/build-step.js +9 -1
- package/dist/build/candidate-builder.d.ts +68 -0
- package/dist/build/candidate-builder.js +91 -0
- package/dist/build/candidate-cli.d.ts +1 -0
- package/dist/build/candidate-cli.js +119 -0
- package/dist/build/candidate-cli.mjs +28220 -0
- package/dist/build/content-objects.d.ts +37 -0
- package/dist/build/content-objects.js +53 -0
- package/dist/build/effect.d.ts +18 -0
- package/dist/build/effect.js +52 -12
- package/dist/build/git-environment.d.ts +3 -0
- package/dist/build/git-environment.js +21 -9
- package/dist/build/local-build.d.ts +13 -3
- package/dist/build/local-build.js +149 -99
- package/dist/build/snapshot-package-store.d.ts +10 -0
- package/dist/build/snapshot-package-store.js +15 -0
- package/dist/build/snapshot-program-scripts.js +1 -1
- package/dist/build/snapshot-programs/snapshot-path.d.ts +2 -0
- package/dist/build/snapshot-programs/snapshot-path.js +6 -0
- package/dist/build/submission-contract.d.ts +461 -17
- package/dist/build/submission-contract.js +136 -26
- package/dist/build/workspace-build.d.ts +7 -2
- package/dist/build/workspace-build.js +55 -11
- package/dist/build-contract.d.ts +3 -0
- package/dist/build-contract.js +3 -0
- package/dist/build-egress.d.ts +62 -0
- package/dist/build-egress.js +148 -0
- package/dist/build.d.ts +1 -0
- package/dist/build.js +1 -0
- package/dist/cloudflare.js +4 -4
- package/dist/frame-binding-wire-script.js +1 -1
- package/dist/frame-binding-wire.d.ts +1 -1
- package/dist/frame-binding-wire.js +1 -1
- package/dist/git-journal.js +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/installation-consent.d.ts +3 -1
- package/dist/installation-consent.js +2 -0
- package/dist/mcp-owner.js +20 -8
- package/dist/pack.d.ts +6 -3
- package/dist/pack.js +28 -11
- package/dist/page/gill-observation.d.ts +4 -0
- package/dist/page/gill-observation.js +31 -0
- package/dist/page/mount.d.ts +4 -8
- package/dist/page/window-launch.d.ts +18 -0
- package/dist/page/window-launch.js +11 -0
- package/dist/page-script.js +4 -4
- package/dist/release.d.ts +11 -0
- package/dist/release.js +4 -1
- package/dist/shell/app-directory.d.ts +50 -45
- package/dist/shell/app-directory.js +19 -18
- package/dist/shell/shell-module.d.ts +26 -2
- package/dist/shell/shell-module.js +51 -2
- package/dist/shell/window-manager.d.ts +5 -1
- package/dist/shell/window-manager.js +21 -2
- package/dist/shell/workbench-directory.d.ts +332 -0
- package/dist/shell/workbench-directory.js +35 -0
- package/dist/shell/workbench-host-types.d.ts +1 -1
- package/dist/shell/workbench-host.js +5 -0
- package/dist/shell/workbench-model.d.ts +3 -1
- package/dist/source/index.d.ts +6 -7
- package/dist/source/index.js +6 -7
- package/dist/source/template-push.d.ts +33 -0
- package/dist/source/template-push.js +223 -0
- package/dist/source/template-repository.d.ts +2 -0
- package/dist/source/template-repository.js +13 -0
- package/dist/source/{template-write.d.ts → template-seed.d.ts} +19 -20
- package/dist/source/template-seed.js +80 -0
- package/dist/standalone/agent-directory.d.ts +4 -0
- package/dist/standalone/agent-directory.js +4 -0
- package/dist/standalone/agents.d.ts +227 -0
- package/dist/standalone/agents.js +834 -0
- package/dist/standalone/app-builds-binding.d.ts +36 -0
- package/dist/standalone/app-builds-binding.js +119 -0
- package/dist/standalone/app-set.d.ts +91 -0
- package/dist/standalone/app-set.js +395 -0
- package/dist/standalone/app-source.d.ts +41 -0
- package/dist/standalone/app-source.js +59 -0
- package/dist/standalone/builds.d.ts +18 -0
- package/dist/standalone/builds.js +101 -0
- package/dist/standalone/computer-egress.d.ts +37 -0
- package/dist/standalone/computer-egress.js +111 -0
- package/dist/standalone/computer.d.ts +122 -0
- package/dist/standalone/computer.js +714 -0
- package/dist/standalone/install-apps-cli.d.ts +2 -0
- package/dist/standalone/install-apps-cli.js +126 -0
- package/dist/standalone/instance.d.ts +35 -0
- package/dist/standalone/instance.js +74 -0
- package/dist/standalone/media.d.ts +76 -0
- package/dist/standalone/media.js +227 -0
- package/dist/standalone/preview.d.ts +51 -0
- package/dist/standalone/preview.js +167 -0
- package/dist/standalone/registry.d.ts +102 -0
- package/dist/standalone/registry.js +193 -0
- package/dist/standalone/tenancy.d.ts +42 -0
- package/dist/standalone/tenancy.js +241 -0
- package/dist/standalone/worker.d.ts +55 -0
- package/dist/standalone/worker.js +147 -0
- package/dist/standalone/workflow-authority-readers.d.ts +16 -0
- package/dist/standalone/workflow-authority-readers.js +35 -0
- package/dist/standalone/workflow-binding.d.ts +216 -0
- package/dist/standalone/workflow-binding.js +217 -0
- package/dist/standalone/workflow-events.d.ts +46 -0
- package/dist/standalone/workflow-events.js +39 -0
- package/dist/standalone/workflow-git-capture.d.ts +7 -0
- package/dist/standalone/workflow-git-capture.js +62 -0
- package/dist/standalone/workflow-host.d.ts +955 -0
- package/dist/standalone/workflow-host.js +778 -0
- package/dist/standalone/workflow-node-capture.d.ts +13 -0
- package/dist/standalone/workflow-node-capture.js +72 -0
- package/dist/standalone/workflow-root-authority.d.ts +27 -0
- package/dist/standalone/workflow-root-authority.js +87 -0
- package/dist/standalone/workflow-schedules.d.ts +52 -0
- package/dist/standalone/workflow-schedules.js +40 -0
- package/dist/standalone/workflow-trigger-host.d.ts +1716 -0
- package/dist/standalone/workflow-trigger-host.js +52 -0
- package/dist/standalone/workflows.d.ts +713 -0
- package/dist/standalone/workflows.js +554 -0
- package/dist/tenancy/auth0.d.ts +64 -0
- package/dist/tenancy/auth0.js +198 -0
- package/dist/tenancy/cloudflare/rate-limit.d.ts +8 -0
- package/dist/tenancy/cloudflare/rate-limit.js +19 -0
- package/dist/tenancy/cloudflare/registry.d.ts +50 -0
- package/dist/tenancy/cloudflare/registry.js +301 -0
- package/dist/tenancy/cloudflare.d.ts +2 -0
- package/dist/tenancy/cloudflare.js +2 -0
- package/dist/tenancy/cold-directory.d.ts +13 -0
- package/dist/tenancy/cold-directory.js +43 -0
- package/dist/tenancy/contracts.d.ts +195 -0
- package/dist/tenancy/contracts.js +1 -0
- package/dist/tenancy/index.d.ts +19 -0
- package/dist/tenancy/index.js +156 -0
- package/dist/tenancy/owner-key.d.ts +26 -0
- package/dist/tenancy/owner-key.js +63 -0
- package/dist/tenancy/oyster.d.ts +4 -0
- package/dist/tenancy/oyster.js +25 -0
- package/dist/tenancy/registry-record.d.ts +7 -0
- package/dist/tenancy/registry-record.js +44 -0
- package/dist/tenancy/registry.d.ts +27 -0
- package/dist/tenancy/registry.js +138 -0
- package/dist/tenancy/resolve.d.ts +7 -0
- package/dist/tenancy/resolve.js +64 -0
- package/dist/workers-git.js +1 -4
- package/dist/workers.d.ts +2 -2
- package/dist/workers.js +3 -1
- package/docs/api/build-contract.md +829 -32
- package/docs/api/build.md +253 -17
- package/docs/api/modules.md +40 -32
- package/docs/api/pack.md +6 -3
- package/docs/api/shell/app-directory.md +267 -0
- package/docs/api/shell/shell-module.md +64 -3
- package/docs/api/shell/workbench-directory.md +119 -0
- package/docs/api/shell/workbench.md +19 -0
- package/docs/api/shell.md +127 -0
- package/docs/api/tenancy/auth0.md +110 -0
- package/docs/api/tenancy/cloudflare.md +221 -0
- package/docs/api/tenancy/cold-directory.md +46 -0
- package/docs/api/tenancy/owner-key.md +63 -0
- package/docs/api/tenancy/registry.md +77 -0
- package/docs/api/tenancy/resolve.md +69 -0
- package/docs/api/tenancy.md +430 -0
- package/docs/api/workers-git.md +140 -21
- package/docs/api/workers.md +15 -2
- package/docs/site/agent-quickstart.mdx +52 -55
- package/examples/standalone/worker.ts +7 -14
- package/examples/standalone/wrangler.jsonc +67 -6
- package/package.json +78 -25
- package/dist/build/r2-artifact-writer.d.ts +0 -31
- package/dist/build/r2-artifact-writer.js +0 -179
- package/dist/source/template-write.js +0 -344
- package/docs/media/architecture.mmd +0 -32
- package/docs/media/architecture.png +0 -0
- package/docs/media/bonsai-network.png +0 -0
- package/docs/media/bonsai-repository.png +0 -0
- package/examples/git/worker.ts +0 -39
- package/examples/git/wrangler.jsonc +0 -32
|
@@ -4,17 +4,23 @@ import { appDeclarationSchema } from "@fungi.computer/puffball/app";
|
|
|
4
4
|
import { backendJsonSchema } from "@fungi.computer/puffball/backend";
|
|
5
5
|
import * as Schema from "effect/Schema";
|
|
6
6
|
import { isSigilName } from "@fungi.computer/sigils";
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const
|
|
7
|
+
/**
|
|
8
|
+
* The largest candidate manifest request body, in bytes. A manifest names
|
|
9
|
+
* objects; their bytes are uploaded one at a time, never inside it.
|
|
10
|
+
*/
|
|
11
|
+
export const TEAM_APP_CANDIDATE_MANIFEST_MAX_BYTES = 1024 * 1024;
|
|
12
|
+
const byteLengthSchema = Schema.Number.pipe(Schema.check(Schema.isInt()), Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.check(Schema.makeFilter(Number.isSafeInteger)));
|
|
13
|
+
/** One content-addressed object a build uploaded before its manifest. */
|
|
14
|
+
export const candidateObjectSchema = Schema.Struct({
|
|
15
|
+
digest: sha256DigestSchema,
|
|
16
|
+
byteLength: byteLengthSchema,
|
|
17
|
+
});
|
|
18
|
+
const uiManifestSchema = Schema.Union([
|
|
13
19
|
Schema.Struct({
|
|
14
20
|
mediaType: Schema.Literal("text/html; charset=utf-8"),
|
|
15
21
|
content: Schema.Struct({
|
|
16
22
|
kind: Schema.Literal("html"),
|
|
17
|
-
|
|
23
|
+
...candidateObjectSchema.fields,
|
|
18
24
|
}),
|
|
19
25
|
}),
|
|
20
26
|
Schema.Struct({
|
|
@@ -22,7 +28,10 @@ const uiOutputSchema = Schema.Union([
|
|
|
22
28
|
content: Schema.Struct({
|
|
23
29
|
kind: Schema.Literal("assets"),
|
|
24
30
|
base: assetBaseSchema,
|
|
25
|
-
assets: Schema.Array(Schema.Struct({
|
|
31
|
+
assets: Schema.Array(Schema.Struct({
|
|
32
|
+
path: assetPathSchema,
|
|
33
|
+
...candidateObjectSchema.fields,
|
|
34
|
+
})),
|
|
26
35
|
}),
|
|
27
36
|
}),
|
|
28
37
|
]);
|
|
@@ -30,41 +39,142 @@ const uiOutputSchema = Schema.Union([
|
|
|
30
39
|
const declaration = {
|
|
31
40
|
declaration: Schema.optionalKey(appDeclarationSchema.check(Schema.makeFilter((declaration) => declaration.icon === undefined || isSigilName(declaration.icon), { message: "App icon must name a registered Sigil" }))),
|
|
32
41
|
};
|
|
33
|
-
|
|
42
|
+
/** A build's output by reference: the same surfaces as its bytes, sized. */
|
|
43
|
+
export const candidateOutputManifestSchema = Schema.Union([
|
|
34
44
|
Schema.Struct({
|
|
35
45
|
kind: Schema.Literal("ui"),
|
|
36
|
-
ui:
|
|
46
|
+
ui: uiManifestSchema,
|
|
37
47
|
...declaration,
|
|
38
48
|
}),
|
|
39
49
|
Schema.Struct({
|
|
40
50
|
kind: Schema.Literal("backend"),
|
|
41
|
-
backend:
|
|
51
|
+
backend: candidateObjectSchema,
|
|
42
52
|
...declaration,
|
|
43
53
|
}),
|
|
44
54
|
Schema.Struct({
|
|
45
55
|
kind: Schema.Literal("app"),
|
|
46
|
-
ui:
|
|
47
|
-
backend:
|
|
56
|
+
ui: uiManifestSchema,
|
|
57
|
+
backend: candidateObjectSchema,
|
|
48
58
|
...declaration,
|
|
49
59
|
}),
|
|
50
60
|
]);
|
|
51
|
-
const
|
|
61
|
+
const provenanceSchema = Schema.Struct({
|
|
62
|
+
sourceRevision: sourceRevisionSchema,
|
|
63
|
+
sourceTreeDigest: sha256DigestSchema,
|
|
64
|
+
buildRecipeDigest: sha256DigestSchema,
|
|
65
|
+
});
|
|
66
|
+
const manifestFields = {
|
|
52
67
|
appId: appIdSchema,
|
|
53
|
-
output:
|
|
54
|
-
provenance:
|
|
55
|
-
sourceRevision: sourceRevisionSchema,
|
|
56
|
-
sourceTreeDigest: sha256DigestSchema,
|
|
57
|
-
buildRecipeDigest: sha256DigestSchema,
|
|
58
|
-
}),
|
|
68
|
+
output: candidateOutputManifestSchema,
|
|
69
|
+
provenance: provenanceSchema,
|
|
59
70
|
};
|
|
60
|
-
/**
|
|
61
|
-
|
|
71
|
+
/**
|
|
72
|
+
* A submitted Team App candidate: its output's objects by digest and size,
|
|
73
|
+
* and its provenance, before the Team assigns a stable Release ID.
|
|
74
|
+
*/
|
|
75
|
+
export const teamAppCandidateManifestSchema = Schema.Struct(manifestFields);
|
|
62
76
|
/** Publisher persistence input after TeamDO reserves retry-stable identities. */
|
|
77
|
+
export const preparedCandidateManifestSchema = Schema.Struct({
|
|
78
|
+
...manifestFields,
|
|
79
|
+
releaseId: releaseIdSchema,
|
|
80
|
+
});
|
|
81
|
+
/** One operator-published byte string; its request has a separate total bound. */
|
|
82
|
+
const bytesSchema = Schema.Uint8ArrayFromBase64.pipe(Schema.check(Schema.makeFilter((bytes) => bytes.byteLength <= 16 * 1024 * 1024)));
|
|
83
|
+
const backendBytesSchema = bytesSchema.pipe(Schema.check(Schema.makeFilter((bytes) => Schema.decodeUnknownResult(backendJsonSchema)(new TextDecoder().decode(bytes), { onExcessProperty: "error" })._tag === "Success")));
|
|
84
|
+
const uiBytesSchema = Schema.Union([
|
|
85
|
+
Schema.Struct({
|
|
86
|
+
mediaType: Schema.Literal("text/html; charset=utf-8"),
|
|
87
|
+
content: Schema.Struct({
|
|
88
|
+
kind: Schema.Literal("html"),
|
|
89
|
+
bytes: bytesSchema,
|
|
90
|
+
}),
|
|
91
|
+
}),
|
|
92
|
+
Schema.Struct({
|
|
93
|
+
mediaType: Schema.Literal("application/vnd.fungi.app-assets+json"),
|
|
94
|
+
content: Schema.Struct({
|
|
95
|
+
kind: Schema.Literal("assets"),
|
|
96
|
+
base: assetBaseSchema,
|
|
97
|
+
assets: Schema.Array(Schema.Struct({ path: assetPathSchema, bytes: bytesSchema })),
|
|
98
|
+
}),
|
|
99
|
+
}),
|
|
100
|
+
]);
|
|
101
|
+
/**
|
|
102
|
+
* A platform App candidate the trusted operator built and packed itself,
|
|
103
|
+
* with its bytes inline: the operator's platform publication request only.
|
|
104
|
+
* A Team candidate never carries bytes; it is a manifest.
|
|
105
|
+
*/
|
|
63
106
|
export const preparedCandidateSchema = Schema.Struct({
|
|
64
|
-
|
|
107
|
+
appId: appIdSchema,
|
|
65
108
|
releaseId: releaseIdSchema,
|
|
109
|
+
output: Schema.Union([
|
|
110
|
+
Schema.Struct({
|
|
111
|
+
kind: Schema.Literal("ui"),
|
|
112
|
+
ui: uiBytesSchema,
|
|
113
|
+
...declaration,
|
|
114
|
+
}),
|
|
115
|
+
Schema.Struct({
|
|
116
|
+
kind: Schema.Literal("backend"),
|
|
117
|
+
backend: backendBytesSchema,
|
|
118
|
+
...declaration,
|
|
119
|
+
}),
|
|
120
|
+
Schema.Struct({
|
|
121
|
+
kind: Schema.Literal("app"),
|
|
122
|
+
ui: uiBytesSchema,
|
|
123
|
+
backend: backendBytesSchema,
|
|
124
|
+
...declaration,
|
|
125
|
+
}),
|
|
126
|
+
]),
|
|
127
|
+
provenance: provenanceSchema,
|
|
66
128
|
});
|
|
67
|
-
/** Canonical
|
|
68
|
-
export function
|
|
69
|
-
return JSON.stringify(Schema.encodeUnknownSync(
|
|
129
|
+
/** Canonical manifest bytes, so a digest is independent of JSON spacing. */
|
|
130
|
+
export function encodeTeamAppCandidateManifest(manifest) {
|
|
131
|
+
return JSON.stringify(Schema.encodeUnknownSync(teamAppCandidateManifestSchema)(manifest));
|
|
132
|
+
}
|
|
133
|
+
/** The hex SHA-256 of a manifest's canonical bytes: its App root's name. */
|
|
134
|
+
export async function digestTeamAppCandidateManifest(manifest) {
|
|
135
|
+
return Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(encodeTeamAppCandidateManifest(manifest)))), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* The media type an uploaded object is stored with. A store that reads an
|
|
139
|
+
* HTML document or backend descriptor by its stored type (the Publisher's)
|
|
140
|
+
* needs it; an asset's type is its manifest's, so it is an octet stream.
|
|
141
|
+
*/
|
|
142
|
+
export const candidateObjectMediaTypes = [
|
|
143
|
+
"text/html; charset=utf-8",
|
|
144
|
+
"application/vnd.fungi.app-backend+json",
|
|
145
|
+
"application/octet-stream",
|
|
146
|
+
];
|
|
147
|
+
/** The stored media type a request names, if it is one an object may carry. */
|
|
148
|
+
export function candidateObjectMediaType(value) {
|
|
149
|
+
return candidateObjectMediaTypes.find((type) => type === value) ?? null;
|
|
150
|
+
}
|
|
151
|
+
/** Every distinct object a manifest names, each once, in manifest order. */
|
|
152
|
+
export function candidateManifestObjects(output) {
|
|
153
|
+
const objects = [];
|
|
154
|
+
const add = (object, mediaType) => {
|
|
155
|
+
const seen = objects.find(({ digest }) => digest === object.digest);
|
|
156
|
+
if (seen === undefined)
|
|
157
|
+
objects.push({
|
|
158
|
+
digest: object.digest,
|
|
159
|
+
byteLength: object.byteLength,
|
|
160
|
+
mediaType,
|
|
161
|
+
});
|
|
162
|
+
else if (seen.byteLength !== object.byteLength)
|
|
163
|
+
throw new Error("One object digest names two sizes");
|
|
164
|
+
};
|
|
165
|
+
if (output.kind !== "backend") {
|
|
166
|
+
const { content } = output.ui;
|
|
167
|
+
if (content.kind === "html")
|
|
168
|
+
add(content, "text/html; charset=utf-8");
|
|
169
|
+
else
|
|
170
|
+
for (const asset of content.assets)
|
|
171
|
+
add(asset, "application/octet-stream");
|
|
172
|
+
}
|
|
173
|
+
if (output.kind !== "ui")
|
|
174
|
+
add(output.backend, "application/vnd.fungi.app-backend+json");
|
|
175
|
+
return objects;
|
|
176
|
+
}
|
|
177
|
+
/** Decode a stored backend descriptor; a manifest's backend must be one. */
|
|
178
|
+
export function decodeCandidateBackend(bytes) {
|
|
179
|
+
return Schema.decodeUnknownSync(backendJsonSchema)(new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }).decode(bytes), { onExcessProperty: "error" });
|
|
70
180
|
}
|
|
@@ -6,8 +6,11 @@ import type { WorkspaceBuildStep } from "./build-step.js";
|
|
|
6
6
|
* packages whose declared stylesheets compile first (`packageStylesheets`)
|
|
7
7
|
* and its own `stylesheets`, compiled in place, in order. A workspace
|
|
8
8
|
* package declares its build products: extra TypeScript configs compiled
|
|
9
|
-
* after its `tsconfig.build.json`, and its stylesheet input and output
|
|
10
|
-
*
|
|
9
|
+
* after its `tsconfig.build.json`, and its stylesheet input and output; or,
|
|
10
|
+
* instead of TypeScript configs, the one `package.json` script that builds
|
|
11
|
+
* it (`script`), which only an admitted Computer build runs. Every path is
|
|
12
|
+
* relative to the manifest that declares it. Unknown build fields are
|
|
13
|
+
* refused, so a builder never silently ignores a policy it does not know.
|
|
11
14
|
*/
|
|
12
15
|
export declare const workspaceBuildPolicySchema: Schema.Struct<{
|
|
13
16
|
readonly react: Schema.optional<Schema.Boolean>;
|
|
@@ -15,6 +18,8 @@ export declare const workspaceBuildPolicySchema: Schema.Struct<{
|
|
|
15
18
|
readonly packageStylesheets: Schema.optional<Schema.$Array<Schema.String>>;
|
|
16
19
|
readonly stylesheets: Schema.optional<Schema.$Array<Schema.String>>;
|
|
17
20
|
readonly typescript: Schema.optional<Schema.$Array<Schema.String>>;
|
|
21
|
+
/** A script name in this manifest's own `scripts`. */
|
|
22
|
+
readonly script: Schema.optional<Schema.NonEmptyString>;
|
|
18
23
|
readonly stylesheet: Schema.optional<Schema.Struct<{
|
|
19
24
|
readonly input: Schema.String;
|
|
20
25
|
readonly output: Schema.String;
|
|
@@ -13,8 +13,11 @@ const manifestPathSchema = Schema.String.pipe(Schema.check(Schema.makeFilter((pa
|
|
|
13
13
|
* packages whose declared stylesheets compile first (`packageStylesheets`)
|
|
14
14
|
* and its own `stylesheets`, compiled in place, in order. A workspace
|
|
15
15
|
* package declares its build products: extra TypeScript configs compiled
|
|
16
|
-
* after its `tsconfig.build.json`, and its stylesheet input and output
|
|
17
|
-
*
|
|
16
|
+
* after its `tsconfig.build.json`, and its stylesheet input and output; or,
|
|
17
|
+
* instead of TypeScript configs, the one `package.json` script that builds
|
|
18
|
+
* it (`script`), which only an admitted Computer build runs. Every path is
|
|
19
|
+
* relative to the manifest that declares it. Unknown build fields are
|
|
20
|
+
* refused, so a builder never silently ignores a policy it does not know.
|
|
18
21
|
*/
|
|
19
22
|
export const workspaceBuildPolicySchema = Schema.Struct({
|
|
20
23
|
react: Schema.optional(Schema.Boolean),
|
|
@@ -22,13 +25,25 @@ export const workspaceBuildPolicySchema = Schema.Struct({
|
|
|
22
25
|
packageStylesheets: Schema.optional(Schema.Array(Schema.String)),
|
|
23
26
|
stylesheets: Schema.optional(Schema.Array(manifestPathSchema)),
|
|
24
27
|
typescript: Schema.optional(Schema.Array(manifestPathSchema)),
|
|
28
|
+
/** A script name in this manifest's own `scripts`. */
|
|
29
|
+
script: Schema.optional(Schema.NonEmptyString),
|
|
25
30
|
stylesheet: Schema.optional(Schema.Struct({ input: manifestPathSchema, output: manifestPathSchema })),
|
|
26
31
|
});
|
|
27
32
|
const manifestSchema = Schema.fromJsonString(Schema.Struct({
|
|
28
33
|
name: Schema.optional(Schema.String),
|
|
29
34
|
dependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
30
|
-
|
|
35
|
+
scripts: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
36
|
+
fungi: Schema.optional(Schema.Struct({ build: Schema.optional(Schema.Unknown) })),
|
|
31
37
|
}));
|
|
38
|
+
/** A manifest's build policy; only `fungi.build` itself is held strict. */
|
|
39
|
+
function policyOf(manifest) {
|
|
40
|
+
const build = manifest.fungi?.build;
|
|
41
|
+
return build === undefined
|
|
42
|
+
? {}
|
|
43
|
+
: Schema.decodeUnknownSync(workspaceBuildPolicySchema, {
|
|
44
|
+
onExcessProperty: "error",
|
|
45
|
+
})(build);
|
|
46
|
+
}
|
|
32
47
|
/** One pnpm workspace project, as `pnpm ls -r --depth -1 --json` lists it. */
|
|
33
48
|
const workspaceProjectsSchema = Schema.fromJsonString(Schema.Array(Schema.Struct({
|
|
34
49
|
name: Schema.String.pipe(Schema.check(Schema.isMinLength(1))),
|
|
@@ -72,10 +87,13 @@ export async function resolveWorkspaceBuild(options) {
|
|
|
72
87
|
return decoded;
|
|
73
88
|
};
|
|
74
89
|
const projects = new Map();
|
|
90
|
+
let rootProject = null;
|
|
75
91
|
for (const project of Schema.decodeUnknownSync(workspaceProjectsSchema)(options.projects)) {
|
|
76
92
|
const directory = within(root, project.path);
|
|
77
|
-
if (directory === "")
|
|
93
|
+
if (directory === "") {
|
|
94
|
+
rootProject = project.name;
|
|
78
95
|
continue;
|
|
96
|
+
}
|
|
79
97
|
if (projects.has(project.name))
|
|
80
98
|
throw new Error(`Workspace package name repeats: ${project.name}`);
|
|
81
99
|
projects.set(project.name, directory);
|
|
@@ -83,15 +101,26 @@ export async function resolveWorkspaceBuild(options) {
|
|
|
83
101
|
const owners = [...projects]
|
|
84
102
|
.filter(([, directory]) => entry.startsWith(`${directory}/`))
|
|
85
103
|
.sort(([, left], [, right]) => right.length - left.length);
|
|
86
|
-
|
|
104
|
+
// A workspace member owns its entry; with none, an App's own repository
|
|
105
|
+
// (one package, the App itself at the root) owns it.
|
|
106
|
+
const owner = owners[0] ??
|
|
107
|
+
(projects.size === 0 && rootProject !== null
|
|
108
|
+
? [rootProject, ""]
|
|
109
|
+
: undefined);
|
|
87
110
|
if (!owner)
|
|
88
111
|
throw new Error(`No workspace package owns ${entry}`);
|
|
89
112
|
const [packageName, directory] = owner;
|
|
90
|
-
|
|
113
|
+
const parent = (path) => {
|
|
114
|
+
const up = dirname(path);
|
|
115
|
+
return up === "." ? "" : up;
|
|
116
|
+
};
|
|
117
|
+
let appDirectory = parent(entry);
|
|
91
118
|
while (appDirectory !== directory &&
|
|
92
119
|
!(await exists(join(root, appDirectory, "package.json"))))
|
|
93
|
-
appDirectory =
|
|
94
|
-
const policy = (await manifest(appDirectory))
|
|
120
|
+
appDirectory = parent(appDirectory);
|
|
121
|
+
const policy = policyOf(await manifest(appDirectory));
|
|
122
|
+
if (policy.script !== undefined)
|
|
123
|
+
throw new Error(`${appDirectory} is the App; its page builds with the recipe, not a script`);
|
|
95
124
|
const dependencies = [];
|
|
96
125
|
const visiting = new Set([directory]);
|
|
97
126
|
const visit = async (from) => {
|
|
@@ -114,20 +143,35 @@ export async function resolveWorkspaceBuild(options) {
|
|
|
114
143
|
await visit(directory);
|
|
115
144
|
const steps = [];
|
|
116
145
|
for (const dependency of dependencies) {
|
|
146
|
+
const declared = await manifest(dependency);
|
|
147
|
+
const own = policyOf(declared);
|
|
148
|
+
if (own.script !== undefined) {
|
|
149
|
+
if (own.typescript !== undefined)
|
|
150
|
+
throw new Error(`${dependency} declares both a build script and TypeScript configs`);
|
|
151
|
+
if (declared.scripts === undefined ||
|
|
152
|
+
!Object.hasOwn(declared.scripts, own.script))
|
|
153
|
+
throw new Error(`${dependency} has no script named ${own.script}`);
|
|
154
|
+
steps.push({ kind: "script", directory: dependency, script: own.script });
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
117
157
|
if (await exists(join(root, dependency, "tsconfig.build.json")))
|
|
118
158
|
steps.push({
|
|
119
159
|
kind: "typescript",
|
|
120
160
|
config: `${dependency}/tsconfig.build.json`,
|
|
121
161
|
});
|
|
122
|
-
for (const config of
|
|
123
|
-
?.typescript ?? [])
|
|
162
|
+
for (const config of own.typescript ?? [])
|
|
124
163
|
steps.push({ kind: "typescript", config: `${dependency}/${config}` });
|
|
125
164
|
}
|
|
165
|
+
const appManifest = await manifest(appDirectory);
|
|
126
166
|
for (const name of policy.packageStylesheets ?? []) {
|
|
127
167
|
const owner = projects.get(name);
|
|
168
|
+
// A package installed from a registry ships its built stylesheet; only a
|
|
169
|
+
// workspace package's stylesheet compiles here.
|
|
170
|
+
if (owner === undefined && appManifest.dependencies?.[name] !== undefined)
|
|
171
|
+
continue;
|
|
128
172
|
if (owner === undefined || !dependencies.includes(owner))
|
|
129
173
|
throw new Error(`Stylesheet package ${name} is not a workspace dependency`);
|
|
130
|
-
const declared = (await manifest(owner)).
|
|
174
|
+
const declared = policyOf(await manifest(owner)).stylesheet;
|
|
131
175
|
if (!declared)
|
|
132
176
|
throw new Error(`${name} declares no stylesheet`);
|
|
133
177
|
steps.push({
|
package/dist/build-contract.d.ts
CHANGED
|
@@ -2,3 +2,6 @@
|
|
|
2
2
|
export * from "./build/effect.js";
|
|
3
3
|
/** Team App candidate wire contract: submission schema and codec. */
|
|
4
4
|
export * from "./build/submission-contract.js";
|
|
5
|
+
export { workspaceBuildPolicy } from "./build/build-policy.js";
|
|
6
|
+
/** A build's uploaded objects in an R2-shaped, content-addressed store. */
|
|
7
|
+
export * from "./build/content-objects.js";
|
package/dist/build-contract.js
CHANGED
|
@@ -2,3 +2,6 @@
|
|
|
2
2
|
export * from "./build/effect.js";
|
|
3
3
|
/** Team App candidate wire contract: submission schema and codec. */
|
|
4
4
|
export * from "./build/submission-contract.js";
|
|
5
|
+
export { workspaceBuildPolicy } from "./build/build-policy.js";
|
|
6
|
+
/** A build's uploaded objects in an R2-shaped, content-addressed store. */
|
|
7
|
+
export * from "./build/content-objects.js";
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type CandidateObject, type CandidateObjectMediaType, type TeamAppCandidateManifest } from "./build-contract.js";
|
|
2
|
+
/** The two facts a build job reports; a host's Team Git event is a superset
|
|
3
|
+
* that also carries pushes, so a host's own record callback is always
|
|
4
|
+
* assignable here. */
|
|
5
|
+
export type BuildGitEvent = Readonly<{
|
|
6
|
+
kind: "build-artifact";
|
|
7
|
+
jobId: string;
|
|
8
|
+
credentialId: string;
|
|
9
|
+
digest: string;
|
|
10
|
+
bytes: number;
|
|
11
|
+
}> | Readonly<{
|
|
12
|
+
kind: "build-status";
|
|
13
|
+
jobId: string;
|
|
14
|
+
credentialId: string;
|
|
15
|
+
status: "succeeded" | "failed";
|
|
16
|
+
detail: string | null;
|
|
17
|
+
}>;
|
|
18
|
+
/** A Git principal admitted to read exactly one live build job's one
|
|
19
|
+
* repository: the job-scoped read clone. It never pushes, and its reads are
|
|
20
|
+
* never recorded as Team Git push events. */
|
|
21
|
+
export type BuildGitPrincipal = Readonly<{
|
|
22
|
+
kind: "build";
|
|
23
|
+
jobId: string;
|
|
24
|
+
credentialId: string;
|
|
25
|
+
}>;
|
|
26
|
+
/** True when an admitted Git principal is a build job's job-scoped read
|
|
27
|
+
* credential rather than an Agent or a Workflow Git pass. A host's Git
|
|
28
|
+
* egress uses this to admit the job-scoped read clone while refusing any
|
|
29
|
+
* write and skipping push-event recording for it. Generic so a host's own
|
|
30
|
+
* wider principal union still narrows away the "build" member afterward,
|
|
31
|
+
* exactly as the literal `principal.kind === "build"` comparison it
|
|
32
|
+
* replaces did. */
|
|
33
|
+
export declare function isBuildGitPrincipal<P extends Readonly<{
|
|
34
|
+
kind: string;
|
|
35
|
+
}>>(principal: P): principal is Extract<P, Readonly<{
|
|
36
|
+
kind: "build";
|
|
37
|
+
}>>;
|
|
38
|
+
/**
|
|
39
|
+
* The build-report handler for `/jobs/:id/(candidate|artifact|status)` and
|
|
40
|
+
* `PUT /jobs/:id/objects/sha256-<hex>`. `admitBuild` is the host's own
|
|
41
|
+
* live-job lookup; `admitObject` admits one object upload for a live job's
|
|
42
|
+
* grant and answers its size bound; `storeObject` streams an admitted object
|
|
43
|
+
* into the host's content-addressed store, which verifies its digest;
|
|
44
|
+
* `submitCandidate` hands an admitted manifest to the host's Publisher
|
|
45
|
+
* custody; `record` delivers a reported fact to the host's own projection.
|
|
46
|
+
* `report` observes a body or JSON decode failure without changing its 400
|
|
47
|
+
* outcome; a host may wire its own structured logger or omit it.
|
|
48
|
+
*/
|
|
49
|
+
export declare function createBuildEgress(options: Readonly<{
|
|
50
|
+
admitBuild: (jobId: string) => Promise<Readonly<{
|
|
51
|
+
credentialId: string;
|
|
52
|
+
}> | null>;
|
|
53
|
+
admitObject: (jobId: string, grant: string) => Promise<Readonly<{
|
|
54
|
+
maxObjectBytes: number;
|
|
55
|
+
}> | null>;
|
|
56
|
+
storeObject: (object: CandidateObject, body: ReadableStream<Uint8Array> | null, mediaType: CandidateObjectMediaType) => Promise<"stored" | "refused">;
|
|
57
|
+
submitCandidate: (jobId: string, manifest: TeamAppCandidateManifest, grant: string, credentialId: string) => Promise<Response>;
|
|
58
|
+
record: (event: BuildGitEvent) => Promise<void>;
|
|
59
|
+
report?: (operation: string, cause: unknown) => void;
|
|
60
|
+
}>): Readonly<{
|
|
61
|
+
fetch(request: Request, url: URL): Promise<Response>;
|
|
62
|
+
}>;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import * as Schema from "effect/Schema";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { teamAppCandidateManifestSchema, TEAM_APP_CANDIDATE_MANIFEST_MAX_BYTES, candidateObjectMediaType, candidateObjectSchema, } from "./build-contract.js";
|
|
4
|
+
/**
|
|
5
|
+
* The Computer's own egress for its build job's `/jobs/:id/*` reports. A
|
|
6
|
+
* live build uploads each built object alone, streamed straight into the
|
|
7
|
+
* host's content-addressed store, then submits the small manifest naming
|
|
8
|
+
* them through Team submission custody, then reports bytes/status. Selection
|
|
9
|
+
* and promotion are never admitted here. The
|
|
10
|
+
* Computer presents no credential of its own: every decision is
|
|
11
|
+
* port-injected so a host (Hub today, Oyster's standalone host once it
|
|
12
|
+
* mounts a Computer build) supplies its own admission, submission and event
|
|
13
|
+
* recording without this module reaching into either host's state.
|
|
14
|
+
*/
|
|
15
|
+
const statusSchema = z.lazy(() => z.strictObject({
|
|
16
|
+
status: z.enum(["succeeded", "failed"]),
|
|
17
|
+
detail: z.string().max(2_000).optional(),
|
|
18
|
+
}));
|
|
19
|
+
const artifactSchema = z.lazy(() => z.strictObject({
|
|
20
|
+
digest: z.string().regex(/^sha256:[0-9a-f]{64}$/),
|
|
21
|
+
bytes: z.number().int().nonnegative(),
|
|
22
|
+
}));
|
|
23
|
+
/** True when an admitted Git principal is a build job's job-scoped read
|
|
24
|
+
* credential rather than an Agent or a Workflow Git pass. A host's Git
|
|
25
|
+
* egress uses this to admit the job-scoped read clone while refusing any
|
|
26
|
+
* write and skipping push-event recording for it. Generic so a host's own
|
|
27
|
+
* wider principal union still narrows away the "build" member afterward,
|
|
28
|
+
* exactly as the literal `principal.kind === "build"` comparison it
|
|
29
|
+
* replaces did. */
|
|
30
|
+
export function isBuildGitPrincipal(principal) {
|
|
31
|
+
return principal.kind === "build";
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The build-report handler for `/jobs/:id/(candidate|artifact|status)` and
|
|
35
|
+
* `PUT /jobs/:id/objects/sha256-<hex>`. `admitBuild` is the host's own
|
|
36
|
+
* live-job lookup; `admitObject` admits one object upload for a live job's
|
|
37
|
+
* grant and answers its size bound; `storeObject` streams an admitted object
|
|
38
|
+
* into the host's content-addressed store, which verifies its digest;
|
|
39
|
+
* `submitCandidate` hands an admitted manifest to the host's Publisher
|
|
40
|
+
* custody; `record` delivers a reported fact to the host's own projection.
|
|
41
|
+
* `report` observes a body or JSON decode failure without changing its 400
|
|
42
|
+
* outcome; a host may wire its own structured logger or omit it.
|
|
43
|
+
*/
|
|
44
|
+
export function createBuildEgress(options) {
|
|
45
|
+
const report = options.report ??
|
|
46
|
+
((operation, cause) => {
|
|
47
|
+
console.error(operation, cause);
|
|
48
|
+
});
|
|
49
|
+
const putObject = async (request, jobId, hex) => {
|
|
50
|
+
const admitted = await options.admitObject(jobId, request.headers.get("X-Team-App-Build") ?? "");
|
|
51
|
+
if (admitted === null)
|
|
52
|
+
return new Response(null, { status: 403 });
|
|
53
|
+
// The stated length bounds what the store takes before a byte moves.
|
|
54
|
+
const length = request.headers.get("Content-Length") ?? "";
|
|
55
|
+
if (!/^(0|[1-9][0-9]{0,14})$/u.test(length))
|
|
56
|
+
return new Response(null, { status: 411 });
|
|
57
|
+
const byteLength = Number(length);
|
|
58
|
+
if (byteLength > admitted.maxObjectBytes) {
|
|
59
|
+
await request.body?.cancel();
|
|
60
|
+
return new Response(null, { status: 413 });
|
|
61
|
+
}
|
|
62
|
+
const mediaType = candidateObjectMediaType(request.headers.get("Content-Type"));
|
|
63
|
+
if (mediaType === null) {
|
|
64
|
+
await request.body?.cancel();
|
|
65
|
+
return new Response(null, { status: 415 });
|
|
66
|
+
}
|
|
67
|
+
const stored = await options.storeObject(Schema.decodeUnknownSync(candidateObjectSchema)({
|
|
68
|
+
digest: `sha256:${hex}`,
|
|
69
|
+
byteLength,
|
|
70
|
+
}), request.body, mediaType);
|
|
71
|
+
return new Response(null, { status: stored === "stored" ? 204 : 400 });
|
|
72
|
+
};
|
|
73
|
+
return Object.freeze({
|
|
74
|
+
async fetch(request, url) {
|
|
75
|
+
if (url.search !== "" || url.username || url.password)
|
|
76
|
+
return new Response(null, { status: 403 });
|
|
77
|
+
const object = /^\/jobs\/([a-zA-Z0-9_-]{1,128})\/objects\/sha256-([0-9a-f]{64})$/u.exec(url.pathname);
|
|
78
|
+
if (object !== null) {
|
|
79
|
+
const [, jobId = "", hex = ""] = object;
|
|
80
|
+
return request.method === "PUT"
|
|
81
|
+
? putObject(request, jobId, hex)
|
|
82
|
+
: new Response(null, { status: 403 });
|
|
83
|
+
}
|
|
84
|
+
const match = /^\/jobs\/([a-zA-Z0-9_-]{1,128})\/(status|artifact|candidate)$/u.exec(url.pathname);
|
|
85
|
+
const jobId = match?.[1];
|
|
86
|
+
const action = match?.[2];
|
|
87
|
+
if (jobId === undefined || request.method !== "POST")
|
|
88
|
+
return new Response(null, { status: 403 });
|
|
89
|
+
const job = await options.admitBuild(jobId);
|
|
90
|
+
if (job === null)
|
|
91
|
+
return new Response(null, { status: 403 });
|
|
92
|
+
if (action === "candidate") {
|
|
93
|
+
let manifest;
|
|
94
|
+
try {
|
|
95
|
+
if (request.body === null)
|
|
96
|
+
return new Response(null, { status: 400 });
|
|
97
|
+
let size = 0;
|
|
98
|
+
const bounded = request.body.pipeThrough(new TransformStream({
|
|
99
|
+
transform(chunk, controller) {
|
|
100
|
+
size += chunk.byteLength;
|
|
101
|
+
if (size > TEAM_APP_CANDIDATE_MANIFEST_MAX_BYTES)
|
|
102
|
+
throw new Error("candidate manifest oversize");
|
|
103
|
+
controller.enqueue(chunk);
|
|
104
|
+
},
|
|
105
|
+
}));
|
|
106
|
+
manifest = Schema.decodeUnknownSync(teamAppCandidateManifestSchema, {
|
|
107
|
+
onExcessProperty: "error",
|
|
108
|
+
})(await new Response(bounded).json());
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return new Response(null, { status: 400 });
|
|
112
|
+
}
|
|
113
|
+
return options.submitCandidate(jobId, manifest, request.headers.get("X-Team-App-Build") ?? "", job.credentialId);
|
|
114
|
+
}
|
|
115
|
+
let json;
|
|
116
|
+
try {
|
|
117
|
+
json = await request.json();
|
|
118
|
+
}
|
|
119
|
+
catch (cause) {
|
|
120
|
+
report("packages/oyster/src/build-egress.ts:report", cause);
|
|
121
|
+
return new Response(null, { status: 400 });
|
|
122
|
+
}
|
|
123
|
+
if (action === "artifact") {
|
|
124
|
+
const artifact = artifactSchema.safeParse(json);
|
|
125
|
+
if (!artifact.success)
|
|
126
|
+
return new Response(null, { status: 400 });
|
|
127
|
+
await options.record({
|
|
128
|
+
kind: "build-artifact",
|
|
129
|
+
jobId,
|
|
130
|
+
credentialId: job.credentialId,
|
|
131
|
+
...artifact.data,
|
|
132
|
+
});
|
|
133
|
+
return new Response(null, { status: 204 });
|
|
134
|
+
}
|
|
135
|
+
const status = statusSchema.safeParse(json);
|
|
136
|
+
if (!status.success)
|
|
137
|
+
return new Response(null, { status: 400 });
|
|
138
|
+
await options.record({
|
|
139
|
+
kind: "build-status",
|
|
140
|
+
jobId,
|
|
141
|
+
credentialId: job.credentialId,
|
|
142
|
+
status: status.data.status,
|
|
143
|
+
detail: status.data.detail ?? null,
|
|
144
|
+
});
|
|
145
|
+
return new Response(null, { status: 204 });
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
}
|
package/dist/build.d.ts
CHANGED
|
@@ -9,3 +9,4 @@ export { frozenSnapshotInstallArguments } from "./build/snapshot-package-store.j
|
|
|
9
9
|
export { repositoryEnvironment, artifactsGitEnvironment, hostedArtifactsGitRemote, } from "./build/git-environment.js";
|
|
10
10
|
/** Captured-dependency build program with guarded filesystem access. */
|
|
11
11
|
export declare const dependencyBuildProgram: string;
|
|
12
|
+
export { stageBuildOutput, writeArtifactRoot, type ImmutableArtifactObjects, type StagedArtifactObject, type StagedBuildOutput, } from "./build/artifact-writer.js";
|
package/dist/build.js
CHANGED
|
@@ -11,3 +11,4 @@ export { frozenSnapshotInstallArguments } from "./build/snapshot-package-store.j
|
|
|
11
11
|
export { repositoryEnvironment, artifactsGitEnvironment, hostedArtifactsGitRemote, } from "./build/git-environment.js";
|
|
12
12
|
/** Captured-dependency build program with guarded filesystem access. */
|
|
13
13
|
export const dependencyBuildProgram = bundledDependencyBuildProgram;
|
|
14
|
+
export { stageBuildOutput, writeArtifactRoot, } from "./build/artifact-writer.js";
|
package/dist/cloudflare.js
CHANGED
|
@@ -154,20 +154,20 @@ export function frameBindingEntrypoint(methods, dispatch) {
|
|
|
154
154
|
}
|
|
155
155
|
};
|
|
156
156
|
}
|
|
157
|
-
const facetHttpReplySchema = z.strictObject({
|
|
157
|
+
const facetHttpReplySchema = z.lazy(() => z.strictObject({
|
|
158
158
|
status: z.number().int(),
|
|
159
159
|
statusText: z.string(),
|
|
160
160
|
headers: z.array(z.tuple([z.string(), z.string()])),
|
|
161
161
|
body: z.instanceof(ReadableStream).nullable(),
|
|
162
|
-
});
|
|
162
|
+
}));
|
|
163
163
|
// App methods return JSON; HTTP carries binary bytes and lifecycle hooks return void.
|
|
164
164
|
// Binding operation results keep their separate, operation-specific decoders.
|
|
165
|
-
const facetHttpInputSchema = z.strictObject({
|
|
165
|
+
const facetHttpInputSchema = z.lazy(() => z.strictObject({
|
|
166
166
|
url: z.string(),
|
|
167
167
|
method: z.string(),
|
|
168
168
|
headers: z.array(z.tuple([z.string(), z.string()])),
|
|
169
169
|
body: z.instanceof(Uint8Array).nullable(),
|
|
170
|
-
});
|
|
170
|
+
}));
|
|
171
171
|
const facetInvocationSchema = rpcHandleSchema({
|
|
172
172
|
invoke: rpcMethod(z.union([z.json(), z.undefined()])).input(z.tuple([z.string(), z.array(z.unknown()), z.unknown()])),
|
|
173
173
|
http: rpcMethod(facetHttpReplySchema).input(z.tuple([facetHttpInputSchema, z.unknown()])),
|