ignotum 0.0.13 → 0.0.15

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 (131) hide show
  1. package/README.md +6 -6
  2. package/dist/cli/bin.mjs +6111 -4448
  3. package/dist/cli/bin.mjs.map +1 -1
  4. package/dist/cli/emscripten-module-D5GzfUNv.mjs +533 -0
  5. package/dist/cli/emscripten-module-D5GzfUNv.mjs.map +1 -0
  6. package/dist/cli/ffi-7IElU4Co.mjs +148 -0
  7. package/dist/cli/ffi-7IElU4Co.mjs.map +1 -0
  8. package/dist/cli/managed-release-WHYLZwaS.mjs +10902 -0
  9. package/dist/cli/managed-release-WHYLZwaS.mjs.map +1 -0
  10. package/dist/cli/managed-release.d.mts +1 -0
  11. package/dist/cli/managed-release.mjs +14 -0
  12. package/dist/cli/managed-release.mjs.map +1 -0
  13. package/dist/cli/module-ES6BEMUI-D36I4NVn.mjs +1289 -0
  14. package/dist/cli/module-ES6BEMUI-D36I4NVn.mjs.map +1 -0
  15. package/dist/runtime/{api-Cv3hMbzo.d.ts → api-B4fwp5a2.d.ts} +4 -4
  16. package/dist/runtime/bootstrap-BISQSn_O.js +916 -0
  17. package/dist/runtime/bootstrap-BISQSn_O.js.map +1 -0
  18. package/dist/runtime/bootstrap-BTFScKAc.d.ts +545 -0
  19. package/dist/runtime/client.d.ts +50 -15
  20. package/dist/runtime/client.js +7 -1138
  21. package/dist/runtime/client.js.map +1 -1
  22. package/dist/runtime/descriptor-CTY4mtwS-cS43EbwS.js +269 -0
  23. package/dist/runtime/descriptor-CTY4mtwS-cS43EbwS.js.map +1 -0
  24. package/dist/runtime/{id-Bt9XWRGL.js → id--1m0NuUL.js} +4 -4
  25. package/dist/runtime/id--1m0NuUL.js.map +1 -0
  26. package/dist/runtime/{id-BzFHf3Wo-DGPCjgrf.d.ts → id-7an3nxTu-DZJSM5xh.d.ts} +2 -2
  27. package/dist/runtime/id-CBOt2kDo.d.ts +1 -0
  28. package/dist/runtime/identity-QjtJRxBD.d.ts +2 -0
  29. package/dist/runtime/image-Djtjy4Z8.js +19 -0
  30. package/dist/runtime/image-Djtjy4Z8.js.map +1 -0
  31. package/dist/runtime/index-D4seNHlX.d.ts +184 -0
  32. package/dist/runtime/internal/api.d.ts +1 -1
  33. package/dist/runtime/internal/api.js +30 -1
  34. package/dist/runtime/internal/api.js.map +1 -0
  35. package/dist/runtime/internal/bootstrap.d.ts +2 -0
  36. package/dist/runtime/internal/bootstrap.js +2 -0
  37. package/dist/runtime/internal/client.d.ts +2 -0
  38. package/dist/runtime/internal/client.js +75 -0
  39. package/dist/runtime/internal/client.js.map +1 -0
  40. package/dist/runtime/internal/host.d.ts +25 -8
  41. package/dist/runtime/internal/host.js +27 -6
  42. package/dist/runtime/internal/host.js.map +1 -1
  43. package/dist/runtime/internal/routes.d.ts +2 -0
  44. package/dist/runtime/internal/routes.js +2 -0
  45. package/dist/runtime/internal/server.d.ts +1 -1
  46. package/dist/runtime/internal/server.js +1 -1
  47. package/dist/runtime/internal/types.d.ts +2 -1
  48. package/dist/runtime/internal/types.js +1 -1
  49. package/dist/runtime/pagination-CBOt2kDo.d.ts +1 -0
  50. package/dist/runtime/pagination-D3qd6s9N.js +33 -0
  51. package/dist/runtime/pagination-D3qd6s9N.js.map +1 -0
  52. package/dist/runtime/result-BkziOG1L.d.ts +1 -0
  53. package/dist/runtime/router-BSBI1oN1.js +2377 -0
  54. package/dist/runtime/router-BSBI1oN1.js.map +1 -0
  55. package/dist/runtime/routes-Ce8TVP-T.js +690 -0
  56. package/dist/runtime/routes-Ce8TVP-T.js.map +1 -0
  57. package/dist/runtime/{schema-1Zs03-iS.js → schema-D1wOqiNH.js} +40 -12
  58. package/dist/runtime/schema-D1wOqiNH.js.map +1 -0
  59. package/dist/runtime/server.d.ts +5 -5
  60. package/dist/runtime/server.js +2 -2
  61. package/dist/runtime/{sync-Bs8J3fIr.d.ts → sync-D-GK2sv9.d.ts} +3 -2
  62. package/dist/runtime/{api-CAgKDij7.js → sync-DzUwA8W9.js} +22 -41
  63. package/dist/runtime/sync-DzUwA8W9.js.map +1 -0
  64. package/dist/runtime/types-DeCCyqLU-BIm7rU-0.d.ts +341 -0
  65. package/package.json +29 -4
  66. package/src/cli/agent-files.ts +14 -8
  67. package/src/cli/build/managed-client.ts +411 -0
  68. package/src/cli/build/managed-server.ts +294 -0
  69. package/src/cli/build/managed.ts +67 -0
  70. package/src/cli/build/output.ts +47 -0
  71. package/src/cli/build/server.ts +51 -220
  72. package/src/cli/client-config.ts +1 -140
  73. package/src/cli/client-styles.ts +1 -0
  74. package/src/cli/codegen.ts +9 -4
  75. package/src/cli/command.ts +28 -5
  76. package/src/cli/control-client.ts +84 -101
  77. package/src/cli/deploy.ts +133 -177
  78. package/src/cli/dev.ts +506 -107
  79. package/src/cli/image-assets.ts +499 -0
  80. package/src/cli/managed-client-boundaries.ts +103 -0
  81. package/src/cli/managed-dev-platform.ts +118 -0
  82. package/src/cli/managed-exports.ts +219 -0
  83. package/src/cli/managed-release-bin.ts +13 -0
  84. package/src/cli/managed-release.ts +260 -0
  85. package/src/cli/module-boundaries.ts +11 -1
  86. package/src/cli/new-app.ts +111 -87
  87. package/src/cli/route-codegen.ts +311 -0
  88. package/src/cli/route-static.ts +710 -0
  89. package/src/client/bootstrap.ts +110 -0
  90. package/src/client/errors.ts +13 -7
  91. package/src/client/files.ts +64 -0
  92. package/src/client/id.ts +9 -5
  93. package/src/client/image.ts +28 -0
  94. package/src/client/index.ts +25 -2
  95. package/src/client/managed-client.ts +729 -0
  96. package/src/client/managed-upgrade.ts +283 -0
  97. package/src/client/recovery-journal.ts +195 -0
  98. package/src/client/route-boundaries.ts +184 -0
  99. package/src/client/router-history.ts +152 -0
  100. package/src/client/router-store.ts +644 -0
  101. package/src/client/router.ts +324 -0
  102. package/src/client/routes.ts +28 -0
  103. package/src/client/sync.ts +66 -592
  104. package/src/dev-runtime/database.ts +77 -2
  105. package/src/dev-runtime/functions.ts +3 -3
  106. package/src/dev-runtime/id.ts +19 -4
  107. package/src/dev-runtime/managed-functions.ts +255 -0
  108. package/src/dev-runtime/managed-sync.ts +263 -0
  109. package/src/dev-runtime/managed-websocket.ts +71 -0
  110. package/src/dev-runtime/migrations.ts +20 -0
  111. package/src/dev-runtime/sync.ts +236 -236
  112. package/src/image-assets.d.ts +36 -0
  113. package/src/internal/client.ts +186 -0
  114. package/src/internal/host.ts +1 -1
  115. package/src/internal/http-paths.ts +0 -3
  116. package/src/internal/routes.ts +45 -0
  117. package/dist/runtime/api-CAgKDij7.js.map +0 -1
  118. package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js +0 -154
  119. package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js.map +0 -1
  120. package/dist/runtime/id-Bt9XWRGL.js.map +0 -1
  121. package/dist/runtime/id-Dz0apuB3.d.ts +0 -1
  122. package/dist/runtime/index-D54flWtH.d.ts +0 -402
  123. package/dist/runtime/pagination-DnKg3dkI-r5ZUxBBx.d.ts +0 -112
  124. package/dist/runtime/pagination-Dz0apuB3.d.ts +0 -1
  125. package/dist/runtime/result-DKAA4gpS.d.ts +0 -1
  126. package/dist/runtime/schema-1Zs03-iS.js.map +0 -1
  127. package/src/cli/build/client.ts +0 -119
  128. package/src/cli/build/public.ts +0 -186
  129. package/src/cli/client-entry.ts +0 -152
  130. package/src/cli/client-plugin.ts +0 -136
  131. package/src/client/app.ts +0 -15
