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
@@ -6,12 +6,11 @@ import {
6
6
  type FunctionKind,
7
7
  type FunctionResolutionError,
8
8
  } from "@ignotum/contracts/runtime/functions";
9
- import {
10
- getFunctionSchema,
11
- type FunctionSchemaCarrier,
12
- type RuntimeSchemaDefinition,
13
- } from "@ignotum/contracts/runtime/schema";
9
+ import { type RuntimeSchemaDefinition } from "@ignotum/contracts/runtime/schema";
14
10
  import { ErrorValueSchema, type ErrorValue } from "@ignotum/contracts/runtime/result";
11
+ import { RequestId } from "@ignotum/contracts/runtime/id";
12
+ import { IdGenerator } from "@ignotum/shared/id";
13
+ import type { RuntimeInvocationResult } from "@ignotum/contracts/runtime/hosted";
15
14
  import {
16
15
  FunctionAddress,
17
16
  apiFunctionParts,
@@ -22,34 +21,21 @@ import {
22
21
  } from "@ignotum/contracts/runtime/sync";
23
22
  import { FunctionRuntime } from "@ignotum/runtime/functions";
24
23
  import { Cause, Context, Effect, Layer, Path, Predicate, Schema, Semaphore } from "effect";
25
- import { nanoid } from "nanoid";
26
24
  import type { ViteDevServer } from "vite";
27
25
  import { normalizePath } from "vite";
28
26
 
29
27
  import { LocalDatabase, type LocalMutationContext, type LocalQueryContext } from "./database.js";
28
+ import {
29
+ inspectRuntimeFunctionDefinition,
30
+ type FunctionArguments,
31
+ type RuntimeFunctionDefinition as InternalRuntimeFunctionDefinition,
32
+ } from "../internal/function-definition.js";
30
33
 
31
34
  export { FunctionUnavailable, InvalidArguments, UnknownFunction, WrongFunctionKind };
32
- export type { FunctionKind };
33
-
34
- type RuntimeFields = Readonly<Record<string, Schema.ConstraintDecoder<unknown>>>;
35
-
36
- declare const FunctionArgumentsTypeId: unique symbol;
37
- export interface FunctionArguments {
38
- readonly [FunctionArgumentsTypeId]?: never;
39
- }
35
+ export type { FunctionArguments, FunctionKind };
40
36
 
41
37
  export type RuntimeHandlerContext = LocalMutationContext | LocalQueryContext;
42
-
43
- export interface RuntimeFunctionDefinition {
44
- readonly _tag: FunctionKind;
45
- readonly args?: RuntimeFields;
46
- readonly returns?: Schema.Codec<unknown, unknown>;
47
- readonly errors?: Schema.Codec<unknown, unknown>;
48
- readonly handler: (
49
- context: RuntimeHandlerContext,
50
- args: FunctionArguments,
51
- ) => Generator<Effect.Effect<unknown, ErrorValue, never>, TransportValue | void, never>;
52
- }
38
+ export type RuntimeFunctionDefinition = InternalRuntimeFunctionDefinition<RuntimeHandlerContext>;
53
39
 
54
40
  export interface ResolvedFunction {
55
41
  readonly definition: RuntimeFunctionDefinition;
@@ -57,52 +43,6 @@ export interface ResolvedFunction {
57
43
  readonly schema: RuntimeSchemaDefinition;
58
44
  }
59
45
 
60
- interface RuntimeFunctionCandidate extends FunctionSchemaCarrier {
61
- readonly _tag?: unknown;
62
- readonly args?: unknown;
63
- readonly returns?: unknown;
64
- readonly errors?: unknown;
65
- readonly handler?: unknown;
66
- }
67
-
68
- const inspectRuntimeFunctionDefinition = (
69
- value: RuntimeFunctionCandidate,
70
- ):
71
- | { readonly definition: RuntimeFunctionDefinition; readonly schema: RuntimeSchemaDefinition }
72
- | undefined => {
73
- const schema = getFunctionSchema(value);
74
- if (
75
- schema === undefined ||
76
- !Predicate.hasProperty(value, "_tag") ||
77
- (value._tag !== "Mutation" && value._tag !== "Query") ||
78
- (Predicate.hasProperty(value, "args") &&
79
- value.args !== undefined &&
80
- !Predicate.isObject(value.args)) ||
81
- (Predicate.hasProperty(value, "returns") &&
82
- value.returns !== undefined &&
83
- !Schema.isSchema(value.returns)) ||
84
- (Predicate.hasProperty(value, "errors") &&
85
- value.errors !== undefined &&
86
- !Schema.isSchema(value.errors)) ||
87
- !Predicate.hasProperty(value, "handler") ||
88
- !Predicate.isFunction(value.handler)
89
- ) {
90
- return undefined;
91
- }
92
-
93
- if (
94
- Predicate.hasProperty(value, "args") &&
95
- Predicate.isObject(value.args) &&
96
- !Object.values(value.args).every(Schema.isSchema)
97
- ) {
98
- return undefined;
99
- }
100
-
101
- // SAFETY: every RuntimeFunctionDefinition property and argument Schema was
102
- // checked above. Predicate.hasProperty does not retain the literal tag type.
103
- return { definition: value as RuntimeFunctionDefinition, schema };
104
- };
105
-
106
46
  export class FunctionRegistry extends Context.Service<
107
47
  FunctionRegistry,
108
48
  {
@@ -113,7 +53,7 @@ export class FunctionRegistry extends Context.Service<
113
53
  ) => Effect.Effect<ResolvedFunction, FunctionResolutionError>;
114
54
  }
115
55
  >()("ignotum/dev-runtime/functions/FunctionRegistry") {
116
- static layer(server: ViteDevServer, projectDirectory: string) {
56
+ static layer(server: ViteDevServer, appDirectory: string) {
117
57
  return Layer.effect(
118
58
  FunctionRegistry,
119
59
  Effect.gen(function* () {
@@ -121,9 +61,7 @@ export class FunctionRegistry extends Context.Service<
121
61
  return FunctionRegistry.of({
122
62
  resolve: Effect.fn("FunctionRegistry.resolve")(function* (functionAddress, kind, args) {
123
63
  const { functionName, moduleName } = apiFunctionParts(functionAddress);
124
- const modulePath = normalizePath(
125
- path.join(projectDirectory, "server", `${moduleName}.ts`),
126
- );
64
+ const modulePath = normalizePath(path.join(appDirectory, "server", `${moduleName}.ts`));
127
65
  const loadedModule = yield* Effect.tryPromise({
128
66
  try: () => server.ssrLoadModule(modulePath),
129
67
  catch: (cause) =>
@@ -136,7 +74,7 @@ export class FunctionRegistry extends Context.Service<
136
74
  const candidate = loadedModule[functionName];
137
75
 
138
76
  const inspected = Predicate.isObject(candidate)
139
- ? inspectRuntimeFunctionDefinition(candidate)
77
+ ? inspectRuntimeFunctionDefinition<RuntimeHandlerContext>(candidate)
140
78
  : undefined;
141
79
  if (inspected === undefined) {
142
80
  return yield* UnknownFunction.make({
@@ -186,13 +124,14 @@ export class FunctionExecutor extends Context.Service<
186
124
  functionAddress: FunctionAddress,
187
125
  kind: FunctionKind,
188
126
  resolved: ResolvedFunction,
189
- ) => Effect.Effect<WireResult>;
127
+ ) => Effect.Effect<RuntimeInvocationResult>;
190
128
  }
191
129
  >()("ignotum/dev-runtime/functions/FunctionExecutor") {
192
130
  static readonly layer = Layer.effect(
193
131
  FunctionExecutor,
194
132
  Effect.gen(function* () {
195
133
  const database = yield* LocalDatabase;
134
+ const ids = yield* IdGenerator;
196
135
  const mutationSemaphore = yield* Semaphore.make(1);
197
136
 
198
137
  const encodeSuccess = (
@@ -235,10 +174,19 @@ export class FunctionExecutor extends Context.Service<
235
174
 
236
175
  const execution =
237
176
  kind === "Query"
238
- ? database.queryTransaction(resolved.schema, invoke)
177
+ ? database.trackedQueryTransaction(resolved.schema, invoke).pipe(
178
+ Effect.map(
179
+ ({ dependencies, observedRevision, value }): RuntimeInvocationResult => ({
180
+ type: "Query",
181
+ result: value,
182
+ dependencies,
183
+ observedRevision,
184
+ }),
185
+ ),
186
+ )
239
187
  : mutationSemaphore.withPermits(1)(
240
188
  database
241
- .mutationTransaction(resolved.schema, (context) =>
189
+ .trackedMutationTransaction(resolved.schema, (context) =>
242
190
  invoke(context).pipe(
243
191
  Effect.flatMap((result) =>
244
192
  result.type === "Failure"
@@ -248,9 +196,20 @@ export class FunctionExecutor extends Context.Service<
248
196
  ),
249
197
  )
250
198
  .pipe(
199
+ Effect.map(
200
+ ({ committedRevision, invalidations, value }): RuntimeInvocationResult => ({
201
+ type: "Mutation",
202
+ result: value,
203
+ invalidations,
204
+ committedRevision,
205
+ }),
206
+ ),
251
207
  Effect.catchTags({
252
208
  MutationApplicationFailure: ({ error }) =>
253
- Effect.succeed({ type: "Failure" as const, error }),
209
+ Effect.succeed<RuntimeInvocationResult>({
210
+ type: "Mutation",
211
+ result: { type: "Failure", error },
212
+ }),
254
213
  }),
255
214
  ),
256
215
  );
@@ -259,16 +218,26 @@ export class FunctionExecutor extends Context.Service<
259
218
  Effect.orDie,
260
219
  Effect.catchCauseIf(
261
220
  (cause) => !Cause.hasInterruptsOnly(cause),
262
- (cause) => {
263
- const requestId = nanoid();
264
- return Effect.logError(`${functionAddress} failed during execution.`).pipe(
265
- Effect.annotateLogs({ cause, function: functionAddress, requestId }),
266
- Effect.as({
221
+ (cause) =>
222
+ Effect.gen(function* () {
223
+ const requestId = yield* ids.generate(RequestId);
224
+ yield* Effect.logError(`${functionAddress} failed during execution.`).pipe(
225
+ Effect.annotateLogs({ cause, function: functionAddress, requestId }),
226
+ );
227
+ const result = {
267
228
  type: "Failure" as const,
268
229
  error: { _tag: "InternalServerError", requestId },
269
- }),
270
- );
271
- },
230
+ };
231
+ if (kind === "Mutation") {
232
+ return { type: "Mutation" as const, result };
233
+ }
234
+ return {
235
+ type: "Query" as const,
236
+ result,
237
+ dependencies: [],
238
+ observedRevision: yield* database.revision,
239
+ };
240
+ }),
272
241
  ),
273
242
  );
274
243
  }),
@@ -1,11 +1,3 @@
1
- import { GeneratedId, IdAlphabet, IdLength } from "@ignotum/contracts/runtime/id";
2
- import { IdGenerator } from "@ignotum/runtime/id";
3
- import { Effect, Layer } from "effect";
4
- import { customAlphabet } from "nanoid";
1
+ import { IdGenerator } from "@ignotum/shared/id";
5
2
 
6
- export const idGeneratorLayer = Layer.sync(IdGenerator, () => {
7
- const nanoid = customAlphabet(IdAlphabet, IdLength);
8
- return IdGenerator.of({
9
- generate: Effect.sync(() => GeneratedId.make(nanoid())),
10
- });
11
- });
3
+ export const idGeneratorLayer = IdGenerator.layer;
@@ -0,0 +1,68 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import * as Migrator from "effect/unstable/sql/Migrator";
3
+ import * as SqlClient from "effect/unstable/sql/SqlClient";
4
+
5
+ const initialDevelopmentSchema = Effect.gen(function* () {
6
+ const sql = yield* SqlClient.SqlClient;
7
+ yield* sql`
8
+ CREATE TABLE tables (
9
+ id TEXT PRIMARY KEY NOT NULL
10
+ CHECK (
11
+ length(id) = 30
12
+ AND substr(id, 1, 6) = 'table_'
13
+ AND substr(id, 7) NOT GLOB '*[^0-9a-z]*'
14
+ ),
15
+ name TEXT NOT NULL UNIQUE
16
+ ) STRICT
17
+ `;
18
+ yield* sql`
19
+ CREATE TABLE documents (
20
+ id TEXT NOT NULL
21
+ CHECK (length(id) = 24 AND id NOT GLOB '*[^0-9a-z]*'),
22
+ tableId TEXT NOT NULL REFERENCES tables(id) ON DELETE RESTRICT,
23
+ createdAt INTEGER NOT NULL,
24
+ updatedAt INTEGER NOT NULL,
25
+ fields TEXT NOT NULL CHECK (json_valid(fields)),
26
+ PRIMARY KEY (tableId, id)
27
+ ) STRICT
28
+ `;
29
+ yield* sql`
30
+ CREATE INDEX documents_table_id_idx
31
+ ON documents (tableId, createdAt, id)
32
+ `;
33
+ yield* sql`
34
+ CREATE TABLE app_state (
35
+ singleton INTEGER PRIMARY KEY NOT NULL CHECK (singleton = 1),
36
+ revision INTEGER NOT NULL CHECK (revision >= 0)
37
+ ) STRICT
38
+ `;
39
+ yield* sql`INSERT INTO app_state (singleton, revision) VALUES (1, 0)`;
40
+ yield* sql`
41
+ CREATE TABLE invocation_replays (
42
+ id TEXT PRIMARY KEY NOT NULL,
43
+ input TEXT NOT NULL,
44
+ result TEXT NOT NULL CHECK (json_valid(result))
45
+ ) STRICT
46
+ `;
47
+ });
48
+
49
+ export const developmentMigrationLoader = Migrator.fromRecord({
50
+ "0001_initial_development_schema": initialDevelopmentSchema,
51
+ });
52
+
53
+ interface DevelopmentDatabaseService {
54
+ readonly migrated: true;
55
+ }
56
+
57
+ export class DevelopmentDatabase extends Context.Service<
58
+ DevelopmentDatabase,
59
+ DevelopmentDatabaseService
60
+ >()("ignotum/dev-runtime/migrations/DevelopmentDatabase") {
61
+ static readonly layer = Layer.effect(
62
+ DevelopmentDatabase,
63
+ Migrator.make({})({ loader: developmentMigrationLoader, table: "migrations" }).pipe(
64
+ Effect.orDie,
65
+ Effect.as(DevelopmentDatabase.of({ migrated: true })),
66
+ ),
67
+ );
68
+ }