ignotum 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +23 -7
  2. package/dist/cli/bin.mjs +32313 -751
  3. package/dist/cli/bin.mjs.map +1 -1
  4. package/dist/runtime/{api-Cpx57mk3.d.ts → api-D2bsAz4T.d.ts} +3 -2
  5. package/dist/runtime/{api-BXXIZc_Q.js → api-DtX8qPrq.js} +58 -27
  6. package/dist/runtime/api-DtX8qPrq.js.map +1 -0
  7. package/dist/runtime/client.d.ts +11 -4
  8. package/dist/runtime/client.js +343 -79
  9. package/dist/runtime/client.js.map +1 -1
  10. package/dist/runtime/{result-B2W-z2wG.js → descriptor-t6BOEGw9-BTHR-ZMv.js} +119 -4
  11. package/dist/runtime/descriptor-t6BOEGw9-BTHR-ZMv.js.map +1 -0
  12. package/dist/runtime/id-Btwac71X-B9OeBzvq.d.ts +9 -0
  13. package/dist/runtime/id-D570vudg.js +26 -0
  14. package/dist/runtime/id-D570vudg.js.map +1 -0
  15. package/dist/runtime/{index-BgWROoyk.d.ts → index-Dl_VQGmA.d.ts} +114 -31
  16. package/dist/runtime/internal/api.d.ts +1 -1
  17. package/dist/runtime/internal/api.js +1 -1
  18. package/dist/runtime/internal/host.d.ts +44 -0
  19. package/dist/runtime/internal/host.js +213 -0
  20. package/dist/runtime/internal/host.js.map +1 -0
  21. package/dist/runtime/internal/server.d.ts +1 -1
  22. package/dist/runtime/internal/server.js +1 -1
  23. package/dist/runtime/internal/types.d.ts +1 -1
  24. package/dist/runtime/internal/types.js +1 -1
  25. package/dist/runtime/{result-C1ZdsM6Y.d.ts → result-BgcHn25t.d.ts} +21 -6
  26. package/dist/runtime/schema-B-jMZEbs.js +227 -0
  27. package/dist/runtime/schema-B-jMZEbs.js.map +1 -0
  28. package/dist/runtime/server.d.ts +2 -2
  29. package/dist/runtime/server.js +2 -2
  30. package/package.json +15 -5
  31. package/src/cli/agent-files.ts +6 -2
  32. package/src/cli/app-configuration.ts +135 -0
  33. package/src/cli/bin.ts +24 -1
  34. package/src/cli/build/artifact.ts +95 -0
  35. package/src/cli/build/client.ts +103 -0
  36. package/src/cli/build/server.ts +526 -0
  37. package/src/cli/client-config.ts +141 -0
  38. package/src/cli/client-entry.ts +152 -0
  39. package/src/cli/client-plugin.ts +86 -16
  40. package/src/cli/codegen.ts +5 -5
  41. package/src/cli/command.ts +41 -9
  42. package/src/cli/control-client.ts +343 -0
  43. package/src/cli/deploy.ts +217 -0
  44. package/src/cli/dev.ts +43 -108
  45. package/src/cli/module-boundaries.ts +183 -0
  46. package/src/cli/{new-project.ts → new-app.ts} +86 -86
  47. package/src/cli/package-manager.ts +9 -9
  48. package/src/client/app.ts +15 -0
  49. package/src/client/errors.ts +1 -17
  50. package/src/client/index.ts +2 -0
  51. package/src/client/sync.ts +172 -120
  52. package/src/dev-runtime/database.ts +253 -96
  53. package/src/dev-runtime/dev-database.ts +14 -14
  54. package/src/dev-runtime/functions.ts +57 -88
  55. package/src/dev-runtime/id.ts +2 -10
  56. package/src/dev-runtime/migrations.ts +68 -0
  57. package/src/dev-runtime/sync.ts +266 -83
  58. package/src/internal/function-definition.ts +77 -0
  59. package/src/internal/host.ts +1 -0
  60. package/src/internal/http-paths.ts +3 -1
  61. package/dist/runtime/api-BXXIZc_Q.js.map +0 -1
  62. package/dist/runtime/result-B2W-z2wG.js.map +0 -1
  63. package/dist/runtime/schema-CNEVLF7D.js +0 -116
  64. package/dist/runtime/schema-CNEVLF7D.js.map +0 -1
