ignotum 0.0.11 → 0.0.13

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 (61) hide show
  1. package/README.md +8 -3
  2. package/dist/cli/bin.mjs +1372 -492
  3. package/dist/cli/bin.mjs.map +1 -1
  4. package/dist/runtime/{api-DzcR7spt.js → api-CAgKDij7.js} +38 -2
  5. package/dist/runtime/api-CAgKDij7.js.map +1 -0
  6. package/dist/runtime/{api-DR_8vfKg.d.ts → api-Cv3hMbzo.d.ts} +4 -3
  7. package/dist/runtime/client.d.ts +39 -8
  8. package/dist/runtime/client.js +505 -131
  9. package/dist/runtime/client.js.map +1 -1
  10. package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js +154 -0
  11. package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js.map +1 -0
  12. package/dist/runtime/id-Bt9XWRGL.js +423 -0
  13. package/dist/runtime/id-Bt9XWRGL.js.map +1 -0
  14. package/dist/runtime/{id-Cs82tq9Q-hmYFyqTa.d.ts → id-BzFHf3Wo-DGPCjgrf.d.ts} +13 -13
  15. package/dist/runtime/id-Dz0apuB3.d.ts +1 -0
  16. package/dist/runtime/{index-2q9FwJud.d.ts → index-D54flWtH.d.ts} +136 -76
  17. package/dist/runtime/internal/api.d.ts +1 -1
  18. package/dist/runtime/internal/api.js +1 -1
  19. package/dist/runtime/internal/host.d.ts +21 -17
  20. package/dist/runtime/internal/host.js +22 -8
  21. package/dist/runtime/internal/host.js.map +1 -1
  22. package/dist/runtime/internal/server.d.ts +1 -1
  23. package/dist/runtime/internal/server.js +1 -1
  24. package/dist/runtime/internal/types.d.ts +1 -1
  25. package/dist/runtime/internal/types.js +1 -1
  26. package/dist/runtime/{pagination-Bt3l7QaC-BYEGmLBE.d.ts → pagination-DnKg3dkI-r5ZUxBBx.d.ts} +34 -6
  27. package/dist/runtime/pagination-Dz0apuB3.d.ts +1 -0
  28. package/dist/runtime/result-DKAA4gpS.d.ts +1 -0
  29. package/dist/runtime/{schema-ERFjT8-m.js → schema-1Zs03-iS.js} +135 -17
  30. package/dist/runtime/schema-1Zs03-iS.js.map +1 -0
  31. package/dist/runtime/server.d.ts +9 -3
  32. package/dist/runtime/server.js +5 -3
  33. package/dist/runtime/server.js.map +1 -1
  34. package/dist/runtime/{sync-a2EdGSdY.d.ts → sync-Bs8J3fIr.d.ts} +2 -2
  35. package/package.json +4 -4
  36. package/src/cli/agent-files.ts +8 -0
  37. package/src/cli/auth-client.ts +1 -1
  38. package/src/cli/bin.ts +13 -3
  39. package/src/cli/build/server.ts +102 -13
  40. package/src/cli/codegen.ts +7 -4
  41. package/src/cli/control-client.ts +30 -8
  42. package/src/cli/deploy.ts +7 -2
  43. package/src/cli/environment.ts +133 -0
  44. package/src/client/files.ts +43 -27
  45. package/src/client/hooks.ts +94 -17
  46. package/src/client/id.ts +259 -0
  47. package/src/client/index.ts +5 -2
  48. package/src/client/page-observers.ts +77 -0
  49. package/src/client/sync.ts +214 -69
  50. package/src/dev-runtime/functions.ts +232 -86
  51. package/src/dev-runtime/id.ts +174 -2
  52. package/src/dev-runtime/query-cache.ts +105 -0
  53. package/src/dev-runtime/sync.ts +187 -26
  54. package/src/server/index.ts +13 -1
  55. package/dist/runtime/api-DzcR7spt.js.map +0 -1
  56. package/dist/runtime/descriptor-C5VA9qRl-DuQsowaQ.js +0 -311
  57. package/dist/runtime/descriptor-C5VA9qRl-DuQsowaQ.js.map +0 -1
  58. package/dist/runtime/file-C1abuMgd.js +0 -173
  59. package/dist/runtime/file-C1abuMgd.js.map +0 -1
  60. package/dist/runtime/pagination-CFJ3xlAt.d.ts +0 -1
  61. package/dist/runtime/schema-ERFjT8-m.js.map +0 -1
