ignotum 0.0.16 → 0.0.18

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 (126) hide show
  1. package/README.md +6 -6
  2. package/dist/cli/bin.mjs +13263 -2689
  3. package/dist/cli/bin.mjs.map +1 -1
  4. package/dist/platform/_headers +3 -0
  5. package/dist/platform/_ignotum/v1.0/client.js +12 -0
  6. package/dist/runtime/{api-B4fwp5a2.d.ts → api-BnUKx9cj-D49Tf0rX.d.ts} +4 -3
  7. package/dist/runtime/api-C-tltz6s.js +31 -0
  8. package/dist/runtime/api-C-tltz6s.js.map +1 -0
  9. package/dist/runtime/artifact-BB5qzoa6.js +42 -0
  10. package/dist/runtime/artifact-BB5qzoa6.js.map +1 -0
  11. package/dist/runtime/{bootstrap-BTFScKAc.d.ts → bootstrap-DjTwGJao-DBx5zdFI.d.ts} +64 -64
  12. package/dist/runtime/{bootstrap-BISQSn_O.js → bootstrap-DlNIfH-r.js} +9 -9
  13. package/dist/runtime/bootstrap-DlNIfH-r.js.map +1 -0
  14. package/dist/runtime/client.d.ts +24 -20
  15. package/dist/runtime/client.js +9 -9
  16. package/dist/runtime/client.js.map +1 -1
  17. package/dist/runtime/id-BI08JkmR.d.ts +1 -0
  18. package/dist/runtime/{id--1m0NuUL.js → id-BrEnxY-r.js} +118 -53
  19. package/dist/runtime/id-BrEnxY-r.js.map +1 -0
  20. package/dist/runtime/{image-Djtjy4Z8.js → image-gofzZeCm.js} +2 -2
  21. package/dist/runtime/image-gofzZeCm.js.map +1 -0
  22. package/dist/runtime/{index-D4seNHlX.d.ts → index-Biaw5vdF.d.ts} +2 -2
  23. package/dist/runtime/internal/api.d.ts +1 -1
  24. package/dist/runtime/internal/api.js +1 -30
  25. package/dist/runtime/internal/bootstrap.d.ts +1 -1
  26. package/dist/runtime/internal/bootstrap.js +1 -1
  27. package/dist/runtime/internal/client.d.ts +2 -2
  28. package/dist/runtime/internal/client.js +4 -4
  29. package/dist/runtime/internal/client.js.map +1 -1
  30. package/dist/runtime/internal/host.d.ts +4 -4
  31. package/dist/runtime/internal/host.js +4 -4
  32. package/dist/runtime/internal/routes.d.ts +1 -1
  33. package/dist/runtime/internal/routes.js +1 -1
  34. package/dist/runtime/internal/server.d.ts +3 -2
  35. package/dist/runtime/internal/server.js +2 -2
  36. package/dist/runtime/internal/server.js.map +1 -1
  37. package/dist/runtime/internal/types.d.ts +2 -2
  38. package/dist/runtime/internal/types.js +1 -1
  39. package/dist/runtime/{pagination-D3qd6s9N.js → pagination--f4kMKf6.js} +2 -2
  40. package/dist/runtime/{pagination-D3qd6s9N.js.map → pagination--f4kMKf6.js.map} +1 -1
  41. package/dist/runtime/pagination-BI08JkmR.d.ts +1 -0
  42. package/dist/runtime/{router-BSBI1oN1.js → router-C1eC9yy5.js} +462 -419
  43. package/dist/runtime/router-C1eC9yy5.js.map +1 -0
  44. package/dist/runtime/routes-DnU39R7c.js +152 -0
  45. package/dist/runtime/routes-DnU39R7c.js.map +1 -0
  46. package/dist/runtime/{schema-D1wOqiNH.js → schema-BSjepoNF.js} +271 -6
  47. package/dist/runtime/schema-BSjepoNF.js.map +1 -0
  48. package/dist/runtime/server.d.ts +5 -4
  49. package/dist/runtime/server.js +3 -3
  50. package/dist/runtime/server.js.map +1 -1
  51. package/dist/runtime/{sync-DzUwA8W9.js → sync-BwgS19Xy.js} +4 -16
  52. package/dist/runtime/sync-BwgS19Xy.js.map +1 -0
  53. package/dist/runtime/{sync-D-GK2sv9.d.ts → sync-By2hX5ev.d.ts} +1 -2
  54. package/dist/runtime/{types-DeCCyqLU-BIm7rU-0.d.ts → types-DeCCyqLU-CHx7KFtx.d.ts} +2 -2
  55. package/package.json +7 -1
  56. package/src/cli/agent-files.ts +2 -0
  57. package/src/cli/build/asset-names.ts +190 -0
  58. package/src/cli/build/managed-client.ts +151 -81
  59. package/src/cli/build/managed-server.ts +4 -4
  60. package/src/cli/build/managed.ts +5 -6
  61. package/src/cli/command.ts +7 -15
  62. package/src/cli/control-client.ts +7 -9
  63. package/src/cli/deploy.ts +45 -15
  64. package/src/cli/dev.ts +50 -99
  65. package/src/cli/image-assets.ts +5 -2
  66. package/src/cli/managed-client-boundaries.ts +6 -6
  67. package/src/cli/managed-dev-platform.ts +52 -66
  68. package/src/cli/managed-exports.ts +5 -66
  69. package/src/cli/new-app.ts +7 -2
  70. package/src/cli/path.ts +11 -0
  71. package/src/cli/route-codegen.ts +2 -1
  72. package/src/cli/route-static.ts +3 -2
  73. package/src/cli/upload-journal.ts +130 -0
  74. package/src/client/bootstrap.ts +1 -110
  75. package/src/client/errors.ts +1 -70
  76. package/src/client/files.ts +1 -233
  77. package/src/client/hooks.ts +1 -367
  78. package/src/client/id.ts +1 -263
  79. package/src/client/image.ts +1 -28
  80. package/src/client/index.ts +1 -126
  81. package/src/client/jsx-dev-runtime.ts +1 -2
  82. package/src/client/jsx-runtime.ts +1 -2
  83. package/src/client/managed-client.ts +1 -729
  84. package/src/client/managed-upgrade.ts +1 -283
  85. package/src/client/page-observers.ts +1 -77
  86. package/src/client/query.ts +1 -17
  87. package/src/client/recovery-journal.ts +1 -195
  88. package/src/client/route-boundaries.ts +1 -184
  89. package/src/client/router-history.ts +1 -152
  90. package/src/client/router-store.ts +1 -644
  91. package/src/client/router.ts +1 -324
  92. package/src/client/routes.ts +1 -28
  93. package/src/client/sync.ts +1 -286
  94. package/src/dev-runtime/id.ts +1 -1
  95. package/src/dev-runtime/managed-functions.ts +10 -18
  96. package/src/dev-runtime/managed-sync.ts +16 -16
  97. package/src/dev-runtime/sync.ts +6 -4
  98. package/src/internal/api.ts +1 -156
  99. package/src/internal/client.ts +1 -186
  100. package/src/internal/host.ts +1 -1
  101. package/src/internal/http-paths.ts +1 -4
  102. package/src/internal/routes.ts +1 -45
  103. package/src/internal/server.ts +1 -3
  104. package/src/internal/types.ts +1 -2
  105. package/src/server/index.ts +1 -27
  106. package/dist/cli/managed-release-WHYLZwaS.mjs +0 -10902
  107. package/dist/cli/managed-release-WHYLZwaS.mjs.map +0 -1
  108. package/dist/cli/managed-release.d.mts +0 -1
  109. package/dist/cli/managed-release.mjs +0 -14
  110. package/dist/cli/managed-release.mjs.map +0 -1
  111. package/dist/runtime/bootstrap-BISQSn_O.js.map +0 -1
  112. package/dist/runtime/descriptor-CTY4mtwS-cS43EbwS.js +0 -269
  113. package/dist/runtime/descriptor-CTY4mtwS-cS43EbwS.js.map +0 -1
  114. package/dist/runtime/id--1m0NuUL.js.map +0 -1
  115. package/dist/runtime/id-CBOt2kDo.d.ts +0 -1
  116. package/dist/runtime/identity-QjtJRxBD.d.ts +0 -2
  117. package/dist/runtime/image-Djtjy4Z8.js.map +0 -1
  118. package/dist/runtime/internal/api.js.map +0 -1
  119. package/dist/runtime/pagination-CBOt2kDo.d.ts +0 -1
  120. package/dist/runtime/router-BSBI1oN1.js.map +0 -1
  121. package/dist/runtime/routes-Ce8TVP-T.js +0 -690
  122. package/dist/runtime/routes-Ce8TVP-T.js.map +0 -1
  123. package/dist/runtime/schema-D1wOqiNH.js.map +0 -1
  124. package/dist/runtime/sync-DzUwA8W9.js.map +0 -1
  125. package/src/cli/managed-release-bin.ts +0 -13
  126. package/src/cli/managed-release.ts +0 -260
