ignotum 0.0.2 → 0.0.4

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 (64) hide show
  1. package/README.md +23 -7
  2. package/dist/cli/bin.mjs +32313 -751
  3. package/dist/cli/bin.mjs.map +1 -1
  4. package/dist/runtime/{api-Cpx57mk3.d.ts → api-D2bsAz4T.d.ts} +3 -2
  5. package/dist/runtime/{api-BXXIZc_Q.js → api-DtX8qPrq.js} +58 -27
  6. package/dist/runtime/api-DtX8qPrq.js.map +1 -0
  7. package/dist/runtime/client.d.ts +11 -4
  8. package/dist/runtime/client.js +343 -79
  9. package/dist/runtime/client.js.map +1 -1
  10. package/dist/runtime/{result-B2W-z2wG.js → descriptor-t6BOEGw9-BTHR-ZMv.js} +119 -4
  11. package/dist/runtime/descriptor-t6BOEGw9-BTHR-ZMv.js.map +1 -0
  12. package/dist/runtime/id-Btwac71X-B9OeBzvq.d.ts +9 -0
  13. package/dist/runtime/id-D570vudg.js +26 -0
  14. package/dist/runtime/id-D570vudg.js.map +1 -0
  15. package/dist/runtime/{index-BgWROoyk.d.ts → index-Dl_VQGmA.d.ts} +114 -31
  16. package/dist/runtime/internal/api.d.ts +1 -1
  17. package/dist/runtime/internal/api.js +1 -1
  18. package/dist/runtime/internal/host.d.ts +44 -0
  19. package/dist/runtime/internal/host.js +213 -0
  20. package/dist/runtime/internal/host.js.map +1 -0
  21. package/dist/runtime/internal/server.d.ts +1 -1
  22. package/dist/runtime/internal/server.js +1 -1
  23. package/dist/runtime/internal/types.d.ts +1 -1
  24. package/dist/runtime/internal/types.js +1 -1
  25. package/dist/runtime/{result-C1ZdsM6Y.d.ts → result-BgcHn25t.d.ts} +21 -6
  26. package/dist/runtime/schema-B-jMZEbs.js +227 -0
  27. package/dist/runtime/schema-B-jMZEbs.js.map +1 -0
  28. package/dist/runtime/server.d.ts +2 -2
  29. package/dist/runtime/server.js +2 -2
  30. package/package.json +15 -5
  31. package/src/cli/agent-files.ts +6 -2
  32. package/src/cli/app-configuration.ts +135 -0
  33. package/src/cli/bin.ts +24 -1
  34. package/src/cli/build/artifact.ts +95 -0
  35. package/src/cli/build/client.ts +103 -0
  36. package/src/cli/build/server.ts +526 -0
  37. package/src/cli/client-config.ts +141 -0
  38. package/src/cli/client-entry.ts +152 -0
  39. package/src/cli/client-plugin.ts +86 -16
  40. package/src/cli/codegen.ts +5 -5
  41. package/src/cli/command.ts +41 -9
  42. package/src/cli/control-client.ts +343 -0
  43. package/src/cli/deploy.ts +217 -0
  44. package/src/cli/dev.ts +43 -108
  45. package/src/cli/module-boundaries.ts +183 -0
  46. package/src/cli/{new-project.ts → new-app.ts} +86 -86
  47. package/src/cli/package-manager.ts +9 -9
  48. package/src/client/app.ts +15 -0
  49. package/src/client/errors.ts +1 -17
  50. package/src/client/index.ts +2 -0
  51. package/src/client/sync.ts +172 -120
  52. package/src/dev-runtime/database.ts +253 -96
  53. package/src/dev-runtime/dev-database.ts +14 -14
  54. package/src/dev-runtime/functions.ts +57 -88
  55. package/src/dev-runtime/id.ts +2 -10
  56. package/src/dev-runtime/migrations.ts +68 -0
  57. package/src/dev-runtime/sync.ts +266 -83
  58. package/src/internal/function-definition.ts +77 -0
  59. package/src/internal/host.ts +1 -0
  60. package/src/internal/http-paths.ts +3 -1
  61. package/dist/runtime/api-BXXIZc_Q.js.map +0 -1
  62. package/dist/runtime/result-B2W-z2wG.js.map +0 -1
  63. package/dist/runtime/schema-CNEVLF7D.js +0 -116
  64. package/dist/runtime/schema-CNEVLF7D.js.map +0 -1