@@ -1,39 +1,36 @@
1
1
  import { Effect, FileSystem, Path, Schema, String } from "effect";
2
2
  import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
3
3
 
4
- import { agentProjectFiles, type ProjectFile } from "./agent-files.js";
4
+ import { agentAppFiles, type AppFile } from "./agent-files.js";
5
5
  import { generate } from "./codegen.js";
6
6
  import { installDependencies, type PackageManager } from "./package-manager.js";
7
7
 
8
- export interface NewProjectOptions {
8
+ export interface NewAppOptions {
9
9
  readonly currentDirectory: string;
10
10
  readonly directory: string;
11
11
  readonly git: boolean;
12
12
  readonly install: boolean;
13
13
  }
14
14
 
15
- export interface NewProjectResult {
15
+ export interface NewAppResult {
16
16
  readonly directory: string;
17
17
  readonly gitInitialized: boolean;
18
18
  readonly packageManager: PackageManager | null;
19
- readonly projectName: string;
19
+ readonly appName: string;
20
20
  }
21
21
 
22
- export class ProjectDirectoryNotEmpty extends Schema.TaggedError<ProjectDirectoryNotEmpty>()(
23
- "ProjectDirectoryNotEmpty",
22
+ export class AppDirectoryNotEmpty extends Schema.TaggedError<AppDirectoryNotEmpty>()(
23
+ "AppDirectoryNotEmpty",
24
24
  {
25
25
  message: Schema.String,
26
26
  path: Schema.String,
27
27
  },
28
28
  ) {}
29
29
 
30
- export class InvalidProjectName extends Schema.TaggedError<InvalidProjectName>()(
31
- "InvalidProjectName",
32
- {
33
- message: Schema.String,
34
- path: Schema.String,
35
- },
36
- ) {}
30
+ export class InvalidAppName extends Schema.TaggedError<InvalidAppName>()("InvalidAppName", {
31
+ message: Schema.String,
32
+ path: Schema.String,
33
+ }) {}
37
34
 
38
35
  export class GitInitializationFailed extends Schema.TaggedError<GitInitializationFailed>()(
39
36
  "GitInitializationFailed",
@@ -44,12 +41,12 @@ export class GitInitializationFailed extends Schema.TaggedError<GitInitializatio
44
41
  },
45
42
  ) {}
46
43
 
47
- const packageJson = (projectName: string): string =>
44
+ const packageJson = (appName: string): string =>
48
45
  `${JSON.stringify(
49
46
  {
50
- name: projectName,
47
+ name: appName,
51
48
  private: true,
52
- version: "0.0.0",
49
+ version: "1.0.0",
53
50
  type: "module",
54
51
  scripts: {
55
52
  typecheck: "ignotum codegen && tsc --noEmit",
@@ -138,12 +135,12 @@ export const increment = mutation({
138
135
  });
139
136
  `;
140
137
 
141
- const app = `import { Result, useMutation, useQuery } from "ignotum/client";
138
+ const app = `import { app, Result, useMutation, useQuery } from "ignotum/client";
142
139
 
143
140
  import { api } from "@/_generated/api.js";
144
141
  import { counterIncrement } from "@/shared/utils.js";
145
142
 
146
- export default function App() {
143
+ function App() {
147
144
  const count = useQuery(api.counter.get);
148
145
  const increment = useMutation(api.counter.increment);
149
146
 
@@ -168,22 +165,30 @@ export default function App() {
168
165
  </main>
169
166
  );
170
167
  }
168
+
169
+ export default app({
170
+ title: "Counter",
171
+ component: App,
172
+ });
171
173
  `;
172
174
 
173
- const utils = `export const counterIncrement = 1;
175
+ const icon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
176
+ <rect width="64" height="64" rx="14" fill="#18181b" />
177
+ <path d="M20 32h24M32 20v24" stroke="#fafafa" stroke-width="6" stroke-linecap="round" />
178
+ </svg>
174
179
  `;
175
180
 
176
- const styles = `@import "tailwindcss";
181
+ const utils = `export const counterIncrement = 1;
177
182
  `;
178
183
 
179
- const readme = (projectName: string): string => `# ${projectName}
184
+ const readme = (appName: string): string => `# ${appName}
180
185
 
181
186
  A small Ignotum counter app.
182
187
 
183
188
  ## Getting started
184
189
 
185
- You need Node.js 22.18 or newer. The project generator installs dependencies by default. If you
186
- created the project with \`--no-install\`, install them now:
190
+ You need Node.js 22.18 or newer. The app generator installs dependencies by default. If you
191
+ created the app with \`--no-install\`, install them now:
187
192
 
188
193
  \`\`\`sh
189
194
  npx ignotum install
@@ -199,15 +204,16 @@ npx ignotum dev
199
204
 
200
205
  Open <http://127.0.0.1:3210>.
201
206
 
202
- The project generator creates \`_generated\`. The dev server checks those files before it starts
207
+ The app generator creates \`_generated\`. The dev server checks those files before it starts
203
208
  and updates them when the schema or server functions change.
204
209
 
205
- ## Project files
210
+ ## App files
206
211
 
207
212
  - \`server/schema.ts\` defines the database tables.
208
213
  - \`server/counter.ts\` defines the query and mutation used by the counter.
209
- - \`client/App.tsx\` is the UI.
210
- - \`client/styles.css\` loads Tailwind CSS.
214
+ - \`client/index.tsx\` defines the app title and UI.
215
+ - \`client/icon.svg\` is the optional app icon.
216
+ - Ignotum loads Tailwind CSS automatically. Custom CSS files are ordinary client modules.
211
217
  - \`shared/utils.ts\` contains code shared across the app.
212
218
  - \`_generated\` contains Ignotum's generated types and bindings. Do not edit it by hand.
213
219
 
@@ -221,43 +227,43 @@ npx tsc --noEmit
221
227
  ## Claude Code
222
228
 
223
229
  If you use Claude Code, rename \`AGENTS.md\` to \`CLAUDE.md\` and \`.agents\` to \`.claude\` so it
224
- can find the project instructions and Ignotum skill.
230
+ can find the app instructions and Ignotum skill.
225
231
  `;
226
232
 
227
- const projectFiles = (projectName: string): ReadonlyArray<ProjectFile> => [
228
- { content: app, path: "client/App.tsx" },
229
- { content: styles, path: "client/styles.css" },
233
+ const appFiles = (appName: string): ReadonlyArray<AppFile> => [
234
+ { content: app, path: "client/index.tsx" },
235
+ { content: icon, path: "client/icon.svg" },
230
236
  { content: counterFunctions, path: "server/counter.ts" },
231
237
  { content: schema, path: "server/schema.ts" },
232
238
  { content: utils, path: "shared/utils.ts" },
233
- { content: packageJson(projectName), path: "package.json" },
239
+ { content: packageJson(appName), path: "package.json" },
234
240
  { content: tsconfig, path: "tsconfig.json" },
235
241
  { content: gitignore, path: ".gitignore" },
236
- { content: readme(projectName), path: "README.md" },
237
- ...agentProjectFiles,
242
+ { content: readme(appName), path: "README.md" },
243
+ ...agentAppFiles,
238
244
  ];
239
245
 
240
- const writeProjectFile = Effect.fn("NewProject.writeProjectFile")(function* (
241
- projectDirectory: string,
242
- file: ProjectFile,
246
+ const writeAppFile = Effect.fn("NewApp.writeAppFile")(function* (
247
+ appDirectory: string,
248
+ file: AppFile,
243
249
  ) {
244
250
  const fileSystem = yield* FileSystem.FileSystem;
245
251
  const path = yield* Path.Path;
246
- const filePath = path.join(projectDirectory, file.path);
252
+ const filePath = path.join(appDirectory, file.path);
247
253
 
248
254
  yield* fileSystem.makeDirectory(path.dirname(filePath), { recursive: true });
249
255
  yield* fileSystem.writeFileString(filePath, file.content, { flag: "wx" });
250
256
  });
251
257
 
252
- const gitExitCode = Effect.fn("NewProject.gitExitCode")(function* (
253
- projectDirectory: string,
258
+ const gitExitCode = Effect.fn("NewApp.gitExitCode")(function* (
259
+ appDirectory: string,
254
260
  args: ReadonlyArray<string>,
255
261
  ) {
256
262
  const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
257
263
  return yield* spawner
258
264
  .exitCode(
259
265
  ChildProcess.make("git", args, {
260
- cwd: projectDirectory,
266
+ cwd: appDirectory,
261
267
  stderr: "ignore",
262
268
  stdout: "ignore",
263
269
  }),
@@ -266,36 +272,36 @@ const gitExitCode = Effect.fn("NewProject.gitExitCode")(function* (
266
272
  Effect.mapError((cause) =>
267
273
  GitInitializationFailed.make({
268
274
  cause,
269
- message: `Could not run Git in ${projectDirectory}.`,
270
- path: projectDirectory,
275
+ message: `Could not run Git in ${appDirectory}.`,
276
+ path: appDirectory,
271
277
  }),
272
278
  ),
273
279
  );
274
280
  });
275
281
 
276
- const runGit = Effect.fn("NewProject.runGit")(function* (
277
- projectDirectory: string,
282
+ const runGit = Effect.fn("NewApp.runGit")(function* (
283
+ appDirectory: string,
278
284
  args: ReadonlyArray<string>,
279
285
  action: string,
280
286
  ) {
281
- const exitCode = yield* gitExitCode(projectDirectory, args);
287
+ const exitCode = yield* gitExitCode(appDirectory, args);
282
288
 
283
289
  if (exitCode !== ChildProcessSpawner.ExitCode(0)) {
284
290
  return yield* GitInitializationFailed.make({
285
- message: `Git exited with code ${exitCode} while ${action} in ${projectDirectory}.`,
286
- path: projectDirectory,
291
+ message: `Git exited with code ${exitCode} while ${action} in ${appDirectory}.`,
292
+ path: appDirectory,
287
293
  });
288
294
  }
289
295
  });
290
296
 
291
- const initializeGit = Effect.fn("NewProject.initializeGit")(function* (projectDirectory: string) {
292
- yield* runGit(projectDirectory, ["init", "--quiet"], "initializing the repository");
293
- yield* runGit(projectDirectory, ["add", "--all"], "staging the initial files");
297
+ const initializeGit = Effect.fn("NewApp.initializeGit")(function* (appDirectory: string) {
298
+ yield* runGit(appDirectory, ["init", "--quiet"], "initializing the repository");
299
+ yield* runGit(appDirectory, ["add", "--all"], "staging the initial files");
294
300
 
295
301
  const [hasName, hasEmail] = yield* Effect.all(
296
302
  [
297
- gitExitCode(projectDirectory, ["config", "user.name"]),
298
- gitExitCode(projectDirectory, ["config", "user.email"]),
303
+ gitExitCode(appDirectory, ["config", "user.name"]),
304
+ gitExitCode(appDirectory, ["config", "user.email"]),
299
305
  ],
300
306
  { concurrency: "unbounded" },
301
307
  ).pipe(
@@ -309,69 +315,63 @@ const initializeGit = Effect.fn("NewProject.initializeGit")(function* (projectDi
309
315
  : ["-c", "user.name=Ignotum", "-c", "user.email=ignotum@localhost"];
310
316
 
311
317
  yield* runGit(
312
- projectDirectory,
318
+ appDirectory,
313
319
  [...identity, "commit", "--quiet", "--no-gpg-sign", "-m", "Init"],
314
320
  "creating the initial commit",
315
321
  );
316
322
  });
317
323
 
318
- export const createProject = Effect.fn("NewProject.createProject")(function* (
319
- options: NewProjectOptions,
320
- ) {
324
+ export const createApp = Effect.fn("NewApp.createApp")(function* (options: NewAppOptions) {
321
325
  const fileSystem = yield* FileSystem.FileSystem;
322
326
  const path = yield* Path.Path;
323
- const projectDirectory = path.resolve(options.currentDirectory, options.directory);
324
- const projectName = String.kebabCase(path.basename(projectDirectory));
327
+ const appDirectory = path.resolve(options.currentDirectory, options.directory);
328
+ const appName = String.kebabCase(path.basename(appDirectory));
325
329
 
326
- if (projectName.length === 0) {
327
- return yield* InvalidProjectName.make({
328
- message: `Could not derive a package name from ${projectDirectory}.`,
329
- path: projectDirectory,
330
+ if (appName.length === 0) {
331
+ return yield* InvalidAppName.make({
332
+ message: `Could not derive a package name from ${appDirectory}.`,
333
+ path: appDirectory,
330
334
  });
331
335
  }
332
336
 
333
- const exists = yield* fileSystem.exists(projectDirectory);
337
+ const exists = yield* fileSystem.exists(appDirectory);
334
338
 
335
339
  if (exists) {
336
- const info = yield* fileSystem.stat(projectDirectory);
340
+ const info = yield* fileSystem.stat(appDirectory);
337
341
  if (info.type !== "Directory") {
338
- return yield* ProjectDirectoryNotEmpty.make({
339
- message: `${projectDirectory} already exists and is not an empty directory.`,
340
- path: projectDirectory,
342
+ return yield* AppDirectoryNotEmpty.make({
343
+ message: `${appDirectory} already exists and is not an empty directory.`,
344
+ path: appDirectory,
341
345
  });
342
346
  }
343
347
 
344
- const entries = yield* fileSystem.readDirectory(projectDirectory);
348
+ const entries = yield* fileSystem.readDirectory(appDirectory);
345
349
  if (entries.length > 0) {
346
- return yield* ProjectDirectoryNotEmpty.make({
347
- message: `${projectDirectory} is not empty. Choose an empty directory.`,
348
- path: projectDirectory,
350
+ return yield* AppDirectoryNotEmpty.make({
351
+ message: `${appDirectory} is not empty. Choose an empty directory.`,
352
+ path: appDirectory,
349
353
  });
350
354
  }
351
355
  } else {
352
- yield* fileSystem.makeDirectory(projectDirectory, { recursive: true });
356
+ yield* fileSystem.makeDirectory(appDirectory, { recursive: true });
353
357
  }
354
358
 
355
- yield* Effect.forEach(
356
- projectFiles(projectName),
357
- (file) => writeProjectFile(projectDirectory, file),
358
- {
359
- discard: true,
360
- },
361
- );
359
+ yield* Effect.forEach(appFiles(appName), (file) => writeAppFile(appDirectory, file), {
360
+ discard: true,
361
+ });
362
362
 
363
- yield* generate(projectDirectory);
363
+ yield* generate(appDirectory);
364
364
 
365
- const packageManager = options.install ? yield* installDependencies(projectDirectory) : null;
365
+ const packageManager = options.install ? yield* installDependencies(appDirectory) : null;
366
366
 
367
367
  if (options.git) {
368
- yield* initializeGit(projectDirectory);
368
+ yield* initializeGit(appDirectory);
369
369
  }
370
370
 
371
371
  return {
372
- directory: projectDirectory,
372
+ directory: appDirectory,
373
373
  gitInitialized: options.git,
374
374
  packageManager,
375
- projectName,
376
- } satisfies NewProjectResult;
375
+ appName,
376
+ } satisfies NewAppResult;
377
377
  });
@@ -13,13 +13,13 @@ export class DependencyInstallationFailed extends Schema.TaggedError<DependencyI
13
13
  ) {}
14
14
 
15
15
  const installWith = Effect.fn("PackageManager.installWith")(function* (
16
- projectDirectory: string,
16
+ appDirectory: string,
17
17
  packageManager: PackageManager,
18
18
  ) {
19
19
  const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
20
20
  const exitCode = yield* spawner.exitCode(
21
21
  ChildProcess.make(packageManager, ["install"], {
22
- cwd: projectDirectory,
22
+ cwd: appDirectory,
23
23
  stderr: "inherit",
24
24
  stdin: "inherit",
25
25
  stdout: "inherit",
@@ -28,8 +28,8 @@ const installWith = Effect.fn("PackageManager.installWith")(function* (
28
28
 
29
29
  if (exitCode !== ChildProcessSpawner.ExitCode(0)) {
30
30
  return yield* DependencyInstallationFailed.make({
31
- message: `${packageManager} install exited with code ${exitCode} in ${projectDirectory}.`,
32
- path: projectDirectory,
31
+ message: `${packageManager} install exited with code ${exitCode} in ${appDirectory}.`,
32
+ path: appDirectory,
33
33
  });
34
34
  }
35
35
 
@@ -37,18 +37,18 @@ const installWith = Effect.fn("PackageManager.installWith")(function* (
37
37
  });
38
38
 
39
39
  export const installDependencies = Effect.fn("PackageManager.installDependencies")(function* (
40
- projectDirectory: string,
40
+ appDirectory: string,
41
41
  ) {
42
- return yield* installWith(projectDirectory, "pnpm").pipe(
42
+ return yield* installWith(appDirectory, "pnpm").pipe(
43
43
  Effect.catchReasons("PlatformError", {
44
- NotFound: () => installWith(projectDirectory, "npm"),
44
+ NotFound: () => installWith(appDirectory, "npm"),
45
45
  }),
46
46
  Effect.catchTags({
47
47
  PlatformError: (cause) =>
48
48
  DependencyInstallationFailed.make({
49
49
  cause,
50
- message: `Could not run pnpm or npm in ${projectDirectory}.`,
51
- path: projectDirectory,
50
+ message: `Could not run pnpm or npm in ${appDirectory}.`,
51
+ path: appDirectory,
52
52
  }),
53
53
  }),
54
54
  );
@@ -0,0 +1,15 @@
1
+ import type { ComponentType } from "preact";
2
+
3
+ export interface AppDefinition<Component extends ComponentType<{}> = ComponentType<{}>> {
4
+ readonly component: Component;
5
+ readonly title: string;
6
+ }
7
+
8
+ export const app = <Component extends ComponentType<{}>>(
9
+ definition: AppDefinition<Component>,
10
+ ): AppDefinition<Component> => {
11
+ if (definition.title.trim().length === 0) {
12
+ throw new Error("The app title must contain a non-whitespace character.");
13
+ }
14
+ return definition;
15
+ };
@@ -1,12 +1,7 @@
1
1
  /** @effect-diagnostics globalConsole:skip-file */
2
2
  import { Cause, Effect, ErrorReporter, Schema } from "effect";
3
3
 
4
- import {
5
- FunctionAddress,
6
- Operation,
7
- ProtocolErrorCode,
8
- RequestId,
9
- } from "@ignotum/contracts/runtime/sync";
4
+ import { FunctionAddress, Operation, ProtocolErrorCode } from "@ignotum/contracts/runtime/sync";
10
5
 
11
6
  export class ConnectionUnavailable extends Schema.TaggedError<ConnectionUnavailable>()(
12
7
  "ConnectionUnavailable",
@@ -50,23 +45,12 @@ export class ServerProtocolError extends Schema.TaggedError<ServerProtocolError>
50
45
  },
51
46
  ) {}
52
47
 
53
- export class MutationOutcomeUnknown extends Schema.TaggedError<MutationOutcomeUnknown>()(
54
- "MutationOutcomeUnknown",
55
- {
56
- cause: Schema.optional(Schema.Defect()),
57
- function: FunctionAddress,
58
- id: RequestId,
59
- message: Schema.String,
60
- },
61
- ) {}
62
-
63
48
  export const ClientInfrastructureError = Schema.Union([
64
49
  ConnectionUnavailable,
65
50
  InvalidClientMessage,
66
51
  InvalidMutationArguments,
67
52
  InvalidServerMessage,
68
53
  ServerProtocolError,
69
- MutationOutcomeUnknown,
70
54
  ]);
71
55
  export type ClientInfrastructureError = typeof ClientInfrastructureError.Type;
72
56
 
@@ -12,6 +12,8 @@ export type { InternalServerError } from "@ignotum/contracts/runtime/result";
12
12
  export { useMutation, useQuery } from "./hooks.js";
13
13
  export { Query } from "./query.js";
14
14
  export type { QuerySkip } from "./query.js";
15
+ export { app } from "./app.js";
16
+ export type { AppDefinition } from "./app.js";
15
17
 
16
18
  export {
17
19
  Component,