@@ -1,9 +1,11 @@
1
- import { d as Result$1 } from "./descriptor-C5VA9qRl-DuQsowaQ.js";
2
- import { r as defineSchema$1 } from "./schema-ERFjT8-m.js";
1
+ import { _ as Result$1 } from "./id-Bt9XWRGL.js";
2
+ import { a as defineSchema$1, i as defineEnv$1, l as Secret$1 } from "./schema-1Zs03-iS.js";
3
3
  //#region src/server/index.ts
4
4
  const Result = Result$1;
5
+ const defineEnv = defineEnv$1;
5
6
  const defineSchema = defineSchema$1;
7
+ const Secret = Secret$1;
6
8
  //#endregion
7
- export { Result, defineSchema };
9
+ export { Result, Secret, defineEnv, defineSchema };
8
10
 
9
11
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","names":["contractResult","defineContractSchema"],"sources":["../../src/server/index.ts"],"sourcesContent":["import { Result as contractResult } from \"@ignotum/contracts/runtime/result\";\nimport type { ErrorValue, Result as ContractResult } from \"@ignotum/contracts/runtime/result\";\nimport { defineSchema as defineContractSchema } from \"@ignotum/contracts/schema\";\n\nexport const Result: typeof contractResult = contractResult;\nexport type Result<Success, Failure extends ErrorValue> = ContractResult<Success, Failure>;\nexport const defineSchema: typeof defineContractSchema = defineContractSchema;\nexport type {\n DefinedSchema,\n FileFormat,\n FileMetadata,\n FileMimeType,\n FileValue,\n SchemaAuthoring,\n} from \"@ignotum/contracts/schema\";\n"],"mappings":";;;AAIA,MAAa,SAAgCA;AAE7C,MAAa,eAA4CC"}
1
+ {"version":3,"file":"server.js","names":["contractResult","defineContractEnv","defineContractSchema","contractSecret"],"sources":["../../src/server/index.ts"],"sourcesContent":["import { Result as contractResult } from \"@ignotum/contracts/runtime/result\";\nimport type { ErrorValue, Result as ContractResult } from \"@ignotum/contracts/runtime/result\";\nimport {\n defineEnv as defineContractEnv,\n defineSchema as defineContractSchema,\n Secret as contractSecret,\n} from \"@ignotum/contracts/schema\";\nimport type { Secret as ContractSecret } from \"@ignotum/contracts/schema\";\n\nexport const Result: typeof contractResult = contractResult;\nexport type Result<Success, Failure extends ErrorValue> = ContractResult<Success, Failure>;\nexport const defineEnv: typeof defineContractEnv = defineContractEnv;\nexport const defineSchema: typeof defineContractSchema = defineContractSchema;\nexport const Secret: typeof contractSecret = contractSecret;\nexport type Secret<Value> = ContractSecret<Value>;\nexport type {\n DefinedEnv,\n DefinedSchema,\n EnvironmentAuthoring,\n FileFormat,\n FileMetadata,\n FileMimeType,\n FileValue,\n SchemaAuthoring,\n} from \"@ignotum/contracts/schema\";\n\nexport type { User, UserId } from \"@ignotum/contracts/id\";\n"],"mappings":";;;AASA,MAAa,SAAgCA;AAE7C,MAAa,YAAsCC;AACnD,MAAa,eAA4CC;AACzD,MAAa,SAAgCC"}
@@ -1,8 +1,8 @@
1
- import "./id-Cs82tq9Q-hmYFyqTa.js";
1
+ import "./id-BzFHf3Wo-DGPCjgrf.js";
2
2
  import { Schema } from "effect";
3
3
  //#region ../contracts/dist/runtime/sync.d.ts
4
4
  declare const FunctionAddress: Schema.TemplateLiteral<readonly ["api.", Schema.String, ".", Schema.String]>;
5
5
  type FunctionAddress = typeof FunctionAddress.Type;
6
6
  //#endregion
7
7
  export { FunctionAddress as t };
8
- //# sourceMappingURL=sync-a2EdGSdY.d.ts.map
8
+ //# sourceMappingURL=sync-Bs8J3fIr.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ignotum",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "Ignotum is an opinionated TypeScript application cloud.",
5
5
  "author": "Johannes Schießl",
6
6
  "repository": {
@@ -80,10 +80,10 @@
80
80
  "@effect/platform-node": "4.0.0-rc.111",
81
81
  "@effect/sql-sqlite-node": "4.0.0-rc.111",
82
82
  "redis": "6.2.1",
83
- "@ignotum/runtime": "1.0.0",
84
83
  "@ignotum/contracts": "1.0.0",
85
- "@ignotum/shared": "1.0.0",
86
- "@ignotum/deployment": "1.0.0"
84
+ "@ignotum/deployment": "1.0.0",
85
+ "@ignotum/runtime": "1.0.0",
86
+ "@ignotum/shared": "1.0.0"
87
87
  },