@@ -0,0 +1,103 @@
1
+ import { Array, Effect, FileSystem, Path, String } from "effect";
2
+ import tailwindcss from "@tailwindcss/vite";
3
+ import { sha256 } from "@ignotum/deployment";
4
+
5
+ import { resolveClientBuildConfig, validateClientFiles } from "../client-config.js";
6
+ import { loadClientEntryTitle } from "../client-entry.js";
7
+ import { clientEntryId, ignotumClientPlugin, renderClientDocument } from "../client-plugin.js";
8
+ import { moduleBoundariesPlugin } from "../module-boundaries.js";
9
+ import { InvalidBuildOutput, runViteBuild } from "./artifact.js";
10
+
11
+ export interface ClientBuildResult {
12
+ readonly assets: number;
13
+ readonly shellPath: string;
14
+ }
15
+
16
+ export const buildClient = Effect.fn("Deploy.buildClient")(function* (
17
+ appDirectory: string,
18
+ outputDirectory: string,
19
+ ) {
20
+ const fileSystem = yield* FileSystem.FileSystem;
21
+ const path = yield* Path.Path;
22
+ const clientFiles = yield* validateClientFiles(appDirectory);
23
+ const clientDirectory = clientFiles.clientDirectory;
24
+ const title = yield* loadClientEntryTitle(clientFiles.entryPath);
25
+ const clientConfig = yield* resolveClientBuildConfig(false);
26
+ const boundaries = yield* moduleBoundariesPlugin("Client", appDirectory);
27
+
28
+ yield* fileSystem.makeDirectory(outputDirectory, { recursive: true });
29
+
30
+ const output = yield* runViteBuild("client", {
31
+ appType: "spa",
32
+ build: {
33
+ assetsDir: "assets",
34
+ emptyOutDir: true,
35
+ outDir: outputDirectory,
36
+ rolldownOptions: {
37
+ input: clientEntryId,
38
+ output: {
39
+ assetFileNames: "assets/[name]-[hash][extname]",
40
+ chunkFileNames: "assets/[name]-[hash].js",
41
+ entryFileNames: "assets/[name]-[hash].js",
42
+ },
43
+ },
44
+ sourcemap: false,
45
+ },
46
+ configFile: false,
47
+ logLevel: "warn",
48
+ mode: "production",
49
+ oxc: {
50
+ jsx: {
51
+ importSource: "ignotum/client",
52
+ runtime: "automatic",
53
+ },
54
+ },
55
+ plugins: [
56
+ boundaries,
57
+ ignotumClientPlugin({ development: false, icon: undefined, loadTitle: undefined }),
58
+ tailwindcss(),
59
+ ],
60
+ publicDir: false,
61
+ resolve: {
62
+ alias: clientConfig.aliases,
63
+ conditions: [...clientConfig.conditions],
64
+ dedupe: ["preact"],
65
+ tsconfigPaths: true,
66
+ },
67
+ root: clientDirectory,
68
+ });
69
+ const entry = Array.findFirst(output, (item) => item.type === "chunk" && item.isEntry);
70
+ const entryFile = yield* Effect.fromOption(entry, () =>
71
+ InvalidBuildOutput.make({ message: "The client build did not emit an entry chunk." }),
72
+ ).pipe(Effect.map((item) => item.fileName));
73
+ const styles = Array.sort(String.Order)(
74
+ Array.map(
75
+ Array.filter(output, (item) => item.type === "asset" && item.fileName.endsWith(".css")),
76
+ (item) => item.fileName,
77
+ ),
78
+ );
79
+ let icon: string | undefined;
80
+ if (clientFiles.iconPath !== undefined) {
81
+ const bytes = yield* fileSystem.readFile(clientFiles.iconPath);
82
+ const digest = yield* sha256(bytes);
83
+ const fileName = `icon-${digest}.svg`;
84
+ const assetsDirectory = path.join(outputDirectory, "assets");
85
+ yield* fileSystem.makeDirectory(assetsDirectory, { recursive: true });
86
+ yield* fileSystem.writeFile(path.join(assetsDirectory, fileName), bytes);
87
+ icon = `/assets/${fileName}`;
88
+ }
89
+ const shellPath = path.join(outputDirectory, "_shell.html");
90
+ const document = renderClientDocument({
91
+ icon,
92
+ scripts: [{ source: entryFile, type: "External" }],
93
+ styles,
94
+ title,
95
+ });
96
+
97
+ yield* fileSystem.writeFileString(shellPath, document);
98
+
99
+ return {
100
+ assets: output.length + (icon === undefined ? 0 : 1),
101
+ shellPath,
102
+ } satisfies ClientBuildResult;
103
+ });
@@ -0,0 +1,526 @@
1
+ import { createRequire } from "node:module";
2
+ import process from "node:process";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ import * as Config from "effect/Config";
6
+ import { Array, Effect, FileSystem, Path, Predicate, Schema, String } from "effect";
7
+ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
8
+ import { createServer, defaultServerConditions, normalizePath } from "vite";
9
+ import type { Plugin, ViteDevServer } from "vite";
10
+
11
+ import {
12
+ ServerBuildManifest,
13
+ SchemaSnapshot,
14
+ schemaSnapshotPath,
15
+ type ServerFunctionArtifact,
16
+ } from "@ignotum/contracts/deployment";
17
+ import { FunctionAddress } from "@ignotum/contracts/runtime/sync";
18
+ import type { FunctionKind } from "@ignotum/contracts/runtime/functions";
19
+ import { isDefinedSchema, type DefinedSchema, type Tables } from "@ignotum/contracts/schema";
20
+ import {
21
+ artifactReference,
22
+ encodeCanonical,
23
+ encodeSchemaSnapshot,
24
+ makeSchemaSnapshot,
25
+ utf8Bytes,
26
+ } from "@ignotum/deployment";
27
+
28
+ import { inspectRuntimeFunctionDefinition } from "../../internal/function-definition.js";
29
+ import { moduleBoundariesPlugin, BuildBoundaryViolation } from "../module-boundaries.js";
30
+ import {
31
+ causeMessage,
32
+ InvalidBuildOutput,
33
+ runViteBuild,
34
+ sourceMapPathTransform,
35
+ ViteBuildFailed,
36
+ type GeneratedChunk,
37
+ } from "./artifact.js";
38
+
39
+ const hostModuleId = "ignotum:host";
40
+ const encodeJavaScriptString = Schema.encodeSync(Schema.fromJsonString(Schema.String));
41
+
42
+ const DiscoveredServerFunction = Schema.Struct({
43
+ address: FunctionAddress,
44
+ exportName: Schema.String,
45
+ kind: Schema.Literals(["Mutation", "Query"]),
46
+ moduleName: Schema.String,
47
+ modulePath: Schema.String,
48
+ });
49
+ interface DiscoveredServerFunction {
50
+ readonly address: FunctionAddress;
51
+ readonly exportName: string;
52
+ readonly kind: FunctionKind;
53
+ readonly moduleName: string;
54
+ readonly modulePath: string;
55
+ }
56
+
57
+ const ServerDiscoveryManifest = Schema.Struct({
58
+ functions: Schema.Array(DiscoveredServerFunction),
59
+ schema: SchemaSnapshot,
60
+ });
61
+ type ServerDiscoveryManifest = typeof ServerDiscoveryManifest.Type;
62
+ const ServerDiscoveryManifestJson = Schema.fromJsonString(ServerDiscoveryManifest);
63
+ const ServerModuleNamesJson = Schema.fromJsonString(Schema.Array(Schema.String));
64
+
65
+ export interface ServerBuildResult {
66
+ readonly functions: number;
67
+ readonly manifestPath: string;
68
+ }
69
+
70
+ export class ServerFunctionDiscoveryFailed extends Schema.TaggedError<ServerFunctionDiscoveryFailed>()(
71
+ "ServerFunctionDiscoveryFailed",
72
+ {
73
+ cause: Schema.Defect(),
74
+ message: Schema.String,
75
+ path: Schema.String,
76
+ },
77
+ ) {}
78
+
79
+ export class InvalidServerFunctionExport extends Schema.TaggedError<InvalidServerFunctionExport>()(
80
+ "InvalidServerFunctionExport",
81
+ {
82
+ exportName: Schema.String,
83
+ message: Schema.String,
84
+ path: Schema.String,
85
+ },
86
+ ) {}
87
+
88
+ const serverConditions = Effect.fn("Deploy.serverConditions")(function* () {
89
+ const fileSystem = yield* FileSystem.FileSystem;
90
+ const sourceServerEntry = fileURLToPath(new URL("../../internal/server.ts", import.meta.url));
91
+ const hasWorkspaceSourceExports = yield* fileSystem.exists(sourceServerEntry);
92
+
93
+ return [...(hasWorkspaceSourceExports ? ["@ignotum/source"] : []), ...defaultServerConditions];
94
+ });
95
+
96
+ const acquireModuleRunner = Effect.fn("Deploy.acquireServerModuleRunner")(function* (
97
+ appDirectory: string,
98
+ conditions: ReadonlyArray<string>,
99
+ ) {
100
+ const boundaries = yield* moduleBoundariesPlugin("Server", appDirectory);
101
+ const discoveryCacheDirectory = yield* Config.string("IGNOTUM_DISCOVERY_CACHE_DIR").pipe(
102
+ Config.withDefault(""),
103
+ );
104
+ return yield* Effect.acquireRelease(
105
+ Effect.tryPromise({
106
+ try: () =>
107
+ createServer({
108
+ appType: "custom",
109
+ configFile: false,
110
+ cacheDir: discoveryCacheDirectory.length > 0 ? discoveryCacheDirectory : undefined,
111
+ css: { postcss: {} },
112
+ logLevel: "warn",
113
+ mode: "production",
114
+ optimizeDeps: { noDiscovery: true },
115
+ plugins: [boundaries],
116
+ resolve: {
117
+ conditions: [...conditions],
118
+ noExternal: true,
119
+ tsconfigPaths: true,
120
+ },
121
+ root: appDirectory,
122
+ server: {
123
+ forwardConsole: false,
124
+ middlewareMode: true,
125
+ fs: { strict: true, allow: [appDirectory] },
126
+ watch: null,
127
+ },
128
+ ssr: {
129
+ noExternal: true,
130
+ resolve: { conditions: [...conditions] },
131
+ },
132
+ }),
133
+ catch: (cause) =>
134
+ Schema.is(BuildBoundaryViolation)(cause)
135
+ ? cause
136
+ : ViteBuildFailed.make({
137
+ cause,
138
+ message: `Vite could not create the server function module runner: ${causeMessage(cause)}`,
139
+ }),
140
+ }),
141
+ (server) => Effect.promise(() => server.close()),
142
+ );
143
+ });
144
+
145
+ const discoverModuleFunctions = Effect.fn("Deploy.discoverServerModule")(function* (
146
+ server: ViteDevServer,
147
+ appDirectory: string,
148
+ moduleName: string,
149
+ ) {
150
+ const path = yield* Path.Path;
151
+ const modulePath = path.join(appDirectory, "server", `${moduleName}.ts`);
152
+ const loadedModule = yield* Effect.tryPromise({
153
+ try: () => server.ssrLoadModule(normalizePath(modulePath)),
154
+ catch: (cause) =>
155
+ Schema.is(BuildBoundaryViolation)(cause)
156
+ ? cause
157
+ : ServerFunctionDiscoveryFailed.make({
158
+ cause,
159
+ message: `Could not load server function module ${moduleName}: ${causeMessage(cause)}`,
160
+ path: modulePath,
161
+ }),
162
+ });
163
+ const functions: globalThis.Array<DiscoveredServerFunction> = [];
164
+
165
+ for (const [exportName, candidate] of Object.entries(loadedModule)) {
166
+ if (!Predicate.isObject(candidate)) continue;
167
+ const inspected = inspectRuntimeFunctionDefinition(candidate);
168
+ if (inspected === undefined) {
169
+ if (
170
+ Predicate.hasProperty(candidate, "_tag") &&
171
+ (candidate._tag === "Mutation" || candidate._tag === "Query")
172
+ ) {
173
+ return yield* InvalidServerFunctionExport.make({
174
+ exportName,
175
+ message: `${moduleName}.${exportName} looks like a server function but is not a valid schema-bound definition.`,
176
+ path: modulePath,
177
+ });
178
+ }
179
+ continue;
180
+ }
181
+
182
+ functions.push({
183
+ address: FunctionAddress.make(`api.${moduleName}.${exportName}`),
184
+ exportName,
185
+ kind: inspected.definition._tag,
186
+ moduleName,
187
+ modulePath,
188
+ });
189
+ }
190
+
191
+ return Array.sortWith(functions, (definition) => definition.address, String.Order);
192
+ });
193
+
194
+ const discoverServerFunctionsInProcess = Effect.fn("Deploy.discoverServerFunctions")(function* (
195
+ appDirectory: string,
196
+ moduleNames: ReadonlyArray<string>,
197
+ conditions: ReadonlyArray<string>,
198
+ ) {
199
+ return yield* Effect.scoped(
200
+ Effect.gen(function* () {
201
+ const server = yield* acquireModuleRunner(appDirectory, conditions);
202
+ const path = yield* Path.Path;
203
+ const schemaPath = path.join(appDirectory, "server", "schema.ts");
204
+ const loadedSchemaModule = yield* Effect.tryPromise({
205
+ try: () => server.ssrLoadModule(normalizePath(schemaPath)),
206
+ catch: (cause) =>
207
+ Schema.is(BuildBoundaryViolation)(cause)
208
+ ? cause
209
+ : ServerFunctionDiscoveryFailed.make({
210
+ cause,
211
+ message: `Could not load the application schema: ${causeMessage(cause)}`,
212
+ path: schemaPath,
213
+ }),
214
+ });
215
+ const schemaCandidate = yield* Schema.decodeUnknownEffect(Schema.ObjectKeyword)(
216
+ loadedSchemaModule.default,
217
+ ).pipe(
218
+ Effect.mapError(() =>
219
+ InvalidBuildOutput.make({
220
+ message: "The default export from server/schema.ts is not an Ignotum schema.",
221
+ }),
222
+ ),
223
+ );
224
+ if (!isDefinedSchema(schemaCandidate)) {
225
+ return yield* InvalidBuildOutput.make({
226
+ message: "The default export from server/schema.ts is not an Ignotum schema.",
227
+ });
228
+ }
229
+ const modules = yield* Effect.forEach(moduleNames, (moduleName) =>
230
+ discoverModuleFunctions(server, appDirectory, moduleName),
231
+ );
232
+ return {
233
+ functions: Array.flatten(modules),
234
+ schema: schemaCandidate,
235
+ } satisfies {
236
+ readonly functions: ReadonlyArray<DiscoveredServerFunction>;
237
+ readonly schema: DefinedSchema<Tables>;
238
+ };
239
+ }),
240
+ );
241
+ });
242
+
243
+ export const runServerDiscoveryWorker = Effect.fn("Deploy.runServerDiscoveryWorker")(function* (
244
+ appDirectory: string,
245
+ moduleNamesJson: string,
246
+ outputPath: string,
247
+ ) {
248
+ const fileSystem = yield* FileSystem.FileSystem;
249
+ const moduleNames = yield* Schema.decodeEffect(ServerModuleNamesJson)(moduleNamesJson).pipe(
250
+ Effect.orDie,
251
+ );
252
+ const conditions = yield* serverConditions();
253
+ const discovered = yield* discoverServerFunctionsInProcess(appDirectory, moduleNames, conditions);
254
+ const schema = yield* Effect.try({
255
+ try: () => makeSchemaSnapshot(discovered.schema),
256
+ catch: () =>
257
+ InvalidBuildOutput.make({
258
+ message: "The application schema does not have canonical Ignotum descriptors.",
259
+ }),
260
+ });
261
+ const encoded = yield* Schema.encodeEffect(ServerDiscoveryManifestJson)({
262
+ functions: discovered.functions,
263
+ schema,
264
+ });
265
+ yield* fileSystem.writeFileString(outputPath, encoded);
266
+ });
267
+
268
+ const discoveryWorkerEntry = Effect.fn("Deploy.discoveryWorkerEntry")(function* () {
269
+ const fileSystem = yield* FileSystem.FileSystem;
270
+ const sourceEntry = fileURLToPath(new URL("../../../dist/cli/bin.mjs", import.meta.url));
271
+ const bundledEntry = fileURLToPath(import.meta.url);
272
+ return (yield* fileSystem.exists(sourceEntry)) ? sourceEntry : bundledEntry;
273
+ });
274
+
275
+ const nodeModulesDirectory = (): string => {
276
+ const resolved = createRequire(import.meta.url).resolve("vite/package.json");
277
+ const marker = `${globalThis.process.platform === "win32" ? "\\" : "/"}node_modules${
278
+ globalThis.process.platform === "win32" ? "\\" : "/"
279
+ }`;
280
+ const index = resolved.indexOf(marker);
281
+ return index < 0 ? resolved : resolved.slice(0, index + marker.length - 1);
282
+ };
283
+
284
+ const discoverServerFunctions = Effect.fn("Deploy.discoverServerFunctionsIsolated")(function* (
285
+ appDirectory: string,
286
+ moduleNames: ReadonlyArray<string>,
287
+ ) {
288
+ const fileSystem = yield* FileSystem.FileSystem;
289
+ const path = yield* Path.Path;
290
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
291
+ const workerEntry = yield* discoveryWorkerEntry();
292
+ return yield* Effect.scoped(
293
+ Effect.gen(function* () {
294
+ const directory = yield* Effect.acquireRelease(
295
+ fileSystem.makeTempDirectory({ prefix: "ignotum-discovery-" }),
296
+ (temporary) =>
297
+ fileSystem.remove(temporary, { force: true, recursive: true }).pipe(Effect.orDie),
298
+ );
299
+ const outputPath = path.join(directory, "discovery.json");
300
+ const encodedModuleNames = yield* Schema.encodeEffect(ServerModuleNamesJson)(moduleNames);
301
+ const packageRoot = path.resolve(path.dirname(workerEntry), "../..");
302
+ const workspaceProbes: Array<string> = [];
303
+ let ancestor = path.resolve(appDirectory);
304
+ while (true) {
305
+ for (const name of [
306
+ "package.json",
307
+ "pnpm-workspace.yaml",
308
+ "lerna.json",
309
+ "deno.json",
310
+ "deno.jsonc",
311
+ ]) {
312
+ workspaceProbes.push(path.join(ancestor, name));
313
+ }
314
+ const parent = path.dirname(ancestor);
315
+ if (parent === ancestor) break;
316
+ ancestor = parent;
317
+ }
318
+ const readPermissions = [
319
+ appDirectory,
320
+ nodeModulesDirectory(),
321
+ packageRoot,
322
+ ...workspaceProbes,
323
+ ].map((allowedPath) => `--allow-fs-read=${allowedPath}`);
324
+ const exitCode = yield* spawner.exitCode(
325
+ ChildProcess.make(
326
+ process.execPath,
327
+ [
328
+ "--permission",
329
+ "--allow-addons",
330
+ ...readPermissions,
331
+ `--allow-fs-write=${directory}`,
332
+ workerEntry,
333
+ "__ignotum_discover_server",
334
+ appDirectory,
335
+ encodedModuleNames,
336
+ outputPath,
337
+ ],
338
+ {
339
+ cwd: appDirectory,
340
+ env: {
341
+ IGNOTUM_DISCOVERY_CACHE_DIR: path.join(directory, "vite-cache"),
342
+ IGNOTUM_ISOLATED_DISCOVERY: "1",
343
+ NODE_ENV: "production",
344
+ },
345
+ stderr: "inherit",
346
+ stdout: "ignore",
347
+ },
348
+ ),
349
+ );
350
+ if (exitCode !== ChildProcessSpawner.ExitCode(0)) {
351
+ return yield* ServerFunctionDiscoveryFailed.make({
352
+ cause: new Error(`Discovery worker exited with code ${exitCode}.`),
353
+ message: "Application metadata discovery failed in the isolated build process.",
354
+ path: appDirectory,
355
+ });
356
+ }
357
+ const encoded = yield* fileSystem.readFileString(outputPath);
358
+ return yield* Schema.decodeEffect(ServerDiscoveryManifestJson)(encoded).pipe(
359
+ Effect.mapError((cause) =>
360
+ ServerFunctionDiscoveryFailed.make({
361
+ cause,
362
+ message: "The isolated build process returned invalid application metadata.",
363
+ path: outputPath,
364
+ }),
365
+ ),
366
+ );
367
+ }),
368
+ );
369
+ });
370
+
371
+ const serverFunctionEntryPlugin = (definition: DiscoveredServerFunction): Plugin => {
372
+ const entryId = `virtual:ignotum/server-function/${definition.address}`;
373
+ const resolvedEntryId = `\0${entryId}`;
374
+ const source = `import { call } from ${encodeJavaScriptString(hostModuleId)}
375
+ import { invoke } from "ignotum/internal/host"
376
+ import { ${definition.exportName} as definition } from ${encodeJavaScriptString(normalizePath(definition.modulePath))}
377
+
378
+ export default async function handler(encodedRequest) {
379
+ return invoke(definition, JSON.parse(encodedRequest), call)
380
+ }
381
+ `;
382
+
383
+ return {
384
+ name: "ignotum:server-function-entry",
385
+ load: (id) => (id === resolvedEntryId ? source : undefined),
386
+ resolveId: (id) => (id === entryId ? resolvedEntryId : undefined),
387
+ };
388
+ };
389
+
390
+ const buildServerFunction = Effect.fn("Deploy.buildServerFunction")(function* (
391
+ appDirectory: string,
392
+ serverOutputDirectory: string,
393
+ conditions: ReadonlyArray<string>,
394
+ definition: DiscoveredServerFunction,
395
+ ) {
396
+ const fileSystem = yield* FileSystem.FileSystem;
397
+ const path = yield* Path.Path;
398
+ const functionDirectory = path.join(serverOutputDirectory, "functions", definition.moduleName);
399
+ const transformSourceMapPath = yield* sourceMapPathTransform(appDirectory);
400
+ const boundaries = yield* moduleBoundariesPlugin("Server", appDirectory);
401
+ const entryId = `virtual:ignotum/server-function/${definition.address}`;
402
+ yield* fileSystem.makeDirectory(functionDirectory, { recursive: true });
403
+
404
+ const output = yield* runViteBuild(definition.address, {
405
+ build: {
406
+ cssCodeSplit: false,
407
+ emptyOutDir: false,
408
+ minify: "oxc",
409
+ modulePreload: false,
410
+ outDir: functionDirectory,
411
+ rolldownOptions: {
412
+ external: [hostModuleId],
413
+ input: entryId,
414
+ preserveEntrySignatures: "strict",
415
+ treeshake: {
416
+ manualPureFunctions: ["mutation", "query"],
417
+ },
418
+ output: {
419
+ codeSplitting: false,
420
+ entryFileNames: `${definition.exportName}-[hash].mjs`,
421
+ format: "es",
422
+ sourcemapPathTransform: transformSourceMapPath,
423
+ },
424
+ },
425
+ sourcemap: true,
426
+ target: "es2020",
427
+ },
428
+ configFile: false,
429
+ logLevel: "warn",
430
+ mode: "production",
431
+ plugins: [boundaries, serverFunctionEntryPlugin(definition)],
432
+ resolve: {
433
+ conditions: [...conditions],
434
+ noExternal: true,
435
+ tsconfigPaths: true,
436
+ },
437
+ root: appDirectory,
438
+ });
439
+ const entries = Array.filter(
440
+ output,
441
+ (item): item is GeneratedChunk => item.type === "chunk" && item.isEntry,
442
+ );
443
+ if (entries.length !== 1) {
444
+ return yield* InvalidBuildOutput.make({
445
+ message: `${definition.address} did not emit exactly one entry chunk.`,
446
+ });
447
+ }
448
+ const entry = entries[0];
449
+ if (entry === undefined) {
450
+ return yield* InvalidBuildOutput.make({
451
+ message: `${definition.address} did not emit an entry chunk.`,
452
+ });
453
+ }
454
+ if (
455
+ entry.dynamicImports.length > 0 ||
456
+ entry.imports.length !== 1 ||
457
+ entry.imports[0] !== hostModuleId ||
458
+ !entry.exports.includes("default")
459
+ ) {
460
+ return yield* InvalidBuildOutput.make({
461
+ message: `${definition.address} is not a self-contained default-exported handler. Imports: ${entry.imports.join(", ") || "none"}; dynamic imports: ${entry.dynamicImports.join(", ") || "none"}; exports: ${entry.exports.join(", ") || "none"}.`,
462
+ });
463
+ }
464
+
465
+ const relativeFile = normalizePath(path.join("functions", definition.moduleName, entry.fileName));
466
+ const relativeSourceMap = `${relativeFile}.map`;
467
+ const absoluteFile = path.join(serverOutputDirectory, relativeFile);
468
+ const absoluteSourceMap = path.join(serverOutputDirectory, relativeSourceMap);
469
+ const sourceMapExists = yield* fileSystem.exists(absoluteSourceMap);
470
+ if (!sourceMapExists) {
471
+ return yield* InvalidBuildOutput.make({
472
+ message: `${definition.address} did not emit a source map.`,
473
+ });
474
+ }
475
+ const bundlePath = normalizePath(path.join("server", relativeFile));
476
+ const sourceMapPath = normalizePath(path.join("server", relativeSourceMap));
477
+
478
+ return {
479
+ address: definition.address,
480
+ kind: definition.kind,
481
+ bundle: yield* artifactReference(bundlePath, yield* fileSystem.readFile(absoluteFile)),
482
+ sourceMap: yield* artifactReference(
483
+ sourceMapPath,
484
+ yield* fileSystem.readFile(absoluteSourceMap),
485
+ ),
486
+ } satisfies ServerFunctionArtifact;
487
+ });
488
+
489
+ export const buildServer = Effect.fn("Deploy.buildServer")(function* (
490
+ appDirectory: string,
491
+ outputDirectory: string,
492
+ functionModules: ReadonlyArray<string>,
493
+ ) {
494
+ const fileSystem = yield* FileSystem.FileSystem;
495
+ const path = yield* Path.Path;
496
+ const conditions = yield* serverConditions();
497
+ const discovered = yield* discoverServerFunctions(appDirectory, functionModules);
498
+
499
+ yield* fileSystem.makeDirectory(outputDirectory, { recursive: true });
500
+ const encodedSnapshot = encodeSchemaSnapshot(discovered.schema);
501
+ const snapshotBytes = utf8Bytes(encodedSnapshot);
502
+ yield* fileSystem.writeFile(path.join(outputDirectory, "schema.json"), snapshotBytes);
503
+ const functions = yield* Effect.forEach(
504
+ discovered.functions,
505
+ (definition) => buildServerFunction(appDirectory, outputDirectory, conditions, definition),
506
+ { concurrency: 4 },
507
+ );
508
+ const manifest = {
509
+ formatVersion: 1,
510
+ schema: yield* artifactReference(schemaSnapshotPath, snapshotBytes),
511
+ functions,
512
+ } satisfies ServerBuildManifest;
513
+ const encodedManifest = `${encodeCanonical(ServerBuildManifest, manifest)}\n`;
514
+ const manifestPath = path.join(outputDirectory, "manifest.json");
515
+ yield* fileSystem.writeFileString(manifestPath, encodedManifest);
516
+ yield* Schema.decodeEffect(Schema.fromJsonString(ServerBuildManifest))(encodedManifest).pipe(
517
+ Effect.mapError(() =>
518
+ InvalidBuildOutput.make({ message: "Ignotum emitted an invalid server manifest." }),
519
+ ),
520
+ );
521
+
522
+ return {
523
+ functions: functions.length,
524
+ manifestPath,
525
+ } satisfies ServerBuildResult;
526
+ });