@@ -1,70 +1,63 @@
1
1
  import { createHash } from "node:crypto";
2
- import { createRequire } from "node:module";
3
2
  import { fileURLToPath } from "node:url";
4
- import { Effect, Path, Schema } from "effect";
5
- import { ExecutionSelection, PlatformRelease } from "@ignotum/contracts/platform";
6
- import { managedPreactPeers } from "./managed-exports.js";
7
- import { localManagedReleaseProvider } from "./managed-release.js";
3
+ import { Effect, FileSystem, Path, Schema } from "effect";
4
+ import { ServingSelection, type PlatformRevision } from "@ignotum/contracts/deployment";
5
+ import { VersionId } from "@ignotum/contracts/runtime/identity";
6
+ import { bundledGuests } from "@ignotum/platform/guests";
7
+ import type { BundledGuest } from "@ignotum/platform/artifacts";
8
+ import { compilerExports } from "@ignotum/platform/compiler-exports";
9
+ import { platformImportsPlugin } from "@ignotum/platform/compiler";
8
10
  import { runViteBuild } from "./build/artifact.js";
9
11
 
10
- export type LocalManagedRelease = Effect.Success<ReturnType<typeof localManagedReleaseProvider>>;
12
+ export interface LocalPlatform {
13
+ readonly revision: PlatformRevision;
14
+ readonly browser: Uint8Array;
15
+ readonly guest: BundledGuest;
16
+ }
11
17
 