88
88
  "engines": {
89
89
  "node": ">=22.18.0"
@@ -1,3 +1,6 @@
1
+ import authentication from "../../../../docs/user/authentication.md?raw";
2
+ import profileSharing from "../../../../docs/user/profile-sharing.md?raw";
3
+ import localAuthentication from "../../../../docs/user/local-authentication.md?raw";
1
4
  import agents from "../../../../docs/agent/AGENTS.md?raw";
2
5
  import skill from "../../../../docs/agent/skills/ignotum/SKILL.md?raw";
3
6
  import api from "../../../../docs/user/api.md?raw";
@@ -8,6 +11,7 @@ import deployApp from "../../../../docs/user/deploy-app.md?raw";
8
11
  import deploymentModel from "../../../../docs/user/deployment-model.md?raw";
9
12
  import deployments from "../../../../docs/user/deployments.md?raw";
10
13
  import errors from "../../../../docs/user/errors.md?raw";
14
+ import environmentVariables from "../../../../docs/user/environment-variables.md?raw";
11
15
  import files from "../../../../docs/user/files.md?raw";
12
16
  import functions from "../../../../docs/user/functions.md?raw";
13
17
  import generatedCode from "../../../../docs/user/generated-code.md?raw";
@@ -37,6 +41,9 @@ export interface AppFile {
37
41
  }
38
42
 
39
43
  const references = [
44
+ ["authentication.md", authentication],
45
+ ["profile-sharing.md", profileSharing],
46
+ ["local-authentication.md", localAuthentication],
40
47
  ["api.md", api],
41
48
  ["app-entry.md", appEntry],
42
49
  ["cli.md", cli],
@@ -45,6 +52,7 @@ const references = [
45
52
  ["deployment-model.md", deploymentModel],
46
53
  ["deployments.md", deployments],
47
54
  ["errors.md", errors],
55
+ ["environment-variables.md", environmentVariables],
48
56
  ["files.md", files],
49
57
  ["functions.md", functions],
50
58
  ["generated-code.md", generatedCode],
@@ -30,7 +30,7 @@ const AuthUrl = Schema.URL.check(
30
30
  Schema.makeFilter(
31
31
  (url) =>
32
32
  url.protocol === "https:" ||
33
- (url.protocol === "http:" && ["127.0.0.1", "::1", "localhost"].includes(url.hostname)),
33
+ (url.protocol === "http:" && ["127.0.0.1", "[::1]", "localhost"].includes(url.hostname)),
34
34
  { message: "The auth URL must use HTTPS unless it targets localhost." },
35
35
  ),
36
36
  );
package/src/cli/bin.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // @effect-diagnostics-next-line nodeBuiltinImport:off The isolated worker returns sensitive metadata over a dedicated inherited pipe.
4
+ import { writeSync } from "node:fs";
3
5
  import process from "node:process";
4
6
 
5
7
  import { NodeRuntime, NodeServices } from "@effect/platform-node";
@@ -9,15 +11,23 @@ import { main } from "./command.js";
9
11
  import { runServerDiscoveryWorker } from "./build/server.js";
10
12
 
11
13
  const discoveryWorker = process.argv[2] === "__ignotum_discover_server";
14
+ const writeDiscovery = (encoded: string): void => {
15
+ const bytes = new TextEncoder().encode(encoded);
16
+ let offset = 0;
17
+ while (offset < bytes.byteLength) {
18
+ offset += writeSync(3, bytes, offset, bytes.byteLength - offset);
19
+ }
20
+ };
12
21
 
13
22
  if (discoveryWorker) {
14
23
  const appDirectory = process.argv[3];
15
24
  const moduleNames = process.argv[4];
16
- const outputPath = process.argv[5];
17
25
  globalThis.fetch = () => Promise.reject(new Error("Network access is disabled during builds."));
18
- (appDirectory === undefined || moduleNames === undefined || outputPath === undefined
26
+ (appDirectory === undefined || moduleNames === undefined
19
27
  ? Effect.die("Invalid isolated discovery arguments.")
20
- : runServerDiscoveryWorker(appDirectory, moduleNames, outputPath)
28
+ : runServerDiscoveryWorker(appDirectory, moduleNames).pipe(
29
+ Effect.tap((encoded) => Effect.sync(() => writeDiscovery(encoded))),
30
+ )
21
31
  ).pipe(
22
32
  // @effect-diagnostics-next-line strictEffectProvide:off The isolated worker is a process entry point.
23
33
  Effect.provide(NodeServices.layer),
@@ -3,7 +3,18 @@ import process from "node:process";
3
3
  import { fileURLToPath } from "node:url";
4
4
 
5
5
  import * as Config from "effect/Config";
6
- import { Array, Effect, FileSystem, Path, Predicate, Schema, String } from "effect";
6
+ import {
7
+ Array,
8
+ Crypto,
9
+ Effect,
10
+ Encoding,
11
+ FileSystem,
12
+ Path,
13
+ Predicate,
14
+ Schema,
15
+ Stream,
16
+ String,
17
+ } from "effect";
7
18
  import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
8
19
  import { createServer, defaultServerConditions, normalizePath } from "vite";
9
20
  import type { Plugin, ViteDevServer } from "vite";
@@ -11,7 +22,10 @@ import type { Plugin, ViteDevServer } from "vite";
11
22
  import {
12
23
  ServerBuildManifest,
13
24
  SchemaSnapshot,
25
+ SchemaSnapshotField,
26
+ EnvironmentArtifact,
14
27
  schemaSnapshotPath,
28
+ serverEnvironmentPath,
15
29
  type ServerFunctionArtifact,
16
30
  } from "@ignotum/contracts/deployment";
17
31
  import { FunctionAddress } from "@ignotum/contracts/runtime/sync";
@@ -19,7 +33,9 @@ import type { FunctionKind } from "@ignotum/contracts/runtime/functions";
19
33
  import {
20
34
  descriptorContainsFile,
21
35
  descriptorFields,
36
+ emptyEnv,
22
37
  getValueDescriptor,
38
+ isDefinedEnv,
23
39
  isDefinedSchema,
24
40
  ValueDescriptor,
25
41
  type DefinedSchema,
@@ -34,6 +50,7 @@ import {
34
50
  } from "@ignotum/deployment";
35
51
 
36
52
  import { inspectRuntimeFunctionDefinition } from "../../internal/function-definition.js";
53
+ import { loadEnvironment } from "../environment.js";
37
54
  import { moduleBoundariesPlugin, BuildBoundaryViolation } from "../module-boundaries.js";
38
55
  import {
39
56
  causeMessage,
@@ -69,6 +86,11 @@ interface DiscoveredServerFunction {
69
86
  }
70
87
 
71
88
  const ServerDiscoveryManifest = Schema.Struct({
89
+ environment: Schema.Struct({
90
+ fields: Schema.Array(SchemaSnapshotField),
91
+ hasDefinition: Schema.Boolean,
92
+ values: Schema.Record(Schema.String, Schema.String),
93
+ }),
72
94
  functions: Schema.Array(DiscoveredServerFunction),
73
95
  schema: SchemaSnapshot,
74
96
  });
@@ -77,6 +99,10 @@ const ServerDiscoveryManifestJson = Schema.fromJsonString(ServerDiscoveryManifes
77
99
  const ServerModuleNamesJson = Schema.fromJsonString(Schema.Array(Schema.String));
78
100
 
79
101
  export interface ServerBuildResult {
102
+ readonly environment: {
103
+ readonly bytes: Uint8Array;
104
+ readonly path: typeof serverEnvironmentPath;
105
+ };
80
106
  readonly functions: number;
81
107
  readonly manifestPath: string;
82
108
  }
@@ -268,6 +294,7 @@ const discoverServerFunctionsInProcess = Effect.fn("Deploy.discoverServerFunctio
268
294
  return yield* Effect.scoped(
269
295
  Effect.gen(function* () {
270
296
  const server = yield* acquireModuleRunner(appDirectory, conditions);
297
+ const fileSystem = yield* FileSystem.FileSystem;
271
298
  const path = yield* Path.Path;
272
299
  const schemaPath = path.join(appDirectory, "server", "schema.ts");
273
300
  const loadedSchemaModule = yield* Effect.tryPromise({
@@ -295,14 +322,59 @@ const discoverServerFunctionsInProcess = Effect.fn("Deploy.discoverServerFunctio
295
322
  message: "The default export from server/schema.ts is not an Ignotum schema.",
296
323
  });
297
324
  }
325
+ const environmentPath = path.join(appDirectory, "server", "env.ts");
326
+ const hasEnvironmentDefinition = yield* fileSystem.exists(environmentPath);
327
+ const environment = hasEnvironmentDefinition
328
+ ? yield* Effect.gen(function* () {
329
+ const loadedEnvironmentModule = yield* Effect.tryPromise({
330
+ try: () => server.ssrLoadModule(normalizePath(environmentPath)),
331
+ catch: (cause) =>
332
+ ServerFunctionDiscoveryFailed.make({
333
+ cause,
334
+ message: "Could not load the application environment declaration.",
335
+ path: environmentPath,
336
+ }),
337
+ });
338
+ if (
339
+ !Predicate.isObject(loadedEnvironmentModule.default) ||
340
+ !isDefinedEnv(loadedEnvironmentModule.default)
341
+ ) {
342
+ return yield* InvalidBuildOutput.make({
343
+ message: "The default export from server/env.ts is not an Ignotum environment.",
344
+ });
345
+ }
346
+ const loaded = yield* loadEnvironment(
347
+ appDirectory,
348
+ loadedEnvironmentModule.default,
349
+ true,
350
+ );
351
+ return {
352
+ fields: loadedEnvironmentModule.default.descriptors,
353
+ hasDefinition: true,
354
+ values: loaded.raw,
355
+ } as const;
356
+ })
357
+ : yield* loadEnvironment(appDirectory, emptyEnv, false).pipe(
358
+ Effect.map((loaded) => ({
359
+ fields: [],
360
+ hasDefinition: false,
361
+ values: loaded.raw,
362
+ })),
363
+ );
298
364
  const modules = yield* Effect.forEach(moduleNames, (moduleName) =>
299
365
  discoverModuleFunctions(server, appDirectory, moduleName),
300
366
  );
301
367
  return {
302
368
  functions: Array.flatten(modules),
369
+ environment,
303
370
  schema: schemaCandidate,
304
371
  } satisfies {
305
372
  readonly functions: ReadonlyArray<DiscoveredServerFunction>;
373
+ readonly environment: {
374
+ readonly fields: ReadonlyArray<SchemaSnapshotField>;
375
+ readonly hasDefinition: boolean;
376
+ readonly values: Readonly<Record<string, string>>;
377
+ };
306
378
  readonly schema: DefinedSchema<Tables>;
307
379
  };
308
380
  }),
@@ -312,9 +384,7 @@ const discoverServerFunctionsInProcess = Effect.fn("Deploy.discoverServerFunctio
312
384
  export const runServerDiscoveryWorker = Effect.fn("Deploy.runServerDiscoveryWorker")(function* (
313
385
  appDirectory: string,
314
386
  moduleNamesJson: string,
315
- outputPath: string,
316
387
  ) {
317
- const fileSystem = yield* FileSystem.FileSystem;
318
388
  const moduleNames = yield* Schema.decodeEffect(ServerModuleNamesJson)(moduleNamesJson).pipe(
319
389
  Effect.orDie,
320
390
  );
@@ -327,11 +397,11 @@ export const runServerDiscoveryWorker = Effect.fn("Deploy.runServerDiscoveryWork
327
397
  message: "The application schema does not have canonical Ignotum descriptors.",
328
398
  }),
329
399
  });
330
- const encoded = yield* Schema.encodeEffect(ServerDiscoveryManifestJson)({
400
+ return yield* Schema.encodeEffect(ServerDiscoveryManifestJson)({
401
+ environment: discovered.environment,
331
402
  functions: discovered.functions,
332
403
  schema,
333
404
  });
334
- yield* fileSystem.writeFileString(outputPath, encoded);
335
405
  });
336
406
 
337
407
  const discoveryWorkerEntry = Effect.fn("Deploy.discoveryWorkerEntry")(function* () {
@@ -365,7 +435,6 @@ const discoverServerFunctions = Effect.fn("Deploy.discoverServerFunctionsIsolate
365
435
  (temporary) =>
366
436
  fileSystem.remove(temporary, { force: true, recursive: true }).pipe(Effect.orDie),
367
437
  );
368
- const outputPath = path.join(directory, "discovery.json");
369
438
  const encodedModuleNames = yield* Schema.encodeEffect(ServerModuleNamesJson)(moduleNames);
370
439
  const packageRoot = path.resolve(path.dirname(workerEntry), "../..");
371
440
  const workspaceProbes: Array<string> = [];
@@ -387,23 +456,25 @@ const discoverServerFunctions = Effect.fn("Deploy.discoverServerFunctionsIsolate
387
456
  const readPermissions = [
388
457
  appDirectory,
389
458
  nodeModulesDirectory(),
459
+ path.dirname(nodeModulesDirectory()),
390
460
  packageRoot,
391
461
  path.join(packageRoot, "node_modules"),
462
+ path.resolve(packageRoot, "../..", "node_modules"),
392
463
  ...workspaceProbes,
393
464
  ].map((allowedPath) => `--allow-fs-read=${allowedPath}`);
394
- const exitCode = yield* spawner.exitCode(
465
+ const handle = yield* spawner.spawn(
395
466
  ChildProcess.make(
396
467
  process.execPath,
397
468
  [
398
469
  "--permission",
399
470
  "--allow-addons",
471
+ "--conditions=@ignotum/source",
400
472
  ...readPermissions,
401
473
  `--allow-fs-write=${directory}`,
402
474
  workerEntry,
403
475
  "__ignotum_discover_server",
404
476
  appDirectory,
405
477
  encodedModuleNames,
406
- outputPath,
407
478
  ],
408
479
  {
409
480
  cwd: appDirectory,
@@ -414,9 +485,14 @@ const discoverServerFunctions = Effect.fn("Deploy.discoverServerFunctionsIsolate
414
485
  },
415
486
  stderr: "inherit",
416
487
  stdout: "ignore",
488
+ additionalFds: { fd3: { type: "output" } },
417
489
  },
418
490
  ),
419
491
  );
492
+ const [encoded, exitCode] = yield* Effect.all(
493
+ [Stream.mkString(Stream.decodeText(handle.getOutputFd(3))), handle.exitCode],
494
+ { concurrency: "unbounded" },
495
+ );
420
496
  if (exitCode !== ChildProcessSpawner.ExitCode(0)) {
421
497
  return yield* ServerFunctionDiscoveryFailed.make({
422
498
  cause: new Error(`Discovery worker exited with code ${exitCode}.`),
@@ -424,13 +500,12 @@ const discoverServerFunctions = Effect.fn("Deploy.discoverServerFunctionsIsolate
424
500
  path: appDirectory,
425
501
  });
426
502
  }
427
- const encoded = yield* fileSystem.readFileString(outputPath);
428
503
  return yield* Schema.decodeEffect(ServerDiscoveryManifestJson)(encoded).pipe(
429
504
  Effect.mapError((cause) =>
430
505
  ServerFunctionDiscoveryFailed.make({
431
506
  cause,
432
507
  message: "The isolated build process returned invalid application metadata.",
433
- path: outputPath,
508
+ path: appDirectory,
434
509
  }),
435
510
  ),
436
511
  );
@@ -445,8 +520,8 @@ const serverFunctionEntryPlugin = (definition: DiscoveredServerFunction): Plugin
445
520
  import { invoke } from "ignotum/internal/host"
446
521
  import { ${definition.exportName} as definition } from ${encodeJavaScriptString(normalizePath(definition.modulePath))}
447
522
 
448
- export default async function handler(encodedRequest) {
449
- return invoke(definition, JSON.parse(encodedRequest), call)
523
+ export default async function handler(encodedRequest, encodedEnvironment) {
524
+ return invoke(definition, JSON.parse(encodedRequest), call, JSON.parse(encodedEnvironment))
450
525
  }
451
526
  `;
452
527
 
@@ -573,6 +648,15 @@ export const buildServer = Effect.fn("Deploy.buildServer")(function* (
573
648
  const path = yield* Path.Path;
574
649
  const conditions = yield* serverConditions();
575
650
  const discovered = yield* discoverServerFunctions(appDirectory, functionModules);
651
+ const crypto = yield* Crypto.Crypto;
652
+ const environmentArtifact = {
653
+ formatVersion: 1,
654
+ nonce: Encoding.encodeBase64Url(yield* crypto.randomBytes(32)),
655
+ values: discovered.environment.values,
656
+ } satisfies EnvironmentArtifact;
657
+ const environmentBytes = utf8Bytes(
658
+ `${encodeCanonical(EnvironmentArtifact, environmentArtifact)}\n`,
659
+ );
576
660
 
577
661
  yield* fileSystem.makeDirectory(outputDirectory, { recursive: true });
578
662
  const encodedSnapshot = encodeSchemaSnapshot(discovered.schema);
@@ -584,7 +668,11 @@ export const buildServer = Effect.fn("Deploy.buildServer")(function* (
584
668
  { concurrency: 4 },
585
669
  );
586
670
  const manifest = {
587
- formatVersion: 2,
671
+ formatVersion: 3,
672
+ environment: {
673
+ artifact: yield* artifactReference(serverEnvironmentPath, environmentBytes),
674
+ fields: discovered.environment.fields,
675
+ },
588
676
  schema: yield* artifactReference(schemaSnapshotPath, snapshotBytes),
589
677
  functions,
590
678
  } satisfies ServerBuildManifest;
@@ -598,6 +686,7 @@ export const buildServer = Effect.fn("Deploy.buildServer")(function* (
598
686
  );
599
687
 
600
688
  return {
689
+ environment: { bytes: environmentBytes, path: serverEnvironmentPath },
601
690
  functions: functions.length,
602
691
  manifestPath,
603
692
  } satisfies ServerBuildResult;
@@ -41,6 +41,7 @@ const isFunctionModuleFile = (fileName: string): boolean =>
41
41
  !fileName.endsWith(".test.ts") &&
42
42
  !fileName.endsWith(".spec.ts") &&
43
43
  fileName !== "index.ts" &&
44
+ fileName !== "env.ts" &&
44
45
  fileName !== "schema.ts" &&
45
46
  !fileName.startsWith("_");
46
47
 
@@ -57,13 +58,13 @@ const moduleNameFromFile = (filePath: string, fileName: string) => {
57
58
  );
58
59
  };
59
60
 
60
- const renderServerBindings = (): string => `${generatedHeader}
61
+ const renderServerBindings = (hasEnvironment: boolean): string => `${generatedHeader}
61
62
 
62
63
  import { bindSchema } from "ignotum/internal/server";
63
64
 
64
- import schema from "../server/schema.js";
65
+ ${hasEnvironment ? 'import environment from "../server/env.js";\n' : ""}import schema from "../server/schema.js";
65
66
 
66
- export const { mutation, query, values } = bindSchema(schema);
67
+ export const { mutation, query, values } = bindSchema(schema${hasEnvironment ? ", environment" : ""});
67
68
  `;
68
69
 
69
70
  const renderTypes = (): string => `${generatedHeader}
@@ -157,6 +158,7 @@ export const generate = Effect.fn("Codegen.generate")(function* (appDirectory: s
157
158
  const path = yield* Path.Path;
158
159
  const serverDirectory = path.join(appDirectory, "server");
159
160
  const schemaPath = path.join(serverDirectory, "schema.ts");
161
+ const environmentPath = path.join(serverDirectory, "env.ts");
160
162
  const schemaExists = yield* fileSystem.exists(schemaPath);
161
163
 
162
164
  if (!schemaExists) {
@@ -167,13 +169,14 @@ export const generate = Effect.fn("Codegen.generate")(function* (appDirectory: s
167
169
  }
168
170
 
169
171
  const entries = yield* fileSystem.readDirectory(serverDirectory);
172
+ const hasEnvironment = yield* fileSystem.exists(environmentPath);
170
173
  const functionFiles = Array.sort(String.Order)(Array.filter(entries, isFunctionModuleFile));
171
174
  const functionModules = yield* Effect.forEach(functionFiles, (fileName) =>
172
175
  moduleNameFromFile(path.join(serverDirectory, fileName), fileName),
173
176
  );
174
177
  const outputs: ReadonlyArray<GeneratedOutput> = [
175
178
  {
176
- content: renderServerBindings(),
179
+ content: renderServerBindings(hasEnvironment),
177
180
  path: path.join(appDirectory, "_generated", "server.ts"),
178
181
  },
179
182
  {
@@ -102,11 +102,12 @@ export class ControlClientUnavailable extends Schema.TaggedError<ControlClientUn
102
102
  ) {}
103
103
 
104
104
  export interface DeploymentUploadFile {
105
+ readonly bytes?: Uint8Array;
105
106
  readonly contentType: string;
106
107
  readonly path: string;
107
108
  readonly sha256: string;
108
109
  readonly size: number;
109
- readonly sourcePath: string;
110
+ readonly sourcePath?: string;
110
111
  }
111
112
 
112
113
  interface ControlClientService {
@@ -168,6 +169,7 @@ export const deploymentUploadFiles = Effect.fn("ControlClient.deploymentUploadFi
168
169
  inventorySha256: string,
169
170
  inventorySize: number,
170
171
  buildDirectory: string,
172
+ memoryFiles: ReadonlyMap<string, Uint8Array> = new Map(),
171
173
  ) {
172
174
  const path = yield* Path.Path;
173
175
  return [
@@ -178,7 +180,18 @@ export const deploymentUploadFiles = Effect.fn("ControlClient.deploymentUploadFi
178
180
  size: inventorySize,
179
181
  sourcePath: path.join(buildDirectory, deploymentInventoryPath),
180
182
  },
181
- ...inventory.files.map((file) => uploadFile(file, path.join(buildDirectory, file.path))),
183
+ ...inventory.files.map((file) => {
184
+ const bytes = memoryFiles.get(file.path);
185
+ return bytes === undefined
186
+ ? uploadFile(file, path.join(buildDirectory, file.path))
187
+ : {
188
+ bytes,
189
+ contentType: file.contentType,
190
+ path: file.path,
191
+ sha256: file.sha256,
192
+ size: file.size,
193
+ };
194
+ }),
182
195
  ];
183
196
  });
184
197
 
@@ -304,17 +317,26 @@ export const controlClientLayer = Layer.effect(
304
317
  deploymentId: DeploymentId,
305
318
  file: DeploymentUploadFile,
306
319
  ) {
307
- const request = yield* HttpClientRequest.put(
320
+ const baseRequest = HttpClientRequest.put(
308
321
  endpoint(config.apiUrl, controlDeploymentFilePath(appId, deploymentId, file.path)),
309
322
  ).pipe(
310
323
  HttpClientRequest.setHeader(controlUploadHeaderNames.contentLength, String(file.size)),
311
324
  HttpClientRequest.setHeader(controlUploadHeaderNames.sha256, file.sha256),
312
- HttpClientRequest.bodyFile(file.sourcePath, { contentType: file.contentType }),
313
- Effect.provideService(FileSystem.FileSystem, fileSystem),
314
- Effect.mapError((cause) =>
315
- unavailable(`Deployment file '${file.path}' could not be opened.`, cause),
316
- ),
317
325
  );
326
+ const request = yield* file.bytes === undefined
327
+ ? file.sourcePath === undefined
328
+ ? Effect.fail(unavailable(`Deployment file '${file.path}' has no upload source.`, {}))
329
+ : HttpClientRequest.bodyFile(baseRequest, file.sourcePath, {
330
+ contentType: file.contentType,
331
+ }).pipe(
332
+ Effect.provideService(FileSystem.FileSystem, fileSystem),
333
+ Effect.mapError((cause) =>
334
+ unavailable(`Deployment file '${file.path}' could not be opened.`, cause),
335
+ ),
336
+ )
337
+ : Effect.succeed(
338
+ HttpClientRequest.bodyUint8Array(baseRequest, file.bytes, file.contentType),
339
+ );
318
340
  const response = yield* executeIdempotent(request, DeploymentFileUploadResponse);
319
341
  return response.status;
320
342
  });
package/src/cli/deploy.ts CHANGED
@@ -22,6 +22,7 @@ export interface DeploymentBuildResult {
22
22
  readonly inventory: DeploymentInventory;
23
23
  readonly inventoryBytes: Uint8Array;
24
24
  readonly outputDirectory: string;
25
+ readonly memoryFiles?: ReadonlyMap<string, Uint8Array>;
25
26
  readonly serverFunctions: number;
26
27
  }
27
28
 
@@ -119,13 +120,15 @@ export const buildDeploymentArtifact = Effect.fn("Deploy.buildArtifact")(functio
119
120
  const serverOutput = path.join(stagingDirectory, "server");
120
121
  const client = yield* buildClient(appDirectory, clientOutput);
121
122
  const server = yield* buildServer(appDirectory, serverOutput, codegen.functionModules);
122
- const payloadFiles = yield* readArtifactDirectory(stagingDirectory);
123
+ const diskPayloadFiles = yield* readArtifactDirectory(stagingDirectory);
124
+ const payloadFiles = [...diskPayloadFiles, server.environment];
123
125
  const inventory = yield* makeDeploymentInventory(payloadFiles);
124
126
  yield* fileSystem.writeFile(
125
127
  path.join(stagingDirectory, deploymentInventoryPath),
126
128
  inventory.bytes,
127
129
  );
128
- const artifactFiles = yield* readArtifactDirectory(stagingDirectory);
130
+ const diskArtifactFiles = yield* readArtifactDirectory(stagingDirectory);
131
+ const artifactFiles = [...diskArtifactFiles, server.environment];
129
132
  yield* validateDeploymentArtifact(artifactFiles);
130
133
  yield* Effect.logInfo("Built deployment artifact").pipe(
131
134
  Effect.annotateLogs({
@@ -140,6 +143,7 @@ export const buildDeploymentArtifact = Effect.fn("Deploy.buildArtifact")(functio
140
143
  clientAssets: client.assets,
141
144
  inventory: inventory.inventory,
142
145
  inventoryBytes: inventory.bytes,
146
+ memoryFiles: new Map([[server.environment.path, server.environment.bytes]]),
143
147
  outputDirectory: buildDirectory,
144
148
  serverFunctions: server.functions,
145
149
  } satisfies DeploymentBuildResult;
@@ -158,6 +162,7 @@ export const uploadDeployment = Effect.fn("Deploy.upload")(function* (
158
162
  inventorySha256,
159
163
  build.inventoryBytes.byteLength,
160
164
  build.outputDirectory,
165
+ build.memoryFiles,
161
166
  );
162
167
  const deployment = yield* control.beginDeployment(configuration.appId, build.inventory);
163
168