package/src/cli/dev.ts CHANGED
@@ -1,26 +1,37 @@
1
+ import { fileURLToPath } from "node:url";
1
2
  import prefresh from "@prefresh/vite";
2
3
  import tailwindcss from "@tailwindcss/vite";
3
- import { Effect, FileSystem, Path, Schema } from "effect";
4
- import { createServer } from "vite";
5
- import type { ViteDevServer } from "vite";
6
-
7
- import { generate } from "./codegen.js";
4
+ import { Crypto, Effect, FileSystem, Option, Path, Schema, Scope } from "effect";
5
+ import { createServer, normalizePath, type Plugin, type ViteDevServer } from "vite";
6
+ import { ChildProcessSpawner } from "effect/unstable/process";
7
+ import { ExecutionSelection, RuntimeRequirements } from "@ignotum/contracts/platform";
8
+ import { managedSelectionPath } from "@ignotum/contracts/managed-sync";
9
+ import { renderDocument } from "@ignotum/shared/document";
10
+ import { compilePatterns, matchLocation, resolveMetadata } from "@ignotum/shared/routing";
11
+ import { sha256 } from "@ignotum/versions";
12
+ import { InvalidClientFile } from "./client-config.js";
13
+ import { clientStylesSource } from "./client-styles.js";
14
+ import { generateClientRoutes, type ClientRouteDiscovery } from "./route-codegen.js";
15
+ import { createImageAssetsPlugin } from "./image-assets.js";
16
+ import { managedClientBoundariesPlugin } from "./managed-client-boundaries.js";
17
+ import { moduleBoundariesPlugin } from "./module-boundaries.js";
18
+ import { managedBrowserExports, managedPreactPeerPlugin } from "./managed-exports.js";
19
+ import { localManagedReleaseProvider } from "./managed-release.js";
8
20
  import {
9
- resolveClientBuildConfig,
10
- validateClientFiles,
11
- type ClientFiles,
12
- } from "./client-config.js";
13
- import { loadClientEntryTitle } from "./client-entry.js";
14
- import { ignotumClientPlugin } from "./client-plugin.js";
21
+ buildLocalBrowserTools,
22
+ localExecutionSelection,
23
+ localPlatformRelease,
24
+ localPlatformUrl,
25
+ type LocalManagedRelease,
26
+ } from "./managed-dev-platform.js";
15
27
  import { acquireDevDatabaseLock, devDatabasePath } from "../dev-runtime/dev-database.js";
