ignotum 0.0.18 → 0.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -62
- package/dist/cli/Undici-C6Asbalt.mjs +30 -0
- package/dist/cli/Undici-C6Asbalt.mjs.map +1 -0
- package/dist/cli/bin.mjs +732 -24022
- package/dist/cli/bin.mjs.map +1 -1
- package/dist/platform/_headers +1 -1
- package/dist/platform/_ignotum/v1.1/client.js +12 -0
- package/dist/runtime/{api-BnUKx9cj-D49Tf0rX.d.ts → api-BnUKx9cj-K-F-SOLs.d.ts} +4 -4
- package/dist/runtime/{api-C-tltz6s.js → api-CrrSjYL1.js} +2 -2
- package/dist/runtime/{api-C-tltz6s.js.map → api-CrrSjYL1.js.map} +1 -1
- package/dist/runtime/{bootstrap-DlNIfH-r.js → bootstrap-CLVnD5vS.js} +4 -4
- package/dist/runtime/bootstrap-CLVnD5vS.js.map +1 -0
- package/dist/runtime/{bootstrap-DjTwGJao-DBx5zdFI.d.ts → bootstrap-DjTwGJao-DPi768T4.d.ts} +3 -3
- package/dist/runtime/client.d.ts +9 -9
- package/dist/runtime/client.js +4 -4
- package/dist/runtime/id-BPFV3D3V.d.ts +1 -0
- package/dist/runtime/{id-7an3nxTu-DZJSM5xh.d.ts → id-D87q-n2D-CDMwTSUR.d.ts} +2 -2
- package/dist/runtime/{id-BrEnxY-r.js → id-rzjaCxeF.js} +11 -2
- package/dist/runtime/{id-BrEnxY-r.js.map → id-rzjaCxeF.js.map} +1 -1
- package/dist/runtime/{index-Biaw5vdF.d.ts → index-BWHInzfU.d.ts} +3 -3
- package/dist/runtime/internal/api.d.ts +1 -1
- package/dist/runtime/internal/api.js +1 -1
- package/dist/runtime/internal/bootstrap.d.ts +1 -1
- package/dist/runtime/internal/bootstrap.js +1 -1
- package/dist/runtime/internal/client.d.ts +1 -1
- package/dist/runtime/internal/client.js +4 -4
- package/dist/runtime/internal/client.js.map +1 -1
- package/dist/runtime/internal/host.d.ts +6 -6
- package/dist/runtime/internal/host.js +3 -3
- package/dist/runtime/internal/routes.d.ts +1 -1
- package/dist/runtime/internal/server.d.ts +1 -1
- package/dist/runtime/internal/server.js +1 -1
- package/dist/runtime/internal/types.d.ts +2 -2
- package/dist/runtime/internal/types.js +1 -1
- package/dist/runtime/pagination-BPFV3D3V.d.ts +1 -0
- package/dist/runtime/{pagination--f4kMKf6.js → pagination-CfEleffG.js} +3 -2
- package/dist/runtime/pagination-CfEleffG.js.map +1 -0
- package/dist/runtime/result-Bq32FA7g.d.ts +1 -0
- package/dist/runtime/{router-C1eC9yy5.js → router-CfNPtoVO.js} +109 -60
- package/dist/runtime/router-CfNPtoVO.js.map +1 -0
- package/dist/runtime/{schema-BSjepoNF.js → schema-JtFb4BbG.js} +3 -3
- package/dist/runtime/{schema-BSjepoNF.js.map → schema-JtFb4BbG.js.map} +1 -1
- package/dist/runtime/server.d.ts +5 -5
- package/dist/runtime/server.js +2 -2
- package/dist/runtime/{sync-By2hX5ev.d.ts → sync-BMmV8oYt.d.ts} +2 -2
- package/dist/runtime/{sync-BwgS19Xy.js → sync-DUfp7S5R.js} +2 -2
- package/dist/runtime/{sync-BwgS19Xy.js.map → sync-DUfp7S5R.js.map} +1 -1
- package/dist/runtime/{types-DeCCyqLU-CHx7KFtx.d.ts → types-BeT6F0Pg-DMvRDN0c.d.ts} +5 -5
- package/package.json +9 -8
- package/src/cli/app-configuration.ts +44 -5
- package/src/cli/auth-client.ts +3 -3
- package/src/cli/build/managed-server.ts +3 -2
- package/src/cli/build/server.ts +2 -2
- package/src/cli/command.ts +18 -11
- package/src/cli/control-client.ts +2 -2
- package/src/cli/deploy.ts +27 -2
- package/src/cli/dev.ts +1 -1
- package/src/cli/managed-dev-platform.ts +13 -4
- package/src/cli/module-boundaries.ts +1 -1
- package/src/cli/new-app.ts +35 -6
- package/src/cli/package-manager.ts +1 -1
- package/src/cli/route-static.ts +7 -2
- package/src/cli/typecheck.ts +66 -0
- package/src/cli/upload-journal.ts +3 -3
- package/src/dev-runtime/database.ts +1 -1
- package/src/dev-runtime/files.ts +1 -1
- package/src/dev-runtime/managed-functions.ts +3 -2
- package/src/dev-runtime/managed-sync.ts +114 -105
- package/src/dev-runtime/managed-websocket.ts +11 -7
- package/src/dev-runtime/migrations.ts +2 -2
- package/src/dev-runtime/sync.ts +3 -3
- package/dist/platform/_ignotum/v1.0/client.js +0 -12
- package/dist/runtime/bootstrap-DlNIfH-r.js.map +0 -1
- package/dist/runtime/id-BI08JkmR.d.ts +0 -1
- package/dist/runtime/pagination--f4kMKf6.js.map +0 -1
- package/dist/runtime/pagination-BI08JkmR.d.ts +0 -1
- package/dist/runtime/result-BkziOG1L.d.ts +0 -1
- package/dist/runtime/router-C1eC9yy5.js.map +0 -1
package/src/cli/command.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NodeHttpClient, NodeRuntime, NodeServices } from "@effect/platform-node";
|
|
2
|
-
import { Effect, Layer, Option, Path, Schema, Terminal } from "effect";
|
|
3
|
-
import { Argument, CliError, Command, Flag } from "effect/
|
|
2
|
+
import { Clock, Effect, Layer, Option, Path, Schema, Terminal } from "effect";
|
|
3
|
+
import { Argument, CliError, Command, Flag } from "effect/cli";
|
|
4
4
|
import { AppSlug } from "@ignotum/contracts/control";
|
|
5
5
|
import { IdGenerator } from "@ignotum/shared/id";
|
|
6
6
|
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
status as readAuthStatus,
|
|
14
14
|
} from "./auth-client.js";
|
|
15
15
|
import { ControlClient } from "./control-client.js";
|
|
16
|
-
import { deploy as runDeploy, resumeVersion } from "./deploy.js";
|
|
16
|
+
import { DeployProgress, deploy as runDeploy, resumeVersion } from "./deploy.js";
|
|
17
17
|
import { resetDevDatabase } from "../dev-runtime/dev-database.js";
|
|
18
18
|
import { dev as runDev } from "./dev.js";
|
|
19
19
|
import { createApp } from "./new-app.js";
|
|
@@ -118,9 +118,9 @@ const devDatabase = Command.make("db").pipe(
|
|
|
118
118
|
const dev = Command.make(
|
|
119
119
|
"dev",
|
|
120
120
|
{
|
|
121
|
-
host: Flag.
|
|
122
|
-
open: Flag.
|
|
123
|
-
port: Flag.
|
|
121
|
+
host: Flag.String("host").pipe(Flag.withDefault("127.0.0.1")),
|
|
122
|
+
open: Flag.Boolean("open").pipe(Flag.withDefault(false)),
|
|
123
|
+
port: Flag.Int("port").pipe(Flag.withSchema(DevPort), Flag.withDefault(3210)),
|
|
124
124
|
},
|
|
125
125
|
Effect.fn("dev")(function* ({ host, open, port }) {
|
|
126
126
|
const path = yield* Path.Path;
|
|
@@ -144,11 +144,11 @@ const dev = Command.make(
|
|
|
144
144
|
const deploy = Command.make(
|
|
145
145
|
"deploy",
|
|
146
146
|
{
|
|
147
|
-
resume: Flag.
|
|
147
|
+
resume: Flag.Boolean("resume").pipe(
|
|
148
148
|
Flag.withDescription("Resume the saved upload or activation."),
|
|
149
149
|
Flag.withDefault(false),
|
|
150
150
|
),
|
|
151
|
-
app: Flag.
|
|
151
|
+
app: Flag.String("app").pipe(
|
|
152
152
|
Flag.withDescription("Create or link this app slug on the first deploy."),
|
|
153
153
|
Flag.withSchema(AppSlug),
|
|
154
154
|
Flag.optional,
|
|
@@ -157,8 +157,14 @@ const deploy = Command.make(
|
|
|
157
157
|
Effect.fn("deploy")(function* ({ app, resume }) {
|
|
158
158
|
const path = yield* Path.Path;
|
|
159
159
|
const terminal = yield* Terminal.Terminal;
|
|
160
|
+
const startedAt = yield* Clock.currentTimeMillis;
|
|
161
|
+
const progress = Effect.fn("deploy.progress")(function* (message: string) {
|
|
162
|
+
const elapsed = ((yield* Clock.currentTimeMillis) - startedAt) / 1000;
|
|
163
|
+
yield* terminal.display(`${message} (${elapsed.toFixed(1)}s)\n`).pipe(Effect.orDie);
|
|
164
|
+
});
|
|
160
165
|
if (resume) {
|
|
161
166
|
const result = yield* resumeVersion(path.resolve("."), Option.getOrUndefined(app)).pipe(
|
|
167
|
+
Effect.provideService(DeployProgress, progress),
|
|
162
168
|
// @effect-diagnostics-next-line strictEffectProvide:off This command owns the authenticated control client.
|
|
163
169
|
Effect.provide(ControlClient.layer),
|
|
164
170
|
Effect.mapError((error) =>
|
|
@@ -171,6 +177,7 @@ const deploy = Command.make(
|
|
|
171
177
|
return;
|
|
172
178
|
}
|
|
173
179
|
const result = yield* runDeploy(path.resolve("."), Option.getOrUndefined(app)).pipe(
|
|
180
|
+
Effect.provideService(DeployProgress, progress),
|
|
174
181
|
// @effect-diagnostics-next-line strictEffectProvide:off The deploy subcommand owns this client layer for its full lifetime.
|
|
175
182
|
Effect.provide(ControlClient.layer),
|
|
176
183
|
Effect.mapError((error) =>
|
|
@@ -201,14 +208,14 @@ const install = Command.make(
|
|
|
201
208
|
const newApp = Command.make(
|
|
202
209
|
"new",
|
|
203
210
|
{
|
|
204
|
-
directory: Argument.
|
|
211
|
+
directory: Argument.String("directory").pipe(
|
|
205
212
|
Argument.withDescription('Directory to create. Use "." for the current directory.'),
|
|
206
213
|
),
|
|
207
|
-
git: Flag.
|
|
214
|
+
git: Flag.Boolean("git").pipe(
|
|
208
215
|
Flag.withDescription("Initialize a Git repository. Use --no-git to skip it."),
|
|
209
216
|
Flag.withDefault(true),
|
|
210
217
|
),
|
|
211
|
-
install: Flag.
|
|
218
|
+
install: Flag.Boolean("install").pipe(
|
|
212
219
|
Flag.withDescription("Install dependencies. Use --no-install to skip it."),
|
|
213
220
|
Flag.withDefault(true),
|
|
214
221
|
),
|
|
@@ -36,8 +36,8 @@ import {
|
|
|
36
36
|
Schedule,
|
|
37
37
|
Schema,
|
|
38
38
|
} from "effect";
|
|
39
|
-
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/
|
|
40
|
-
import type { HttpClientRequest as HttpRequest } from "effect/
|
|
39
|
+
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http";
|
|
40
|
+
import type { HttpClientRequest as HttpRequest } from "effect/http/HttpClientRequest";
|
|
41
41
|
import { AuthCommandError, AuthCredentialStore } from "./auth-client.js";
|
|
42
42
|
|
|
43
43
|
export interface HostedControlConfig {
|
package/src/cli/deploy.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { Effect, Path, Schema } from "effect";
|
|
1
|
+
import { Context, Effect, Path, Schema } from "effect";
|
|
2
2
|
import type { AppSlug, ControlVersion } from "@ignotum/contracts/control";
|
|
3
3
|
import { VersionId, VersionNumber } from "@ignotum/contracts/runtime/identity";
|
|
4
4
|
import { IdGenerator } from "@ignotum/shared/id";
|
|
5
5
|
import { appUrl, configureApp, type AppConfiguration } from "./app-configuration.js";
|
|
6
6
|
import { buildManagedVersion, type ManagedVersionBuildResult } from "./build/managed.js";
|
|
7
|
+
import { generateClientRoutes } from "./route-codegen.js";
|
|
8
|
+
import { checkAppTypes } from "./typecheck.js";
|
|
7
9
|
import {
|
|
8
10
|
ControlClient,
|
|
9
11
|
ControlClientUnavailable,
|
|
@@ -13,6 +15,11 @@ import {
|
|
|
13
15
|
|
|
14
16
|
import { saveUpload, loadUpload, restoreUploadBuild, clearUpload } from "./upload-journal.js";
|
|
15
17
|
|
|
18
|
+
export const DeployProgress = Context.Reference<(message: string) => Effect.Effect<void>>(
|
|
19
|
+
"ignotum/cli/deploy/DeployProgress",
|
|
20
|
+
{ defaultValue: () => () => Effect.void },
|
|
21
|
+
);
|
|
22
|
+
|
|
16
23
|
export class VersionUploadInterrupted extends Schema.TaggedError<VersionUploadInterrupted>()(
|
|
17
24
|
"VersionUploadInterrupted",
|
|
18
25
|
{ cause: Schema.Defect(), versionId: VersionId, message: Schema.String },
|
|
@@ -65,7 +72,9 @@ export const uploadVersion = Effect.fn("Deploy.upload")(function* (
|
|
|
65
72
|
savedVersionId?: VersionId,
|
|
66
73
|
) {
|
|
67
74
|
const control = yield* ControlClient;
|
|
75
|
+
const progress = yield* DeployProgress;
|
|
68
76
|
const versionId = savedVersionId ?? (yield* (yield* IdGenerator).generate(VersionId));
|
|
77
|
+
yield* progress("Preparing upload");
|
|
69
78
|
const admission = yield* control
|
|
70
79
|
.beginVersion(configuration.appId, versionId, build.declaration)
|
|
71
80
|
.pipe(
|
|
@@ -112,9 +121,13 @@ export const uploadVersion = Effect.fn("Deploy.upload")(function* (
|
|
|
112
121
|
}
|
|
113
122
|
return Effect.succeed({ ...target, bytes } satisfies VersionUploadFile);
|
|
114
123
|
});
|
|
124
|
+
yield* progress(`Uploading 0/${files.length} files`);
|
|
115
125
|
yield* Effect.forEach(
|
|
116
126
|
files,
|
|
117
|
-
(file) =>
|
|
127
|
+
(file, index) =>
|
|
128
|
+
control
|
|
129
|
+
.uploadVersionFile(configuration.appId, versionId, file)
|
|
130
|
+
.pipe(Effect.andThen(() => progress(`Uploading ${index + 1}/${files.length} files`))),
|
|
118
131
|
// The hosted control plane runs within Cloudflare's per-request CPU budget.
|
|
119
132
|
// Serialize uploads so several validation-heavy requests cannot contend in
|
|
120
133
|
// the same isolate and exhaust that budget together.
|
|
@@ -129,6 +142,7 @@ export const uploadVersion = Effect.fn("Deploy.upload")(function* (
|
|
|
129
142
|
}),
|
|
130
143
|
}),
|
|
131
144
|
);
|
|
145
|
+
yield* progress("Validating version");
|
|
132
146
|
const version = yield* control.finalizeVersion(configuration.appId, versionId).pipe(
|
|
133
147
|
Effect.catchTags({
|
|
134
148
|
ControlClientUnavailable: (cause) =>
|
|
@@ -140,6 +154,7 @@ export const uploadVersion = Effect.fn("Deploy.upload")(function* (
|
|
|
140
154
|
}),
|
|
141
155
|
Effect.flatMap(readyVersion),
|
|
142
156
|
);
|
|
157
|
+
yield* progress(`Activating Version ${version.number}`);
|
|
143
158
|
const selection = yield* control
|
|
144
159
|
.activateVersion(configuration.appId, version.number)
|
|
145
160
|
.pipe(Effect.mapError((cause) => activationError(version, cause)));
|
|
@@ -159,7 +174,14 @@ export const deploy = Effect.fn("Deploy.run")(function* (
|
|
|
159
174
|
appDirectory: string,
|
|
160
175
|
requestedSlug: AppSlug | undefined,
|
|
161
176
|
) {
|
|
177
|
+
const progress = yield* DeployProgress;
|
|
178
|
+
yield* progress("Generating bindings");
|
|
179
|
+
yield* generateClientRoutes(appDirectory);
|
|
180
|
+
yield* progress("Checking TypeScript");
|
|
181
|
+
yield* checkAppTypes(appDirectory);
|
|
182
|
+
yield* progress("Building app");
|
|
162
183
|
const build = yield* buildVersionArtifact(appDirectory);
|
|
184
|
+
yield* progress("Linking app");
|
|
163
185
|
const configuration = yield* configureApp(appDirectory, requestedSlug);
|
|
164
186
|
const versionId = yield* (yield* IdGenerator).generate(VersionId);
|
|
165
187
|
yield* saveUpload(appDirectory, configuration, versionId, build);
|
|
@@ -172,6 +194,8 @@ export const resumeVersion = Effect.fn("Deploy.resume")(function* (
|
|
|
172
194
|
appDirectory: string,
|
|
173
195
|
requestedSlug?: AppSlug,
|
|
174
196
|
) {
|
|
197
|
+
const progress = yield* DeployProgress;
|
|
198
|
+
yield* progress("Restoring saved deployment");
|
|
175
199
|
const configuration = yield* configureApp(appDirectory, requestedSlug);
|
|
176
200
|
const control = yield* ControlClient;
|
|
177
201
|
const journal = yield* loadUpload(appDirectory, configuration);
|
|
@@ -189,6 +213,7 @@ export const resumeVersion = Effect.fn("Deploy.resume")(function* (
|
|
|
189
213
|
return { ...activated, appUrl: appUrl(configuration.slug) };
|
|
190
214
|
}
|
|
191
215
|
const version = yield* readyVersion(existing);
|
|
216
|
+
yield* progress(`Activating Version ${version.number}`);
|
|
192
217
|
const selection = yield* control
|
|
193
218
|
.activateVersion(configuration.appId, version.number)
|
|
194
219
|
.pipe(Effect.mapError((cause) => activationError(version, cause)));
|
package/src/cli/dev.ts
CHANGED
|
@@ -3,7 +3,7 @@ import prefresh from "@prefresh/vite";
|
|
|
3
3
|
import tailwindcss from "@tailwindcss/vite";
|
|
4
4
|
import { Crypto, Effect, FileSystem, Option, Path, Schema, Scope } from "effect";
|
|
5
5
|
import { createServer, normalizePath, type Plugin, type ViteDevServer } from "vite";
|
|
6
|
-
import { ChildProcessSpawner } from "effect/
|
|
6
|
+
import { ChildProcessSpawner } from "effect/process";
|
|
7
7
|
import {
|
|
8
8
|
PlatformRequirement,
|
|
9
9
|
revisionPrefix,
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { Effect, FileSystem, Path, Schema } from "effect";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
revisionPrefix,
|
|
6
|
+
ServingSelection,
|
|
7
|
+
type PlatformRevision,
|
|
8
|
+
} from "@ignotum/contracts/deployment";
|
|
5
9
|
import { VersionId } from "@ignotum/contracts/runtime/identity";
|
|
6
10
|
import { bundledGuests } from "@ignotum/platform/guests";
|
|
7
11
|
import type { BundledGuest } from "@ignotum/platform/artifacts";
|
|
8
12
|
import { compilerExports } from "@ignotum/platform/compiler-exports";
|
|
9
13
|
import { platformImportsPlugin } from "@ignotum/platform/compiler";
|
|
14
|
+
import { revisions } from "@ignotum/platform/registry";
|
|
10
15
|
import { runViteBuild } from "./build/artifact.js";
|
|
11
16
|
|
|
12
17
|
export interface LocalPlatform {
|
|
@@ -15,9 +20,11 @@ export interface LocalPlatform {
|
|
|
15
20
|
readonly guest: BundledGuest;
|
|
16
21
|
}
|
|
17
22
|
|
|
23
|
+
const localPlatformRevision = revisions[0];
|
|
24
|
+
|
|
18
25
|
export const loadLocalPlatform = Effect.fn("ManagedDev.loadPlatform")(function* () {
|
|
19
26
|
const fs = yield* FileSystem.FileSystem;
|
|
20
|
-
const revision =
|
|
27
|
+
const revision = localPlatformRevision;
|
|
21
28
|
const guest = bundledGuests.find(
|
|
22
29
|
(candidate) =>
|
|
23
30
|
candidate.manifest.revision.major === revision.major &&
|
|
@@ -25,7 +32,9 @@ export const loadLocalPlatform = Effect.fn("ManagedDev.loadPlatform")(function*
|
|
|
25
32
|
);
|
|
26
33
|
if (guest === undefined) return yield* Effect.die("The local platform guest is missing.");
|
|
27
34
|
const browser = yield* fs.readFile(
|
|
28
|
-
fileURLToPath(
|
|
35
|
+
fileURLToPath(
|
|
36
|
+
new URL(`../../dist/platform${revisionPrefix(revision)}/client.js`, import.meta.url),
|
|
37
|
+
),
|
|
29
38
|
);
|
|
30
39
|
return { revision, browser, guest } satisfies LocalPlatform;
|
|
31
40
|
});
|
|
@@ -45,7 +54,7 @@ export const localServingSelection = (
|
|
|
45
54
|
formatVersion: 1,
|
|
46
55
|
appId: `app_${digest}`,
|
|
47
56
|
versionNumber: 1,
|
|
48
|
-
platform:
|
|
57
|
+
platform: localPlatformRevision,
|
|
49
58
|
activationGeneration,
|
|
50
59
|
deploymentEpoch,
|
|
51
60
|
dataHistoryId: `hist_${digest}`,
|
|
@@ -71,7 +71,7 @@ export const moduleBoundariesPlugin = Effect.fn("Deploy.moduleBoundariesPlugin")
|
|
|
71
71
|
const fileSystem = yield* FileSystem.FileSystem;
|
|
72
72
|
const runPromise = Effect.runPromiseWith(yield* Effect.context<never>());
|
|
73
73
|
const isolatedDiscovery =
|
|
74
|
-
(yield* Config.
|
|
74
|
+
(yield* Config.String("IGNOTUM_ISOLATED_DISCOVERY").pipe(Config.withDefault("0"))) === "1";
|
|
75
75
|
const realAppDirectory = isolatedDiscovery
|
|
76
76
|
? normalizePath(cleanModuleId(appDirectory))
|
|
77
77
|
: normalizePath(yield* fileSystem.realPath(cleanModuleId(appDirectory)));
|
package/src/cli/new-app.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Effect, FileSystem, Path, Schema, String } from "effect";
|
|
2
|
-
import { ChildProcess, ChildProcessSpawner } from "effect/
|
|
2
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/process";
|
|
3
3
|
import cliPackageJson from "../../package.json" with { type: "json" };
|
|
4
4
|
|
|
5
5
|
import { agentAppFiles, type AppFile } from "./agent-files.js";
|
|
@@ -145,7 +145,7 @@ const icon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
|
145
145
|
const utils = `export const counterIncrement = 1;
|
|
146
146
|
`;
|
|
147
147
|
|
|
148
|
-
const home = `import { Result, useMutation, useQuery } from "ignotum/client";
|
|
148
|
+
const home = `import { Result, useMutation, useQuery, useState } from "ignotum/client";
|
|
149
149
|
import { api } from "@/_generated/api.js";
|
|
150
150
|
import { route } from "@/_generated/client.js";
|
|
151
151
|
import { counterIncrement } from "@/shared/utils.js";
|
|
@@ -155,6 +155,24 @@ export default route({
|
|
|
155
155
|
component: function Counter() {
|
|
156
156
|
const count = useQuery(api.counter.get);
|
|
157
157
|
const increment = useMutation(api.counter.increment);
|
|
158
|
+
const [busy, setBusy] = useState(false);
|
|
159
|
+
const [error, setError] = useState("");
|
|
160
|
+
const incrementCounter = async () => {
|
|
161
|
+
if (busy) return;
|
|
162
|
+
setBusy(true);
|
|
163
|
+
setError("");
|
|
164
|
+
try {
|
|
165
|
+
Result.match(await increment(), {
|
|
166
|
+
value: () => {},
|
|
167
|
+
error: (failure) => setError("Increment failed: " + failure._tag),
|
|
168
|
+
internalError: (failure) => setError("Increment failed. Request ID: " + failure.requestId),
|
|
169
|
+
});
|
|
170
|
+
} catch {
|
|
171
|
+
setError("The increment could not be confirmed. Check the current counter before trying again.");
|
|
172
|
+
} finally {
|
|
173
|
+
setBusy(false);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
158
176
|
|
|
159
177
|
return (
|
|
160
178
|
<section class="text-center">
|
|
@@ -165,15 +183,20 @@ export default route({
|
|
|
165
183
|
<>
|
|
166
184
|
<p class="my-6 text-5xl tabular-nums">{value}</p>
|
|
167
185
|
<button
|
|
168
|
-
class="rounded bg-zinc-900 px-4 py-2 text-white"
|
|
186
|
+
class="rounded bg-zinc-900 px-4 py-2 text-white disabled:opacity-50"
|
|
169
187
|
type="button"
|
|
170
|
-
|
|
188
|
+
disabled={busy}
|
|
189
|
+
onClick={() => void incrementCounter()}
|
|
171
190
|
>
|
|
172
|
-
Increment by
|
|
191
|
+
{busy ? "Incrementing..." : "Increment by " + counterIncrement}
|
|
173
192
|
</button>
|
|
174
193
|
</>
|
|
175
194
|
),
|
|
195
|
+
internalError: (failure) => (
|
|
196
|
+
<p role="alert" class="mt-6">Counter could not be loaded. Request ID: {failure.requestId}</p>
|
|
197
|
+
),
|
|
176
198
|
})}
|
|
199
|
+
{error !== "" && <p role="alert" class="mt-4">{error}</p>}
|
|
177
200
|
</section>
|
|
178
201
|
);
|
|
179
202
|
},
|
|
@@ -254,7 +277,9 @@ After generating bindings, run \`npx tsc --noEmit\` to check the app.
|
|
|
254
277
|
|
|
255
278
|
## Deploy
|
|
256
279
|
|
|
257
|
-
Sign in with \`npx ignotum auth login\`, then run \`npx ignotum deploy
|
|
280
|
+
Sign in with \`npx ignotum auth login\`, then run \`npx ignotum deploy\`.
|
|
281
|
+
The first deploy tries the app name saved when this project was created. Use
|
|
282
|
+
\`npx ignotum deploy --app another-slug\` to choose a different slug.
|
|
258
283
|
Later deployments use \`npx ignotum deploy\`. The command prints the hosted URL and confirms
|
|
259
284
|
that the numbered App Version is live. If activation cannot be confirmed, follow the printed
|
|
260
285
|
\`ignotum deploy --resume\` command to finish without creating another version.
|
|
@@ -278,6 +303,10 @@ export const appFiles = (appName: string): ReadonlyArray<AppFile> => [
|
|
|
278
303
|
{ content: schema, path: "server/schema.ts" },
|
|
279
304
|
{ content: utils, path: "shared/utils.ts" },
|
|
280
305
|
{ content: packageJson(appName), path: "package.json" },
|
|
306
|
+
{
|
|
307
|
+
content: `${JSON.stringify({ formatVersion: 1, suggestedSlug: appName }, null, 2)}\n`,
|
|
308
|
+
path: "ignotum.json",
|
|
309
|
+
},
|
|
281
310
|
{
|
|
282
311
|
content: `minimumReleaseAgeExclude:\n - ignotum@${cliPackageJson.version}\nallowBuilds:\n msgpackr-extract: false\n`,
|
|
283
312
|
path: "pnpm-workspace.yaml",
|
package/src/cli/route-static.ts
CHANGED
|
@@ -589,7 +589,9 @@ export const createRouteExtractor = Effect.fn("Routes.createExtractor")(function
|
|
|
589
589
|
`Unsupported URL validator '${method}' or argument count.`,
|
|
590
590
|
);
|
|
591
591
|
}
|
|
592
|
-
return yield* Schema.decodeUnknownEffect(UrlValueDescriptor
|
|
592
|
+
return yield* Schema.decodeUnknownEffect(UrlValueDescriptor, {
|
|
593
|
+
onExcessProperty: "error",
|
|
594
|
+
})(output).pipe(
|
|
593
595
|
Effect.mapError((error) =>
|
|
594
596
|
sourceError(module, node, field, `Invalid ${method} URL descriptor: ${String(error)}`),
|
|
595
597
|
),
|
|
@@ -660,7 +662,9 @@ export const createRouteExtractor = Effect.fn("Routes.createExtractor")(function
|
|
|
660
662
|
const extracted = yield* value(expression, name);
|
|
661
663
|
const validate =
|
|
662
664
|
name === "meta"
|
|
663
|
-
? Schema.decodeUnknownEffect(StaticMetadata.fields.meta
|
|
665
|
+
? Schema.decodeUnknownEffect(StaticMetadata.fields.meta, {
|
|
666
|
+
onExcessProperty: "error",
|
|
667
|
+
})(extracted).pipe(Effect.asVoid)
|
|
664
668
|
: name === "titleTemplate"
|
|
665
669
|
? Schema.decodeUnknownEffect(LayoutMetadata.fields.titleTemplate)(extracted).pipe(
|
|
666
670
|
Effect.asVoid,
|
|
@@ -677,6 +681,7 @@ export const createRouteExtractor = Effect.fn("Routes.createExtractor")(function
|
|
|
677
681
|
}
|
|
678
682
|
const decodedMetadata = yield* Schema.decodeUnknownEffect(
|
|
679
683
|
kind === "route" ? StaticMetadata : LayoutMetadata,
|
|
684
|
+
{ onExcessProperty: "error" },
|
|
680
685
|
)(Object.fromEntries(metadata)).pipe(
|
|
681
686
|
Effect.mapError((error) => sourceError(module, call, "metadata", String(error))),
|
|
682
687
|
);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { Effect, FileSystem, Path, Schema } from "effect";
|
|
4
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/process";
|
|
5
|
+
|
|
6
|
+
const CompilerPackage = Schema.Struct({ bin: Schema.Struct({ tsc: Schema.String }) });
|
|
7
|
+
|
|
8
|
+
export class AppTypecheckFailed extends Schema.TaggedError<AppTypecheckFailed>()(
|
|
9
|
+
"AppTypecheckFailed",
|
|
10
|
+
{ message: Schema.String, cause: Schema.optional(Schema.Defect()) },
|
|
11
|
+
) {}
|
|
12
|
+
|
|
13
|
+
export const checkAppTypes = Effect.fn("Typecheck.checkAppTypes")(function* (appDirectory: string) {
|
|
14
|
+
const fs = yield* FileSystem.FileSystem;
|
|
15
|
+
const path = yield* Path.Path;
|
|
16
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
17
|
+
const configuration = path.join(appDirectory, "tsconfig.json");
|
|
18
|
+
if (!(yield* fs.exists(configuration))) {
|
|
19
|
+
return yield* AppTypecheckFailed.make({
|
|
20
|
+
message: "Deployment requires a tsconfig.json in the app directory.",
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const packagePath = yield* Effect.try({
|
|
24
|
+
try: () =>
|
|
25
|
+
createRequire(path.join(appDirectory, "package.json")).resolve("typescript/package.json"),
|
|
26
|
+
catch: (cause) =>
|
|
27
|
+
AppTypecheckFailed.make({
|
|
28
|
+
cause,
|
|
29
|
+
message:
|
|
30
|
+
"TypeScript is not installed for this app. Install TypeScript as a dev dependency, then deploy again.",
|
|
31
|
+
}),
|
|
32
|
+
});
|
|
33
|
+
const compiler = yield* fs.readFileString(packagePath).pipe(
|
|
34
|
+
Effect.flatMap(Schema.decodeEffect(Schema.fromJsonString(CompilerPackage))),
|
|
35
|
+
Effect.mapError((cause) =>
|
|
36
|
+
AppTypecheckFailed.make({
|
|
37
|
+
cause,
|
|
38
|
+
message: "Could not read the installed TypeScript compiler.",
|
|
39
|
+
}),
|
|
40
|
+
),
|
|
41
|
+
);
|
|
42
|
+
const exitCode = yield* spawner
|
|
43
|
+
.exitCode(
|
|
44
|
+
ChildProcess.make(
|
|
45
|
+
process.execPath,
|
|
46
|
+
[
|
|
47
|
+
path.resolve(path.dirname(packagePath), compiler.bin.tsc),
|
|
48
|
+
"--project",
|
|
49
|
+
configuration,
|
|
50
|
+
"--noEmit",
|
|
51
|
+
],
|
|
52
|
+
{ cwd: appDirectory, stdin: "ignore", stdout: "inherit", stderr: "inherit" },
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
.pipe(
|
|
56
|
+
Effect.mapError((cause) =>
|
|
57
|
+
AppTypecheckFailed.make({ cause, message: "Could not run the app's TypeScript compiler." }),
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
if (exitCode !== ChildProcessSpawner.ExitCode(0)) {
|
|
61
|
+
return yield* AppTypecheckFailed.make({
|
|
62
|
+
message:
|
|
63
|
+
"TypeScript checking failed. Fix the errors above before deploying. No version was uploaded.",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
});
|
|
@@ -87,9 +87,9 @@ export const restoreUploadBuild = Effect.fn("UploadJournal.restoreBuild")(functi
|
|
|
87
87
|
const outputDirectory = path.join(appDirectory, ".ignotum/build");
|
|
88
88
|
const declarationBytes = yield* fs.readFile(path.join(outputDirectory, "declaration.json"));
|
|
89
89
|
if ((yield* sha256(declarationBytes)) !== journal.declarationSha256) return yield* invalid();
|
|
90
|
-
const declaration = yield* Schema.decodeEffect(Schema.fromJsonString(UploadDeclaration)
|
|
91
|
-
|
|
92
|
-
).pipe(Effect.mapError(invalid));
|
|
90
|
+
const declaration = yield* Schema.decodeEffect(Schema.fromJsonString(UploadDeclaration), {
|
|
91
|
+
onExcessProperty: "error",
|
|
92
|
+
})(new TextDecoder().decode(declarationBytes)).pipe(Effect.mapError(invalid));
|
|
93
93
|
const entries = yield* Effect.forEach(
|
|
94
94
|
declaration.definition.artifacts,
|
|
95
95
|
Effect.fn(function* (artifact) {
|
package/src/dev-runtime/files.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { fileGrantUrlPrefix, fileUploadUrlPrefix } from "@ignotum/contracts/runt
|
|
|
15
15
|
import type { InvocationId } from "@ignotum/contracts/runtime/identity";
|
|
16
16
|
import { IdGenerator } from "@ignotum/shared/id";
|
|
17
17
|
import { Context, Effect, FileSystem, Layer, Option, Path, Schema } from "effect";
|
|
18
|
-
import { SqlClient } from "effect/
|
|
18
|
+
import { SqlClient } from "effect/sql";
|
|
19
19
|
|
|
20
20
|
interface Preparation {
|
|
21
21
|
readonly function: FunctionAddress;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { Base64 } from "effect/encoding";
|
|
1
2
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
2
|
-
import { Crypto, Effect,
|
|
3
|
+
import { Crypto, Effect, FileSystem, Path, Predicate, Schema } from "effect";
|
|
3
4
|
import type { ViteDevServer } from "vite";
|
|
4
5
|
import { parseSync } from "oxc-parser";
|
|
5
6
|
import { prepareManagedExecution, type ManagedExecution } from "@ignotum/runtime/quickjs";
|
|
@@ -165,7 +166,7 @@ export const createManagedFunctionLoader = Effect.fn("ManagedFunctions.createLoa
|
|
|
165
166
|
}
|
|
166
167
|
if (module.sourceMap !== undefined) {
|
|
167
168
|
rewritten = rewritten.replace(/^\/\/# sourceMappingURL=.*$/gm, "");
|
|
168
|
-
rewritten += `\n//# sourceMappingURL=data:application/json;base64,${
|
|
169
|
+
rewritten += `\n//# sourceMappingURL=data:application/json;base64,${Base64.encode(module.sourceMap.bytes)}\n`;
|
|
169
170
|
}
|
|
170
171
|
yield* fs.writeFileString(fileURLToPath(urls.get(module.id)!), rewritten);
|
|
171
172
|
}
|