12
- export const localPlatformUrl = (release: string, module: string) =>
13
- `/_ignotum/platform/${release}/${module}`;
18
+ export const loadLocalPlatform = Effect.fn("ManagedDev.loadPlatform")(function* () {
19
+ const fs = yield* FileSystem.FileSystem;
20
+ const revision = { major: 1, minor: 0 } as const;
21
+ const guest = bundledGuests.find(
22
+ (candidate) =>
23
+ candidate.manifest.revision.major === revision.major &&
24
+ candidate.manifest.revision.minor === revision.minor,
25
+ );
26
+ if (guest === undefined) return yield* Effect.die("The local platform guest is missing.");
27
+ const browser = yield* fs.readFile(
28
+ fileURLToPath(new URL("../../dist/platform/_ignotum/v1.0/client.js", import.meta.url)),
29
+ );
30
+ return { revision, browser, guest } satisfies LocalPlatform;
31
+ });
14
32
 
15
- export const localExecutionSelection = (appDirectory: string, provider: LocalManagedRelease) => {
16
- const digest = (value: string) => createHash("sha256").update(value).digest("hex").slice(0, 24);
17
- return Schema.decodeUnknownSync(ExecutionSelection)({
18
- formatVersion: 1,
19
- appId: `app_${digest(appDirectory)}`,
20
- versionId: `dep_${digest(appDirectory)}`,
21
- activationGeneration: 1,
22
- platformReleaseId: `rel_${digest(provider.browser.sha256 + provider.guest.sha256)}`,
23
- platformGeneration: 1,
24
- dataHistoryId: `hist_${digest(appDirectory)}`,
25
- });
33
+ export const localVersionId = (appDirectory: string) => {
34
+ const digest = createHash("sha256").update(appDirectory).digest("hex").slice(0, 24);
35
+ return VersionId.make(`dep_${digest}`);
26
36
  };
27
37
 
28
- export const localPlatformRelease = (
29
- provider: LocalManagedRelease,
30
- selection: ExecutionSelection,
31
- ) =>
32
- Schema.decodeUnknownSync(PlatformRelease)({
38
+ export const localServingSelection = (
39
+ appDirectory: string,
40
+ deploymentEpoch = 1,
41
+ activationGeneration = 1,
42
+ ) => {
43
+ const digest = createHash("sha256").update(appDirectory).digest("hex").slice(0, 24);
44
+ return Schema.decodeUnknownSync(ServingSelection)({
33
45
  formatVersion: 1,
34
- id: selection.platformReleaseId,
35
- browser: {
36
- graph: {
37
- sha256: provider.browser.sha256,
38
- entry: provider.browser.manifest.entries["ignotum/internal/bootstrap"],
39
- },
40
- clientApiVersion: 1,
41
- protocolVersion: 1,
42
- },
43
- guest: {
44
- graph: {
45
- sha256: provider.guest.sha256,
46
- entry: provider.guest.manifest.entries["ignotum/internal/host"],
47
- },
48
- serverApiVersion: 1,
49
- protocolVersion: 1,
50
- },
51
- gateway: { implementation: provider.browser.sha256, browserProtocols: [1] },
52
- host: { implementation: provider.guest.sha256, guestProtocols: [1] },
53
- capabilities: ["operations", "routing", "images"],
54
- persistentState: [],
55
- publication: { state: "Ready", eligible: true },
46
+ appId: `app_${digest}`,
47
+ versionNumber: 1,
48
+ platform: { major: 1, minor: 0 },
49
+ activationGeneration,
50
+ deploymentEpoch,
51
+ dataHistoryId: `hist_${digest}`,
56
52
  });
53
+ };
57
54
 
58
- /** Development tools share the selected renderer; they never enter application modules. */
59
- export const buildLocalBrowserTools = Effect.fn("ManagedDev.buildBrowserTools")(function* (
60
- provider: LocalManagedRelease,
61
- selection: ExecutionSelection,
62
- ) {
55
+ /** Development tools use the same single platform module as application code. */
56
+ export const buildLocalBrowserTools = Effect.fn("ManagedDev.buildBrowserTools")(function* () {
63
57
  const path = yield* Path.Path;
64
- const require = createRequire(import.meta.resolve("@prefresh/vite"));
65
58
  const entries = {
66
- core: require.resolve("@prefresh/core"),
67
- utils: require.resolve("@prefresh/utils"),
59
+ core: fileURLToPath(import.meta.resolve("@prefresh/core")),
60
+ utils: fileURLToPath(import.meta.resolve("@prefresh/utils")),
68
61
  debug: fileURLToPath(import.meta.resolve("preact/debug")),
69
62
  };
70
63
  const sources = new Map([
@@ -80,23 +73,15 @@ export const buildLocalBrowserTools = Effect.fn("ManagedDev.buildBrowserTools")(
80
73
  logLevel: "warn",
81
74
  define: { "process.env.NODE_ENV": JSON.stringify("development") },
82
75
  plugins: [
76
+ platformImportsPlugin(compilerExports),
83
77
  {
84
- name: "ignotum:local-tooling-renderer",
78
+ name: "ignotum:local-browser-tools",
85
79
  generateBundle(_options, bundle) {
86
80
  for (const item of Object.values(bundle))
87
81
  if (item.type === "chunk") files.set(item.fileName, item.code);
88
82
  },
89
- load(id) {
90
- return sources.get(id);
91
- },
92
- resolveId(id) {
93
- if (sources.has(`\0${id}`)) return `\0${id}`;
94
- if (managedPreactPeers.some((peer) => peer.specifier === id)) {
95
- const module = provider.resolve("Browser", id);
96
- if (module === undefined) this.error(`Missing managed rendering peer ${id}.`);
97
- return { id: localPlatformUrl(selection.platformReleaseId, module), external: true };
98
- }
99
- },
83
+ load: (id) => sources.get(id),
84
+ resolveId: (id) => (sources.has(`\0${id}`) ? `\0${id}` : undefined),
100
85
  },
101
86
  ],
102
87
  build: {
@@ -105,6 +90,7 @@ export const buildLocalBrowserTools = Effect.fn("ManagedDev.buildBrowserTools")(
105
90
  target: "es2020",
106
91
  rolldownOptions: {
107
92
  input: { core: "ignotum:dev-core", utils: "ignotum:dev-utils", debug: "ignotum:dev-debug" },
93
+ external: ["ignotum/platform"],
108
94
  preserveEntrySignatures: "strict",
109
95
  output: {
110
96
  format: "es",
@@ -2,74 +2,13 @@ import { Effect, FileSystem, Path, Schema } from "effect";
2
2
  import { satisfies, validRange } from "semver";
3
3
  import type { Plugin } from "vite";
4
4
 
5
- export interface ManagedExport {
6
- readonly specifier: string;
7
- /** Relative to the SDK src directory, or a Preact package specifier. */
8
- readonly source: string;
9
- readonly fileName: string;
10
- }
11
-
12
5
  export const managedPreactVersion = "10.29.8";
13
6
  export const managedPreactPeers = [
14
- { specifier: "preact", source: "preact", fileName: "preact" },
15
- { specifier: "preact/hooks", source: "preact/hooks", fileName: "preact-hooks" },
16
- { specifier: "preact/jsx-runtime", source: "preact/jsx-runtime", fileName: "preact-jsx-runtime" },
17
- {
18
- specifier: "preact/jsx-dev-runtime",
19
- source: "preact/jsx-dev-runtime",
20
- fileName: "preact-jsx-dev-runtime",
21
- },
22
- ] as const satisfies ReadonlyArray<ManagedExport>;
23
-
24
- export const managedBrowserExports = [
25
- { specifier: "ignotum/client", source: "client/index.ts", fileName: "client" },
26
- {
27
- specifier: "ignotum/client/jsx-runtime",
28
- source: "client/jsx-runtime.ts",
29
- fileName: "jsx-runtime",
30
- },
31
- {
32
- specifier: "ignotum/client/jsx-dev-runtime",
33
- source: "client/jsx-dev-runtime.ts",
34
- fileName: "jsx-dev-runtime",
35
- },
36
- { specifier: "ignotum/internal/api", source: "internal/api.ts", fileName: "api" },
37
- { specifier: "ignotum/internal/client", source: "internal/client.ts", fileName: "bindings" },
38
- { specifier: "ignotum/internal/routes", source: "internal/routes.ts", fileName: "routes" },
39
- { specifier: "ignotum/internal/bootstrap", source: "client/bootstrap.ts", fileName: "bootstrap" },
40
- ...managedPreactPeers,
41
- ] as const satisfies ReadonlyArray<ManagedExport>;
42
-
43
- export const managedGuestExports = [
44
- { specifier: "ignotum/server", source: "server/index.ts", fileName: "server" },
45
- { specifier: "ignotum/internal/server", source: "internal/server.ts", fileName: "bindings" },
46
- { specifier: "ignotum/internal/api", source: "internal/api.ts", fileName: "api" },
47
- { specifier: "ignotum/internal/host", source: "internal/host.ts", fileName: "host" },
48
- ] as const satisfies ReadonlyArray<ManagedExport>;
49
-
50
- export const managedCompatibility = {
51
- clientApiVersion: 1,
52
- serverApiVersion: 1,
53
- protocolVersions: [1],
54
- capabilities: ["operations", "routing", "images"],
55
- } as const;
56
-
57
- export const resolveLocalManagedExport = Effect.fn("Deploy.resolveLocalManagedExport")(
58
- function* (options: {
59
- readonly target: "browser" | "guest";
60
- readonly sdkSourceRoot: string;
61
- readonly specifier: string;
62
- }) {
63
- const path = yield* Path.Path;
64
- const entries = options.target === "browser" ? managedBrowserExports : managedGuestExports;
65
- const entry = entries.find((entry) => entry.specifier === options.specifier);
66
- return entry === undefined
67
- ? undefined
68
- : entry.source.startsWith("preact")
69
- ? entry.source
70
- : path.resolve(options.sdkSourceRoot, entry.source);
71
- },
72
- );
7
+ { specifier: "preact", fileName: "preact" },
8
+ { specifier: "preact/hooks", fileName: "preact-hooks" },
9
+ { specifier: "preact/jsx-runtime", fileName: "preact-jsx-runtime" },
10
+ { specifier: "preact/jsx-dev-runtime", fileName: "preact-jsx-dev-runtime" },
11
+ ] as const;
73
12
 
74
13
  export class ManagedPeerError extends Schema.TaggedError<ManagedPeerError>()("ManagedPeerError", {
75
14
  packageName: Schema.String,
@@ -1,5 +1,6 @@
1
1
  import { Effect, FileSystem, Path, Schema, String } from "effect";
2
2
  import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
3
+ import cliPackageJson from "../../package.json" with { type: "json" };
3
4
 
4
5
  import { agentAppFiles, type AppFile } from "./agent-files.js";
5
6
  import { generateClientRoutes } from "./route-codegen.js";
@@ -52,7 +53,7 @@ const packageJson = (appName: string): string =>
52
53
  typecheck: "ignotum codegen && tsc --noEmit",
53
54
  },
54
55
  dependencies: {
55
- ignotum: "latest",
56
+ ignotum: cliPackageJson.version,
56
57
  },
57
58
  devDependencies: {
58
59
  typescript: "^7.0.2",
@@ -256,7 +257,7 @@ After generating bindings, run \`npx tsc --noEmit\` to check the app.
256
257
  Sign in with \`npx ignotum auth login\`, then run \`npx ignotum deploy --app my-app\`.
257
258
  Later deployments use \`npx ignotum deploy\`. The command prints the hosted URL and confirms
258
259
  that the numbered App Version is live. If activation cannot be confirmed, follow the printed
259
- \`ignotum deploy --resume <version-id>\` command to finish without creating another version.
260
+ \`ignotum deploy --resume\` command to finish without creating another version.
260
261
 
261
262
  ## Coding agents
262
263
 
@@ -277,6 +278,10 @@ export const appFiles = (appName: string): ReadonlyArray<AppFile> => [
277
278
  { content: schema, path: "server/schema.ts" },
278
279
  { content: utils, path: "shared/utils.ts" },
279
280
  { content: packageJson(appName), path: "package.json" },
281
+ {
282
+ content: `minimumReleaseAgeExclude:\n - ignotum@${cliPackageJson.version}\nallowBuilds:\n msgpackr-extract: false\n`,
283
+ path: "pnpm-workspace.yaml",
284
+ },
280
285
  { content: tsconfig, path: "tsconfig.json" },
281
286
  { content: gitignore, path: ".gitignore" },
282
287
  { content: readme(appName), path: "README.md" },
@@ -0,0 +1,11 @@
1
+ import type { Path } from "effect";
2
+
3
+ export const PathContainment = {
4
+ contains(path: Path.Path, directory: string, child: string): boolean {
5
+ const relative = path.relative(directory, child);
6
+ return (
7
+ relative === "" ||
8
+ (!path.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path.sep}`))
9
+ );
10
+ },
11
+ };
@@ -15,6 +15,7 @@ import {
15
15
  writeOutput,
16
16
  } from "./codegen.js";
17
17
  import { createRouteExtractor, InvalidRouteSource, sourceError } from "./route-static.js";
18
+ import { PathContainment } from "./path.js";
18
19
 
19
20
  export interface DiscoveredClientModule {
20
21
  readonly path: string;
@@ -54,7 +55,7 @@ export const discoverClientRoutes = Effect.fn("Routes.discoverClientRoutes")(fun
54
55
  const real = yield* fs
55
56
  .realPath(directory)
56
57
  .pipe(Effect.mapError(() => diagnostic(directory, "Cannot read route directory.")));
57
- if (real !== clientDirectory && !real.startsWith(`${clientDirectory}/`))
58
+ if (!PathContainment.contains(path, clientDirectory, real))
58
59
  return yield* diagnostic(directory, "Route trees cannot escape client/ through symlinks.");
59
60
  if (seen.has(real)) return yield* diagnostic(directory, "Repeated or cyclic route directory.");
60
61
  seen.add(real);
@@ -5,6 +5,7 @@ import {
5
5
  StaticMetadata,
6
6
  UrlValueDescriptor,
7
7
  } from "@ignotum/contracts/app-definition";
8
+ import { PathContainment } from "./path.js";
8
9
 
9
10
  export class InvalidRouteSource extends Schema.TaggedError<InvalidRouteSource>()(
10
11
  "InvalidRouteSource",
@@ -126,8 +127,8 @@ export const createRouteExtractor = Effect.fn("Routes.createExtractor")(function
126
127
  .realPath(file)
127
128
  .pipe(Effect.mapError(() => fail("Cannot read source module.")));
128
129
  if (
129
- !real.startsWith(`${appDirectory}/`) ||
130
- real.startsWith(`${appDirectory}/server/`) ||
130
+ !PathContainment.contains(path, appDirectory, real) ||
131
+ PathContainment.contains(path, path.join(appDirectory, "server"), real) ||
131
132
  !/\.(ts|tsx)$/.test(real) ||
132
133
  real.endsWith(".d.ts")
133
134
  )
@@ -0,0 +1,130 @@
1
+ import { Effect, FileSystem, Path, Schema } from "effect";
2
+ import { UploadDeclaration } from "@ignotum/contracts/app-definition";
3
+ import { EnvironmentArtifact, Sha256 } from "@ignotum/contracts/version";
4
+ import { AppId, VersionId } from "@ignotum/contracts/runtime/identity";
5
+ import * as Versioned from "@ignotum/contracts/versioned";
6
+ import { encodeCanonical, sha256, utf8Bytes } from "@ignotum/versions";
7
+ import { AppConfigurationInvalid, type AppConfiguration } from "./app-configuration.js";
8
+ import type { ManagedVersionBuildResult } from "./build/managed.js";
9
+ import { readEnvironmentRaw } from "./environment.js";
10
+
11
+ const UploadJournalV1 = Schema.Struct({
12
+ formatVersion: Schema.Literal(1),
13
+ appId: AppId,
14
+ versionId: VersionId,
15
+ apiUrl: Schema.String,
16
+ declarationSha256: Sha256,
17
+ environmentNonce: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{43}$/)),
18
+ hasEnvironmentDefinition: Schema.Boolean,
19
+ });
20
+ const UploadJournal = Versioned.initial(UploadJournalV1);
21
+ export type UploadJournal = typeof UploadJournal.Type;
22
+ const invalid = () =>
23
+ AppConfigurationInvalid.make({
24
+ message:
25
+ "The saved upload does not match this app, build, or environment. Run ignotum deploy to start a new upload.",
26
+ });
27
+
28
+ export const saveUpload = Effect.fn("UploadJournal.save")(function* (
29
+ appDirectory: string,
30
+ configuration: AppConfiguration,
31
+ versionId: VersionId,
32
+ build: ManagedVersionBuildResult,
33
+ ) {
34
+ const fs = yield* FileSystem.FileSystem;
35
+ const path = yield* Path.Path;
36
+ const environment = yield* Schema.decodeEffect(Schema.fromJsonString(EnvironmentArtifact))(
37
+ new TextDecoder().decode(build.environment.bytes),
38
+ ).pipe(Effect.mapError(invalid));
39
+ const journal: UploadJournal = {
40
+ formatVersion: 1,
41
+ appId: configuration.appId,
42
+ versionId,
43
+ apiUrl: configuration.apiUrl.href,
44
+ declarationSha256: yield* sha256(build.declarationBytes),
45
+ environmentNonce: environment.nonce,
46
+ hasEnvironmentDefinition: yield* fs.exists(path.join(appDirectory, "server/env.ts")),
47
+ };
48
+ const directory = path.join(appDirectory, ".ignotum");
49
+ yield* fs.makeDirectory(directory, { recursive: true });
50
+ yield* Effect.scoped(
51
+ Effect.gen(function* () {
52
+ const temporary = yield* fs.makeTempFileScoped({ directory, prefix: ".upload-" });
53
+ yield* fs.writeFileString(temporary, encodeCanonical(UploadJournal, journal) + "\n");
54
+ yield* fs.rename(temporary, path.join(directory, "upload.json"));
55
+ }),
56
+ );
57
+ return journal;
58
+ });
59
+
60
+ export const loadUpload = Effect.fn("UploadJournal.load")(function* (
61
+ appDirectory: string,
62
+ configuration: AppConfiguration,
63
+ ) {
64
+ const fs = yield* FileSystem.FileSystem;
65
+ const path = yield* Path.Path;
66
+ const text = yield* fs.readFileString(path.join(appDirectory, ".ignotum/upload.json")).pipe(
67
+ Effect.mapError(() =>
68
+ AppConfigurationInvalid.make({
69
+ message: "No saved upload is available. Run ignotum deploy first.",
70
+ }),
71
+ ),
72
+ );
73
+ const journal = yield* Schema.decodeEffect(Schema.fromJsonString(UploadJournal))(text).pipe(
74
+ Effect.mapError(invalid),
75
+ );
76
+ if (journal.appId !== configuration.appId || journal.apiUrl !== configuration.apiUrl.href)
77
+ return yield* invalid();
78
+ return journal;
79
+ });
80
+
81
+ export const restoreUploadBuild = Effect.fn("UploadJournal.restoreBuild")(function* (
82
+ appDirectory: string,
83
+ journal: UploadJournal,
84
+ ) {
85
+ const fs = yield* FileSystem.FileSystem;
86
+ const path = yield* Path.Path;
87
+ const outputDirectory = path.join(appDirectory, ".ignotum/build");
88
+ const declarationBytes = yield* fs.readFile(path.join(outputDirectory, "declaration.json"));
89
+ if ((yield* sha256(declarationBytes)) !== journal.declarationSha256) return yield* invalid();
90
+ const declaration = yield* Schema.decodeEffect(Schema.fromJsonString(UploadDeclaration))(
91
+ new TextDecoder().decode(declarationBytes),
92
+ ).pipe(Effect.mapError(invalid));
93
+ const entries = yield* Effect.forEach(
94
+ declaration.definition.artifacts,
95
+ Effect.fn(function* (artifact) {
96
+ const bytes = yield* fs.readFile(path.join(outputDirectory, artifact.path));
97
+ if (bytes.byteLength !== artifact.size || (yield* sha256(bytes)) !== artifact.sha256)
98
+ return yield* invalid();
99
+ return [artifact.path, bytes] as const;
100
+ }),
101
+ );
102
+ const raw = yield* readEnvironmentRaw(appDirectory, journal.hasEnvironmentDefinition);
103
+ const environmentBytes = utf8Bytes(
104
+ encodeCanonical(EnvironmentArtifact, {
105
+ formatVersion: 1,
106
+ nonce: journal.environmentNonce,
107
+ values: raw.raw,
108
+ }) + "\n",
109
+ );
110
+ const artifact = declaration.environment?.artifact;
111
+ if (
112
+ artifact === undefined ||
113
+ artifact.size !== environmentBytes.byteLength ||
114
+ artifact.sha256 !== (yield* sha256(environmentBytes))
115
+ )
116
+ return yield* invalid();
117
+ return {
118
+ declaration,
119
+ declarationBytes,
120
+ outputDirectory,
121
+ bytes: new Map(entries),
122
+ environment: { path: artifact.path, bytes: environmentBytes },
123
+ } satisfies ManagedVersionBuildResult;
124
+ });
125
+
126
+ export const clearUpload = Effect.fn("UploadJournal.clear")(function* (appDirectory: string) {
127
+ const fs = yield* FileSystem.FileSystem;
128
+ const path = yield* Path.Path;
129
+ yield* fs.remove(path.join(appDirectory, ".ignotum/upload.json"), { force: true });
130
+ });
@@ -1,110 +1 @@
1
- import type { ExecutionSelection } from "@ignotum/contracts/platform";
2
- import { createElement, Fragment, render, type ComponentChildren } from "preact";
3
- import { useLayoutEffect, useState } from "preact/hooks";
4
- import { mountManagedSync } from "./sync.js";
5
- import type { ManagedBrowserClient } from "./managed-client.js";
6
- import { RouterContext, useRouterSnapshot } from "./router.js";
7
- // oxlint-disable-next-line anti-slop-effect/no-service-constructor-imports -- Each browser mount owns its router and releases it on disposal.
8
- import { RouterStore, type ClientRegistration } from "./router-store.js";
9
-
10
- import { Branch, LoadFailure } from "./route-boundaries.js";
11
-
12
- const RouterView = ({ router }: { readonly router: RouterStore }): ComponentChildren => {
13
- const snapshot = useRouterSnapshot(router);
14
- useLayoutEffect(() => {
15
- router.rendered(snapshot.commit);
16
- }, [router, snapshot]);
17
- return createElement(
18
- RouterContext.Provider,
19
- { value: router },
20
- snapshot.branch === undefined || snapshot.location === undefined
21
- ? createElement("div", { role: "status" }, snapshot.failure === undefined ? "Loading…" : null)
22
- : createElement(Branch, {
23
- branch: snapshot.branch,
24
- location: snapshot.location,
25
- router,
26
- failure: snapshot.failure,
27
- }),
28
- snapshot.failure !== undefined && snapshot.branch === undefined
29
- ? createElement(LoadFailure, { failure: snapshot.failure, router })
30
- : null,
31
- );
32
- };
33
-
34
- const mounts = new WeakSet<Element>();
35
-
36
- const ManagedStatus = ({ client }: { readonly client: ManagedBrowserClient }) => {
37
- const [snapshot, setSnapshot] = useState(client.upgrade.getSnapshot);
38
- useLayoutEffect(
39
- () => client.upgrade.subscribe(() => setSnapshot(client.upgrade.getSnapshot())),
40
- [client],
41
- );
42
- if (snapshot.phase === "ready" && snapshot.recovery.length === 0) return null;
43
- return createElement(
44
- "aside",
45
- {
46
- "data-ignotum-update": snapshot.phase,
47
- role: snapshot.phase === "error" ? "alert" : "status",
48
- },
49
- snapshot.message ?? (snapshot.phase === "connecting" ? "Connecting to the app…" : null),
50
- snapshot.recovery.length === 0
51
- ? null
52
- : createElement(
53
- "ul",
54
- null,
55
- snapshot.recovery.map((message) => createElement("li", { key: message }, message)),
56
- ),
57
- snapshot.phase === "error"
58
- ? createElement("button", { type: "button", onClick: () => client.retry() }, "Reload app")
59
- : null,
60
- snapshot.recovery.length === 0
61
- ? null
62
- : createElement(
63
- "button",
64
- { type: "button", onClick: client.upgrade.dismissRecovery },
65
- "Dismiss",
66
- ),
67
- );
68
- };
69
-
70
- /** The platform owns mounting. Generated registrations contain no Preact render/bootstrap code. */
71
- export const mountClient = (registration: ClientRegistration, selection: ExecutionSelection) => {
72
- const root = window.document.getElementById("app");
73
- if (root === null) throw new Error("Ignotum mount element is missing.");
74
- if (mounts.has(root)) throw new Error("The Ignotum client is already mounted.");
75
- const router = new RouterStore(registration, selection, window);
76
- const managed = mountManagedSync(selection, window, {
77
- stopAdmission: router.stopForUpgrade,
78
- reload: router.upgrade,
79
- });
80
- router.setAdmission({
81
- validate: managed.client.validate,
82
- recoverLoad: managed.client.recoverLoad,
83
- });
84
- render(
85
- createElement(
86
- Fragment,
87
- null,
88
- createElement(ManagedStatus, { client: managed.client }),
89
- createElement(RouterView, { router }),
90
- ),
91
- root,
92
- );
93
- const ready = managed.client
94
- .whenReady()
95
- .then((accepted) => (accepted ? router.start() : { status: "cancelled" as const }));
96
- mounts.add(root);
97
- let disposed = false;
98
- return {
99
- ready,
100
- upgrade: () => managed.client.upgrade.require(),
101
- dispose: () => {
102
- if (disposed) return;
103
- disposed = true;
104
- mounts.delete(root);
105
- router.dispose();
106
- managed.dispose();
107
- render(null, root);
108
- },
109
- };
110
- };
1
+ export * from "@ignotum/platform/versions/1/browser/bootstrap";
@@ -1,70 +1 @@
1
- /** @effect-diagnostics globalConsole:skip-file */
2
- import { Cause, Effect, ErrorReporter, Schema } from "effect";
3
-
4
- import { FunctionAddress } from "@ignotum/contracts/runtime/sync";
5
-
6
- export class ConnectionUnavailable extends Schema.TaggedError<ConnectionUnavailable>()(
7
- "ConnectionUnavailable",
8
- {
9
- cause: Schema.optional(Schema.Defect()),
10
- message: Schema.String,
11
- },
12
- ) {}
13
-
14
- export class PlatformUpgrading extends Schema.TaggedError<PlatformUpgrading>()(
15
- "PlatformUpgrading",
16
- {
17
- message: Schema.String,
18
- },
19
- ) {}
20
-
21
- export class InvalidMutationArguments extends Schema.TaggedError<InvalidMutationArguments>()(
22
- "InvalidMutationArguments",
23
- {
24
- cause: Schema.Defect(),
25
- function: FunctionAddress,
26
- message: Schema.String,
27
- },
28
- ) {}
29
-
30
- export class InvalidServerMessage extends Schema.TaggedError<InvalidServerMessage>()(
31
- "InvalidServerMessage",
32
- {
33
- cause: Schema.Defect(),
34
- message: Schema.String,
35
- },
36
- ) {}
37
-
38
- export class ServerError extends Schema.TaggedError<ServerError>()("ServerError", {
39
- code: Schema.Literals([
40
- "PlatformUpgrading",
41
- "SessionExpired",
42
- "InvalidMessage",
43
- "Unavailable",
44
- "InvalidArguments",
45
- "InvocationIdConflict",
46
- "ResourceLimitExceeded",
47
- ]),
48
- message: Schema.String,
49
- }) {}
50
-
51
- export const ClientInfrastructureError = Schema.Union([
52
- PlatformUpgrading,
53
- ConnectionUnavailable,
54
- InvalidMutationArguments,
55
- InvalidServerMessage,
56
- ServerError,
57
- ]);
58
- export type ClientInfrastructureError = typeof ClientInfrastructureError.Type;
59
-
60
- const browserErrorReporter = ErrorReporter.make(({ attributes, error }) => {
61
- globalThis.console.error("Ignotum infrastructure error", error, attributes);
62
- });
63
-
64
- export const clientErrorReporterLayer = ErrorReporter.layer([browserErrorReporter]);
65
-
66
- export const reportClientError = Effect.fn("ClientErrorReporter.report")(function* (
67
- error: ClientInfrastructureError,
68
- ) {
69
- yield* ErrorReporter.report(Cause.fail(error));
70
- });
1
+ export * from "@ignotum/platform/versions/1/browser/errors";