16
- import { ignotumSyncPlugin } from "../dev-runtime/sync.js";
17
-
28
+ import { ignotumManagedSyncPlugin } from "../dev-runtime/sync.js";
29
+ import { createManagedFunctionLoader } from "../dev-runtime/managed-functions.js";
30
+ import { buildManagedServer } from "./build/server.js";
31
+ import { generate } from "./codegen.js";
18
32
  export class ViteStartupFailed extends Schema.TaggedError<ViteStartupFailed>()(
19
33
  "ViteStartupFailed",
20
- {
21
- cause: Schema.Defect(),
22
- message: Schema.String,
23
- },
34
+ { cause: Schema.Defect(), message: Schema.String },
24
35
  ) {}
25
36
 
26
37
  export interface DevOptions {
@@ -30,111 +41,499 @@ export interface DevOptions {
30
41
  readonly appDirectory: string;
31
42
  }
32
43
 
33
- const acquireViteServer = Effect.fn("Dev.acquireViteServer")(function* (
34
- options: DevOptions,
35
- clientFiles: ClientFiles,
44
+ const registrationId = "virtual:ignotum/dev-registration";
45
+ const stylesId = "virtual:ignotum/dev-styles.css";
46
+ const moduleUrl = (id: string) => `/@id/__x00__${id}`;
47
+ const toolingPrefix = "/_ignotum/development/tools/";
48
+
49
+ export interface ManagedDevOptions extends DevOptions {
50
+ readonly applicationName?: string;
51
+ readonly sdkDirectory?: string;
52
+ readonly provider?: LocalManagedRelease;
53
+ }
54
+
55
+ export const renderDevRegistration = (discovery: ClientRouteDiscovery, applicationName: string) => `
56
+ import { mountClient } from "ignotum/internal/bootstrap";
57
+ import ${JSON.stringify(`${toolingPrefix}debug.js`)};
58
+ import ${JSON.stringify(stylesId)};
59
+ const registration = {
60
+ applicationName: ${JSON.stringify(applicationName)},
61
+ routes: ${JSON.stringify(discovery.routes)},
62
+ layouts: ${JSON.stringify(discovery.layouts)},
63
+ modules: new Map([${discovery.modules.map((module) => `[${JSON.stringify(module.id)}, { load: () => import(${JSON.stringify(`/@fs/${normalizePath(module.path)}`)}) }]`).join(",\n")}]),
64
+ };
65
+ let mounted;
66
+ export default function mount(selection) {
67
+ import.meta.hot.data.selection = selection;
68
+ mounted?.dispose();
69
+ mounted = mountClient(registration, selection);
70
+ return mounted;
71
+ }
72
+ if (import.meta.hot) {
73
+ import.meta.hot.accept();
74
+ import.meta.hot.dispose(() => mounted?.dispose());
75
+ if (import.meta.hot.data.selection) mount(import.meta.hot.data.selection);
76
+ }
77
+ `;
78
+
79
+ /** Internal assembly until all CLI producers switch together. The caller owns its Effect scope. */
80
+ export const acquireManagedDevServer = Effect.fn("ManagedDev.acquireServer")(function* (
81
+ options: ManagedDevOptions,
36
82
  ) {
37
- const fileSystem = yield* FileSystem.FileSystem;
83
+ const fs = yield* FileSystem.FileSystem;
38
84
  const path = yield* Path.Path;
39
- const runPromise = Effect.runPromiseWith(yield* Effect.context<never>());
40
- const clientConfig = yield* resolveClientBuildConfig(true);
41
- const clientDirectory = clientFiles.clientDirectory;
42
- const databasePath = yield* devDatabasePath(options.appDirectory);
43
- const stateDirectory = path.dirname(databasePath);
44
- yield* fileSystem.makeDirectory(stateDirectory, { recursive: true });
45
-
46
- return yield* Effect.acquireRelease(
47
- Effect.tryPromise({
48
- try: () =>
49
- prefresh().then((prefreshPlugin) =>
50
- createServer({
51
- appType: "spa",
52
- configFile: false,
53
- mode: "development",
54
- root: clientDirectory,
55
- plugins: [
56
- ignotumSyncPlugin(options.appDirectory, databasePath),
57
- ignotumClientPlugin({
58
- development: true,
59
- icon: clientFiles.iconPath === undefined ? undefined : "/icon.svg",
60
- loadTitle: () =>
61
- runPromise(
62
- loadClientEntryTitle(clientFiles.entryPath).pipe(
63
- Effect.provideService(FileSystem.FileSystem, fileSystem),
64
- ),
65
- ),
66
- }),
67
- prefreshPlugin,
68
- tailwindcss(),
69
- ],
70
- oxc: {
71
- jsx: {
72
- importSource: "ignotum/client",
73
- runtime: "automatic",
74
- },
75
- },
76
- optimizeDeps: {
77
- include: [
78
- "preact",
79
- "preact/hooks",
80
- "preact/jsx-runtime",
81
- "ignotum/client",
82
- "ignotum/client/jsx-dev-runtime",
83
- ],
84
- },
85
- resolve: {
86
- alias: clientConfig.aliases,
87
- conditions: [...clientConfig.conditions],
88
- dedupe: ["preact", "preact/hooks"],
89
- tsconfigPaths: true,
85
+ const appDirectory = yield* fs.realPath(options.appDirectory);
86
+ const appName = options.applicationName ?? path.basename(appDirectory);
87
+ const clientDirectory = path.join(appDirectory, "client");
88
+ const sdkDirectory = options.sdkDirectory ?? fileURLToPath(new URL("../../", import.meta.url));
89
+ const validateFiles = Effect.fn("ManagedDev.validateFiles")(function* () {
90
+ for (const publicPath of [
91
+ path.join(appDirectory, "public"),
92
+ path.join(clientDirectory, "public"),
93
+ ]) {
94
+ if (
95
+ (yield* fs.exists(publicPath)) ||
96
+ Option.isSome(yield* Effect.option(fs.readLink(publicPath)))
97
+ )
98
+ return yield* InvalidClientFile.make({
99
+ path: publicPath,
100
+ message:
101
+ "App public directories are not supported. Import supported images from client/_assets instead.",
102
+ });
103
+ }
104
+ const iconPath = path.join(clientDirectory, "icon.svg");
105
+ if (!(yield* fs.exists(iconPath))) return undefined;
106
+ if ((yield* fs.stat(iconPath)).type !== "File")
107
+ return yield* InvalidClientFile.make({
108
+ path: iconPath,
109
+ message: "The client icon must be a regular file.",
110
+ });
111
+ const bytes = yield* fs.readFile(iconPath);
112
+ return { bytes, url: `/_ignotum/assets/${yield* sha256(bytes)}/icon.svg` };
113
+ });
114
+ let icon = yield* validateFiles();
115
+ let discovery = yield* generateClientRoutes(appDirectory);
116
+ let patterns = yield* compilePatterns(discovery.routes, discovery.layouts);
117
+ let provider = options.provider ?? (yield* localManagedReleaseProvider(sdkDirectory));
118
+ let selection = localExecutionSelection(appDirectory, provider);
119
+ let tools = yield* buildLocalBrowserTools(provider, selection);
120
+ const runPromise = Effect.runPromiseWith(
121
+ yield* Effect.context<
122
+ | FileSystem.FileSystem
123
+ | Path.Path
124
+ | Crypto.Crypto
125
+ | Scope.Scope
126
+ | ChildProcessSpawner.ChildProcessSpawner
127
+ >(),
128
+ );
129
+ let diagnostic: unknown;
130
+ let serverDiagnostic: unknown;
131
+ let serverWork = Promise.resolve();
132
+ let viteServer: ViteDevServer | undefined;
133
+ let work = Promise.resolve();
134
+ const images = yield* createImageAssetsPlugin(appDirectory);
135
+ const boundaries = yield* moduleBoundariesPlugin("Client", appDirectory);
136
+ const platform = yield* managedClientBoundariesPlugin(appDirectory);
137
+ const peers = yield* managedPreactPeerPlugin({ resolvePeer: (specifier) => specifier });
138
+ // These checks run before the resolver turns supported externals into selected graph URLs.
139
+ const originalPlatformResolve = platform.resolveId;
140
+ platform.resolveId = function (specifier, importer) {
141
+ if (importer === `\0${registrationId}` && specifier === "ignotum/internal/bootstrap")
142
+ return {
143
+ id: localPlatformUrl(selection.platformReleaseId, provider.resolve("Browser", specifier)!),
144
+ external: true as const,
145
+ };
146
+ const resolved = originalPlatformResolve.call(this, specifier, importer);
147
+ if (resolved !== undefined && resolved.external) {
148
+ const module = provider.resolve("Browser", specifier);
149
+ if (module === undefined) this.error(`The local platform does not supply ${specifier}.`);
150
+ return { id: localPlatformUrl(selection.platformReleaseId, module), external: true as const };
151
+ }
152
+ return resolved;
153
+ };
154
+ const originalPeerResolve = peers.resolveId;
155
+ peers.resolveId = async function (specifier, importer) {
156
+ const resolved = await originalPeerResolve.call(this, specifier, importer);
157
+ return resolved === undefined
158
+ ? undefined
159
+ : {
160
+ id: localPlatformUrl(
161
+ selection.platformReleaseId,
162
+ provider.resolve("Browser", specifier)!,
163
+ ),
164
+ external: true as const,
165
+ };
166
+ };
167
+ const databasePath = yield* devDatabasePath(appDirectory);
168
+ yield* fs.makeDirectory(path.dirname(databasePath), { recursive: true });
169
+ yield* acquireDevDatabaseLock(appDirectory);
170
+ let loader = yield* createManagedFunctionLoader(appDirectory, {
171
+ guest: provider.guest,
172
+ release: localPlatformRelease(provider, selection),
173
+ requirements: Schema.decodeUnknownSync(RuntimeRequirements)({
174
+ clientApiVersion: 1,
175
+ serverApiVersion: 1,
176
+ capabilities: ["operations", "routing", "images"],
177
+ }),
178
+ });
179
+ const compileServer = Effect.fn("ManagedDev.compileServer")(function* () {
180
+ const generated = yield* generate(appDirectory);
181
+ const build = yield* buildManagedServer(
182
+ appDirectory,
183
+ path.join(appDirectory, ".ignotum/dev/server"),
184
+ generated.functionModules,
185
+ );
186
+ yield* loader.reload(build);
187
+ });
188
+ yield* compileServer();
189
+ const sync = ignotumManagedSyncPlugin(appDirectory, databasePath, {
190
+ selection: () => selection,
191
+ isServerDependency: (file) => {
192
+ const relative = normalizePath(path.relative(appDirectory, file));
193
+ return (
194
+ !relative.startsWith("../") &&
195
+ !path.isAbsolute(relative) &&
196
+ !relative.startsWith("client/") &&
197
+ !relative.startsWith("_generated/") &&
198
+ !relative.startsWith(".ignotum/") &&
199
+ !relative.includes("node_modules/") &&
200
+ /\.(ts|tsx|js|mjs)$/.test(relative)
201
+ );
202
+ },
203
+ loadModule: async (filename) => {
204
+ await serverWork;
205
+ return loader.ssrLoadModule(filename);
206
+ },
207
+ beforeReload: () => {
208
+ const compile = () => runPromise(compileServer());
209
+ serverWork = serverWork.then(compile, compile).then(
210
+ () => {
211
+ const recovered = serverDiagnostic !== undefined;
212
+ serverDiagnostic = undefined;
213
+ if (recovered) viteServer?.ws.send({ type: "full-reload" });
214
+ },
215
+ (cause) => {
216
+ loader.invalidate(cause);
217
+ serverDiagnostic = cause;
218
+ viteServer?.ws.send({ type: "error", err: { message: String(cause), stack: "" } });
219
+ throw cause;
220
+ },
221
+ );
222
+ return serverWork;
223
+ },
224
+ });
225
+ const plugin: Plugin = {
226
+ name: "ignotum:managed-development",
227
+ enforce: "pre",
228
+ resolveId(id) {
229
+ if (id === registrationId || id === stylesId) return `\0${id}`;
230
+ if (id === "@prefresh/core" || id === "@prefresh/utils")
231
+ return {
232
+ id: `${toolingPrefix}${id.slice("@prefresh/".length)}.js`,
233
+ external: true as const,
234
+ };
235
+ if (id.startsWith(toolingPrefix) || id.startsWith("/_ignotum/platform/"))
236
+ return { id, external: true as const };
237
+ },
238
+ load(id) {
239
+ if (id === `\0${registrationId}`) {
240
+ if (diagnostic !== undefined) throw diagnostic;
241
+ if (serverDiagnostic !== undefined) throw serverDiagnostic;
242
+ return renderDevRegistration(discovery, appName);
243
+ }
244
+ if (id === `\0${stylesId}`) return clientStylesSource;
245
+ },
246
+ configResolved(config) {
247
+ // Prefresh adds optimizer entries. Its tools are already compiled against the managed renderer.
248
+ config.optimizeDeps.include = [];
249
+ config.optimizeDeps.noDiscovery = true;
250
+ config.optimizeDeps.exclude = [
251
+ ...managedBrowserExports.map((entry) => entry.specifier),
252
+ "@prefresh/core",
253
+ "@prefresh/utils",
254
+ ];
255
+ },
256
+ configureServer(server) {
257
+ viteServer = server;
258
+ server.watcher.add([clientDirectory, path.join(appDirectory, "_shared")]);
259
+ const regenerate = async () => {
260
+ try {
261
+ const nextIcon = await runPromise(validateFiles());
262
+ const next = await runPromise(generateClientRoutes(appDirectory));
263
+ const nextPatterns = await runPromise(compilePatterns(next.routes, next.layouts));
264
+ const changed =
265
+ JSON.stringify({
266
+ routes: next.routes,
267
+ layouts: next.layouts,
268
+ modules: next.modules,
269
+ icon: nextIcon?.url,
270
+ }) !==
271
+ JSON.stringify({
272
+ routes: discovery.routes,
273
+ layouts: discovery.layouts,
274
+ modules: discovery.modules,
275
+ icon: icon?.url,
276
+ });
277
+ discovery = next;
278
+ patterns = nextPatterns;
279
+ icon = nextIcon;
280
+ const recovered = diagnostic !== undefined;
281
+ diagnostic = undefined;
282
+ server.watcher.add(discovery.dependencies);
283
+ if (changed || recovered) {
284
+ const module = server.moduleGraph.getModuleById(`\0${registrationId}`);
285
+ if (module !== undefined) await server.reloadModule(module);
286
+ }
287
+ } catch (error) {
288
+ diagnostic = error;
289
+ server.ws.send({
290
+ type: "error",
291
+ err: {
292
+ message: String(error),
293
+ stack: error instanceof Error ? (error.stack ?? "") : "",
90
294
  },
91
- server: {
92
- forwardConsole: {
93
- logLevels: ["warn", "error"],
94
- unhandledErrors: true,
295
+ });
296
+ }
297
+ };
298
+ const onChange = (file: string) => {
299
+ const relative = normalizePath(path.relative(appDirectory, file));
300
+ if (
301
+ relative.startsWith("_generated/") ||
302
+ relative.startsWith(".ignotum/") ||
303
+ relative.includes("node_modules/")
304
+ )
305
+ return;
306
+ work = work.then(regenerate);
307
+ };
308
+ server.watcher.on("addDir", onChange);
309
+ server.watcher.on("unlinkDir", onChange);
310
+ server.watcher.on("add", onChange);
311
+ server.watcher.on("unlink", onChange);
312
+ server.watcher.on("change", onChange);
313
+ server.httpServer?.once("close", () => {
314
+ server.watcher.off("addDir", onChange);
315
+ server.watcher.off("unlinkDir", onChange);
316
+ server.watcher.off("add", onChange);
317
+ server.watcher.off("unlink", onChange);
318
+ server.watcher.off("change", onChange);
319
+ });
320
+ server.middlewares.use((request, response, next) => {
321
+ const url = new URL(
322
+ request.url ?? "/",
323
+ `http://${request.headers.host ?? "ignotum.local"}`,
324
+ );
325
+ const send = (body: string | Uint8Array, contentType: string, immutable = false) => {
326
+ response.setHeader("content-type", contentType);
327
+ response.setHeader(
328
+ "cache-control",
329
+ immutable ? "public, max-age=31536000, immutable" : "no-store",
330
+ );
331
+ response.setHeader("x-content-type-options", "nosniff");
332
+ response.end(request.method === "HEAD" ? undefined : body);
333
+ };
334
+ if (url.pathname === managedSelectionPath) {
335
+ send(JSON.stringify({ protocolVersion: 1, selection }), "application/json");
336
+ return;
337
+ }
338
+ const platformPrefix = `/_ignotum/platform/${selection.platformReleaseId}/`;
339
+ if (url.pathname.startsWith(platformPrefix)) {
340
+ const modulePath = url.pathname.slice(platformPrefix.length);
341
+ const allowed = provider.browser.manifest.modules.some(
342
+ (module) => module.path === modulePath,
343
+ );
344
+ const bytes = allowed ? provider.read("Browser", modulePath) : undefined;
345
+ if (bytes === undefined) {
346
+ response.statusCode = 404;
347
+ response.end();
348
+ } else send(bytes, "text/javascript", true);
349
+ return;
350
+ }
351
+ if (url.pathname.startsWith(toolingPrefix)) {
352
+ const code = tools.get(url.pathname.slice(toolingPrefix.length));
353
+ if (code === undefined) {
354
+ response.statusCode = 404;
355
+ response.end();
356
+ } else send(code, "text/javascript");
357
+ return;
358
+ }
359
+ if (icon !== undefined && url.pathname === icon.url) {
360
+ send(icon.bytes, "image/svg+xml", true);
361
+ return;
362
+ }
363
+ if (
364
+ url.pathname === "/icon.svg" ||
365
+ url.pathname.startsWith("/server/") ||
366
+ url.pathname.startsWith("/.")
367
+ ) {
368
+ response.statusCode = 404;
369
+ response.end();
370
+ return;
371
+ }
372
+ const isDocument =
373
+ (request.method === "GET" || request.method === "HEAD") &&
374
+ !url.pathname.startsWith("/_ignotum/") &&
375
+ !url.pathname.startsWith("/@") &&
376
+ !url.searchParams.has("import") &&
377
+ (request.headers.accept?.includes("text/html") ?? false);
378
+ if (!isDocument) {
379
+ next();
380
+ return;
381
+ }
382
+ void work
383
+ .then(async () => {
384
+ if (diagnostic !== undefined) throw diagnostic;
385
+ if (serverDiagnostic !== undefined) throw serverDiagnostic;
386
+ const outcome = await runPromise(matchLocation(patterns, request.url ?? "/"));
387
+ const metadata = await runPromise(
388
+ resolveMetadata({
389
+ applicationName: appName,
390
+ layouts: outcome.layouts.map((layout) => layout.metadata),
391
+ page: outcome._tag === "NotFound" ? undefined : outcome.route?.metadata,
392
+ outcome: outcome._tag,
393
+ }),
394
+ );
395
+ const imports = Object.fromEntries(
396
+ Object.entries(provider.browser.manifest.entries).map(([specifier, module]) => [
397
+ specifier,
398
+ localPlatformUrl(selection.platformReleaseId, module),
399
+ ]),
400
+ );
401
+ const html = renderDocument({
402
+ metadata,
403
+ selection,
404
+ importMap: { imports },
405
+ browserEntry: imports["ignotum/internal/bootstrap"]!,
406
+ registration: { module: moduleUrl(registrationId), exportName: "default" },
407
+ styles: [],
408
+ icon: icon?.url,
409
+ });
410
+ response.statusCode =
411
+ outcome._tag === "NotFound" ? 404 : outcome._tag === "Invalid" ? 400 : 200;
412
+ send(
413
+ html.replace("</head>", '<script type="module" src="/@vite/client"></script></head>'),
414
+ "text/html; charset=utf-8",
415
+ );
416
+ })
417
+ .catch(next);
418
+ });
419
+ },
420
+ };
421
+ const server = yield* Effect.acquireRelease(
422
+ Effect.tryPromise({
423
+ try: async () =>
424
+ createServer({
425
+ appType: "custom",
426
+ configFile: false,
427
+ envDir: false,
428
+ mode: "development",
429
+ root: appDirectory,
430
+ publicDir: false,
431
+ plugins: [
432
+ plugin,
433
+ images.plugin,
434
+ boundaries,
435
+ platform,
436
+ peers,
437
+ sync,
438
+ await prefresh(),
439
+ tailwindcss(),
440
+ ],
441
+ oxc: { jsx: { importSource: "ignotum/client", runtime: "automatic" } },
442
+ optimizeDeps: { noDiscovery: true, include: [] },
443
+ resolve: {
444
+ alias: [
445
+ {
446
+ find: /^tailwindcss$/,
447
+ replacement: fileURLToPath(import.meta.resolve("tailwindcss/index.css")),
95
448
  },
96
- host: options.host,
97
- open: options.open,
98
- port: options.port,
99
- strictPort: true,
100
- },
101
- }),
102
- ),
449
+ ],
450
+ tsconfigPaths: true,
451
+ },
452
+ server: {
453
+ preTransformRequests: false,
454
+ host: options.host,
455
+ port: options.port,
456
+ open: options.open,
457
+ strictPort: true,
458
+ forwardConsole: { logLevels: ["warn", "error"], unhandledErrors: true },
459
+ },
460
+ }),
103
461
  catch: (cause) =>
104
462
  ViteStartupFailed.make({
105
463
  cause,
106
- message: "Ignotum could not create the Vite development server.",
464
+ message: "Ignotum could not create the managed development server.",
107
465
  }),
108
466
  }),
109
- (server) => Effect.promise(() => server.close()),
110
- );
111
- });
112
-
113
- const listen = Effect.fn("Dev.listen")(function* (server: ViteDevServer) {
114
- yield* Effect.tryPromise({
115
- try: () => server.listen(),
116
- catch: (cause) =>
117
- ViteStartupFailed.make({
118
- cause,
119
- message: "Ignotum could not start the Vite development server.",
467
+ (server) =>
468
+ Effect.promise(async () => {
469
+ const closing = server.close();
470
+ await sync.api.dispose();
471
+ await closing;
472
+ await Promise.allSettled([work, serverWork]);
120
473
  }),
121
- });
474
+ );
475
+ return {
476
+ server,
477
+ selection: () => selection,
478
+ settled: () => work,
479
+ replacePlatform: (next: LocalManagedRelease) => {
480
+ const replace = async () => {
481
+ const nextSelection = Schema.decodeUnknownSync(ExecutionSelection)({
482
+ ...localExecutionSelection(appDirectory, next),
483
+ platformGeneration: selection.platformGeneration + 1,
484
+ });
485
+ const nextTools = await runPromise(buildLocalBrowserTools(next, nextSelection));
486
+ const nextLoader = await runPromise(
487
+ createManagedFunctionLoader(appDirectory, {
488
+ guest: next.guest,
489
+ release: localPlatformRelease(next, nextSelection),
490
+ requirements: Schema.decodeUnknownSync(RuntimeRequirements)({
491
+ clientApiVersion: 1,
492
+ serverApiVersion: 1,
493
+ capabilities: ["operations", "routing", "images"],
494
+ }),
495
+ }),
496
+ );
497
+ const generated = await runPromise(generate(appDirectory));
498
+ const nextBuild = await runPromise(
499
+ buildManagedServer(
500
+ appDirectory,
501
+ path.join(appDirectory, ".ignotum/dev/server"),
502
+ generated.functionModules,
503
+ ),
504
+ );
505
+ await runPromise(nextLoader.reload(nextBuild));
506
+ loader = nextLoader;
507
+ provider = next;
508
+ tools = nextTools;
509
+ selection = Schema.decodeUnknownSync(ExecutionSelection)(nextSelection);
510
+ serverDiagnostic = undefined;
511
+ server.environments.client.moduleGraph.invalidateAll();
512
+ // Managed sync checks the new selection even for sockets with no active queries.
513
+ await sync.api.invalidate();
514
+ };
515
+ const replacement = serverWork.then(replace, replace);
516
+ // A rejected candidate leaves the current platform available to subsequent loads.
517
+ serverWork = replacement.catch(() => {});
518
+ return replacement;
519
+ },
520
+ };
122
521
  });
123
522
 
124
- export const dev = Effect.fn("Dev.run")(function* (options: DevOptions) {
125
- const clientFiles = yield* validateClientFiles(options.appDirectory);
126
- yield* generate(options.appDirectory);
127
-
523
+ export const dev = Effect.fn("ManagedDev.run")(function* (options: ManagedDevOptions) {
128
524
  return yield* Effect.scoped(
129
525
  Effect.gen(function* () {
130
- yield* acquireDevDatabaseLock(options.appDirectory);
131
- const server = yield* acquireViteServer(options, clientFiles);
132
-
133
- yield* listen(server);
134
- yield* Effect.sync(() => {
135
- server.printUrls();
136
- server.bindCLIShortcuts({ print: true });
526
+ const { server } = yield* acquireManagedDevServer(options);
527
+ yield* Effect.tryPromise({
528
+ try: () => server.listen(),
529
+ catch: (cause) =>
530
+ ViteStartupFailed.make({
531
+ cause,
532
+ message: "Ignotum could not start the development server.",
533
+ }),
137
534
  });
535
+ server.printUrls();
536
+ server.bindCLIShortcuts({ print: true });
138
537
  return yield* Effect.never;
139
538
  }),
140
539
  );