ignotum 0.0.10 → 0.0.12
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 +8 -3
- package/dist/cli/bin.mjs +563 -104
- package/dist/cli/bin.mjs.map +1 -1
- package/dist/runtime/{api-jCl8Hzry.d.ts → api-5XSrIeqW.d.ts} +4 -3
- package/dist/runtime/client.d.ts +6 -5
- package/dist/runtime/client.js +170 -75
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/{descriptor-C5VA9qRl-BhD-WQeO.js → descriptor-XzDX2JDw-j3O6YHgW.js} +26 -6
- package/dist/runtime/descriptor-XzDX2JDw-j3O6YHgW.js.map +1 -0
- package/dist/runtime/{id-Cs82tq9Q-Caqfx54f.d.ts → id-Cs82tq9Q-CK-maMgN.d.ts} +13 -13
- package/dist/runtime/{index-D6VLTbDB.d.ts → index-CrWg4Z0y.d.ts} +132 -76
- package/dist/runtime/internal/api.d.ts +1 -1
- package/dist/runtime/internal/host.d.ts +18 -17
- package/dist/runtime/internal/host.js +11 -6
- package/dist/runtime/internal/host.js.map +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 +1 -1
- package/dist/runtime/internal/types.js +1 -1
- package/dist/runtime/pagination-CX5IPbEi.d.ts +1 -0
- package/dist/runtime/{pagination-Bt3l7QaC-D_zI5zsu.d.ts → pagination-D-R9NR61-CpIoHFoI.d.ts} +8 -5
- package/dist/runtime/result-DIjKM-p4.d.ts +1 -0
- package/dist/runtime/{schema-C00wobuy.js → schema-B9XxyRO8.js} +132 -16
- package/dist/runtime/schema-B9XxyRO8.js.map +1 -0
- package/dist/runtime/server.d.ts +7 -3
- package/dist/runtime/server.js +5 -3
- package/dist/runtime/server.js.map +1 -1
- package/dist/runtime/{sync-bMQq9tXx.d.ts → sync-avN7NkcU.d.ts} +2 -2
- package/package.json +1 -1
- package/src/cli/agent-files.ts +2 -0
- package/src/cli/auth-client.ts +1 -1
- package/src/cli/bin.ts +13 -3
- package/src/cli/build/server.ts +102 -13
- package/src/cli/codegen.ts +7 -4
- package/src/cli/control-client.ts +30 -8
- package/src/cli/deploy.ts +7 -2
- package/src/cli/environment.ts +133 -0
- package/src/client/files.ts +43 -27
- package/src/client/hooks.ts +26 -17
- package/src/client/page-observers.ts +77 -0
- package/src/client/sync.ts +71 -59
- package/src/dev-runtime/functions.ts +127 -4
- package/src/dev-runtime/sync.ts +38 -9
- package/src/server/index.ts +11 -1
- package/dist/runtime/descriptor-C5VA9qRl-BhD-WQeO.js.map +0 -1
- package/dist/runtime/pagination-DcIkTOFs.d.ts +0 -1
- package/dist/runtime/schema-C00wobuy.js.map +0 -1
- package/src/dev-runtime/id.ts +0 -3
package/dist/cli/bin.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire, isBuiltin } from "node:module";
|
|
3
|
+
import * as NFS from "node:fs";
|
|
4
|
+
import { writeSync } from "node:fs";
|
|
3
5
|
import process$1 from "node:process";
|
|
4
6
|
import * as Deferred from "effect/Deferred";
|
|
5
7
|
import * as Effect$1 from "effect/Effect";
|
|
@@ -35,7 +37,6 @@ import { isIP } from "node:net";
|
|
|
35
37
|
import * as EffectCrypto from "effect/Crypto";
|
|
36
38
|
import * as NodeCrypto from "node:crypto";
|
|
37
39
|
import * as Queue from "effect/Queue";
|
|
38
|
-
import * as NFS from "node:fs";
|
|
39
40
|
import * as OS from "node:os";
|
|
40
41
|
import { homedir } from "node:os";
|
|
41
42
|
import * as Path$2 from "node:path";
|
|
@@ -70,7 +71,7 @@ import * as RcRef from "effect/RcRef";
|
|
|
70
71
|
import * as Terminal$1 from "effect/Terminal";
|
|
71
72
|
import * as readline from "node:readline";
|
|
72
73
|
import * as Runtime from "effect/Runtime";
|
|
73
|
-
import { Array as Array$1, Brand, Cause, Config, Context, Crypto, DateTime, Effect, Effectable, Encoding, FiberSet, FileSystem, Function as Function$1, HashMap, HashSet, Layer, ManagedRuntime, MutableHashMap, MutableHashSet, Option, Path as Path$1, Predicate, PubSub, Redacted, Ref, Result, Schedule, Schema, SchemaGetter, Semaphore, Stream, String as String$1, Terminal } from "effect";
|
|
74
|
+
import { Array as Array$1, Brand, Cause, Config, ConfigProvider, Context, Crypto, DateTime, Effect, Effectable, Encoding, FiberSet, FileSystem, Function as Function$1, HashMap, HashSet, Layer, ManagedRuntime, MutableHashMap, MutableHashSet, Option, Path as Path$1, Predicate, PubSub, Redacted, Ref, Result, Schedule, Schema, SchemaGetter, Semaphore, Stream, String as String$1, Struct, Terminal } from "effect";
|
|
74
75
|
import { Argument, CliError, Command, Flag, Prompt } from "effect/unstable/cli";
|
|
75
76
|
import { customAlphabet } from "nanoid";
|
|
76
77
|
import { createAuthClient } from "better-auth/client";
|
|
@@ -29547,7 +29548,8 @@ const TransportValueSchema = Schema.suspend(() => Schema.Union([
|
|
|
29547
29548
|
Schema.Array(TransportValueSchema),
|
|
29548
29549
|
Schema.Record(Schema.String, Schema.UndefinedOr(TransportValueSchema))
|
|
29549
29550
|
]));
|
|
29550
|
-
Schema.toType(TransportValueSchema);
|
|
29551
|
+
const TransportValueTypeSchema = Schema.toType(TransportValueSchema);
|
|
29552
|
+
const TransportObjectTypeSchema = Schema.Record(Schema.String, Schema.UndefinedOr(TransportValueTypeSchema));
|
|
29551
29553
|
const collectDatePaths = (value, path, paths) => {
|
|
29552
29554
|
if (Predicate.isDate(value)) {
|
|
29553
29555
|
Schema.decodeSync(Schema.Date)(value);
|
|
@@ -29567,7 +29569,7 @@ const datePathsOf = (value) => {
|
|
|
29567
29569
|
collectDatePaths(value, [], paths);
|
|
29568
29570
|
return paths;
|
|
29569
29571
|
};
|
|
29570
|
-
const datePathsOfObject = (value) => datePathsOf(Schema.decodeUnknownSync(
|
|
29572
|
+
const datePathsOfObject = (value) => datePathsOf(Schema.decodeUnknownSync(TransportObjectTypeSchema)(value));
|
|
29571
29573
|
//#endregion
|
|
29572
29574
|
//#region ../contracts/dist/runtime/sync.js
|
|
29573
29575
|
const FunctionNamePart = Schema.String.check(Schema.isPattern(/^[A-Za-z_$][A-Za-z0-9_$]*$/));
|
|
@@ -29734,7 +29736,7 @@ var FunctionUnavailable = class extends Schema.TaggedError()("FunctionUnavailabl
|
|
|
29734
29736
|
message: Schema.String
|
|
29735
29737
|
}) {};
|
|
29736
29738
|
//#endregion
|
|
29737
|
-
//#region ../contracts/dist/descriptor-
|
|
29739
|
+
//#region ../contracts/dist/descriptor-XzDX2JDw.js
|
|
29738
29740
|
const LiteralValue = Schema.Union([
|
|
29739
29741
|
Schema.String,
|
|
29740
29742
|
Schema.Finite,
|
|
@@ -29799,6 +29801,10 @@ const ValueDescriptor = Schema.Union([
|
|
|
29799
29801
|
type: Schema.Literal("record"),
|
|
29800
29802
|
value: Schema.suspend(() => ValueDescriptor)
|
|
29801
29803
|
}),
|
|
29804
|
+
Schema.Struct({
|
|
29805
|
+
type: Schema.Literal("secret"),
|
|
29806
|
+
value: Schema.suspend(() => ValueDescriptor)
|
|
29807
|
+
}),
|
|
29802
29808
|
Schema.Struct({ type: Schema.Literal("string") }),
|
|
29803
29809
|
Schema.Struct({
|
|
29804
29810
|
type: Schema.Literal("union"),
|
|
@@ -29806,7 +29812,7 @@ const ValueDescriptor = Schema.Union([
|
|
|
29806
29812
|
})
|
|
29807
29813
|
]);
|
|
29808
29814
|
const ValueDescriptorTypeId = Symbol.for("ignotum/schema/ValueDescriptor");
|
|
29809
|
-
Function$1.dual(2, (value, descriptor) => {
|
|
29815
|
+
const attachValueDescriptor = Function$1.dual(2, (value, descriptor) => {
|
|
29810
29816
|
Object.defineProperty(value, ValueDescriptorTypeId, {
|
|
29811
29817
|
configurable: false,
|
|
29812
29818
|
enumerable: false,
|
|
@@ -29825,13 +29831,29 @@ const descriptorContainsFile = (descriptor) => {
|
|
|
29825
29831
|
case "array":
|
|
29826
29832
|
case "nullable":
|
|
29827
29833
|
case "optional":
|
|
29828
|
-
case "record":
|
|
29834
|
+
case "record":
|
|
29835
|
+
case "secret": return descriptorContainsFile(descriptor.value);
|
|
29829
29836
|
case "error":
|
|
29830
29837
|
case "object": return descriptor.fields.some((field) => descriptorContainsFile(field.value));
|
|
29831
29838
|
case "union": return descriptor.members.some(descriptorContainsFile);
|
|
29832
29839
|
default: return false;
|
|
29833
29840
|
}
|
|
29834
29841
|
};
|
|
29842
|
+
const descriptorAllowedInEnvironment = (descriptor) => {
|
|
29843
|
+
switch (descriptor.type) {
|
|
29844
|
+
case "error":
|
|
29845
|
+
case "file":
|
|
29846
|
+
case "id": return false;
|
|
29847
|
+
case "array":
|
|
29848
|
+
case "nullable":
|
|
29849
|
+
case "optional":
|
|
29850
|
+
case "record":
|
|
29851
|
+
case "secret": return descriptorAllowedInEnvironment(descriptor.value);
|
|
29852
|
+
case "object": return descriptor.fields.every((field) => descriptorAllowedInEnvironment(field.value));
|
|
29853
|
+
case "union": return descriptor.members.every(descriptorAllowedInEnvironment);
|
|
29854
|
+
default: return true;
|
|
29855
|
+
}
|
|
29856
|
+
};
|
|
29835
29857
|
const descriptorFields = (fields) => {
|
|
29836
29858
|
const entries = [];
|
|
29837
29859
|
for (const name of Reflect.ownKeys(fields)) {
|
|
@@ -29875,6 +29897,7 @@ const ArtifactKind = Schema.Literals([
|
|
|
29875
29897
|
"ClientShell",
|
|
29876
29898
|
"FunctionBundle",
|
|
29877
29899
|
"ServerManifest",
|
|
29900
|
+
"ServerEnvironment",
|
|
29878
29901
|
"SourceMap",
|
|
29879
29902
|
"SchemaSnapshot"
|
|
29880
29903
|
]);
|
|
@@ -29895,9 +29918,17 @@ const ClientRoute = Schema.Struct({
|
|
|
29895
29918
|
pathname: ClientPath,
|
|
29896
29919
|
artifact: ArtifactReference
|
|
29897
29920
|
});
|
|
29898
|
-
const
|
|
29921
|
+
const DeploymentInventoryV1 = Schema.Struct({
|
|
29899
29922
|
formatVersion: Schema.Literal(1),
|
|
29900
29923
|
files: Schema.Array(ArtifactFile)
|
|
29924
|
+
});
|
|
29925
|
+
const DeploymentInventoryV2 = Schema.Struct({
|
|
29926
|
+
formatVersion: Schema.Literal(2),
|
|
29927
|
+
files: Schema.Array(ArtifactFile)
|
|
29928
|
+
});
|
|
29929
|
+
const DeploymentInventory = upgrade(initial(DeploymentInventoryV1), DeploymentInventoryV2, (inventory) => ({
|
|
29930
|
+
formatVersion: 2,
|
|
29931
|
+
files: inventory.files
|
|
29901
29932
|
}));
|
|
29902
29933
|
const ClientManifest = initial(Schema.Struct({
|
|
29903
29934
|
formatVersion: Schema.Literal(1),
|
|
@@ -29917,6 +29948,15 @@ const SchemaSnapshotField = Schema.Struct({
|
|
|
29917
29948
|
name: Schema.String,
|
|
29918
29949
|
value: ValueDescriptor
|
|
29919
29950
|
});
|
|
29951
|
+
const EnvironmentArtifact = initial(Schema.Struct({
|
|
29952
|
+
formatVersion: Schema.Literal(1),
|
|
29953
|
+
nonce: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{43}$/)),
|
|
29954
|
+
values: Schema.Record(Schema.String, Schema.String)
|
|
29955
|
+
}));
|
|
29956
|
+
const ServerEnvironmentArtifact = Schema.Struct({
|
|
29957
|
+
artifact: ArtifactReference,
|
|
29958
|
+
fields: Schema.Array(SchemaSnapshotField)
|
|
29959
|
+
});
|
|
29920
29960
|
const SchemaSnapshotIndex = Schema.Struct({
|
|
29921
29961
|
name: Schema.String,
|
|
29922
29962
|
fields: Schema.Array(Schema.String)
|
|
@@ -29970,10 +30010,19 @@ const ServerBuildManifestV2 = Schema.Struct({
|
|
|
29970
30010
|
schema: ArtifactReference,
|
|
29971
30011
|
functions: Schema.Array(ServerFunctionArtifact)
|
|
29972
30012
|
});
|
|
29973
|
-
const ServerBuildManifest = upgrade(initial(ServerBuildManifestV1), ServerBuildManifestV2, (manifest) => ({
|
|
30013
|
+
const ServerBuildManifest = upgrade(upgrade(initial(ServerBuildManifestV1), ServerBuildManifestV2, (manifest) => ({
|
|
29974
30014
|
formatVersion: 2,
|
|
29975
30015
|
schema: manifest.schema,
|
|
29976
30016
|
functions: manifest.functions
|
|
30017
|
+
})), Schema.Struct({
|
|
30018
|
+
formatVersion: Schema.Literal(3),
|
|
30019
|
+
schema: ArtifactReference,
|
|
30020
|
+
environment: Schema.optional(ServerEnvironmentArtifact),
|
|
30021
|
+
functions: Schema.Array(ServerFunctionArtifact)
|
|
30022
|
+
}), (manifest) => ({
|
|
30023
|
+
formatVersion: 3,
|
|
30024
|
+
schema: manifest.schema,
|
|
30025
|
+
functions: manifest.functions
|
|
29977
30026
|
}));
|
|
29978
30027
|
const deploymentInventoryPath = ArtifactPath.make("inventory.json");
|
|
29979
30028
|
const clientManifestPath = ArtifactPath.make("client/manifest.json");
|
|
@@ -29992,6 +30041,7 @@ const clientPublicFileExtensions = [
|
|
|
29992
30041
|
];
|
|
29993
30042
|
const serverManifestPath = ArtifactPath.make("server/manifest.json");
|
|
29994
30043
|
const schemaSnapshotPath = ArtifactPath.make("server/schema.json");
|
|
30044
|
+
const serverEnvironmentPath = ArtifactPath.make("server/environment.json");
|
|
29995
30045
|
const deploymentArtifactLimits = {
|
|
29996
30046
|
fileBytes: 16777216,
|
|
29997
30047
|
fileCount: 512,
|
|
@@ -30090,7 +30140,7 @@ var IdGenerator = class IdGenerator extends Context.Service()("@ignotum/shared/i
|
|
|
30090
30140
|
};
|
|
30091
30141
|
//#endregion
|
|
30092
30142
|
//#region package.json
|
|
30093
|
-
var version = "0.0.
|
|
30143
|
+
var version = "0.0.12";
|
|
30094
30144
|
//#endregion
|
|
30095
30145
|
//#region src/cli/codegen.ts
|
|
30096
30146
|
const generatedHeader = "// Generated by `ignotum codegen`. Do not edit.";
|
|
@@ -30106,7 +30156,7 @@ var GeneratedFileConflict = class extends Schema.TaggedError()("GeneratedFileCon
|
|
|
30106
30156
|
message: Schema.String,
|
|
30107
30157
|
path: Schema.String
|
|
30108
30158
|
}) {};
|
|
30109
|
-
const isFunctionModuleFile = (fileName) => fileName.endsWith(".ts") && !fileName.endsWith(".test.ts") && !fileName.endsWith(".spec.ts") && fileName !== "index.ts" && fileName !== "schema.ts" && !fileName.startsWith("_");
|
|
30159
|
+
const isFunctionModuleFile = (fileName) => fileName.endsWith(".ts") && !fileName.endsWith(".test.ts") && !fileName.endsWith(".spec.ts") && fileName !== "index.ts" && fileName !== "env.ts" && fileName !== "schema.ts" && !fileName.startsWith("_");
|
|
30110
30160
|
const moduleNameFromFile = (filePath, fileName) => {
|
|
30111
30161
|
const moduleName = fileName.slice(0, -3);
|
|
30112
30162
|
return Schema.decodeEffect(FunctionNamePart)(moduleName).pipe(Effect.mapError(() => InvalidFunctionModuleName.make({
|
|
@@ -30114,13 +30164,13 @@ const moduleNameFromFile = (filePath, fileName) => {
|
|
|
30114
30164
|
path: filePath
|
|
30115
30165
|
})));
|
|
30116
30166
|
};
|
|
30117
|
-
const renderServerBindings = () => `${generatedHeader}
|
|
30167
|
+
const renderServerBindings = (hasEnvironment) => `${generatedHeader}
|
|
30118
30168
|
|
|
30119
30169
|
import { bindSchema } from "ignotum/internal/server";
|
|
30120
30170
|
|
|
30121
|
-
import schema from "../server/schema.js";
|
|
30171
|
+
${hasEnvironment ? "import environment from \"../server/env.js\";\n" : ""}import schema from "../server/schema.js";
|
|
30122
30172
|
|
|
30123
|
-
export const { mutation, query, values } = bindSchema(schema);
|
|
30173
|
+
export const { mutation, query, values } = bindSchema(schema${hasEnvironment ? ", environment" : ""});
|
|
30124
30174
|
`;
|
|
30125
30175
|
const renderTypes = () => `${generatedHeader}
|
|
30126
30176
|
|
|
@@ -30185,16 +30235,18 @@ const generate = Effect.fn("Codegen.generate")(function* (appDirectory) {
|
|
|
30185
30235
|
const path = yield* Path$1.Path;
|
|
30186
30236
|
const serverDirectory = path.join(appDirectory, "server");
|
|
30187
30237
|
const schemaPath = path.join(serverDirectory, "schema.ts");
|
|
30238
|
+
const environmentPath = path.join(serverDirectory, "env.ts");
|
|
30188
30239
|
if (!(yield* fileSystem.exists(schemaPath))) return yield* SchemaNotFound.make({
|
|
30189
30240
|
message: `No Ignotum schema found at ${schemaPath}.`,
|
|
30190
30241
|
path: schemaPath
|
|
30191
30242
|
});
|
|
30192
30243
|
const entries = yield* fileSystem.readDirectory(serverDirectory);
|
|
30244
|
+
const hasEnvironment = yield* fileSystem.exists(environmentPath);
|
|
30193
30245
|
const functionFiles = Array$1.sort(String$1.Order)(Array$1.filter(entries, isFunctionModuleFile));
|
|
30194
30246
|
const functionModules = yield* Effect.forEach(functionFiles, (fileName) => moduleNameFromFile(path.join(serverDirectory, fileName), fileName));
|
|
30195
30247
|
const outputs = [
|
|
30196
30248
|
{
|
|
30197
|
-
content: renderServerBindings(),
|
|
30249
|
+
content: renderServerBindings(hasEnvironment),
|
|
30198
30250
|
path: path.join(appDirectory, "_generated", "server.ts")
|
|
30199
30251
|
},
|
|
30200
30252
|
{
|
|
@@ -30225,7 +30277,7 @@ const Credential = initial(Schema.Struct({
|
|
|
30225
30277
|
}));
|
|
30226
30278
|
const AuthUrl = Schema.URL.check(Schema.makeFilter((url) => url.protocol === "https:" || url.protocol === "http:" && [
|
|
30227
30279
|
"127.0.0.1",
|
|
30228
|
-
"::1",
|
|
30280
|
+
"[::1]",
|
|
30229
30281
|
"localhost"
|
|
30230
30282
|
].includes(url.hostname), { message: "The auth URL must use HTTPS unless it targets localhost." }));
|
|
30231
30283
|
var AuthCommandError = class extends Schema.TaggedError()("AuthCommandError", {
|
|
@@ -30438,7 +30490,7 @@ const uploadFile = (file, sourcePath) => ({
|
|
|
30438
30490
|
size: file.size,
|
|
30439
30491
|
sourcePath
|
|
30440
30492
|
});
|
|
30441
|
-
const deploymentUploadFiles = Effect.fn("ControlClient.deploymentUploadFiles")(function* (inventory, inventorySha256, inventorySize, buildDirectory) {
|
|
30493
|
+
const deploymentUploadFiles = Effect.fn("ControlClient.deploymentUploadFiles")(function* (inventory, inventorySha256, inventorySize, buildDirectory, memoryFiles = /* @__PURE__ */ new Map()) {
|
|
30442
30494
|
const path = yield* Path$1.Path;
|
|
30443
30495
|
return [{
|
|
30444
30496
|
contentType: "application/json; charset=utf-8",
|
|
@@ -30446,7 +30498,16 @@ const deploymentUploadFiles = Effect.fn("ControlClient.deploymentUploadFiles")(f
|
|
|
30446
30498
|
sha256: inventorySha256,
|
|
30447
30499
|
size: inventorySize,
|
|
30448
30500
|
sourcePath: path.join(buildDirectory, deploymentInventoryPath)
|
|
30449
|
-
}, ...inventory.files.map((file) =>
|
|
30501
|
+
}, ...inventory.files.map((file) => {
|
|
30502
|
+
const bytes = memoryFiles.get(file.path);
|
|
30503
|
+
return bytes === void 0 ? uploadFile(file, path.join(buildDirectory, file.path)) : {
|
|
30504
|
+
bytes,
|
|
30505
|
+
contentType: file.contentType,
|
|
30506
|
+
path: file.path,
|
|
30507
|
+
sha256: file.sha256,
|
|
30508
|
+
size: file.size
|
|
30509
|
+
};
|
|
30510
|
+
})];
|
|
30450
30511
|
});
|
|
30451
30512
|
const controlClientLayer = Layer.effect(ControlClient, Effect.gen(function* () {
|
|
30452
30513
|
const config = yield* HostedControlConfiguration;
|
|
@@ -30496,7 +30557,8 @@ const controlClientLayer = Layer.effect(ControlClient, Effect.gen(function* () {
|
|
|
30496
30557
|
return yield* execute(request, ControlDeployment);
|
|
30497
30558
|
});
|
|
30498
30559
|
const uploadDeploymentFile = Effect.fn("ControlClient.uploadDeploymentFile")(function* (appId, deploymentId, file) {
|
|
30499
|
-
const
|
|
30560
|
+
const baseRequest = HttpClientRequest.put(endpoint(config.apiUrl, controlDeploymentFilePath(appId, deploymentId, file.path))).pipe(HttpClientRequest.setHeader(controlUploadHeaderNames.contentLength, String(file.size)), HttpClientRequest.setHeader(controlUploadHeaderNames.sha256, file.sha256));
|
|
30561
|
+
const request = yield* file.bytes === void 0 ? file.sourcePath === void 0 ? Effect.fail(unavailable(`Deployment file '${file.path}' has no upload source.`, {})) : HttpClientRequest.bodyFile(baseRequest, file.sourcePath, { contentType: file.contentType }).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.mapError((cause) => unavailable(`Deployment file '${file.path}' could not be opened.`, cause))) : Effect.succeed(HttpClientRequest.bodyUint8Array(baseRequest, file.bytes, file.contentType));
|
|
30500
30562
|
return (yield* executeIdempotent(request, DeploymentFileUploadResponse)).status;
|
|
30501
30563
|
});
|
|
30502
30564
|
const finalizeDeployment = Effect.fn("ControlClient.finalizeDeployment")(function* (appId, deploymentId) {
|
|
@@ -30517,15 +30579,12 @@ const controlClientLayer = Layer.effect(ControlClient, Effect.gen(function* () {
|
|
|
30517
30579
|
}));
|
|
30518
30580
|
//#endregion
|
|
30519
30581
|
//#region ../contracts/dist/json.js
|
|
30520
|
-
const
|
|
30521
|
-
|
|
30522
|
-
if (value === null || Predicate.isString(value) || Predicate.isNumber(value) || Predicate.isBoolean(value)) return
|
|
30523
|
-
|
|
30524
|
-
const field = Schema.decodeUnknownSync(Schema.Json)(value[key]);
|
|
30525
|
-
return `${encodeScalar$1(key)}:${encodeCanonicalJson(field)}`;
|
|
30526
|
-
}).join(",")}}`;
|
|
30527
|
-
return `[${Schema.decodeUnknownSync(Schema.Array(Schema.Json))(value).map(encodeCanonicalJson).join(",")}]`;
|
|
30582
|
+
const encodeJson = (value) => {
|
|
30583
|
+
if (Array$1.isArray(value)) return `[${value.map(encodeJson).join(",")}]`;
|
|
30584
|
+
if (value === null || Predicate.isString(value) || Predicate.isNumber(value) || Predicate.isBoolean(value)) return JSON.stringify(value);
|
|
30585
|
+
return `{${Array$1.sortWith(Object.entries(value), ([key]) => key, String$1.Order).map(([key, field]) => `${JSON.stringify(key)}:${encodeJson(field)}`).join(",")}}`;
|
|
30528
30586
|
};
|
|
30587
|
+
const encodeCanonicalJson = (value) => encodeJson(Schema.decodeSync(Schema.Json)(value));
|
|
30529
30588
|
//#endregion
|
|
30530
30589
|
//#region ../contracts/dist/runtime/result.js
|
|
30531
30590
|
const ResultTypeId = Symbol.for("ignotum/runtime/result/Result");
|
|
@@ -30644,7 +30703,9 @@ const encodePaginationCursor = (query, position) => PaginationCursor.make(Encodi
|
|
|
30644
30703
|
position
|
|
30645
30704
|
})));
|
|
30646
30705
|
const decodePaginationCursor = (cursor) => Schema.decodeSync(CursorPayloadJson)(Result.getOrThrow(Encoding.decodeBase64UrlString(cursor)));
|
|
30647
|
-
|
|
30706
|
+
//#endregion
|
|
30707
|
+
//#region ../contracts/dist/schema/values.js
|
|
30708
|
+
const LiteralValueSchema = Schema.Union([
|
|
30648
30709
|
Schema.String,
|
|
30649
30710
|
Schema.Finite,
|
|
30650
30711
|
Schema.Boolean
|
|
@@ -30656,14 +30717,220 @@ const schemaIndexLimits = {
|
|
|
30656
30717
|
keyBytes: 4096,
|
|
30657
30718
|
nameBytes: 64
|
|
30658
30719
|
};
|
|
30720
|
+
const fresh = (value) => value.pipe(Schema.annotate({}));
|
|
30721
|
+
const string = () => attachValueDescriptor(fresh(Schema.String), { type: "string" });
|
|
30722
|
+
const number = () => attachValueDescriptor(fresh(Schema.Finite), { type: "number" });
|
|
30723
|
+
const integer = () => attachValueDescriptor(fresh(Schema.Int), { type: "integer" });
|
|
30724
|
+
const boolean = () => attachValueDescriptor(fresh(Schema.Boolean), { type: "boolean" });
|
|
30725
|
+
const date = () => attachValueDescriptor(fresh(Schema.DateFromMillis), { type: "date" });
|
|
30726
|
+
const never = () => attachValueDescriptor(fresh(Schema.Never), { type: "never" });
|
|
30727
|
+
const nullValue = () => attachValueDescriptor(fresh(Schema.Null), { type: "null" });
|
|
30728
|
+
const literal = (value) => {
|
|
30729
|
+
Schema.decodeSync(LiteralValueSchema)(value);
|
|
30730
|
+
return attachValueDescriptor(Schema.Literal(value), {
|
|
30731
|
+
type: "literal",
|
|
30732
|
+
value
|
|
30733
|
+
});
|
|
30734
|
+
};
|
|
30735
|
+
const literals = (...members) => {
|
|
30736
|
+
Schema.decodeSync(Schema.Array(LiteralValueSchema))(members);
|
|
30737
|
+
return attachValueDescriptor(Schema.Literals(members), {
|
|
30738
|
+
type: "literals",
|
|
30739
|
+
values: members
|
|
30740
|
+
});
|
|
30741
|
+
};
|
|
30742
|
+
const requireDescriptor = (value) => {
|
|
30743
|
+
const descriptor = getValueDescriptor(value);
|
|
30744
|
+
if (descriptor === void 0) throw new Error("Ignotum value combinators require a values validator.");
|
|
30745
|
+
return descriptor;
|
|
30746
|
+
};
|
|
30747
|
+
const array = (value) => {
|
|
30748
|
+
return attachValueDescriptor(Schema.Array(value), {
|
|
30749
|
+
type: "array",
|
|
30750
|
+
value: requireDescriptor(value)
|
|
30751
|
+
});
|
|
30752
|
+
};
|
|
30753
|
+
const assertFieldsExist = (fields, keys, operation) => {
|
|
30754
|
+
for (const key of keys) if (!Object.hasOwn(fields, key)) throw new Error(`Cannot ${operation} unknown field '${String(key)}'.`);
|
|
30755
|
+
};
|
|
30756
|
+
const fixedObject = (fields) => {
|
|
30757
|
+
const pick = (...keys) => {
|
|
30758
|
+
assertFieldsExist(fields, keys, "pick");
|
|
30759
|
+
return fixedObject(Struct.pick(fields, keys));
|
|
30760
|
+
};
|
|
30761
|
+
const omit = (...keys) => {
|
|
30762
|
+
assertFieldsExist(fields, keys, "omit");
|
|
30763
|
+
return fixedObject(Struct.omit(fields, keys));
|
|
30764
|
+
};
|
|
30765
|
+
const extend = (addedFields, ..._validation) => {
|
|
30766
|
+
for (const key of Reflect.ownKeys(addedFields)) if (Object.hasOwn(fields, key)) throw new Error(`Cannot extend existing field '${String(key)}'.`);
|
|
30767
|
+
return fixedObject(Struct.assign(fields, addedFields));
|
|
30768
|
+
};
|
|
30769
|
+
const override = (overriddenFields, ..._validation) => {
|
|
30770
|
+
assertFieldsExist(fields, Reflect.ownKeys(overriddenFields), "override");
|
|
30771
|
+
return fixedObject(Struct.assign(fields, overriddenFields));
|
|
30772
|
+
};
|
|
30773
|
+
const partial = () => {
|
|
30774
|
+
const partialFields = Struct.map(fields, Schema.optional);
|
|
30775
|
+
for (const name of Reflect.ownKeys(fields)) {
|
|
30776
|
+
const field = fields[name];
|
|
30777
|
+
const partialField = partialFields[name];
|
|
30778
|
+
if (field === void 0 || partialField === void 0) continue;
|
|
30779
|
+
attachValueDescriptor(partialField, {
|
|
30780
|
+
type: "optional",
|
|
30781
|
+
value: requireDescriptor(field)
|
|
30782
|
+
});
|
|
30783
|
+
}
|
|
30784
|
+
return fixedObject(partialFields);
|
|
30785
|
+
};
|
|
30786
|
+
const schema = attachValueDescriptor(Schema.Struct(fields), {
|
|
30787
|
+
type: "object",
|
|
30788
|
+
fields: descriptorFields(fields)
|
|
30789
|
+
});
|
|
30790
|
+
return Object.assign(schema, {
|
|
30791
|
+
extend,
|
|
30792
|
+
omit,
|
|
30793
|
+
override,
|
|
30794
|
+
partial,
|
|
30795
|
+
pick
|
|
30796
|
+
});
|
|
30797
|
+
};
|
|
30798
|
+
const object = (fields) => fixedObject(fields);
|
|
30799
|
+
const optional = (value) => {
|
|
30800
|
+
return attachValueDescriptor(Schema.optional(value), {
|
|
30801
|
+
type: "optional",
|
|
30802
|
+
value: requireDescriptor(value)
|
|
30803
|
+
});
|
|
30804
|
+
};
|
|
30805
|
+
const nullable = (value) => {
|
|
30806
|
+
return attachValueDescriptor(Schema.NullOr(value), {
|
|
30807
|
+
type: "nullable",
|
|
30808
|
+
value: requireDescriptor(value)
|
|
30809
|
+
});
|
|
30810
|
+
};
|
|
30811
|
+
const record = (value) => {
|
|
30812
|
+
return attachValueDescriptor(Schema.Record(Schema.String, value), {
|
|
30813
|
+
type: "record",
|
|
30814
|
+
value: requireDescriptor(value)
|
|
30815
|
+
});
|
|
30816
|
+
};
|
|
30817
|
+
const union = (...members) => {
|
|
30818
|
+
return attachValueDescriptor(Schema.Union(members), {
|
|
30819
|
+
type: "union",
|
|
30820
|
+
members: members.map(requireDescriptor)
|
|
30821
|
+
});
|
|
30822
|
+
};
|
|
30823
|
+
Redacted.isRedacted, Redacted.value;
|
|
30824
|
+
function secret(value = string()) {
|
|
30825
|
+
return attachValueDescriptor(Schema.RedactedFromValue(value, { disallowEncode: true }), {
|
|
30826
|
+
type: "secret",
|
|
30827
|
+
value: requireDescriptor(value)
|
|
30828
|
+
});
|
|
30829
|
+
}
|
|
30830
|
+
const environmentValues = {
|
|
30831
|
+
array,
|
|
30832
|
+
boolean,
|
|
30833
|
+
date,
|
|
30834
|
+
integer,
|
|
30835
|
+
literal,
|
|
30836
|
+
literals,
|
|
30837
|
+
never,
|
|
30838
|
+
null: nullValue,
|
|
30839
|
+
nullable,
|
|
30840
|
+
number,
|
|
30841
|
+
object,
|
|
30842
|
+
optional,
|
|
30843
|
+
record,
|
|
30844
|
+
secret,
|
|
30845
|
+
string,
|
|
30846
|
+
union
|
|
30847
|
+
};
|
|
30659
30848
|
//#endregion
|
|
30660
30849
|
//#region ../contracts/dist/runtime/schema.js
|
|
30661
30850
|
const FunctionSchemaTypeId = Symbol.for("ignotum/runtime/schema/FunctionSchema");
|
|
30851
|
+
const FunctionEnvironmentTypeId = Symbol.for("ignotum/runtime/schema/FunctionEnvironment");
|
|
30662
30852
|
const getFunctionSchema = (value) => value[FunctionSchemaTypeId];
|
|
30853
|
+
const getFunctionEnvironment = (value) => value[FunctionEnvironmentTypeId];
|
|
30663
30854
|
//#endregion
|
|
30664
30855
|
//#region ../contracts/dist/schema/index.js
|
|
30665
30856
|
const SchemaDefinitionTypeId = Symbol.for("ignotum/schema/SchemaDefinition");
|
|
30666
30857
|
const isDefinedSchema = (value) => value._tag === "Schema" && Predicate.isObject(value[SchemaDefinitionTypeId]);
|
|
30858
|
+
const EnvironmentDefinitionTypeId = Symbol.for("ignotum/schema/EnvironmentDefinition");
|
|
30859
|
+
const EnvironmentDecoderTypeId = Symbol.for("ignotum/schema/EnvironmentDecoder");
|
|
30860
|
+
const environmentLimits = {
|
|
30861
|
+
bytes: 65536,
|
|
30862
|
+
variables: 128
|
|
30863
|
+
};
|
|
30864
|
+
var EnvironmentValueInvalid = class extends Schema.TaggedError()("EnvironmentValueInvalid", {
|
|
30865
|
+
key: Schema.optional(Schema.String),
|
|
30866
|
+
message: Schema.String
|
|
30867
|
+
}) {};
|
|
30868
|
+
const environmentName = /^[A-Z_][A-Z0-9_]*$/;
|
|
30869
|
+
const defineEnv = (define) => {
|
|
30870
|
+
const fields = define({ values: environmentValues });
|
|
30871
|
+
if (Reflect.ownKeys(fields).length > environmentLimits.variables) throw new Error(`Environments cannot declare more than ${environmentLimits.variables} variables.`);
|
|
30872
|
+
for (const name of Reflect.ownKeys(fields)) {
|
|
30873
|
+
if (!Predicate.isString(name)) throw new Error("Environment variable names must be strings.");
|
|
30874
|
+
if (!environmentName.test(name)) throw new Error(`Environment variable '${name}' must match ${environmentName.source}.`);
|
|
30875
|
+
if (name.startsWith("IGNOTUM_")) throw new Error(`Environment variable '${name}' uses the reserved IGNOTUM_ prefix.`);
|
|
30876
|
+
}
|
|
30877
|
+
const descriptors = descriptorFields(fields);
|
|
30878
|
+
for (const field of descriptors) if (!descriptorAllowedInEnvironment(field.value)) throw new Error(`Environment variable '${field.name}' uses an unsupported validator.`);
|
|
30879
|
+
const environment = {
|
|
30880
|
+
_tag: "Environment",
|
|
30881
|
+
fields,
|
|
30882
|
+
descriptors,
|
|
30883
|
+
schema: Schema.Struct(fields),
|
|
30884
|
+
[EnvironmentDefinitionTypeId]: fields
|
|
30885
|
+
};
|
|
30886
|
+
Object.defineProperty(environment, EnvironmentDecoderTypeId, {
|
|
30887
|
+
enumerable: false,
|
|
30888
|
+
value: (raw) => decodeEnvironment(environment, raw)
|
|
30889
|
+
});
|
|
30890
|
+
return Object.freeze(environment);
|
|
30891
|
+
};
|
|
30892
|
+
const emptyEnv = defineEnv(() => ({}));
|
|
30893
|
+
const isDefinedEnv = (value) => value._tag === "Environment" && Predicate.isObject(value[EnvironmentDefinitionTypeId]);
|
|
30894
|
+
const utf8ByteLength = (value) => {
|
|
30895
|
+
let bytes = 0;
|
|
30896
|
+
for (const character of value) {
|
|
30897
|
+
const point = character.codePointAt(0) ?? 0;
|
|
30898
|
+
bytes += point <= 127 ? 1 : point <= 2047 ? 2 : point <= 65535 ? 3 : 4;
|
|
30899
|
+
}
|
|
30900
|
+
return bytes;
|
|
30901
|
+
};
|
|
30902
|
+
const invalidValue = (key) => EnvironmentValueInvalid.make({
|
|
30903
|
+
key,
|
|
30904
|
+
message: `Environment variable '${key}' does not match its declaration.`
|
|
30905
|
+
});
|
|
30906
|
+
const decodeEnvironment = Effect.fn("Environment.decode")(function* (environment, raw) {
|
|
30907
|
+
const declared = new Set(Object.keys(environment.fields));
|
|
30908
|
+
const supplied = Object.keys(raw);
|
|
30909
|
+
if (supplied.length > environmentLimits.variables) return yield* EnvironmentValueInvalid.make({ message: `Environments cannot contain more than ${environmentLimits.variables} variables.` });
|
|
30910
|
+
const rawJson = JSON.stringify(raw);
|
|
30911
|
+
if (utf8ByteLength(rawJson) > environmentLimits.bytes) return yield* EnvironmentValueInvalid.make({ message: `Environments cannot exceed ${environmentLimits.bytes} UTF-8 bytes.` });
|
|
30912
|
+
for (const key of supplied) if (!declared.has(key)) return yield* EnvironmentValueInvalid.make({
|
|
30913
|
+
key,
|
|
30914
|
+
message: `Environment variable '${key}' is not declared in server/env.ts.`
|
|
30915
|
+
});
|
|
30916
|
+
const decoded = {};
|
|
30917
|
+
for (const [key, field] of Object.entries(environment.fields)) {
|
|
30918
|
+
const input = Object.hasOwn(raw, key) ? raw[key] : void 0;
|
|
30919
|
+
const direct = yield* Effect.result(Schema.decodeUnknownEffect(field)(input));
|
|
30920
|
+
if (Result.isSuccess(direct)) {
|
|
30921
|
+
if (direct.success !== void 0) Reflect.set(decoded, key, direct.success);
|
|
30922
|
+
continue;
|
|
30923
|
+
}
|
|
30924
|
+
if (input === void 0) return yield* invalidValue(key);
|
|
30925
|
+
const parsed = yield* Effect.result(Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))(input));
|
|
30926
|
+
if (Result.isFailure(parsed)) return yield* invalidValue(key);
|
|
30927
|
+
const fromJson = yield* Effect.result(Schema.decodeUnknownEffect(field)(parsed.success));
|
|
30928
|
+
if (Result.isFailure(fromJson)) return yield* invalidValue(key);
|
|
30929
|
+
if (fromJson.success !== void 0) Reflect.set(decoded, key, fromJson.success);
|
|
30930
|
+
}
|
|
30931
|
+
return Object.freeze(decoded);
|
|
30932
|
+
});
|
|
30933
|
+
const decodeDefinedEnvironment = (environment, raw) => environment[EnvironmentDecoderTypeId](raw);
|
|
30667
30934
|
//#endregion
|
|
30668
30935
|
//#region ../deployment/dist/index.js
|
|
30669
30936
|
const encodeCanonical = Function$1.dual(2, (schema, value) => {
|
|
@@ -30728,6 +30995,7 @@ const artifactKind = (path) => {
|
|
|
30728
30995
|
if (path.startsWith(clientRoutePathPrefix) && isClientPublicFile(path)) return "ClientPublicFile";
|
|
30729
30996
|
if (path === serverManifestPath) return "ServerManifest";
|
|
30730
30997
|
if (path === schemaSnapshotPath) return "SchemaSnapshot";
|
|
30998
|
+
if (path === serverEnvironmentPath) return "ServerEnvironment";
|
|
30731
30999
|
if (path.startsWith("server/functions/") && path.endsWith(".mjs.map")) return "SourceMap";
|
|
30732
31000
|
if (path.startsWith("server/functions/") && path.endsWith(".mjs")) return "FunctionBundle";
|
|
30733
31001
|
};
|
|
@@ -30745,7 +31013,7 @@ const makeArtifactFile = Effect.fn("Deployment.makeArtifactFile")(function* (inp
|
|
|
30745
31013
|
const makeInventoryFromNormalized = Effect.fn("Deployment.makeInventoryFromNormalized")(function* (files) {
|
|
30746
31014
|
const inventoryFiles = yield* Effect.forEach(files, makeArtifactFile, { concurrency: "unbounded" });
|
|
30747
31015
|
return {
|
|
30748
|
-
formatVersion:
|
|
31016
|
+
formatVersion: 2,
|
|
30749
31017
|
files: Array$1.sortWith(inventoryFiles, (file) => file.path, String$1.Order)
|
|
30750
31018
|
};
|
|
30751
31019
|
});
|
|
@@ -30827,6 +31095,11 @@ const validateManifest = Effect.fn("Deployment.validateManifest")(function* (man
|
|
|
30827
31095
|
const addresses = /* @__PURE__ */ new Set();
|
|
30828
31096
|
const referencedPaths = /* @__PURE__ */ new Set([serverManifestPath, schemaSnapshotPath]);
|
|
30829
31097
|
const references = [manifest.schema];
|
|
31098
|
+
if (manifest.environment !== void 0) {
|
|
31099
|
+
if (manifest.environment.artifact.path !== serverEnvironmentPath) return yield* invalid$3(`The server manifest must reference '${serverEnvironmentPath}' as its environment.`, manifest.environment.artifact.path);
|
|
31100
|
+
references.push(manifest.environment.artifact);
|
|
31101
|
+
referencedPaths.add(serverEnvironmentPath);
|
|
31102
|
+
}
|
|
30830
31103
|
for (const definition of manifest.functions) {
|
|
30831
31104
|
if (addresses.has(definition.address)) return yield* invalid$3(`Duplicate function address '${definition.address}'.`);
|
|
30832
31105
|
addresses.add(definition.address);
|
|
@@ -30844,6 +31117,26 @@ const validateManifest = Effect.fn("Deployment.validateManifest")(function* (man
|
|
|
30844
31117
|
}
|
|
30845
31118
|
for (const file of inventory.files) if (file.path.startsWith("server/") && !referencedPaths.has(file.path)) return yield* invalid$3(`Unexpected server entrypoint '${file.path}'.`, file.path);
|
|
30846
31119
|
});
|
|
31120
|
+
const validateArtifactFile = Effect.fn("Deployment.validateArtifactFile")(function* (file, entry) {
|
|
31121
|
+
const actual = yield* makeArtifactFile(file);
|
|
31122
|
+
if (entry === void 0 || encodeCanonical(ArtifactFile, actual) !== encodeCanonical(ArtifactFile, entry)) return yield* invalid$3(`Artifact file '${file.path}' does not match the deployment inventory.`, file.path);
|
|
31123
|
+
});
|
|
31124
|
+
const decodeServerMetadata = Effect.fn("Deployment.decodeServerMetadata")(function* (files, inventory) {
|
|
31125
|
+
const manifest = yield* decodeJsonFile(ServerBuildManifest, yield* findFile(files, serverManifestPath));
|
|
31126
|
+
const schema = yield* decodeJsonFile(SchemaSnapshot, yield* findFile(files, schemaSnapshotPath));
|
|
31127
|
+
yield* validateManifest(manifest, inventory);
|
|
31128
|
+
let environment;
|
|
31129
|
+
if (manifest.environment !== void 0) {
|
|
31130
|
+
environment = yield* decodeJsonFile(EnvironmentArtifact, yield* findFile(files, serverEnvironmentPath));
|
|
31131
|
+
if (Object.keys(environment.values).length > environmentLimits.variables) return yield* invalid$3("The server environment contains too many variables.", serverEnvironmentPath);
|
|
31132
|
+
if (utf8Bytes(encodeCanonicalJson(environment.values)).byteLength > environmentLimits.bytes) return yield* invalid$3("The server environment exceeds its size limit.", serverEnvironmentPath);
|
|
31133
|
+
}
|
|
31134
|
+
return {
|
|
31135
|
+
manifest,
|
|
31136
|
+
schema,
|
|
31137
|
+
environment
|
|
31138
|
+
};
|
|
31139
|
+
});
|
|
30847
31140
|
Effect.fn("Deployment.validateDeploymentMetadata")(function* (input) {
|
|
30848
31141
|
yield* validateDeploymentInventory(input.inventory);
|
|
30849
31142
|
const inventoryFile = {
|
|
@@ -30870,22 +31163,21 @@ Effect.fn("Deployment.validateDeploymentMetadata")(function* (input) {
|
|
|
30870
31163
|
manifestFile,
|
|
30871
31164
|
schemaFile
|
|
30872
31165
|
];
|
|
31166
|
+
if (input.environmentBytes !== void 0) metadataFiles.push({
|
|
31167
|
+
path: serverEnvironmentPath,
|
|
31168
|
+
bytes: input.environmentBytes
|
|
31169
|
+
});
|
|
30873
31170
|
const entries = new Map(input.inventory.files.map((file) => [file.path, file]));
|
|
30874
|
-
for (const file of metadataFiles)
|
|
30875
|
-
const entry = entries.get(file.path);
|
|
30876
|
-
const actual = yield* makeArtifactFile(file);
|
|
30877
|
-
if (entry === void 0 || encodeCanonical(ArtifactFile, actual) !== encodeCanonical(ArtifactFile, entry)) return yield* invalid$3(`Artifact file '${file.path}' does not match the deployment inventory.`, file.path);
|
|
30878
|
-
}
|
|
31171
|
+
for (const file of metadataFiles) yield* validateArtifactFile(file, entries.get(file.path));
|
|
30879
31172
|
const clientManifest = yield* decodeJsonFile(ClientManifest, clientManifestFile);
|
|
30880
|
-
const manifest = yield*
|
|
30881
|
-
const schema = yield* decodeJsonFile(SchemaSnapshot, schemaFile);
|
|
31173
|
+
const { manifest, schema, environment } = yield* decodeServerMetadata(metadataFiles, input.inventory);
|
|
30882
31174
|
yield* validateClientManifest(clientManifest, input.inventory);
|
|
30883
|
-
yield* validateManifest(manifest, input.inventory);
|
|
30884
31175
|
return {
|
|
30885
31176
|
clientManifest,
|
|
30886
31177
|
inventory: input.inventory,
|
|
30887
31178
|
manifest,
|
|
30888
|
-
schema
|
|
31179
|
+
schema,
|
|
31180
|
+
environment
|
|
30889
31181
|
};
|
|
30890
31182
|
});
|
|
30891
31183
|
const validateDeploymentArtifact = Effect.fn("Deployment.validateDeploymentArtifact")(function* (inputs) {
|
|
@@ -30897,21 +31189,18 @@ const validateDeploymentArtifact = Effect.fn("Deployment.validateDeploymentArtif
|
|
|
30897
31189
|
const actualInventory = yield* makeInventoryFromNormalized(payloadFiles);
|
|
30898
31190
|
if (encodeCanonical(DeploymentInventory, inventory) !== encodeCanonical(DeploymentInventory, actualInventory)) return yield* invalid$3("The deployment inventory does not match the artifact files.");
|
|
30899
31191
|
const clientManifestFile = yield* findFile(files, clientManifestPath);
|
|
30900
|
-
const manifestFile = yield* findFile(files, serverManifestPath);
|
|
30901
|
-
const schemaFile = yield* findFile(files, schemaSnapshotPath);
|
|
30902
31192
|
const clientManifest = yield* decodeJsonFile(ClientManifest, clientManifestFile);
|
|
30903
|
-
const manifest = yield*
|
|
30904
|
-
const schema = yield* decodeJsonFile(SchemaSnapshot, schemaFile);
|
|
31193
|
+
const { manifest, schema, environment } = yield* decodeServerMetadata(files, inventory);
|
|
30905
31194
|
yield* validateClientManifest(clientManifest, inventory);
|
|
30906
|
-
yield* validateManifest(manifest, inventory);
|
|
30907
31195
|
return {
|
|
30908
31196
|
clientManifest,
|
|
31197
|
+
environment,
|
|
30909
31198
|
inventory,
|
|
30910
31199
|
manifest,
|
|
30911
31200
|
schema
|
|
30912
31201
|
};
|
|
30913
31202
|
});
|
|
30914
|
-
Effect.fn("Deployment.loadRuntimeDeploymentArtifact")(function* (read) {
|
|
31203
|
+
Effect.fn("Deployment.loadRuntimeDeploymentArtifact")(function* (read, readEnvironment = read) {
|
|
30915
31204
|
const inventoryBytes = yield* read(deploymentInventoryPath, deploymentArtifactLimits.inventoryBytes);
|
|
30916
31205
|
const inventory = yield* decodeJsonFile(DeploymentInventory, {
|
|
30917
31206
|
path: deploymentInventoryPath,
|
|
@@ -30920,23 +31209,20 @@ Effect.fn("Deployment.loadRuntimeDeploymentArtifact")(function* (read) {
|
|
|
30920
31209
|
yield* validateDeploymentInventory(inventory);
|
|
30921
31210
|
const serverEntries = inventory.files.filter((file) => file.path.startsWith("server/"));
|
|
30922
31211
|
const serverFiles = yield* Effect.forEach(serverEntries, (entry) => Effect.gen(function* () {
|
|
31212
|
+
const reader = entry.kind === "ServerEnvironment" ? readEnvironment : read;
|
|
30923
31213
|
const input = {
|
|
30924
31214
|
path: entry.path,
|
|
30925
|
-
bytes: yield*
|
|
31215
|
+
bytes: yield* reader(entry.path, entry.size)
|
|
30926
31216
|
};
|
|
30927
|
-
|
|
30928
|
-
if (encodeCanonical(ArtifactFile, actual) !== encodeCanonical(ArtifactFile, entry)) return yield* invalid$3(`Artifact file '${entry.path}' does not match the deployment inventory.`, entry.path);
|
|
31217
|
+
yield* validateArtifactFile(input, entry);
|
|
30929
31218
|
return input;
|
|
30930
31219
|
}), { concurrency: 4 });
|
|
30931
|
-
const
|
|
30932
|
-
const schemaFile = yield* findFile(serverFiles, schemaSnapshotPath);
|
|
30933
|
-
const manifest = yield* decodeJsonFile(ServerBuildManifest, manifestFile);
|
|
30934
|
-
const schema = yield* decodeJsonFile(SchemaSnapshot, schemaFile);
|
|
30935
|
-
yield* validateManifest(manifest, inventory);
|
|
31220
|
+
const { manifest, schema, environment } = yield* decodeServerMetadata(serverFiles, inventory);
|
|
30936
31221
|
return {
|
|
30937
31222
|
inventory,
|
|
30938
31223
|
manifest,
|
|
30939
31224
|
schema,
|
|
31225
|
+
environment,
|
|
30940
31226
|
files: new Map(serverFiles.map((file) => [file.path, file.bytes]))
|
|
30941
31227
|
};
|
|
30942
31228
|
});
|
|
@@ -31657,6 +31943,78 @@ const inspectRuntimeFunctionDefinition = (value) => {
|
|
|
31657
31943
|
};
|
|
31658
31944
|
};
|
|
31659
31945
|
//#endregion
|
|
31946
|
+
//#region src/cli/environment.ts
|
|
31947
|
+
var EnvironmentConfigurationInvalid = class extends Schema.TaggedError()("EnvironmentConfigurationInvalid", {
|
|
31948
|
+
message: Schema.String,
|
|
31949
|
+
path: Schema.String
|
|
31950
|
+
}) {};
|
|
31951
|
+
const collectKeys = Effect.fn("Environment.collectKeys")(function* (provider, providerPath = [], nameParts = []) {
|
|
31952
|
+
const node = yield* provider.load(providerPath);
|
|
31953
|
+
if (node === void 0) return [];
|
|
31954
|
+
const own = node._tag === "Value" || node.value !== void 0 ? [nameParts.join("_")] : [];
|
|
31955
|
+
if (node._tag === "Value") return own;
|
|
31956
|
+
if (node._tag === "Array") {
|
|
31957
|
+
const children = yield* Effect.forEach(Array$1.range(0, node.length - 1), (index) => collectKeys(provider, [...providerPath, index], [...nameParts, index.toString()]));
|
|
31958
|
+
return [...own, ...Array$1.flatten(children)];
|
|
31959
|
+
}
|
|
31960
|
+
const children = yield* Effect.forEach(Array$1.fromIterable(node.keys), (key) => collectKeys(provider, [...providerPath, key], [...nameParts, key]));
|
|
31961
|
+
return [...own, ...Array$1.flatten(children)];
|
|
31962
|
+
});
|
|
31963
|
+
const nodeValue = (node) => node?._tag === "Value" ? node.value : node?.value;
|
|
31964
|
+
const readEnvironmentRaw = Effect.fn("Environment.readRaw")(function* (appDirectory, hasDefinition) {
|
|
31965
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
31966
|
+
const environmentPath = (yield* Path$1.Path).join(appDirectory, ".env.ignotum");
|
|
31967
|
+
const exists = yield* fileSystem.exists(environmentPath);
|
|
31968
|
+
if (!hasDefinition && exists) return yield* EnvironmentConfigurationInvalid.make({
|
|
31969
|
+
message: ".env.ignotum exists but server/env.ts does not define its variables.",
|
|
31970
|
+
path: environmentPath
|
|
31971
|
+
});
|
|
31972
|
+
if (!hasDefinition) return {
|
|
31973
|
+
path: environmentPath,
|
|
31974
|
+
raw: {}
|
|
31975
|
+
};
|
|
31976
|
+
if (!exists) return yield* EnvironmentConfigurationInvalid.make({
|
|
31977
|
+
message: "server/env.ts requires a project-root .env.ignotum file.",
|
|
31978
|
+
path: environmentPath
|
|
31979
|
+
});
|
|
31980
|
+
const provider = yield* ConfigProvider.fromDotEnv({
|
|
31981
|
+
path: environmentPath,
|
|
31982
|
+
preserveEmptyStrings: true
|
|
31983
|
+
}).pipe(Effect.mapError(() => EnvironmentConfigurationInvalid.make({
|
|
31984
|
+
message: "Ignotum could not read the project environment file.",
|
|
31985
|
+
path: environmentPath
|
|
31986
|
+
})));
|
|
31987
|
+
const keys = yield* collectKeys(provider).pipe(Effect.mapError(() => EnvironmentConfigurationInvalid.make({
|
|
31988
|
+
message: "Ignotum could not inspect the project environment file.",
|
|
31989
|
+
path: environmentPath
|
|
31990
|
+
})));
|
|
31991
|
+
const entries = yield* Effect.forEach(keys, (key) => provider.load([key]).pipe(Effect.map((node) => [key, nodeValue(node)]), Effect.mapError(() => EnvironmentConfigurationInvalid.make({
|
|
31992
|
+
message: `Ignotum could not read environment variable '${key}'.`,
|
|
31993
|
+
path: environmentPath
|
|
31994
|
+
}))));
|
|
31995
|
+
return {
|
|
31996
|
+
path: environmentPath,
|
|
31997
|
+
raw: Object.fromEntries(entries.flatMap(([key, value]) => value === void 0 ? [] : [[key, value]]))
|
|
31998
|
+
};
|
|
31999
|
+
});
|
|
32000
|
+
const loadEnvironment = Effect.fn("Environment.load")(function* (appDirectory, definition, hasDefinition) {
|
|
32001
|
+
const loaded = yield* readEnvironmentRaw(appDirectory, hasDefinition);
|
|
32002
|
+
if (!hasDefinition) return {
|
|
32003
|
+
definition: emptyEnv,
|
|
32004
|
+
raw: {},
|
|
32005
|
+
value: Object.freeze({})
|
|
32006
|
+
};
|
|
32007
|
+
const raw = loaded.raw;
|
|
32008
|
+
return {
|
|
32009
|
+
definition,
|
|
32010
|
+
raw,
|
|
32011
|
+
value: yield* decodeDefinedEnvironment(definition, raw).pipe(Effect.mapError((error) => EnvironmentConfigurationInvalid.make({
|
|
32012
|
+
message: error.message,
|
|
32013
|
+
path: loaded.path
|
|
32014
|
+
})))
|
|
32015
|
+
};
|
|
32016
|
+
});
|
|
32017
|
+
//#endregion
|
|
31660
32018
|
//#region src/cli/build/server.ts
|
|
31661
32019
|
const hostModuleId = "ignotum:host";
|
|
31662
32020
|
const encodeJavaScriptString = Schema.encodeSync(Schema.fromJsonString(Schema.String));
|
|
@@ -31671,6 +32029,11 @@ const DiscoveredServerFunction = Schema.Struct({
|
|
|
31671
32029
|
returns: Schema.optional(ValueDescriptor)
|
|
31672
32030
|
});
|
|
31673
32031
|
const ServerDiscoveryManifest = Schema.Struct({
|
|
32032
|
+
environment: Schema.Struct({
|
|
32033
|
+
fields: Schema.Array(SchemaSnapshotField),
|
|
32034
|
+
hasDefinition: Schema.Boolean,
|
|
32035
|
+
values: Schema.Record(Schema.String, Schema.String)
|
|
32036
|
+
}),
|
|
31674
32037
|
functions: Schema.Array(DiscoveredServerFunction),
|
|
31675
32038
|
schema: SchemaSnapshot
|
|
31676
32039
|
});
|
|
@@ -31803,7 +32166,9 @@ const discoverModuleFunctions = Effect.fn("Deploy.discoverServerModule")(functio
|
|
|
31803
32166
|
const discoverServerFunctionsInProcess = Effect.fn("Deploy.discoverServerFunctions")(function* (appDirectory, moduleNames, conditions) {
|
|
31804
32167
|
return yield* Effect.scoped(Effect.gen(function* () {
|
|
31805
32168
|
const server = yield* acquireModuleRunner(appDirectory, conditions);
|
|
31806
|
-
const
|
|
32169
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
32170
|
+
const path = yield* Path$1.Path;
|
|
32171
|
+
const schemaPath = path.join(appDirectory, "server", "schema.ts");
|
|
31807
32172
|
const loadedSchemaModule = yield* Effect.tryPromise({
|
|
31808
32173
|
try: () => server.ssrLoadModule(normalizePath(schemaPath)),
|
|
31809
32174
|
catch: (cause) => Schema.is(BuildBoundaryViolation)(cause) ? cause : ServerFunctionDiscoveryFailed.make({
|
|
@@ -31814,15 +32179,37 @@ const discoverServerFunctionsInProcess = Effect.fn("Deploy.discoverServerFunctio
|
|
|
31814
32179
|
});
|
|
31815
32180
|
const schemaCandidate = yield* Schema.decodeUnknownEffect(Schema.ObjectKeyword)(loadedSchemaModule.default).pipe(Effect.mapError(() => InvalidBuildOutput.make({ message: "The default export from server/schema.ts is not an Ignotum schema." })));
|
|
31816
32181
|
if (!isDefinedSchema(schemaCandidate)) return yield* InvalidBuildOutput.make({ message: "The default export from server/schema.ts is not an Ignotum schema." });
|
|
32182
|
+
const environmentPath = path.join(appDirectory, "server", "env.ts");
|
|
32183
|
+
const environment = (yield* fileSystem.exists(environmentPath)) ? yield* Effect.gen(function* () {
|
|
32184
|
+
const loadedEnvironmentModule = yield* Effect.tryPromise({
|
|
32185
|
+
try: () => server.ssrLoadModule(normalizePath(environmentPath)),
|
|
32186
|
+
catch: (cause) => ServerFunctionDiscoveryFailed.make({
|
|
32187
|
+
cause,
|
|
32188
|
+
message: "Could not load the application environment declaration.",
|
|
32189
|
+
path: environmentPath
|
|
32190
|
+
})
|
|
32191
|
+
});
|
|
32192
|
+
if (!Predicate.isObject(loadedEnvironmentModule.default) || !isDefinedEnv(loadedEnvironmentModule.default)) return yield* InvalidBuildOutput.make({ message: "The default export from server/env.ts is not an Ignotum environment." });
|
|
32193
|
+
const loaded = yield* loadEnvironment(appDirectory, loadedEnvironmentModule.default, true);
|
|
32194
|
+
return {
|
|
32195
|
+
fields: loadedEnvironmentModule.default.descriptors,
|
|
32196
|
+
hasDefinition: true,
|
|
32197
|
+
values: loaded.raw
|
|
32198
|
+
};
|
|
32199
|
+
}) : yield* loadEnvironment(appDirectory, emptyEnv, false).pipe(Effect.map((loaded) => ({
|
|
32200
|
+
fields: [],
|
|
32201
|
+
hasDefinition: false,
|
|
32202
|
+
values: loaded.raw
|
|
32203
|
+
})));
|
|
31817
32204
|
const modules = yield* Effect.forEach(moduleNames, (moduleName) => discoverModuleFunctions(server, appDirectory, moduleName));
|
|
31818
32205
|
return {
|
|
31819
32206
|
functions: Array$1.flatten(modules),
|
|
32207
|
+
environment,
|
|
31820
32208
|
schema: schemaCandidate
|
|
31821
32209
|
};
|
|
31822
32210
|
}));
|
|
31823
32211
|
});
|
|
31824
|
-
const runServerDiscoveryWorker = Effect.fn("Deploy.runServerDiscoveryWorker")(function* (appDirectory, moduleNamesJson
|
|
31825
|
-
const fileSystem = yield* FileSystem.FileSystem;
|
|
32212
|
+
const runServerDiscoveryWorker = Effect.fn("Deploy.runServerDiscoveryWorker")(function* (appDirectory, moduleNamesJson) {
|
|
31826
32213
|
const moduleNames = yield* Schema.decodeEffect(ServerModuleNamesJson)(moduleNamesJson).pipe(Effect.orDie);
|
|
31827
32214
|
const conditions = yield* serverConditions();
|
|
31828
32215
|
const discovered = yield* discoverServerFunctionsInProcess(appDirectory, moduleNames, conditions);
|
|
@@ -31830,11 +32217,11 @@ const runServerDiscoveryWorker = Effect.fn("Deploy.runServerDiscoveryWorker")(fu
|
|
|
31830
32217
|
try: () => makeSchemaSnapshot(discovered.schema),
|
|
31831
32218
|
catch: () => InvalidBuildOutput.make({ message: "The application schema does not have canonical Ignotum descriptors." })
|
|
31832
32219
|
});
|
|
31833
|
-
|
|
32220
|
+
return yield* Schema.encodeEffect(ServerDiscoveryManifestJson)({
|
|
32221
|
+
environment: discovered.environment,
|
|
31834
32222
|
functions: discovered.functions,
|
|
31835
32223
|
schema
|
|
31836
32224
|
});
|
|
31837
|
-
yield* fileSystem.writeFileString(outputPath, encoded);
|
|
31838
32225
|
});
|
|
31839
32226
|
const discoveryWorkerEntry = Effect.fn("Deploy.discoveryWorkerEntry")(function* () {
|
|
31840
32227
|
const fileSystem = yield* FileSystem.FileSystem;
|
|
@@ -31858,7 +32245,6 @@ const discoverServerFunctions = Effect.fn("Deploy.discoverServerFunctionsIsolate
|
|
|
31858
32245
|
force: true,
|
|
31859
32246
|
recursive: true
|
|
31860
32247
|
}).pipe(Effect.orDie));
|
|
31861
|
-
const outputPath = path.join(directory, "discovery.json");
|
|
31862
32248
|
const encodedModuleNames = yield* Schema.encodeEffect(ServerModuleNamesJson)(moduleNames);
|
|
31863
32249
|
const packageRoot = path.resolve(path.dirname(workerEntry), "../..");
|
|
31864
32250
|
const workspaceProbes = [];
|
|
@@ -31878,20 +32264,22 @@ const discoverServerFunctions = Effect.fn("Deploy.discoverServerFunctionsIsolate
|
|
|
31878
32264
|
const readPermissions = [
|
|
31879
32265
|
appDirectory,
|
|
31880
32266
|
nodeModulesDirectory(),
|
|
32267
|
+
path.dirname(nodeModulesDirectory()),
|
|
31881
32268
|
packageRoot,
|
|
31882
32269
|
path.join(packageRoot, "node_modules"),
|
|
32270
|
+
path.resolve(packageRoot, "../..", "node_modules"),
|
|
31883
32271
|
...workspaceProbes
|
|
31884
32272
|
].map((allowedPath) => `--allow-fs-read=${allowedPath}`);
|
|
31885
|
-
const
|
|
32273
|
+
const handle = yield* spawner.spawn(ChildProcess.make(process$1.execPath, [
|
|
31886
32274
|
"--permission",
|
|
31887
32275
|
"--allow-addons",
|
|
32276
|
+
"--conditions=@ignotum/source",
|
|
31888
32277
|
...readPermissions,
|
|
31889
32278
|
`--allow-fs-write=${directory}`,
|
|
31890
32279
|
workerEntry,
|
|
31891
32280
|
"__ignotum_discover_server",
|
|
31892
32281
|
appDirectory,
|
|
31893
|
-
encodedModuleNames
|
|
31894
|
-
outputPath
|
|
32282
|
+
encodedModuleNames
|
|
31895
32283
|
], {
|
|
31896
32284
|
cwd: appDirectory,
|
|
31897
32285
|
env: {
|
|
@@ -31900,18 +32288,19 @@ const discoverServerFunctions = Effect.fn("Deploy.discoverServerFunctionsIsolate
|
|
|
31900
32288
|
NODE_ENV: "production"
|
|
31901
32289
|
},
|
|
31902
32290
|
stderr: "inherit",
|
|
31903
|
-
stdout: "ignore"
|
|
32291
|
+
stdout: "ignore",
|
|
32292
|
+
additionalFds: { fd3: { type: "output" } }
|
|
31904
32293
|
}));
|
|
32294
|
+
const [encoded, exitCode] = yield* Effect.all([Stream.mkString(Stream.decodeText(handle.getOutputFd(3))), handle.exitCode], { concurrency: "unbounded" });
|
|
31905
32295
|
if (exitCode !== ChildProcessSpawner$1.ExitCode(0)) return yield* ServerFunctionDiscoveryFailed.make({
|
|
31906
32296
|
cause: /* @__PURE__ */ new Error(`Discovery worker exited with code ${exitCode}.`),
|
|
31907
32297
|
message: "Application metadata discovery failed in the isolated build process.",
|
|
31908
32298
|
path: appDirectory
|
|
31909
32299
|
});
|
|
31910
|
-
const encoded = yield* fileSystem.readFileString(outputPath);
|
|
31911
32300
|
return yield* Schema.decodeEffect(ServerDiscoveryManifestJson)(encoded).pipe(Effect.mapError((cause) => ServerFunctionDiscoveryFailed.make({
|
|
31912
32301
|
cause,
|
|
31913
32302
|
message: "The isolated build process returned invalid application metadata.",
|
|
31914
|
-
path:
|
|
32303
|
+
path: appDirectory
|
|
31915
32304
|
})));
|
|
31916
32305
|
}));
|
|
31917
32306
|
});
|
|
@@ -31922,8 +32311,8 @@ const serverFunctionEntryPlugin = (definition) => {
|
|
|
31922
32311
|
import { invoke } from "ignotum/internal/host"
|
|
31923
32312
|
import { ${definition.exportName} as definition } from ${encodeJavaScriptString(normalizePath(definition.modulePath))}
|
|
31924
32313
|
|
|
31925
|
-
export default async function handler(encodedRequest) {
|
|
31926
|
-
return invoke(definition, JSON.parse(encodedRequest), call)
|
|
32314
|
+
export default async function handler(encodedRequest, encodedEnvironment) {
|
|
32315
|
+
return invoke(definition, JSON.parse(encodedRequest), call, JSON.parse(encodedEnvironment))
|
|
31927
32316
|
}
|
|
31928
32317
|
`;
|
|
31929
32318
|
return {
|
|
@@ -32010,13 +32399,24 @@ const buildServer = Effect.fn("Deploy.buildServer")(function* (appDirectory, out
|
|
|
32010
32399
|
const path = yield* Path$1.Path;
|
|
32011
32400
|
const conditions = yield* serverConditions();
|
|
32012
32401
|
const discovered = yield* discoverServerFunctions(appDirectory, functionModules);
|
|
32402
|
+
const crypto = yield* Crypto.Crypto;
|
|
32403
|
+
const environmentArtifact = {
|
|
32404
|
+
formatVersion: 1,
|
|
32405
|
+
nonce: Encoding.encodeBase64Url(yield* crypto.randomBytes(32)),
|
|
32406
|
+
values: discovered.environment.values
|
|
32407
|
+
};
|
|
32408
|
+
const environmentBytes = utf8Bytes(`${encodeCanonical(EnvironmentArtifact, environmentArtifact)}\n`);
|
|
32013
32409
|
yield* fileSystem.makeDirectory(outputDirectory, { recursive: true });
|
|
32014
32410
|
const encodedSnapshot = encodeSchemaSnapshot(discovered.schema);
|
|
32015
32411
|
const snapshotBytes = utf8Bytes(encodedSnapshot);
|
|
32016
32412
|
yield* fileSystem.writeFile(path.join(outputDirectory, "schema.json"), snapshotBytes);
|
|
32017
32413
|
const functions = yield* Effect.forEach(discovered.functions, (definition) => buildServerFunction(appDirectory, outputDirectory, conditions, definition), { concurrency: 4 });
|
|
32018
32414
|
const manifest = {
|
|
32019
|
-
formatVersion:
|
|
32415
|
+
formatVersion: 3,
|
|
32416
|
+
environment: {
|
|
32417
|
+
artifact: yield* artifactReference(serverEnvironmentPath, environmentBytes),
|
|
32418
|
+
fields: discovered.environment.fields
|
|
32419
|
+
},
|
|
32020
32420
|
schema: yield* artifactReference(schemaSnapshotPath, snapshotBytes),
|
|
32021
32421
|
functions
|
|
32022
32422
|
};
|
|
@@ -32025,6 +32425,10 @@ const buildServer = Effect.fn("Deploy.buildServer")(function* (appDirectory, out
|
|
|
32025
32425
|
yield* fileSystem.writeFileString(manifestPath, encodedManifest);
|
|
32026
32426
|
yield* Schema.decodeEffect(Schema.fromJsonString(ServerBuildManifest))(encodedManifest).pipe(Effect.mapError(() => InvalidBuildOutput.make({ message: "Ignotum emitted an invalid server manifest." })));
|
|
32027
32427
|
return {
|
|
32428
|
+
environment: {
|
|
32429
|
+
bytes: environmentBytes,
|
|
32430
|
+
path: serverEnvironmentPath
|
|
32431
|
+
},
|
|
32028
32432
|
functions: functions.length,
|
|
32029
32433
|
manifestPath
|
|
32030
32434
|
};
|
|
@@ -32090,10 +32494,10 @@ const buildDeploymentArtifact = Effect.fn("Deploy.buildArtifact")(function* (app
|
|
|
32090
32494
|
const serverOutput = path.join(stagingDirectory, "server");
|
|
32091
32495
|
const client = yield* buildClient(appDirectory, clientOutput);
|
|
32092
32496
|
const server = yield* buildServer(appDirectory, serverOutput, codegen.functionModules);
|
|
32093
|
-
const payloadFiles = yield* readArtifactDirectory(stagingDirectory);
|
|
32497
|
+
const payloadFiles = [...yield* readArtifactDirectory(stagingDirectory), server.environment];
|
|
32094
32498
|
const inventory = yield* makeDeploymentInventory(payloadFiles);
|
|
32095
32499
|
yield* fileSystem.writeFile(path.join(stagingDirectory, deploymentInventoryPath), inventory.bytes);
|
|
32096
|
-
const artifactFiles = yield* readArtifactDirectory(stagingDirectory);
|
|
32500
|
+
const artifactFiles = [...yield* readArtifactDirectory(stagingDirectory), server.environment];
|
|
32097
32501
|
yield* validateDeploymentArtifact(artifactFiles);
|
|
32098
32502
|
yield* Effect.logInfo("Built deployment artifact").pipe(Effect.annotateLogs({
|
|
32099
32503
|
files: inventory.inventory.files.length,
|
|
@@ -32104,6 +32508,7 @@ const buildDeploymentArtifact = Effect.fn("Deploy.buildArtifact")(function* (app
|
|
|
32104
32508
|
clientAssets: client.assets,
|
|
32105
32509
|
inventory: inventory.inventory,
|
|
32106
32510
|
inventoryBytes: inventory.bytes,
|
|
32511
|
+
memoryFiles: /* @__PURE__ */ new Map([[server.environment.path, server.environment.bytes]]),
|
|
32107
32512
|
outputDirectory: buildDirectory,
|
|
32108
32513
|
serverFunctions: server.functions
|
|
32109
32514
|
};
|
|
@@ -32112,7 +32517,7 @@ const buildDeploymentArtifact = Effect.fn("Deploy.buildArtifact")(function* (app
|
|
|
32112
32517
|
const uploadDeployment = Effect.fn("Deploy.upload")(function* (configuration, build) {
|
|
32113
32518
|
const control = yield* ControlClient;
|
|
32114
32519
|
const inventorySha256 = yield* sha256(build.inventoryBytes);
|
|
32115
|
-
const files = yield* deploymentUploadFiles(build.inventory, inventorySha256, build.inventoryBytes.byteLength, build.outputDirectory);
|
|
32520
|
+
const files = yield* deploymentUploadFiles(build.inventory, inventorySha256, build.inventoryBytes.byteLength, build.outputDirectory, build.memoryFiles);
|
|
32116
32521
|
const deployment = yield* control.beginDeployment(configuration.appId, build.inventory);
|
|
32117
32522
|
yield* Effect.forEach(files, (file) => control.uploadDeploymentFile(configuration.appId, deployment.id, file), {
|
|
32118
32523
|
concurrency: 4,
|
|
@@ -32817,17 +33222,7 @@ Schema.Struct({
|
|
|
32817
33222
|
});
|
|
32818
33223
|
//#endregion
|
|
32819
33224
|
//#region ../shared/dist/http-body.js
|
|
32820
|
-
const
|
|
32821
|
-
const byteLength = chunks.reduce((total, chunk) => total + chunk.byteLength, 0);
|
|
32822
|
-
const bytes = new Uint8Array(byteLength);
|
|
32823
|
-
let offset = 0;
|
|
32824
|
-
for (const chunk of chunks) {
|
|
32825
|
-
bytes.set(chunk, offset);
|
|
32826
|
-
offset += chunk.byteLength;
|
|
32827
|
-
}
|
|
32828
|
-
return bytes;
|
|
32829
|
-
};
|
|
32830
|
-
const collectStreamBytes = Function$1.dual(3, (stream, maxBytes, onLimitReached) => Stream.mapError(stream, (error) => error).pipe(Stream.limitBytes(maxBytes, () => Stream.fail(onLimitReached())), Stream.runCollect, Effect.map(joinChunks)));
|
|
33225
|
+
const collectStreamBytes = Function$1.dual(3, (stream, maxBytes, onLimitReached) => stream.pipe(Stream.limitBytes(maxBytes, () => Stream.fail(onLimitReached())), Stream.mkUint8Array));
|
|
32831
33226
|
//#endregion
|
|
32832
33227
|
//#region ../runtime/dist/sync.js
|
|
32833
33228
|
const makeDependencyIndex = () => {
|
|
@@ -33592,14 +33987,49 @@ var LocalDatabase = class LocalDatabase extends Context.Service()("ignotum/dev-r
|
|
|
33592
33987
|
}));
|
|
33593
33988
|
};
|
|
33594
33989
|
//#endregion
|
|
33595
|
-
//#region src/dev-runtime/id.ts
|
|
33596
|
-
const idGeneratorLayer = IdGenerator.layer;
|
|
33597
|
-
//#endregion
|
|
33598
33990
|
//#region src/dev-runtime/functions.ts
|
|
33991
|
+
const loadDevelopmentEnvironment = Effect.fn("DevelopmentEnvironment.load")(function* (server, appDirectory) {
|
|
33992
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
33993
|
+
const definitionPath = (yield* Path$1.Path).join(appDirectory, "server", "env.ts");
|
|
33994
|
+
if (!(yield* fileSystem.exists(definitionPath))) return yield* loadEnvironment(appDirectory, emptyEnv, false);
|
|
33995
|
+
const loaded = yield* Effect.tryPromise({
|
|
33996
|
+
try: () => server.ssrLoadModule(normalizePath(definitionPath)),
|
|
33997
|
+
catch: () => EnvironmentConfigurationInvalid.make({
|
|
33998
|
+
message: "Ignotum could not load server/env.ts.",
|
|
33999
|
+
path: definitionPath
|
|
34000
|
+
})
|
|
34001
|
+
});
|
|
34002
|
+
if (!Predicate.isObject(loaded.default) || !isDefinedEnv(loaded.default)) return yield* EnvironmentConfigurationInvalid.make({
|
|
34003
|
+
message: "The default export from server/env.ts is not an Ignotum environment.",
|
|
34004
|
+
path: definitionPath
|
|
34005
|
+
});
|
|
34006
|
+
return yield* loadEnvironment(appDirectory, loaded.default, true);
|
|
34007
|
+
});
|
|
34008
|
+
var DevelopmentEnvironment = class DevelopmentEnvironment extends Context.Service()("ignotum/dev-runtime/functions/DevelopmentEnvironment") {
|
|
34009
|
+
static layer(server, appDirectory) {
|
|
34010
|
+
return Layer.effect(DevelopmentEnvironment, Effect.gen(function* () {
|
|
34011
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
34012
|
+
const path = yield* Path$1.Path;
|
|
34013
|
+
const load = loadDevelopmentEnvironment(server, appDirectory).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.provideService(Path$1.Path, path));
|
|
34014
|
+
const initial = yield* load;
|
|
34015
|
+
const current = yield* Ref.make(Result.succeed(initial));
|
|
34016
|
+
const fromResult = Result.match({
|
|
34017
|
+
onFailure: Effect.fail,
|
|
34018
|
+
onSuccess: Effect.succeed
|
|
34019
|
+
});
|
|
34020
|
+
const reload = load.pipe(Effect.result, Effect.flatMap((result) => Ref.set(current, result).pipe(Effect.andThen(fromResult(result)))), Effect.asVoid);
|
|
34021
|
+
return DevelopmentEnvironment.of({
|
|
34022
|
+
reload,
|
|
34023
|
+
snapshot: Ref.get(current).pipe(Effect.flatMap(fromResult))
|
|
34024
|
+
});
|
|
34025
|
+
}));
|
|
34026
|
+
}
|
|
34027
|
+
};
|
|
33599
34028
|
var FunctionRegistry = class FunctionRegistry extends Context.Service()("ignotum/dev-runtime/functions/FunctionRegistry") {
|
|
33600
34029
|
static layer(server, appDirectory) {
|
|
33601
34030
|
return Layer.effect(FunctionRegistry, Effect.gen(function* () {
|
|
33602
34031
|
const path = yield* Path$1.Path;
|
|
34032
|
+
const developmentEnvironment = yield* DevelopmentEnvironment;
|
|
33603
34033
|
return FunctionRegistry.of({ resolve: Effect.fn("FunctionRegistry.resolve")(function* (functionAddress, kind, args) {
|
|
33604
34034
|
const { functionName, moduleName } = apiFunctionParts(functionAddress);
|
|
33605
34035
|
const modulePath = normalizePath(path.join(appDirectory, "server", `${moduleName}.ts`));
|
|
@@ -33617,6 +34047,11 @@ var FunctionRegistry = class FunctionRegistry extends Context.Service()("ignotum
|
|
|
33617
34047
|
message: `Unknown server function ${functionAddress}.`
|
|
33618
34048
|
});
|
|
33619
34049
|
const definition = inspected.definition;
|
|
34050
|
+
if (getFunctionEnvironment(candidate) === void 0) return yield* FunctionUnavailable.make({
|
|
34051
|
+
cause: /* @__PURE__ */ new Error(`${functionAddress} has no environment binding.`),
|
|
34052
|
+
function: functionAddress,
|
|
34053
|
+
message: `Could not load ${functionAddress}.`
|
|
34054
|
+
});
|
|
33620
34055
|
if (definition._tag !== kind) return yield* WrongFunctionKind.make({
|
|
33621
34056
|
actual: definition._tag,
|
|
33622
34057
|
expected: kind,
|
|
@@ -33629,6 +34064,11 @@ var FunctionRegistry = class FunctionRegistry extends Context.Service()("ignotum
|
|
|
33629
34064
|
message: `Invalid arguments for ${functionAddress}.`
|
|
33630
34065
|
}))),
|
|
33631
34066
|
definition,
|
|
34067
|
+
environment: (yield* developmentEnvironment.snapshot.pipe(Effect.mapError((cause) => FunctionUnavailable.make({
|
|
34068
|
+
cause,
|
|
34069
|
+
function: functionAddress,
|
|
34070
|
+
message: `Could not load ${functionAddress}.`
|
|
34071
|
+
})))).value,
|
|
33632
34072
|
schema: inspected.schema
|
|
33633
34073
|
};
|
|
33634
34074
|
}) });
|
|
@@ -33664,7 +34104,10 @@ var FunctionExecutor = class FunctionExecutor extends Context.Service()("ignotum
|
|
|
33664
34104
|
};
|
|
33665
34105
|
}));
|
|
33666
34106
|
return FunctionExecutor.of({ execute: Effect.fn("FunctionExecutor.execute")(function* (functionAddress, kind, resolved) {
|
|
33667
|
-
const invoke = (context) => Effect.gen(() => resolved.definition.handler(
|
|
34107
|
+
const invoke = (context) => Effect.gen(() => resolved.definition.handler(Object.freeze({
|
|
34108
|
+
...context,
|
|
34109
|
+
env: resolved.environment ?? Object.freeze({})
|
|
34110
|
+
}), resolved.args)).pipe(Effect.matchEffect({
|
|
33668
34111
|
onFailure: (error) => encodeFailure(resolved.definition, error),
|
|
33669
34112
|
onSuccess: (value) => encodeSuccess(resolved.definition, value)
|
|
33670
34113
|
}));
|
|
@@ -33751,7 +34194,7 @@ const validPng = (bytes) => bytes.length >= 20 && startsWith(bytes, [
|
|
|
33751
34194
|
10,
|
|
33752
34195
|
26,
|
|
33753
34196
|
10
|
|
33754
|
-
]) && ascii(bytes.subarray(bytes.length -
|
|
34197
|
+
]) && ascii(bytes.subarray(bytes.length - 8, bytes.length - 4)) === "IEND";
|
|
33755
34198
|
const validGif = (bytes) => bytes.length >= 14 && (ascii(bytes.subarray(0, 6)) === "GIF87a" || ascii(bytes.subarray(0, 6)) === "GIF89a") && bytes.at(-1) === 59;
|
|
33756
34199
|
const validWebp = (bytes) => {
|
|
33757
34200
|
if (bytes.length < 12 || ascii(bytes.subarray(0, 4)) !== "RIFF" || ascii(bytes.subarray(8, 12)) !== "WEBP") return false;
|
|
@@ -33984,6 +34427,7 @@ const localSyncIdentity = {
|
|
|
33984
34427
|
deploymentId: DeploymentId.make("dep_000000000000000000000001"),
|
|
33985
34428
|
generation: DeploymentGeneration.make(0)
|
|
33986
34429
|
};
|
|
34430
|
+
const reloadEnvironmentAndInvalidate = (reload, publish) => reload.pipe(Effect.ensuring(publish));
|
|
33987
34431
|
const queryInvalidationLayer = Layer.effect(QueryInvalidation, Effect.gen(function* () {
|
|
33988
34432
|
const pubsub = yield* PubSub.unbounded();
|
|
33989
34433
|
const latestRevision = yield* Ref.make(AppStateRevision.make(0));
|
|
@@ -34238,12 +34682,14 @@ const runSession = Effect.fn("SyncServer.runSession")(function* (socket) {
|
|
|
34238
34682
|
var SyncHandlers = class extends Context.Service()("ignotum/dev-runtime/sync/SyncHandlers") {};
|
|
34239
34683
|
const makeHandlersLayer = (server, appDirectory, databasePath) => {
|
|
34240
34684
|
const sqliteLayer = layer({ filename: databasePath });
|
|
34241
|
-
const persistenceLayer = Layer.merge(LocalDatabase.layer, mutationReplayLayer).pipe(Layer.provideMerge(DevelopmentDatabase.layer), Layer.provide(Layer.merge(
|
|
34242
|
-
const executorLayer = FunctionExecutor.layer.pipe(Layer.provide(persistenceLayer), Layer.provide(
|
|
34243
|
-
const
|
|
34244
|
-
const
|
|
34685
|
+
const persistenceLayer = Layer.merge(LocalDatabase.layer, mutationReplayLayer).pipe(Layer.provideMerge(DevelopmentDatabase.layer), Layer.provide(Layer.merge(IdGenerator.layer, sqliteLayer)));
|
|
34686
|
+
const executorLayer = FunctionExecutor.layer.pipe(Layer.provide(persistenceLayer), Layer.provide(IdGenerator.layer));
|
|
34687
|
+
const environmentLayer = DevelopmentEnvironment.layer(server, appDirectory).pipe(Layer.provide(layer$1), Layer.orDie);
|
|
34688
|
+
const devFunctionRuntimeLayer = functionRuntimeLayer.pipe(Layer.provide(Layer.merge(FunctionRegistry.layer(server, appDirectory).pipe(Layer.provide(Layer.merge(layer$1, environmentLayer))), executorLayer)));
|
|
34689
|
+
const dependencies = Layer.mergeAll(devFunctionRuntimeLayer, environmentLayer, queryInvalidationLayer, persistenceLayer, layer$1, localApplicationFilesLayer(appDirectory).pipe(Layer.provideMerge(DevelopmentDatabase.layer), Layer.provide(Layer.mergeAll(IdGenerator.layer, sqliteLayer, layer$1))));
|
|
34245
34690
|
return Layer.effect(SyncHandlers, Effect.gen(function* () {
|
|
34246
34691
|
const invalidation = yield* QueryInvalidation;
|
|
34692
|
+
const environment = yield* DevelopmentEnvironment;
|
|
34247
34693
|
const fileSystem = yield* FileSystem.FileSystem;
|
|
34248
34694
|
const path = yield* Path$1.Path;
|
|
34249
34695
|
const scope = yield* Effect.scope;
|
|
@@ -34297,10 +34743,17 @@ const makeHandlersLayer = (server, appDirectory, databasePath) => {
|
|
|
34297
34743
|
const http = yield* makeHandler(httpApp, { scope });
|
|
34298
34744
|
const upgrade = yield* makeUpgradeHandler(Effect.succeed(webSocketServer), socketApp, { scope });
|
|
34299
34745
|
const serverDirectory = path.join(appDirectory, "server");
|
|
34746
|
+
const environmentPath = path.join(appDirectory, ".env.ignotum");
|
|
34300
34747
|
const reloadUnsafe = Effect.fn("SyncServer.reload")(function* (file, event) {
|
|
34748
|
+
const isEnvironmentValues = path.resolve(file) === path.resolve(environmentPath);
|
|
34301
34749
|
const relative = path.relative(serverDirectory, file);
|
|
34302
|
-
|
|
34303
|
-
if (
|
|
34750
|
+
const isServerTypeScript = !relative.startsWith("..") && !path.isAbsolute(relative) && file.endsWith(".ts");
|
|
34751
|
+
if (!isEnvironmentValues && !isServerTypeScript) return;
|
|
34752
|
+
if (isServerTypeScript && event !== "change") yield* generate(appDirectory).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.provideService(Path$1.Path, path));
|
|
34753
|
+
if (isEnvironmentValues || relative === "env.ts") {
|
|
34754
|
+
yield* reloadEnvironmentAndInvalidate(environment.reload, invalidation.publish({ type: "All" }));
|
|
34755
|
+
return;
|
|
34756
|
+
}
|
|
34304
34757
|
yield* invalidation.publish({ type: "All" });
|
|
34305
34758
|
});
|
|
34306
34759
|
const reload = (file, event) => reloadSemaphore.withPermits(1)(reloadUnsafe(file, event)).pipe(Effect.catchCause((cause) => Effect.logError("Could not reload Ignotum server functions.").pipe(Effect.annotateLogs({
|
|
@@ -34465,7 +34918,7 @@ const dev$1 = Effect.fn("Dev.run")(function* (options) {
|
|
|
34465
34918
|
var AGENTS_default = "# Ignotum app instructions\n\nThis repository is an Ignotum app. Read `.agents/skills/ignotum/SKILL.md` before changing the\nschema, server functions, generated references, client code, app structure, or development setup.\n\n## Source boundaries\n\n- `server/schema.ts` defines tables and indexes.\n- TypeScript files directly inside `server` define public queries and mutations.\n- `client/index.tsx` default-exports the app definition and root component.\n- `shared` contains code that is safe in both client and server builds.\n- `_generated` is compiler output. Never edit it.\n\nUse `ignotum/server` only for schema authoring. Use generated builders from\n`@/_generated/server.js` in function modules. Use `ignotum/client` and\n`@/_generated/api.js` in client code. Keep the `.js` suffix on generated and aliased imports.\n\nDo not add an HTML entry, framework configuration, Tailwind configuration, API route, direct\ndatabase client, or backend server. Those do not belong to an Ignotum app.\n\n## Before finishing\n\nRun the app's existing checks. When no project-specific command says otherwise, regenerate the\nbindings and typecheck:\n\n```sh\nnpx ignotum codegen\nnpx tsc --noEmit\n```\n\nDo not reset the development database unless the user explicitly asks to discard local data.\n";
|
|
34466
34919
|
//#endregion
|
|
34467
34920
|
//#region ../../docs/agent/skills/ignotum/SKILL.md?raw
|
|
34468
|
-
var SKILL_default = "---\nname: ignotum\ndescription: Build and modify an Ignotum app, including its database, functions, files, Web code, generated references, local workflow, and deployment configuration.\n---\n\n# Work on an Ignotum app\n\nUse this skill for changes to an Ignotum app. The bundled references describe the installed\nIgnotum release. Prefer them to assumptions based on another backend or UI framework.\n\n## Inspect before editing\n\nRead the files that define the part of the app you are changing:\n\n- `server/schema.ts` for tables, fields, indexes, and generated document types;\n- TypeScript files directly inside `server` for queries and mutations;\n- `client/index.tsx` and imported client modules for the UI;\n- `shared` for code imported by both sides;\n- `package.json` and `tsconfig.json` for the available scripts and aliases.\n\nInspect `_generated` when you need the exact generated type or function address. Never edit it.\nGenerated files can lag behind source until `ignotum codegen` or the dev server runs.\n\nRead the smallest relevant set of references before changing code. If a task crosses several\nareas, read each corresponding page completely.\n\n## Choose the reference\n\n| Task | Reference |\n| ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |\n| Create or understand an app | [Quickstart](references/quickstart.md) and [Project structure](references/project-structure.md) |\n| Change tables, fields, or IDs | [Schema](references/schema.md) and [Values](references/values.md) |\n| Define or query an index | [Indexes](references/indexes.md) |\n| Read or paginate data | [Reading data](references/reading-data.md) and [Pagination](references/pagination.md) |\n| Write data | [Writing data](references/writing-data.md) |\n| Add or call a query | [Functions](references/functions.md) and [Queries](references/queries.md) |\n| Add or call a mutation | [Functions](references/functions.md) and [Mutations](references/mutations.md) |\n| Define or handle application errors | [Errors](references/errors.md) |\n| Accept, store, return, or use application files | [Files](references/files.md) |\n| Change the app entry or Preact code | [Web](references/web.md), [App entry](references/app-entry.md), and [Preact](references/preact.md) |\n| Change styles, the icon, or public assets | [Styling](references/styling.md) and [Public assets](references/public-assets.md) |\n| Reason about transactions, ordering, retries, or live updates | [Guarantees](references/guarantees.md) |\n| Run, configure, or reset local development | [Local development](references/local-development.md) |\n| Update or inspect generated bindings | [Generated code](references/generated-code.md) |\n| Deploy or change the app link | [Deploy an app](references/deploy-app.md) and [Deployment model](references/deployment-model.md) |\n| Look up commands and flags | [CLI](references/cli.md) |\n| Check hosted capacity or argument sizes | [Limits](references/limits.md) |\n| Build deployment tooling against `api.ignotum.cloud` | [HTTP API](references/api.md) |\n\n## Preserve the app model\n\nAn Ignotum app has a fixed division of responsibility:\n\n- Define the schema only in `server/schema.ts` with `defineSchema` from `ignotum/server`.\n- Define functions in `.ts` files directly inside `server`. Nested files do not become API modules.\n- Import schema-bound `query`, `mutation`, `values`, and `Result` from\n `@/_generated/server.js`.\n- Import generated client references from `@/_generated/api.js`.\n- Import JSX, hooks, UI types, `app`, and query or mutation hooks from `ignotum/client`.\n- Put cross-boundary code in `shared` only when it uses APIs available to both the browser and\n server function environment.\n\nDo not add React, a separate Preact package, a router, an HTML entry, Vite configuration, Tailwind\nconfiguration, an ORM, a direct database connection, custom API routes, or a second backend unless\nthe user explicitly asks to leave the Ignotum application model. Authentication, actions,\nschedules, and workflows are not current Ignotum features.\n\nClient code cannot import server modules, server generated bindings, Node built-ins, or\n`ignotum/server`. Server code cannot import client modules, client generated references, Node\nbuilt-ins, or `ignotum/client`. Dynamic imports and native add-ons do not belong in server\nfunctions.\n\n## Change the schema carefully\n\nUse validators for every stored field. They provide runtime validation as well as TypeScript types.\nUse table-specific IDs for relationships and declare an index for ordered or filtered access that\nmust avoid a full table read.\n\nBefore changing a deployed app's fields, check\n[Deployment model](references/deployment-model.md). After the first\nactivation, hosted deployment requires the same table names, field names, and field validators.\nIndex-only changes are allowed. Do not promise that adding an optional field is compatible.\n\nEvery stored document receives `id`, `createdAt`, and `updatedAt`. Do not declare those fields in\nthe schema or write them through insert, patch, or replace input.\n\n## Write functions through generated builders\n\nHandlers are generator functions. Use `yield*` for database operations and `return` for the public\nvalue. Declare `args`, `returns`, and application `errors` so the client receives accurate types and\nruntime checks.\n\nChoose the function kind by behavior:\n\n- A query reads one committed state and cannot write.\n- A mutation may read and write. Its writes commit together only after a valid success result.\n- Use a declared application error for an expected domain outcome. Do not throw it or convert it to\n a generic exception.\n\nUse `find` when a missing document is an ordinary branch. Use `get` when absence should fail with\n`DocumentNotFound`. `patch`, `replace`, and `delete` fail with the same error when their target is\nmissing. Check with `find` first when a missing write target should be ignored.\n\nIndexes order by their declared fields, then `createdAt`, then `id`. Add equality bounds for a\nleading prefix before using a range bound on the next field. Do not emulate an indexed filter by\ncollecting an unbounded table unless the table is intentionally small.\n\n## Use the client result model\n\n`useQuery` returns `Pending`, a value, or a declared error. Render each reachable state with\n`Result.match`. Pass `Query.skip` when the arguments are not ready instead of inventing placeholder\narguments.\n\n`useMutation` returns a stable typed function. Its promise resolves to the declared result and can\nreject for client or protocol failures. Internal function failures use the result's\n`InternalServerError` branch. A resolved success means its commit is complete, but subscribed\nqueries may update afterward.\n\nFor long ordered lists, use `usePaginatedQuery` with a function that calls `.paginate(...)`. Keep\ndocument IDs as rendering keys. Loaded pages remain live and do not form a frozen historical\nsnapshot.\n\nThe required `client/index.tsx` must default-export one direct `app({ title, component })` call.\nImport `app` by that name from `ignotum/client`, use a non-empty quoted title, and provide only the\n`title` and `component` properties. The optional icon path is exactly `client/icon.svg`. Put\nsupported public files in top-level `public`, not `client/public`.\n\n## Verify the change\n\nUse existing app scripts when they are stricter. Otherwise run:\n\n```sh\nnpx ignotum codegen\nnpx tsc --noEmit\n```\n\nRun focused tests when the app has them. For behavior that depends on live results, also run the dev\nserver and exercise the affected query or mutation from the Web part.\n\nTreat `.ignotum/dev/state.db` as user data. Stop the dev server before `npx ignotum dev db reset`,\nand reset it only when the user asked for a clean local database. Treat `.ignotum/app.json` as the\nhosted app link; do not delete or rewrite it to solve a deployment problem without confirming that\nthe app should be relinked.\n";
|
|
34921
|
+
var SKILL_default = "---\nname: ignotum\ndescription: Build and modify an Ignotum app, including its database, functions, files, Web code, generated references, local workflow, and deployment configuration.\n---\n\n# Work on an Ignotum app\n\nUse this skill for changes to an Ignotum app. The bundled references describe the installed\nIgnotum release. Prefer them to assumptions based on another backend or UI framework.\n\n## Inspect before editing\n\nRead the files that define the part of the app you are changing:\n\n- `server/schema.ts` for tables, fields, indexes, and generated document types;\n- TypeScript files directly inside `server` for queries and mutations;\n- `client/index.tsx` and imported client modules for the UI;\n- `shared` for code imported by both sides;\n- `package.json` and `tsconfig.json` for the available scripts and aliases.\n\nInspect `_generated` when you need the exact generated type or function address. Never edit it.\nGenerated files can lag behind source until `ignotum codegen` or the dev server runs.\n\nRead the smallest relevant set of references before changing code. If a task crosses several\nareas, read each corresponding page completely.\n\n## Choose the reference\n\n| Task | Reference |\n| ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |\n| Create or understand an app | [Quickstart](references/quickstart.md) and [Project structure](references/project-structure.md) |\n| Change tables, fields, or IDs | [Schema](references/schema.md) and [Values](references/values.md) |\n| Define or query an index | [Indexes](references/indexes.md) |\n| Read or paginate data | [Reading data](references/reading-data.md) and [Pagination](references/pagination.md) |\n| Write data | [Writing data](references/writing-data.md) |\n| Add or call a query | [Functions](references/functions.md) and [Queries](references/queries.md) |\n| Add or call a mutation | [Functions](references/functions.md) and [Mutations](references/mutations.md) |\n| Define or handle application errors | [Errors](references/errors.md) |\n| Declare or use server environment variables | [Environment variables](references/environment-variables.md) |\n| Accept, store, return, or use application files | [Files](references/files.md) |\n| Change the app entry or Preact code | [Web](references/web.md), [App entry](references/app-entry.md), and [Preact](references/preact.md) |\n| Change styles, the icon, or public assets | [Styling](references/styling.md) and [Public assets](references/public-assets.md) |\n| Reason about transactions, ordering, retries, or live updates | [Guarantees](references/guarantees.md) |\n| Run, configure, or reset local development | [Local development](references/local-development.md) |\n| Update or inspect generated bindings | [Generated code](references/generated-code.md) |\n| Deploy or change the app link | [Deploy an app](references/deploy-app.md) and [Deployment model](references/deployment-model.md) |\n| Look up commands and flags | [CLI](references/cli.md) |\n| Check hosted capacity or argument sizes | [Limits](references/limits.md) |\n| Build deployment tooling against `api.ignotum.cloud` | [HTTP API](references/api.md) |\n\n## Preserve the app model\n\nAn Ignotum app has a fixed division of responsibility:\n\n- Define the schema only in `server/schema.ts` with `defineSchema` from `ignotum/server`.\n- Define server configuration only in `server/env.ts` with `defineEnv` and supply it from the\n project-root `.env.ignotum`.\n- Define functions in `.ts` files directly inside `server`. Nested files do not become API modules.\n- Import schema-bound `query`, `mutation`, `values`, and `Result` from\n `@/_generated/server.js`.\n- Import generated client references from `@/_generated/api.js`.\n- Import JSX, hooks, UI types, `app`, and query or mutation hooks from `ignotum/client`.\n- Put cross-boundary code in `shared` only when it uses APIs available to both the browser and\n server function environment.\n\nDo not add React, a separate Preact package, a router, an HTML entry, Vite configuration, Tailwind\nconfiguration, an ORM, a direct database connection, custom API routes, or a second backend unless\nthe user explicitly asks to leave the Ignotum application model. Authentication, actions,\nschedules, and workflows are not current Ignotum features.\n\nClient code cannot import server modules, server generated bindings, Node built-ins, or\n`ignotum/server`. Server code cannot import client modules, client generated references, Node\nbuilt-ins, or `ignotum/client`. Dynamic imports and native add-ons do not belong in server\nfunctions.\n\n## Change the schema carefully\n\nUse validators for every stored field. They provide runtime validation as well as TypeScript types.\nUse table-specific IDs for relationships and declare an index for ordered or filtered access that\nmust avoid a full table read.\n\nBefore changing a deployed app's fields, check\n[Deployment model](references/deployment-model.md). After the first\nactivation, hosted deployment requires the same table names, field names, and field validators.\nIndex-only changes are allowed. Do not promise that adding an optional field is compatible.\n\nEvery stored document receives `id`, `createdAt`, and `updatedAt`. Do not declare those fields in\nthe schema or write them through insert, patch, or replace input.\n\n## Write functions through generated builders\n\nHandlers are generator functions. Use `yield*` for database operations and `return` for the public\nvalue. Declare `args`, `returns`, and application `errors` so the client receives accurate types and\nruntime checks.\n\nChoose the function kind by behavior:\n\n- A query reads one committed state and cannot write.\n- A mutation may read and write. Its writes commit together only after a valid success result.\n- Use a declared application error for an expected domain outcome. Do not throw it or convert it to\n a generic exception.\n\nUse `find` when a missing document is an ordinary branch. Use `get` when absence should fail with\n`DocumentNotFound`. `patch`, `replace`, and `delete` fail with the same error when their target is\nmissing. Check with `find` first when a missing write target should be ignored.\n\nIndexes order by their declared fields, then `createdAt`, then `id`. Add equality bounds for a\nleading prefix before using a range bound on the next field. Do not emulate an indexed filter by\ncollecting an unbounded table unless the table is intentionally small.\n\n## Use the client result model\n\n`useQuery` returns `Pending`, a value, or a declared error. Render each reachable state with\n`Result.match`. Pass `Query.skip` when the arguments are not ready instead of inventing placeholder\narguments.\n\n`useMutation` returns a stable typed function. Its promise resolves to the declared result and can\nreject for client or protocol failures. Internal function failures use the result's\n`InternalServerError` branch. A resolved success means its commit is complete, but subscribed\nqueries may update afterward.\n\nFor long ordered lists, use `usePaginatedQuery` with a function that calls `.paginate(...)`. Keep\ndocument IDs as rendering keys. Loaded pages remain live and do not form a frozen historical\nsnapshot.\n\nThe required `client/index.tsx` must default-export one direct `app({ title, component })` call.\nImport `app` by that name from `ignotum/client`, use a non-empty quoted title, and provide only the\n`title` and `component` properties. The optional icon path is exactly `client/icon.svg`. Put\nsupported public files in top-level `public`, not `client/public`.\n\n## Verify the change\n\nUse existing app scripts when they are stricter. Otherwise run:\n\n```sh\nnpx ignotum codegen\nnpx tsc --noEmit\n```\n\nRun focused tests when the app has them. For behavior that depends on live results, also run the dev\nserver and exercise the affected query or mutation from the Web part.\n\nTreat `.ignotum/dev/state.db` as user data. Stop the dev server before `npx ignotum dev db reset`,\nand reset it only when the user asked for a clean local database. Treat `.ignotum/app.json` as the\nhosted app link; do not delete or rewrite it to solve a deployment problem without confirming that\nthe app should be relinked.\n";
|
|
34469
34922
|
//#endregion
|
|
34470
34923
|
//#region src/cli/agent-files.ts
|
|
34471
34924
|
const references = [
|
|
@@ -34474,29 +34927,30 @@ const references = [
|
|
|
34474
34927
|
["cli.md", "---\ntitle: CLI\ndescription: Look up Ignotum commands, flags, and environment variables.\nsection: Reference\nslug: cli\norder: 72\n---\n\n# CLI\n\nRun commands through the local or downloaded `ignotum` package:\n\n```sh\nnpx ignotum <command>\n```\n\n## Commands\n\n| Command | Purpose |\n| ---------------------- | ------------------------------------------------------------------ |\n| `ignotum new <path>` | Create an app in an empty directory. |\n| `ignotum install` | Install dependencies with pnpm, or npm if pnpm is absent. |\n| `ignotum codegen` | Update files under `_generated`. |\n| `ignotum dev` | Run the local development server. |\n| `ignotum dev db reset` | Delete the local development database while the server is stopped. |\n| `ignotum auth login` | Sign in with GitHub through the device authorization flow. |\n| `ignotum auth logout` | Revoke the session and remove it from this computer. |\n| `ignotum auth status` | Show the signed-in account and team. |\n| `ignotum deploy` | Build, upload, and activate the app. |\n\nUse `ignotum <command> --help` for the command's generated help.\n\n## `new`\n\n```sh\nnpx ignotum new my-app\nnpx ignotum new . --no-git --no-install\n```\n\nThe target directory must be empty. The command installs dependencies and initializes a Git\nrepository by default. Use `--no-install` or `--no-git` to skip either action.\n\n## `dev`\n\n```sh\nnpx ignotum dev --host 0.0.0.0 --port 3000 --open\n```\n\n| Flag | Default | Meaning |\n| -------- | ----------- | ------------------------------------- |\n| `--host` | `127.0.0.1` | Address used by the local server. |\n| `--port` | `3210` | TCP port from 1 through 65,535. |\n| `--open` | off | Open the app after the server starts. |\n\n## `deploy`\n\nUse `--app <slug>` to create or link the hosted app on the first deployment:\n\n```sh\nnpx ignotum deploy --app my-app\n```\n\nLater deployments read the link from `.ignotum/app.json` and do not need the flag.\n\n## `auth`\n\nRun `npx ignotum auth login` once before deploying. The terminal prints a short code and opens the\napproval page. Confirm that the browser and terminal show the same code before approving it.\n`auth status` checks the saved session, and `auth logout` revokes it.\n\n## Environment variables\n\n| Variable | Meaning |\n| ------------------ | -------------------------------------------------------- |\n| `IGNOTUM_AUTH_URL` | Auth base URL. Defaults to `https://auth.ignotum.cloud`. |\n| `IGNOTUM_API_URL` | API base URL. Defaults to `https://api.ignotum.cloud`. |\n\n`IGNOTUM_API_URL` must use HTTPS. A numeric loopback address such as `127.0.0.1` or `[::1]` may\nuse HTTP for local platform development.\n"],
|
|
34475
34928
|
["database.md", "---\ntitle: Database overview\nlabel: Overview\ndescription: Understand the persistent data model in an Ignotum app.\nsection: Database\nslug: database\norder: 10\n---\n\n# Database overview\n\nEach Ignotum app has a database described by `server/schema.ts`. A schema contains tables, stored\nfields, and indexes. Every stored document also has a table-specific ID and creation and update\ntimestamps.\n\nQueries and mutations read through `ctx.db`. Only mutations can insert, patch, replace, or delete\ndocuments. All reads and writes in one mutation commit together.\n\n- [Schema](schema.md) defines tables, fields, IDs, and schema changes.\n- [Values](values.md) lists the validators used for stored fields and function contracts.\n- [Reading data](reading-data.md) covers document and table reads.\n- [Writing data](writing-data.md) covers database changes and transaction behavior.\n- [Indexes](indexes.md) covers index definitions, ranges, and ordering.\n- [Pagination](pagination.md) covers cursor-based database and Web pagination.\n\nNext: [Schema](schema.md)\n"],
|
|
34476
34929
|
["deploy-app.md", "---\ntitle: Deploy an app\ndescription: Link an app and publish a deployment to Ignotum Cloud.\nsection: Deployments\nslug: deploy\norder: 51\n---\n\n# Deploy an app\n\nSign in through the browser device flow:\n\n```sh\nnpx ignotum auth login\n```\n\nThe command opens `auth.ignotum.cloud`, asks you to sign in with GitHub, and requires you to approve\nthe code shown in the terminal. The CLI stores the resulting session under your user config\ndirectory with owner-only file permissions. It never writes the session into the app directory.\nIgnotum Cloud accounts are currently invite-only; the email returned by GitHub must have a pending\nplatform invite before the first sign-in.\n\n## First deployment\n\nChoose the app slug on the first deployment:\n\n```sh\nnpx ignotum deploy --app my-app\n```\n\nSlugs use lowercase letters, numbers, and hyphens. They are 1 through 63 characters, cannot start\nor end with a hyphen, and must be globally available. The deployed app uses\n`https://my-app.ignotum.app`.\n\nIf the slug already belongs to your account, the CLI links the local directory to that app. If it\nis available, the CLI creates the app. If you omit `--app`, it asks for a slug in an interactive\nterminal.\n\nThe CLI writes the app ID, slug, and API URL to `.ignotum/app.json`. The generated `.gitignore`\nexcludes `.ignotum`.\n\n## Later deployments\n\nOnce linked, run:\n\n```sh\nnpx ignotum deploy\n```\n\nThe command updates generated code, builds and validates the complete app, uploads a new\ndeployment, and activates it. There is no separate public build command.\n\nThe active deployment stays unchanged if any of these steps fail. See\n[Deployment model](deployment-model.md) for activation, schema compatibility, and interrupted\ndeployments.\n\nNext: [Deployment model](deployment-model.md)\n"],
|
|
34477
|
-
["deployment-model.md", "---\ntitle: Deployment model\ndescription: Understand activation, compatibility, and failure behavior.\nsection: Deployments\nslug: deployment-model\norder: 52\n---\n\n# Deployment model\n\nIgnotum creates a new immutable deployment for each successful `ignotum deploy` run. The active\ndeployment does not change until the new deployment has uploaded, passed validation, and activated.\n\n## Activation\n\nActivation switches the Web part and functions together. Connected browser sessions reload after\nthey learn that the deployment changed, so a session does not intentionally combine Web code from\none deployment with functions from another.\n\nThe CLI has no rollback command.
|
|
34478
|
-
["deployments.md", "---\ntitle: Deployments overview\nlabel: Overview\ndescription: Understand what an Ignotum deployment publishes.\nsection: Deployments\nslug: deployments\norder: 50\n---\n\n# Deployments overview\n\nA deployment publishes the complete Ignotum app. It contains the Web part, functions, schema, and
|
|
34930
|
+
["deployment-model.md", "---\ntitle: Deployment model\ndescription: Understand activation, compatibility, and failure behavior.\nsection: Deployments\nslug: deployment-model\norder: 52\n---\n\n# Deployment model\n\nIgnotum creates a new immutable deployment for each successful `ignotum deploy` run. The active\ndeployment does not change until the new deployment has uploaded, passed validation, and activated.\nThe deployment also captures `.env.ignotum`; later edits do not change an existing deployment.\n\n## Activation\n\nActivation switches the Web part and functions together. Connected browser sessions reload after\nthey learn that the deployment changed, so a session does not intentionally combine Web code from\none deployment with functions from another.\n\nThe CLI has no rollback command. An older deployment retains its original environment if it is\nreactivated through the HTTP API. Deploying older source again creates a new deployment with the\ncurrent `.env.ignotum` values.\n\n## Schema compatibility\n\nThe first active deployment fixes the stored table and field schema. Later deployments may change\nWeb code, functions, and indexes. Changing a table name, adding or removing a stored field, or\nchanging a field validator causes activation to fail with `SchemaIncompatible`. The active\ndeployment and stored data remain unchanged.\n\nAdding, removing, renaming, or reordering indexes is supported. Ignotum prepares new indexes before\nthe new deployment starts accepting mutations.\n\n## Failed and interrupted deployments\n\nA build, upload, validation, compatibility, or activation failure leaves the current deployment\nactive.\n\nIf an upload or finalization request loses its connection, the CLI prints the new deployment ID.\nUnfinished uploads expire after 24 hours. If activation cannot be confirmed, use the\n[HTTP API](api.md) to inspect the app's `activeDeploymentId`, or run `ignotum deploy` again to create\nand activate a new deployment.\n\nSee [Guarantees](guarantees.md) for the activation contract and [Limits](limits.md) for deployment\nretention.\n"],
|
|
34931
|
+
["deployments.md", "---\ntitle: Deployments overview\nlabel: Overview\ndescription: Understand what an Ignotum deployment publishes.\nsection: Deployments\nslug: deployments\norder: 50\n---\n\n# Deployments overview\n\nA deployment publishes the complete Ignotum app. It contains the Web part, functions, schema,\nserver environment snapshot, and public assets. Ignotum activates these parts together after the\ncomplete deployment passes its checks.\n\nUse `ignotum deploy` to link a local project to a hosted app and publish it. A failed build, upload,\nvalidation, or activation does not replace the active deployment.\n\n- [Deploy an app](deploy-app.md) covers the command, first-time linking, and later deployments.\n- [Deployment model](deployment-model.md) covers activation, schema compatibility, and interrupted\n deployments.\n- [Limits](limits.md) lists deployment size and retention limits.\n\nNext: [Deploy an app](deploy-app.md)\n"],
|
|
34479
34932
|
["errors.md", "---\ntitle: Errors\ndescription: Define, return, and handle typed application errors.\nsection: Functions\nslug: errors\norder: 24\n---\n\n# Errors\n\nFunctions can return typed application errors to the client. Ignotum keeps runtime failures and\nthrown exceptions separate from those public errors.\n\n## Application errors\n\nDefine an application error with `values.error`. Its name becomes `_tag`:\n\n```ts\nconst TodoNotFound = values.error(\"TodoNotFound\", {\n id: values.id(\"todos\"),\n});\n```\n\n`Result.fail` deliberately stops the operation with a typed error:\n\n```ts\nyield * Result.fail(TodoNotFound({ id: args.id }));\n```\n\nIn a mutation, an application error rolls back every write from that invocation. In a query, it\nbecomes the settled failure for the subscription at the revision the query observed.\n\nThe `errors` field is optional. If omitted, Ignotum infers the handler's remaining application\nerrors. If supplied, it is the public contract and the handler must conform to it:\n\n```ts\nexport const toggle = mutation({\n args: {\n id: values.id(\"todos\"),\n },\n returns: values.boolean(),\n errors: TodoNotFound,\n\n handler: function* (ctx, args) {\n const todo = yield* ctx.db.get(\"todos\", args.id).catch({\n DocumentNotFound: (error) => Result.fail(TodoNotFound({ id: error.id })),\n });\n\n const completed = !todo.completed;\n yield* ctx.db.patch(\"todos\", args.id, { completed });\n return completed;\n },\n});\n```\n\nCombine public errors with `values.union`:\n\n```ts\nerrors: values.union(InvalidTodoText, TodoLimitReached),\n```\n\n## Catch and recover\n\nEvery Result operation has a partial, tag-based `catch`. Handlers receive the narrowed error type.\nUnmatched errors continue through the channel:\n\n```ts\nconst settings =\n yield *\n loadSettings().catch({\n SettingsNotFound: () => defaultSettings,\n });\n```\n\nReturn a plain value to recover. Return `Result.fail(...)` to map one error to another. Unknown tag\nnames fail the TypeScript check.\n\nUse `Result.try` for one catch boundary around several operations:\n\n```ts\nconst author =\n yield *\n Result.try(function* () {\n const membership = yield* ctx.db.get(\"memberships\", topic.membershipId);\n return yield* ctx.db.get(\"users\", membership.userId);\n }).catch({\n DocumentNotFound: () => Result.fail(InvalidAuthor({ topicId: topic.id })),\n });\n```\n\nThere is no async variant. Ignotum operations always use `yield*` in server code.\n\n## Standalone results\n\n`Result.succeed` remains useful for helpers that return a Result:\n\n```ts\nfunction validateName(name: string) {\n if (name.length === 0) {\n return Result.fail(InvalidName({}));\n }\n\n return Result.succeed(name.trim());\n}\n```\n\nA handler can use the helper with `const name = yield* validateName(args.name)`. Normal handlers do\nnot wrap successful returns in `Result.succeed`.\n\n## Internal failures and defects\n\nDatabase outages, internal runtime failures, and thrown JavaScript exceptions are not application\nerrors. Ignotum logs their full cause and sends only:\n\n```ts\n{\n _tag: \"InternalServerError\",\n requestId: \"...\",\n}\n```\n\nEvery generated client function includes `InternalServerError` in its Result error union. The\nrequest ID links the client-visible failure to server logs without exposing private details.\n`InternalServerError` is reserved by Ignotum: never define it with `values.error` or include it in a\nfunction's `errors` schema.\n\nAn internal failure also rolls back a mutation. It does not become one of the application's\ndeclared errors, even if a thrown value happens to have the same shape.\n\n## Web matching\n\nThe client `Result.match` separates `error` and `internalError`. `error` handles only errors declared\nby the function. `internalError` handles the reserved platform error. If `internalError` is absent,\nmatching that result throws the error to the nearest UI error boundary.\n\nProtocol and connection failures are outside the application `Result`. A mutation promise rejects\nfor an operation-scoped protocol failure. A query hook throws a subscription infrastructure\nfailure during rendering so the UI error boundary can handle it.\n"],
|
|
34933
|
+
["environment-variables.md", "---\ntitle: Environment variables\ndescription: Declare, validate, and read server-only configuration.\nsection: Functions\nslug: environment-variables\norder: 24\n---\n\n# Environment variables\n\nIgnotum provides typed, server-only configuration through `ctx.env`. Declare the variables in\n`server/env.ts` and supply their values in `.env.ignotum` at the project root. The same file is used\nfor local development and deployment.\n\n```ts\nimport { defineEnv } from \"ignotum/server\";\n\nexport default defineEnv(({ values }) => ({\n API_BASE_URL: values.string(),\n API_TOKEN: values.secret(),\n RETRY_LIMIT: values.integer(),\n SERVICE_ACCOUNT: values.secret(\n values.object({\n clientEmail: values.string(),\n privateKey: values.string(),\n }),\n ),\n}));\n```\n\nNames must contain uppercase letters, digits, and underscores, cannot begin with a digit, and cannot\nuse the reserved `IGNOTUM_` prefix.\n\nAdd the matching values to `.env.ignotum`:\n\n```dotenv\nAPI_BASE_URL=https://api.example.com\nAPI_TOKEN=replace-me\nRETRY_LIMIT=3\nSERVICE_ACCOUNT={\"clientEmail\":\"service@example.com\",\"privateKey\":\"replace-me\"}\n```\n\nEach value is tried as a string first. If that does not match its validator, Ignotum parses it as\nJSON and tries again. Numbers, booleans, arrays, records, and objects therefore use JSON syntax. If\na union accepts a string, the original string wins. Empty strings are preserved, and values are not\nread from the process environment.\n\nThe declaration and values must agree exactly. A missing file, missing required variable, extra\nvariable, or invalid value stops development startup or deployment. A `.env.ignotum` file without\n`server/env.ts` is also an error. When neither file exists, `ctx.env` is an empty object.\n\n## Read values in a function\n\nPlain values are available directly. Secret values require an explicit unwrap:\n\n```ts\nimport { Secret } from \"ignotum/server\";\nimport { query, values } from \"@/_generated/server.js\";\n\nexport const status = query({\n returns: values.string(),\n handler: function* (ctx) {\n const token = Secret.value(ctx.env.API_TOKEN);\n return `${ctx.env.API_BASE_URL}:${token.length}`;\n },\n});\n```\n\n`values.secret()` is shorthand for `values.secret(values.string())`. A secret can wrap another\nenvironment validator, including an object. Until it is unwrapped, string conversion, inspection,\nand JSON serialization display `<redacted>` rather than its value. Once application code calls\n`Secret.value`, it is responsible for the plain value and can disclose or store it.\n\nSecret validators are available only inside `defineEnv`. They cannot be used for database fields,\nfunction arguments, results, or application errors.\n\n## Development and deployment\n\nChanging `.env.ignotum` during development reloads the validated environment and refreshes active\nqueries. An invalid edit makes functions unavailable until the file is fixed; Ignotum does not keep\nserving the previous values.\n\nEvery deployment captures an immutable snapshot of `.env.ignotum`. Changing the file requires a\nnew deployment. Reactivating an older deployment restores the environment captured with that\ndeployment.\n\nEnvironment values stay out of browser output and local build files. Hosted values are kept in\nprivate storage encrypted by the cloud provider and are supplied only to the isolated function\nruntime. This Private Alpha release does not provide customer-managed encryption keys or\noperator-blind encryption. Authorized Ignotum platform operators and the cloud storage provider\nremain within the trust boundary.\n\nKeep `.env.ignotum` out of Git. New Ignotum projects ignore `.env.*` and retain the `!.env.example`\nexception, so a value-free `.env.example` can document the required names. Existing projects should\nadd an equivalent ignore rule.\n\nSee [Functions](functions.md), [Values](values.md), and [Limits](limits.md).\n"],
|
|
34480
34934
|
["files.md", "---\ntitle: Files\ndescription: Validate, store, return, and use application files.\nsection: Files\nslug: files\norder: 30\n---\n\n# Files\n\nApplication files are database values. A Web form can pass a browser `File` to a mutation. The\nmutation stores its validated file value in a document, and a query can return it to Web code.\n\n## Declare a file value\n\nUse `values.file` in `server/schema.ts`:\n\n```ts\nimport { defineSchema } from \"ignotum/server\";\n\nexport default defineSchema(({ table, values }) => ({\n profiles: table({\n name: values.string(),\n avatar: values.optional(values.file({ formats: [\"jpeg\", \"png\", \"webp\"], maxBytes: 2_000_000 })),\n }),\n}));\n```\n\n`formats` must be a non-empty subset of `jpeg`, `png`, `webp`, `avif`, and `gif`. `maxBytes` is\noptional and cannot exceed the 10 MiB per-file limit. PDF, audio, video, SVG, archives, code, and\nexecutables are not accepted as application files.\n\n## Accept and store a file\n\nA mutation can accept the same file validator and store the result:\n\n```ts\nimport { mutation, values } from \"@/_generated/server.js\";\n\nexport const setAvatar = mutation({\n args: {\n profileId: values.id(\"profiles\"),\n avatar: values.file({ formats: [\"jpeg\", \"png\", \"webp\"], maxBytes: 2_000_000 }),\n },\n\n handler: function* (ctx, args) {\n yield* ctx.db.patch(\"profiles\", args.profileId, { avatar: args.avatar });\n },\n});\n```\n\nThe handler receives `name`, `format`, `mimeType`, and `size`, not the file bytes. Hosted functions\ncannot open or inspect file contents. Ignotum validates the file before invoking the mutation.\n\nMutation returns and application errors cannot contain files. Query arguments cannot contain\nfiles.\n\nIn Web code, pass a native `File` to the generated mutation caller:\n\n```tsx\nimport { Result, useMutation } from \"ignotum/client\";\nimport { api } from \"@/_generated/api.js\";\nimport type { Id } from \"@/_generated/types.js\";\n\nfunction AvatarForm({ profileId }: { profileId: Id<\"profiles\"> }) {\n const setAvatar = useMutation(api.profiles.setAvatar);\n\n const change = (event: Event) => {\n const input = event.currentTarget as HTMLInputElement;\n const avatar = input.files?.[0];\n if (avatar === undefined) return;\n\n void setAvatar({ profileId, avatar }).then(\n Result.match({\n value: () => console.log(\"Saved\"),\n error: () => console.log(\"Could not save\"),\n }),\n );\n };\n\n return <input type=\"file\" accept=\"image/jpeg,image/png,image/webp\" onChange={change} />;\n}\n```\n\nThe browser's `File.type` must identify an allowed format. Ignotum also verifies the file bytes.\n\n## Return and use a file\n\nQueries can return file fields, including nested files and complete documents:\n\n```ts\nexport const get = query({\n args: { id: values.id(\"profiles\") },\n returns: values.doc(\"profiles\"),\n\n handler: function* (ctx, args) {\n return yield* ctx.db.get(\"profiles\", args.id);\n },\n});\n```\n\nUse `Files.url` on the current query value:\n\n```tsx\nimport { Files, Result, useQuery } from \"ignotum/client\";\n\nconst profile = useQuery(api.profiles.get, { id: profileId });\n\nreturn Result.match(profile, {\n pending: () => <span>Loading...</span>,\n value: (value) =>\n value.avatar === undefined ? (\n <span>No avatar</span>\n ) : (\n <img src={Files.url(value.avatar)} alt=\"\" />\n ),\n error: () => <span>Unavailable</span>,\n});\n```\n\nThe returned same-origin URL works in images, links, CSS URLs, and `fetch`. It is not a permanent\nURL. It remains valid while the query subscription and its current result authorize that file.\nUnsubscribing, disconnecting, replacing the result, making the query stale, or deleting the final\ndatabase reference revokes access. Do not save the URL as application data.\n\nCalling `Files.url` on a native file or a value that did not come from an active query throws.\n\n## Copy and remove references\n\nA file value returned by a query can be passed to another compatible mutation argument:\n\n```tsx\nconst copyAvatar = useMutation(api.profiles.copyAvatar);\nvoid copyAvatar({ targetId, avatar: source.avatar });\n```\n\nThe destination validator must allow the file's format and size. Removing one field or document\nremoves that reference. Deleting the final database reference deletes the application file. A\nstale Web value cannot restore it afterward.\n\nDocument changes and file references commit together. A failed mutation does not leave references\npartially changed.\n\n## Limits\n\n- One file is limited to 10 MiB.\n- One mutation may add up to 8 new files and 25 MiB of file data.\n- One app may have up to 100 MiB of uncommitted file data.\n- One query result may contain up to 256 distinct files.\n\nApplication files differ from files in `public`. Public assets belong to a deployment and have\nstable paths. See [Public assets](public-assets.md).\n\nNext: [Deployments](deployments.md)\n"],
|
|
34481
|
-
["functions.md", "---\ntitle: Functions overview\nlabel: Overview\ndescription: Define callable application logic and use its generated API.\nsection: Functions\nslug: functions\norder: 21\n---\n\n# Functions overview\n\nQueries and mutations are callable application functions. Define them in `.ts` files directly\ninside `server`. The file name becomes the generated API module, and each exported function keeps\nits export name:\n\n```text\nserver/todos.ts -> api.todos.list\nserver/users.ts -> api.users.get\n```\n\nFunction module names must be valid TypeScript identifiers. Ignotum ignores `schema.ts`,\n`index.ts`, files ending in `.test.ts` or `.spec.ts`, and names beginning with `_`. Nested files do\nnot become API modules.\n\n## Definitions\n\nImport schema-bound builders from the generated server file:\n\n```ts\nimport { mutation, query, values } from \"@/_generated/server.js\";\n```\n\nKeep the `.js` suffix. The generated definitions provide types and runtime validation for\narguments, successful returns, and application errors.\n\nThe `args` object describes one object parameter. Omit it when the function takes no arguments.\nOmit `returns` only when the handler returns `void`. Ignotum does not infer a runtime validator\nfrom a TypeScript return type.\n\nHandlers are generator functions. Use `yield*` for database and `Result` operations, then use an\nordinary `return` for a successful value. Do not mark a handler `async`.\n\n## Generated API\n\nCode generation writes browser references such as `api.todos.list` to `_generated/api.ts`. Web\ncode passes a reference to `useQuery`, `usePaginatedQuery`, or `useMutation`. The reference carries\nthe function's arguments, result, and errors into the call's TypeScript type.\n\n[Queries](queries.md) and [Mutations](mutations.md) cover each complete workflow.\n\n## Runtime boundary\n\nFunctions can import `_generated/server.ts`, `_generated/types.ts`, other server modules, shared\nmodules, and supported dependencies. They cannot import client files, `_generated/api.ts`,\n`ignotum/client`, Node built-ins, native add-ons, or dynamic imports.\n\nHosted functions have no direct network, filesystem, environment, or process access. Use
|
|
34482
|
-
["generated-code.md", "---\ntitle: Generated code\ndescription: Understand generated function references, builders, and data types.\nsection: Development\nslug: generated-code\norder: 61\n---\n\n# Generated code\n\nIgnotum generates the bindings that connect the schema, functions, and Web code. The development\nserver updates them before startup and when the relevant source changes.\n\nRun generation directly before a typecheck or an editor session without the development server:\n\n```sh\nnpx ignotum codegen\nnpx tsc --noEmit\n```\n\nCode generation writes three files:\n\n| File | Contents |\n| ---------------------- | ------------------------------------------------------- |\n| `_generated/server.ts` | Schema-bound `query`, `mutation`, and `values` exports. |\n| `_generated/api.ts` | Web references such as `api.todos.list`. |\n| `_generated/types.ts` | `DataModel`, `Doc`, and `Id` types. |\n\nDo not edit these files. Ignotum refuses to overwrite a conflicting file that lacks its generated\nheader. Move the conflicting file elsewhere and run `codegen` again.\n\nKeep the `.js` suffix when importing a generated module:\n\n```ts\nimport { mutation, query, values } from \"@/_generated/server.js\";\nimport { api } from \"@/_generated/api.js\";\nimport type { Doc, Id } from \"@/_generated/types.js\";\n```\n\nThe API file changes when function modules or exports change. Server builders and data types change\nwith the schema. See [Functions](functions.md) for function module naming
|
|
34935
|
+
["functions.md", "---\ntitle: Functions overview\nlabel: Overview\ndescription: Define callable application logic and use its generated API.\nsection: Functions\nslug: functions\norder: 21\n---\n\n# Functions overview\n\nQueries and mutations are callable application functions. Define them in `.ts` files directly\ninside `server`. The file name becomes the generated API module, and each exported function keeps\nits export name:\n\n```text\nserver/todos.ts -> api.todos.list\nserver/users.ts -> api.users.get\n```\n\nFunction module names must be valid TypeScript identifiers. Ignotum ignores `schema.ts`, `env.ts`,\n`index.ts`, files ending in `.test.ts` or `.spec.ts`, and names beginning with `_`. Nested files do\nnot become API modules.\n\n## Definitions\n\nImport schema-bound builders from the generated server file:\n\n```ts\nimport { mutation, query, values } from \"@/_generated/server.js\";\n```\n\nKeep the `.js` suffix. The generated definitions provide types and runtime validation for\narguments, successful returns, and application errors.\n\nThe `args` object describes one object parameter. Omit it when the function takes no arguments.\nOmit `returns` only when the handler returns `void`. Ignotum does not infer a runtime validator\nfrom a TypeScript return type.\n\nHandlers are generator functions. Use `yield*` for database and `Result` operations, then use an\nordinary `return` for a successful value. Do not mark a handler `async`.\n\n## Generated API\n\nCode generation writes browser references such as `api.todos.list` to `_generated/api.ts`. Web\ncode passes a reference to `useQuery`, `usePaginatedQuery`, or `useMutation`. The reference carries\nthe function's arguments, result, and errors into the call's TypeScript type.\n\n[Queries](queries.md) and [Mutations](mutations.md) cover each complete workflow.\n\n## Runtime boundary\n\nFunctions can import `_generated/server.ts`, `_generated/types.ts`, other server modules, shared\nmodules, and supported dependencies. They cannot import client files, `_generated/api.ts`,\n`ignotum/client`, Node built-ins, native add-ons, or dynamic imports.\n\nHosted functions have no direct network, filesystem, process environment, or process access. Use\nthe APIs provided through the function context. Typed server configuration is available through\n`ctx.env`; see [Environment variables](environment-variables.md).\n\nSee [Values](values.md) for validators, [Errors](errors.md) for the typed error model, and\n[Limits](limits.md) for execution and data limits.\n\nNext: [Queries](queries.md)\n"],
|
|
34936
|
+
["generated-code.md", "---\ntitle: Generated code\ndescription: Understand generated function references, builders, and data types.\nsection: Development\nslug: generated-code\norder: 61\n---\n\n# Generated code\n\nIgnotum generates the bindings that connect the schema, functions, and Web code. The development\nserver updates them before startup and when the relevant source changes.\n\nRun generation directly before a typecheck or an editor session without the development server:\n\n```sh\nnpx ignotum codegen\nnpx tsc --noEmit\n```\n\nCode generation writes three files:\n\n| File | Contents |\n| ---------------------- | ------------------------------------------------------- |\n| `_generated/server.ts` | Schema-bound `query`, `mutation`, and `values` exports. |\n| `_generated/api.ts` | Web references such as `api.todos.list`. |\n| `_generated/types.ts` | `DataModel`, `Doc`, and `Id` types. |\n\nDo not edit these files. Ignotum refuses to overwrite a conflicting file that lacks its generated\nheader. Move the conflicting file elsewhere and run `codegen` again.\n\nKeep the `.js` suffix when importing a generated module:\n\n```ts\nimport { mutation, query, values } from \"@/_generated/server.js\";\nimport { api } from \"@/_generated/api.js\";\nimport type { Doc, Id } from \"@/_generated/types.js\";\n```\n\nThe API file changes when function modules or exports change. Server builders and data types change\nwith the schema. When `server/env.ts` exists, `_generated/server.ts` also types `ctx.env`; it does\nnot export environment values or `Secret`. See [Functions](functions.md) for function module naming\nrules.\n"],
|
|
34483
34937
|
["guarantees.md", "---\ntitle: Guarantees\ndescription: Understand consistency, atomicity, ordering, retries, and realtime updates.\nsection: Reference\nslug: guarantees\norder: 70\n---\n\n# Guarantees\n\nThis page states the behavior application code can rely on. It also marks the boundaries where\nIgnotum deliberately makes no promise.\n\n## Queries read one committed state\n\nA completed query result corresponds to one committed app-state revision. It does not combine\nreads from different successful mutations.\n\nQueries may run while mutations are committing. If app data changes during a hosted query,\nIgnotum discards that attempt and runs it again. The hosted runtime makes at most four consistency\nattempts within 15 seconds. If changes keep preventing a stable result, the query fails instead of\nreturning a mixed result.\n\nQueries have no write access. A query result can be a success, a declared application error, or an\ninternal failure. All three are tied to the revision observed by that execution.\n\n## Mutations are atomic\n\nA mutation either commits all of its writes or none of them.\n\nIgnotum commits only after the handler returns a value that matches its return validator. These\noutcomes commit no writes:\n\n- a declared application error;\n- an uncaught `DocumentNotFound` or other application error;\n- a thrown exception;\n- an invalid return value or error value;\n- an execution, data, or storage limit failure;\n- an internal platform failure before commit.\n\nDocument changes, index changes, the mutation result used for retries, and the new app-state\nrevision become durable together. A caller that receives a successful mutation result can rely on\nthe writes having committed.\n\n## Mutations are serialized per app\n\nOnly one mutation changes a given app at a time. A mutation sees every mutation that committed\nbefore it began, and it sees its own earlier writes. No other mutation can interleave between its\nreads and writes.\n\nDifferent apps do not share this serialization. Queries may also execute while a mutation is\nrunning, subject to the consistent-query rule above.\n\nDo not infer an ordering between mutation calls started concurrently. If one business action must\nhappen after another, await the first mutation result before starting the second, or combine the\nsteps in one mutation.\n\n## Reconnect retries do not repeat a retained mutation\n\nEach browser mutation call has one invocation ID. The client retains the call while a connection\nis unavailable and sends the same ID, function, and arguments after a reconnect to the same\ndeployment.\n\nIgnotum records the first completed result for that invocation ID. A retry with the same input\nreturns that result without running the handler or applying its writes again. This applies to\nsuccessful mutations and declared application errors.\n\nA reused ID with different input is rejected. An invocation's input includes the deployment,\nfunction, operation kind, and arguments.\n\nHosted retry records last for seven days and are capped at the newest 10,000 results per app. The\ndeduplication guarantee ends when a record expires or is evicted. Use an application-level unique\nkey when duplicate prevention must last longer.\n\nThe client does not replay pending mutations after it detects a different deployment. It reloads\nthe page instead, because running an old call against new server code would change its meaning.\n\n## Realtime queries follow committed writes\n\nWhile a query remains subscribed and the connection can make progress, Ignotum refreshes it after\na successful mutation that may affect one of its reads. Failed mutations cause no refresh because\nthey changed no data.\n\nDependency tracking follows the database operation:\n\n| Read | Writes that may refresh it |\n| ---------------- | ----------------------------------------------------------------------- |\n| `find` or `get` | A change to that document. |\n| Full table query | A change in that table. |\n| Indexed query | A change whose old or new index position intersects the relevant range. |\n\nBounded indexed reads track the part of the range that can affect their result. Ignotum may refresh\na query whose returned value ends up unchanged. It favors an unnecessary rerun over missing a\nrelevant write.\n\nThe mutation promise does not wait for every affected query to render its next value. The commit\nis complete when the promise resolves, while query refresh and UI rendering continue separately.\n\nAfter a temporary disconnect, the browser keeps the latest settled query result. It recreates the\nsubscription after reconnecting. Ignotum checks recovered hosted query state against current app\nstate before serving it, so a commit that happened during recovery cannot make an older cached\nresult look current.\n\nThere is no delivery-time guarantee. A broken connection or unavailable runtime can delay an\nupdate, and infrastructure failures may reach the UI error boundary. Realtime queries are current\nresults, not an event log. Intermediate values may be coalesced when several mutations happen\nbefore a refresh completes.\n\n## Results do not move backward\n\nEach query snapshot carries the revision it observed. The client ignores a snapshot older than the\nlatest revision already accepted for that subscription. A slow earlier refresh cannot replace a\nnewer result.\n\nThe revision is an ordering value for complete query results. It is not exposed as an application\nevent number, and the public client API does not provide a change stream or resume token.\n\n## Pagination is not a frozen snapshot\n\nOne page is consistent in the same way as any other query result. Several pages loaded at different\ntimes do not share one historical snapshot.\n\nThe cursor records the query and the last returned position. It prevents using a cursor with a\ndifferent table, index, range, or direction. It does not freeze rows or index positions. If data is\ninserted, deleted, or reordered between page loads, an item can move across the cursor boundary.\n\nThe client subscribes to every loaded page, so earlier pages can also change while the list is\nopen. Render with document IDs and treat the combined list as a live ordered view.\n\n## Deployment activation is all or nothing\n\nA deployment contains its client and server code together. Ignotum changes the active deployment\nonly after the complete upload passes validation and any new indexes are ready. A build, upload,\nvalidation, or compatibility failure leaves the current deployment active.\n\nActivation reloads connected clients. A session never intentionally combines the client from one\ndeployment with functions from another.\n\n## What is not guaranteed\n\nIgnotum does not promise:\n\n- an order for mutation calls started concurrently;\n- permanent mutation deduplication beyond the published retention limits;\n- delivery of every intermediate query value;\n- a time bound for realtime updates or reconnects to succeed;\n- snapshot isolation across several pagination calls;\n- uniqueness for an application index;\n- uninterrupted hosted availability during a platform or persistent-data outage.\n\n[Limits](limits.md) lists the bounds that qualify these guarantees.\n"],
|
|
34484
34938
|
["how-ignotum-works.md", "---\ntitle: How Ignotum works\ndescription: Understand how data, functions, files, Web code, and deployments fit together.\nsection: Start\nslug: how-ignotum-works\norder: 3\n---\n\n# How Ignotum works\n\nAn Ignotum app is one project with a database, functions, application files, and a browser-facing\nWeb part. A deployment publishes all of them together.\n\n## Database and functions\n\nThe schema names the app's tables and describes every stored field. Queries read the database.\nMutations read and write it in atomic operations.\n\nEach exported query or mutation receives a generated reference. Web code uses that reference\ninstead of a handwritten route or request type. The function's argument, result, and application\nerror definitions determine the corresponding TypeScript types in the browser.\n\n## Realtime results\n\nCalling a query from Web code creates a subscription. Ignotum records the database reads made by\nthe query and refreshes it after a committed mutation may have changed those reads. The UI receives\nthe current complete result rather than a stream of database operations.\n\nSeveral components using the same query and arguments share one browser subscription. A temporary\ndisconnect keeps the latest settled result while the client reconnects and recreates active\nsubscriptions.\n\n## Files\n\nApplication files are values that can appear in mutation arguments and database documents. Web\ncode passes a browser `File` to a mutation. The mutation stores the validated file value in a\ndocument, and a query can return it to Web code with a URL for the current query result.\n\nFiles in the top-level `public` directory are different. They are fixed deployment assets with\nstable paths and do not belong to database documents.\n\n## Web\n\nThe Web part starts at `client/index.tsx`. Its default `app(...)` export supplies the page title and\nroot Preact component. It calls functions through generated references and renders their typed\nresults.\n\n## Deployment\n\n`ignotum deploy` publishes the Web code, functions, schema, and public assets as one deployment.\nActivation changes them together. Existing browser sessions reload when they detect the new\ndeployment.\n\nNext: [Project structure](project-structure.md)\n"],
|
|
34485
34939
|
["index.md", "---\ntitle: Ignotum\nlabel: Introduction\ndescription: Learn the application model used by Ignotum.\nsection: Start\nslug: /\norder: 1\n---\n\n# Ignotum\n\n> Ignotum is currently in private alpha. APIs and hosted service behavior may change before public\n> availability.\n\nIgnotum is an application cloud for TypeScript apps. An app defines its data, callable functions,\nbrowser UI, and files in one project. The CLI runs the project locally and deploys it as one unit.\n\nThe database stores application data. Queries read that data, mutations change it, and generated\nreferences let the Web part call both with matching TypeScript types. Active queries receive new\nresults after relevant mutations commit.\n\nStart with the [Quickstart](quickstart.md). It creates a small app and follows the path from schema\nto deployment. [How Ignotum works](how-ignotum-works.md) explains how the parts fit together, and\n[Project structure](project-structure.md) explains the `client`, `server`, and `shared` directories.\n\nNext: [Quickstart](quickstart.md)\n"],
|
|
34486
34940
|
["indexes.md", "---\ntitle: Indexes\ndescription: Define indexes and use them for ranges and ordering.\nsection: Database\nslug: indexes\norder: 15\n---\n\n# Indexes\n\nAn index gives documents an order that functions can select and bound. Declare each index with its\ntable in `server/schema.ts`:\n\n```ts\ntodos: table({\n channel: values.string(),\n priority: values.integer(),\n text: values.string(),\n}).index(\"by_channel_priority\", [\"channel\", \"priority\"]),\n```\n\nIndex fields must be required booleans, dates, IDs, integers, numbers, strings, or literals. Field\norder matters. `by_channel_priority` supports a range for one channel, or for one channel and its\npriorities. It is not a priority-only index.\n\nAn index name must be non-empty, cannot begin with `$`, and is limited to 64 UTF-8 bytes. A table\ncannot repeat a name, repeat a field inside one index, or declare two indexes with the same fields\nin the same order. See [Limits](limits.md) for index counts and key size.\n\n## Select an index\n\nUse the declared name in a function:\n\n```ts\nconst todos =\n yield *\n ctx.db\n .query(\"todos\")\n .index(\"by_channel_priority\", (range) =>\n range.eq(\"channel\", args.channel).gte(\"priority\", args.minimumPriority),\n )\n .collect();\n```\n\nThe range builder follows the declared field order. Match any number of leading fields with `eq`,\nthen optionally bound the next field:\n\n| Method | Bound |\n| ------------------- | ------------------------------------------------- |\n| `eq(field, value)` | Equal to `value`. May continue to the next field. |\n| `gt(field, value)` | Greater than `value`. |\n| `gte(field, value)` | Greater than or equal to `value`. |\n| `lt(field, value)` | Less than `value`. |\n| `lte(field, value)` | Less than or equal to `value`. |\n\nChain one lower and one upper bound on the same field for a two-sided range:\n\n```ts\nconst thisWeek =\n yield *\n ctx.db\n .query(\"events\")\n .index(\"by_workspace_start\", (range) =>\n range\n .eq(\"workspaceId\", args.workspaceId)\n .gte(\"start\", args.weekStart)\n .lt(\"start\", args.nextWeek),\n )\n .collect();\n```\n\nCalling `index` without a range reads the whole index in its declared order:\n\n```ts\nconst byPriority = yield * ctx.db.query(\"todos\").index(\"by_priority\").collect();\n```\n\nEvery index orders equal declared values by `createdAt`, then `id`. After matching all declared\nfields with `eq`, those fields are also available to the range builder.\n\n`order(\"desc\")` reverses the complete index order. It cannot sort by a field outside the selected\nindex.\n\nUse [Pagination](pagination.md) for large or incremental index results.\n"],
|
|
34487
|
-
["limits.md", "---\ntitle: Limits\ndescription: Understand hosted limits for functions, data, connections, and deployments.\nsection: Reference\nslug: limits\norder: 71\n---\n\n# Limits\n\nThese limits apply to hosted Ignotum apps. The local dev server does not reproduce every hosted\nlimit, so an operation that works locally can still be rejected after deployment.\n\nIgnotum reports sync admission, execution time, result size, and app storage quota failures as\n`ResourceLimitExceeded`. Some lower-level document, collection, memory, stack, or operation limit\nfailures become `InternalServerError` instead. A deployment that exceeds a limit fails before\nactivation, so the current deployment stays active.\n\n## Functions\n\n| Limit | Value |\n| --------------------------------------- | ---------: |\n| Function arguments | 16 KiB |\n| Function result | 1 MiB |\n| Execution time | 10 seconds |\n| Memory | 32 MiB |\n| Stack | 512 KiB |\n| Ignotum operations during one execution | 1,000 |\n\nArgument and result sizes use their JSON representation. The result limit applies to successful\nresults and application errors.\n\nAn Ignotum operation is a call through the function context, such as a database read or write. A\nfunction stops when it reaches the execution time or operation limit.\n\nIgnotum retries a query when app data changes while it runs. One logical execution makes at\nmost four attempts and stops after 15 seconds. The query fails temporarily if it cannot read a\nconsistent result in that time.\n\n## App data\n\n| Limit | Value |\n| --------------------------------------- | -------------: |\n| Stored fields in one document | 256 KiB |\n| Documents returned by `collect()` | 1,000 |\n| Documents returned by `take()` | 1,000 |\n| Documents returned by one page | 1,000 |\n| Document fields returned by `collect()` | 1 MiB |\n| Stored app data | 64 MiB per app |\n\nThe 1 MiB function-result limit still applies to `collect()`. Document IDs and timestamps take some\nspace in that result, so a collection can reach the result limit before its fields reach 1 MiB.\n\nIf a mutation would take the app over its storage limit, Ignotum rolls back the whole mutation.\nThe quota covers stored documents, application indexes, and the records Ignotum keeps to process\nmutations safely. Deployment files do not count as stored app data.\n\n## Schema indexes\n\n| Limit | Value |\n| ---------------------------- | -------------: |\n| Indexes on one table | 16 |\n| Indexes in one schema | 128 |\n| Declared fields in one index | 8 |\n| Index name | 64 UTF-8 bytes |\n| One encoded index key | 4 KiB |\n\nEvery index also includes `createdAt` and `id` after its declared fields. The key limit applies to\nthe combined encoded field values and those final ordering values. A document must fit every index\ndeclared for its table.\n\n## Realtime connections and calls\n\n| Limit | Value |\n| ------------------------- | ---------------------------: |\n| Live connections | 256 per app |\n| Incoming realtime message | 64 KiB |\n| Subscriptions | 128 per connection |\n| Active unique queries | 64 per app |\n| Realtime query refresh | 4 attempts within 15 seconds |\n| Concurrent operations | 32 per app |\n| Mutation execution | 1 at a time per app |\n| Unresolved mutations | 32 per app |\n| Mutation calls | 60 per minute per app |\n\nA unique query is one function and argument combination. Several components or browser tabs can\nsubscribe to the same unique query without using another unique-query slot.\n\nA realtime refresh can repeat a query execution when app data changes again during the refresh.\n\nThe unresolved-mutation limit protects calls whose outcome is not known yet, such as a call waiting\nfor a retry after a connection failure. Once Ignotum receives a final result, that call no longer\ncounts toward the limit.\n\nIgnotum runs mutations for the same app one at a time. Queries and other live calls can still use\nthe remaining concurrent-operation slots.\n\n## Mutation retries\n\nThe client keeps the ID of a pending mutation and reuses it after a reconnect. Ignotum remembers up\nto 10,000 mutation results per app for seven days. During that period, a retry returns the recorded\nresult instead of running the mutation again.\n\nDo not treat this as permanent duplicate protection. A mutation may run again after its record has\nexpired or fallen outside the 10,000 most recent results.\n\n## Deployments\n\n| Limit | Value |\n| ------------------------------ | ------: |\n| Files listed in one deployment | 512 |\n| One listed file | 16 MiB |\n| Deployment inventory | 1 MiB |\n| Server files combined | 64 MiB |\n| Listed files combined | 128 MiB |\n\nThe inventory contains at most 512 listed files. Its own `inventory.json` upload is additional.\nRequests with JSON bodies to `api.ignotum.cloud` are also limited to 1 MiB.\n\nIgnotum never deletes the active deployment. It also protects the three newest deployments that\ncompleted successfully. An older inactive deployment becomes eligible for deletion after seven\ndays. An unfinished upload becomes eligible after 24 hours.\n"],
|
|
34488
|
-
["local-development.md", "---\ntitle: Local development\ndescription: Run an Ignotum app locally and manage its local data.\nsection: Development\nslug: local-development\norder: 60\n---\n\n# Local development\n\nRun the development server from the app directory:\n\n```sh\nnpx ignotum dev\n```\n\nThe command requires `client/index.tsx` and `server/schema.ts`. It updates generated code, starts\nthe Web and function runtimes, and prints the local URL. The default is\n<http://127.0.0.1:3210>.\n\nClient changes use hot reload. Changes to server `.ts` files reload the function modules and\nrefresh active queries. Adding or removing a function module also updates the API references.\n\nThe development server uses one local database for the app directory. A second server for the same\napp fails with a message that identifies the process holding the database lock.\n\n## Options\n\nUse flags to change the listener or open a browser:\n\n```sh\nnpx ignotum dev --host 0.0.0.0 --port 3000 --open\n```\n\n| Flag | Default | Meaning |\n| -------- | ----------- | ------------------------------------------------------------------ |\n| `--host` | `127.0.0.1` | Address used by the local server. |\n| `--port` | `3210` | TCP port from 1 through 65,535. The command fails if it is in use. |\n| `--open` | off | Open the app in the default browser after startup. |\n\n## Local data\n\nLocal data survives restarts and lives under `.ignotum/dev`. The generated project ignores\n`.ignotum` in Git.\n\nStop the development server before resetting the database:\n\n```sh\nnpx ignotum dev db reset\n```\n\nThe command refuses to reset data while a live development server holds the lock. A successful\nreset removes the local database. The next start creates an empty database from the current schema.\n\nLocal development does not reproduce every hosted resource limit or the hosted function runtime.\nRun the typecheck and review [Limits](limits.md) before deployment.\n\nNext: [Generated code](generated-code.md)\n"],
|
|
34941
|
+
["limits.md", "---\ntitle: Limits\ndescription: Understand hosted limits for functions, data, connections, and deployments.\nsection: Reference\nslug: limits\norder: 71\n---\n\n# Limits\n\nThese limits apply to hosted Ignotum apps. The local dev server does not reproduce every hosted\nlimit, so an operation that works locally can still be rejected after deployment.\n\nIgnotum reports sync admission, execution time, result size, and app storage quota failures as\n`ResourceLimitExceeded`. Some lower-level document, collection, memory, stack, or operation limit\nfailures become `InternalServerError` instead. A deployment that exceeds a limit fails before\nactivation, so the current deployment stays active.\n\n## Functions\n\n| Limit | Value |\n| --------------------------------------- | ---------: |\n| Function arguments | 16 KiB |\n| Function result | 1 MiB |\n| Execution time | 10 seconds |\n| Memory | 32 MiB |\n| Stack | 512 KiB |\n| Ignotum operations during one execution | 1,000 |\n\nArgument and result sizes use their JSON representation. The result limit applies to successful\nresults and application errors.\n\nOne server environment may contain at most 128 variables and 64 KiB of values as JSON.\n\nAn Ignotum operation is a call through the function context, such as a database read or write. A\nfunction stops when it reaches the execution time or operation limit.\n\nIgnotum retries a query when app data changes while it runs. One logical execution makes at\nmost four attempts and stops after 15 seconds. The query fails temporarily if it cannot read a\nconsistent result in that time.\n\n## App data\n\n| Limit | Value |\n| --------------------------------------- | -------------: |\n| Stored fields in one document | 256 KiB |\n| Documents returned by `collect()` | 1,000 |\n| Documents returned by `take()` | 1,000 |\n| Documents returned by one page | 1,000 |\n| Document fields returned by `collect()` | 1 MiB |\n| Stored app data | 64 MiB per app |\n\nThe 1 MiB function-result limit still applies to `collect()`. Document IDs and timestamps take some\nspace in that result, so a collection can reach the result limit before its fields reach 1 MiB.\n\nIf a mutation would take the app over its storage limit, Ignotum rolls back the whole mutation.\nThe quota covers stored documents, application indexes, and the records Ignotum keeps to process\nmutations safely. Deployment files do not count as stored app data.\n\n## Schema indexes\n\n| Limit | Value |\n| ---------------------------- | -------------: |\n| Indexes on one table | 16 |\n| Indexes in one schema | 128 |\n| Declared fields in one index | 8 |\n| Index name | 64 UTF-8 bytes |\n| One encoded index key | 4 KiB |\n\nEvery index also includes `createdAt` and `id` after its declared fields. The key limit applies to\nthe combined encoded field values and those final ordering values. A document must fit every index\ndeclared for its table.\n\n## Realtime connections and calls\n\n| Limit | Value |\n| ------------------------- | ---------------------------: |\n| Live connections | 256 per app |\n| Incoming realtime message | 64 KiB |\n| Subscriptions | 128 per connection |\n| Active unique queries | 64 per app |\n| Realtime query refresh | 4 attempts within 15 seconds |\n| Concurrent operations | 32 per app |\n| Mutation execution | 1 at a time per app |\n| Unresolved mutations | 32 per app |\n| Mutation calls | 60 per minute per app |\n\nA unique query is one function and argument combination. Several components or browser tabs can\nsubscribe to the same unique query without using another unique-query slot.\n\nA realtime refresh can repeat a query execution when app data changes again during the refresh.\n\nThe unresolved-mutation limit protects calls whose outcome is not known yet, such as a call waiting\nfor a retry after a connection failure. Once Ignotum receives a final result, that call no longer\ncounts toward the limit.\n\nIgnotum runs mutations for the same app one at a time. Queries and other live calls can still use\nthe remaining concurrent-operation slots.\n\n## Mutation retries\n\nThe client keeps the ID of a pending mutation and reuses it after a reconnect. Ignotum remembers up\nto 10,000 mutation results per app for seven days. During that period, a retry returns the recorded\nresult instead of running the mutation again.\n\nDo not treat this as permanent duplicate protection. A mutation may run again after its record has\nexpired or fallen outside the 10,000 most recent results.\n\n## Deployments\n\n| Limit | Value |\n| ------------------------------ | ------: |\n| Files listed in one deployment | 512 |\n| One listed file | 16 MiB |\n| Deployment inventory | 1 MiB |\n| Server files combined | 64 MiB |\n| Listed files combined | 128 MiB |\n\nThe inventory contains at most 512 listed files. Its own `inventory.json` upload is additional.\nRequests with JSON bodies to `api.ignotum.cloud` are also limited to 1 MiB.\n\nIgnotum never deletes the active deployment. It also protects the three newest deployments that\ncompleted successfully. An older inactive deployment becomes eligible for deletion after seven\ndays. An unfinished upload becomes eligible after 24 hours.\n"],
|
|
34942
|
+
["local-development.md", "---\ntitle: Local development\ndescription: Run an Ignotum app locally and manage its local data.\nsection: Development\nslug: local-development\norder: 60\n---\n\n# Local development\n\nRun the development server from the app directory:\n\n```sh\nnpx ignotum dev\n```\n\nThe command requires `client/index.tsx` and `server/schema.ts`. It updates generated code, starts\nthe Web and function runtimes, and prints the local URL. The default is\n<http://127.0.0.1:3210>.\n\nClient changes use hot reload. Changes to server `.ts` files reload the function modules and\nrefresh active queries. Adding or removing a function module also updates the API references.\nChanges to `.env.ignotum` validate and reload `ctx.env`, then refresh active queries. Functions stay\nunavailable after an invalid edit until the file is fixed.\n\nThe development server uses one local database for the app directory. A second server for the same\napp fails with a message that identifies the process holding the database lock.\n\n## Options\n\nUse flags to change the listener or open a browser:\n\n```sh\nnpx ignotum dev --host 0.0.0.0 --port 3000 --open\n```\n\n| Flag | Default | Meaning |\n| -------- | ----------- | ------------------------------------------------------------------ |\n| `--host` | `127.0.0.1` | Address used by the local server. |\n| `--port` | `3210` | TCP port from 1 through 65,535. The command fails if it is in use. |\n| `--open` | off | Open the app in the default browser after startup. |\n\n## Local data\n\nLocal data survives restarts and lives under `.ignotum/dev`. The generated project ignores\n`.ignotum` in Git.\n\nStop the development server before resetting the database:\n\n```sh\nnpx ignotum dev db reset\n```\n\nThe command refuses to reset data while a live development server holds the lock. A successful\nreset removes the local database. The next start creates an empty database from the current schema.\n\nLocal development does not reproduce every hosted resource limit or the hosted function runtime.\nRun the typecheck and review [Limits](limits.md) before deployment.\n\nNext: [Generated code](generated-code.md)\n"],
|
|
34489
34943
|
["mutations.md", "---\ntitle: Mutations\ndescription: Define and call typed mutations.\nsection: Functions\nslug: mutations\norder: 23\n---\n\n# Mutations\n\nA mutation reads and changes application data in one atomic operation. Web code calls it through a\ngenerated reference and receives a typed value or application error.\n\n## Define a mutation\n\nImport `mutation` and schema-bound `values` from `_generated/server.ts`:\n\n```ts\nimport { mutation, values } from \"@/_generated/server.js\";\n\nexport const create = mutation({\n args: { text: values.string() },\n returns: values.id(\"todos\"),\n\n handler: function* (ctx, { text }) {\n return yield* ctx.db.insert(\"todos\", {\n text,\n completed: false,\n });\n },\n});\n```\n\nMutation `ctx.db` includes all read methods plus `insert`, `patch`, `replace`, and `delete`. See\n[Writing data](writing-data.md) for their exact behavior.\n\nAll reads and writes in one handler belong to one transaction. A successful return commits them\ntogether. An application error, thrown exception, invalid return, limit failure, or platform\nfailure commits none of them. Other mutations for the same app do not interleave with the handler.\n\n## Call a mutation from Web code\n\n`useMutation` takes the generated reference and returns a stable typed function:\n\n```tsx\nimport { Result, useMutation } from \"ignotum/client\";\nimport { api } from \"@/_generated/api.js\";\n\nconst createTodo = useMutation(api.todos.create);\n\nvoid createTodo({ text }).then(\n Result.match({\n value: (id) => console.log(id),\n error: {\n InvalidTodoText: ({ text }) => console.log(`Invalid text: ${text}`),\n TodoLimitReached: ({ limit }) => console.log(`The limit is ${limit}`),\n },\n internalError: ({ requestId }) => console.log(`Request ${requestId} failed.`),\n }),\n);\n```\n\nAn argument-free mutation returns a zero-argument function:\n\n```tsx\nconst clearTodos = useMutation(api.todos.clear);\nvoid clearTodos();\n```\n\nThe promise resolves with a settled `Result` after the mutation commits or returns a declared\napplication error. It rejects for client or protocol failures, including unsupported argument\nvalues, an unavailable function, or a hosted limit that prevents the call from starting.\n\n## Handle the result\n\nThe client `Result` export contains `match`. Server operations such as `Result.fail`,\n`Result.succeed`, `Result.try`, `yield*`, and `.catch()` are not available in browser code.\n\nApplication errors use the `error` handler. A single function receives their union. An object must\nhandle every `_tag` separately. The optional `internalError` handler receives an internal failure's\nrequest ID. Without that handler, `Result.match` throws the error so an error boundary can handle\nit. See [Errors](errors.md).\n\n## Reconnects and retries\n\nThe client assigns one invocation ID to a mutation call. If the connection drops before the result\narrives, it keeps the promise pending and sends the same invocation after a matching reconnect.\nIgnotum returns a retained result instead of applying the mutation twice.\n\nA deployment change reloads the page and does not replay pending work against different function\ncode. Retry records have retention limits and are not permanent duplicate protection for business\noperations. See [Guarantees](guarantees.md) and [Limits](limits.md).\n\nSuccessful mutations can refresh active queries after the commit. The mutation result can reach\nits caller before every affected query renders its next value.\n\nNext: [Files](files.md)\n"],
|
|
34490
34944
|
["pagination.md", "---\ntitle: Pagination\ndescription: Return cursor-based pages and load them from Web code.\nsection: Database\nslug: pagination\norder: 16\n---\n\n# Pagination\n\nA paginated query accepts `values.pagination()` and returns `values.page(...)`:\n\n```ts\nconst Todo = values.doc(\"todos\");\n\nexport const list = query({\n args: {\n completed: values.boolean(),\n pagination: values.pagination(),\n },\n returns: values.page(Todo),\n\n handler: function* (ctx, { completed, pagination }) {\n return yield* ctx.db\n .query(\"todos\")\n .index(\"by_completed\", (range) => range.eq(\"completed\", completed))\n .order(\"desc\")\n .paginate(pagination);\n },\n});\n```\n\nThe result has `items` and `nextCursor`. A `null` cursor means the query was exhausted. Pass a\nnon-null cursor back with the same table, index, range, and order. A cursor from another query is\nrejected. Treat cursors as opaque strings.\n\nPage sizes are integers from 1 through 1,000. A page returns no more than the requested size.\n\n## Load pages in Web code\n\n`usePaginatedQuery` supplies the cursor and page size. Callers pass only the function's other\narguments:\n\n```tsx\nimport { Result, usePaginatedQuery } from \"ignotum/client\";\n\nconst events = usePaginatedQuery(api.events.list, {\n project: \"api\",\n level: \"error\",\n});\n\nreturn Result.match(events, {\n pending: () => <p>Loading...</p>,\n value: ({ items, loadMore, status }) => (\n <>\n {items.map((event) => (\n <p key={event.id}>{event.message}</p>\n ))}\n {status !== \"Exhausted\" && (\n <button disabled={status === \"LoadingMore\"} onClick={loadMore}>\n {status === \"LoadingMore\" ? \"Loading...\" : \"Load more\"}\n </button>\n )}\n </>\n ),\n});\n```\n\nThe default page size is 20. Pass an options object as the third argument to use a value from 1\nthrough 1,000:\n\n```tsx\nconst events = usePaginatedQuery(api.events.list, { project: \"api\" }, { pageSize: 50 });\n```\n\nIf pagination is the query's only argument, omit the second argument. `Query.skip` is accepted in\nplace of arguments and keeps the result pending.\n\nThe successful value has these fields:\n\n| Field | Meaning |\n| ------------ | --------------------------------------------- |\n| `items` | Items from every loaded page, in query order. |\n| `loadMore()` | Requests one more page. |\n| `status` | `CanLoadMore`, `LoadingMore`, or `Exhausted`. |\n\nEarlier items remain visible while another page loads. Changing the query arguments or page size\nstarts again at the first page.\n\n## Live page behavior\n\nPages do not freeze the database at the first page's revision. Each call reads current committed\nstate and continues after its cursor position. Data that moves across that position between calls\ncan appear on a different page or no longer appear in the remaining pages.\n\nEach loaded page remains subscribed. Use item IDs when rendering and allow items to move in a\nchanging list. See [Guarantees](guarantees.md) for the complete pagination contract.\n"],
|
|
34491
34945
|
["preact.md", "---\ntitle: Preact\ndescription: Use the Preact components, hooks, events, and types exposed by Ignotum.\nsection: Web\nslug: preact\norder: 42\n---\n\n# Preact\n\n`ignotum/client` supplies Preact's JSX runtime. Import components, hooks, and types from this entry\npoint instead of adding a second UI runtime.\n\nThe main exports are:\n\n| Area | Exports |\n| ----------- | --------------------------------------------------------------------------------------- |\n| Elements | `Fragment`, `createElement`, `h`, `cloneElement`, `createContext`, `createRef` |\n| Components | `Component`, `isValidElement`, `toChildArray` |\n| Hooks | `useState`, `useEffect`, `useMemo`, `useCallback`, `useReducer`, `useRef`, `useContext` |\n| Other hooks | `useId`, `useLayoutEffect`, `useImperativeHandle`, `useDebugValue`, `useErrorBoundary` |\n\nThe entry point also exports Preact's component, event, JSX, ref, and hook types. TypeScript usually\ninfers them from JSX and hook calls.\n\nUse the JSX `class` attribute. Event handlers receive Preact's typed DOM events:\n\n```tsx\nimport { useState, type TargetedInputEvent } from \"ignotum/client\";\n\nfunction Search() {\n const [text, setText] = useState(\"\");\n\n const update = (event: TargetedInputEvent<HTMLInputElement>) => {\n setText(event.currentTarget.value);\n };\n\n return <input class=\"rounded border px-3 py-2\" value={text} onInput={update} />;\n}\n```\n\nIgnotum-specific browser exports include `app`, `Files`, `Query`, `Result`, `useQuery`,\n`usePaginatedQuery`, and `useMutation`. Their complete workflows are documented in\n[Files](files.md), [Queries](queries.md), and [Mutations](mutations.md).\n\nNext: [Styling](styling.md)\n"],
|
|
34492
|
-
["project-structure.md", "---\ntitle: Project structure\ndescription: Place Web code, functions, data definitions, and shared code in an Ignotum app.\nsection: Start\nslug: project-structure\norder: 4\n---\n\n# Project structure\n\nAn Ignotum project separates browser code from function code while keeping both in one app:\n\n```text\nclient/\n index.tsx\nserver/\n schema.ts\n todos.ts\nshared/\n_generated/\npublic/\n```\n\nOnly `client/index.tsx` and `server/schema.ts` are required. The other paths appear when the app\nneeds them.\n\n## `client`\n\n`client` contains the Web part. Its `index.tsx` file default-exports one `app(...)` definition:\n\n```tsx\nimport { app } from \"ignotum/client\";\n\nfunction App() {\n return <main>My app</main>;\n}\n\nexport default app({\n title: \"My app\",\n component: App,\n});\n```\n\nClient modules import browser APIs and UI exports from `ignotum/client`. They may also import\n`_generated/api.ts`, `_generated/types.ts`, other client modules, shared modules, and\nbrowser-compatible dependencies. They cannot import server modules or `ignotum/server`.\n\n## `server`\n\n`server/schema.ts` defines the database schema. Other `.ts` files directly inside `server` may\nexport queries and mutations. Their file names become generated API modules. For example,\n`server/todos.ts` produces references such as `api.todos.list`.\n\nSchema definitions import `defineSchema` from `ignotum/server`. Functions import schema-bound\n`query`, `mutation`, and `values` from `@/_generated/server.js`. Server modules may import other\nserver modules, shared modules, generated server and data types, and supported dependencies. They\ncannot import client modules or `ignotum/client`.\n\n## `shared`\n\n`shared` contains code that is valid in both runtimes. Shared modules cannot depend on browser-only\nor function-only APIs. Put a type, validator-independent helper, or constant here only when both\nsides use it.\n\n## `_generated`\n\nIgnotum writes generated function references, schema-bound builders, and data types to\n`_generated`. Do not edit these files. See [Generated code](generated-code.md).\n\n## `public`\n\nThe optional top-level `public` directory contains fixed files that keep their relative paths as\nWeb URLs. It is separate from application files stored in database documents. See\n[Public assets](public-assets.md).\n\nNext: [Schema](schema.md)\n"],
|
|
34946
|
+
["project-structure.md", "---\ntitle: Project structure\ndescription: Place Web code, functions, data definitions, and shared code in an Ignotum app.\nsection: Start\nslug: project-structure\norder: 4\n---\n\n# Project structure\n\nAn Ignotum project separates browser code from function code while keeping both in one app:\n\n```text\nclient/\n index.tsx\nserver/\n env.ts\n schema.ts\n todos.ts\nshared/\n_generated/\npublic/\n```\n\nOnly `client/index.tsx` and `server/schema.ts` are required. The other paths appear when the app\nneeds them.\n\n## `client`\n\n`client` contains the Web part. Its `index.tsx` file default-exports one `app(...)` definition:\n\n```tsx\nimport { app } from \"ignotum/client\";\n\nfunction App() {\n return <main>My app</main>;\n}\n\nexport default app({\n title: \"My app\",\n component: App,\n});\n```\n\nClient modules import browser APIs and UI exports from `ignotum/client`. They may also import\n`_generated/api.ts`, `_generated/types.ts`, other client modules, shared modules, and\nbrowser-compatible dependencies. They cannot import server modules or `ignotum/server`.\n\n## `server`\n\n`server/schema.ts` defines the database schema. The optional `server/env.ts` declares typed,\nserver-only [environment variables](environment-variables.md). Other `.ts` files directly inside `server` may\nexport queries and mutations. Their file names become generated API modules. For example,\n`server/todos.ts` produces references such as `api.todos.list`.\n\nSchema definitions import `defineSchema` from `ignotum/server`. Functions import schema-bound\n`query`, `mutation`, and `values` from `@/_generated/server.js`. Server modules may import other\nserver modules, shared modules, generated server and data types, and supported dependencies. They\ncannot import client modules or `ignotum/client`.\n\n## `shared`\n\n`shared` contains code that is valid in both runtimes. Shared modules cannot depend on browser-only\nor function-only APIs. Put a type, validator-independent helper, or constant here only when both\nsides use it.\n\n## `_generated`\n\nIgnotum writes generated function references, schema-bound builders, and data types to\n`_generated`. Do not edit these files. See [Generated code](generated-code.md).\n\n## `public`\n\nThe optional top-level `public` directory contains fixed files that keep their relative paths as\nWeb URLs. It is separate from application files stored in database documents. See\n[Public assets](public-assets.md).\n\nNext: [Schema](schema.md)\n"],
|
|
34493
34947
|
["public-assets.md", "---\ntitle: Public assets\ndescription: Add a favicon and fixed files to the Web part.\nsection: Web\nslug: public-assets\norder: 44\n---\n\n# Public assets\n\nPublic assets belong to a deployment and keep stable URL paths. They are different from\n[application files](files.md), which belong to database documents and require a current query\nresult.\n\n## Favicon\n\nAdd `client/icon.svg` to use an SVG favicon. Ignotum discovers this exact path. Without the file,\nthe page has no favicon link.\n\nThe icon must be a regular file. Other SVG files cannot go in `public`, but imported SVG assets may\nstill be part of client code.\n\n## `public`\n\nPut files that need fixed URLs in a top-level `public` directory next to `client` and `server`:\n\n```text\npublic/\n images/\n logo.png\n manual.pdf\n```\n\nThese files become `/images/logo.png` and `/manual.pdf`. `client/public` is not supported.\n\nIgnotum accepts AVIF, GIF, ICO, JPEG, PNG, WebP, and PDF. It checks the file signature against the\nextension. Entries must be regular files or directories, not symbolic links. Each path segment may\ncontain letters, numbers, `.`, `_`, `~`, or `-`. The first segment cannot be `_ignotum`, which is\nreserved for platform paths.\n\nEach file is limited to 16 MiB, and all public files count toward the deployment limits. See\n[Limits](limits.md).\n"],
|
|
34494
34948
|
["queries.md", "---\ntitle: Queries\ndescription: Define, call, and subscribe to typed queries.\nsection: Functions\nslug: queries\norder: 22\n---\n\n# Queries\n\nA query reads application data and returns a typed result. Web code calls it through a generated\nreference and stays subscribed to relevant data changes.\n\n## Define a query\n\nImport `query` and schema-bound `values` from `_generated/server.ts`. Define the arguments, return\nvalue, and handler together:\n\n```ts\nimport { query, values } from \"@/_generated/server.js\";\n\nexport const get = query({\n args: { id: values.id(\"todos\") },\n returns: values.doc(\"todos\"),\n\n handler: function* (ctx, { id }) {\n return yield* ctx.db.get(\"todos\", id);\n },\n});\n```\n\nQueries have a read-only `ctx.db`. Use [Reading data](reading-data.md) for document and table reads,\nand [Indexes](indexes.md) for filtered and ordered results.\n\n## Call a query from Web code\n\nThe exported function above becomes `api.todos.get`. Pass that reference to `useQuery`:\n\n```tsx\nimport { Query, Result, useQuery } from \"ignotum/client\";\nimport { api } from \"@/_generated/api.js\";\nimport type { Id } from \"@/_generated/types.js\";\n\nfunction Todo({ id }: { id: Id<\"todos\"> | undefined }) {\n const todo = useQuery(api.todos.get, id === undefined ? Query.skip : { id });\n\n return Result.match(todo, {\n pending: () => <p>Loading...</p>,\n value: (value) => <p>{value.text}</p>,\n error: {\n DocumentNotFound: () => <p>The todo no longer exists.</p>,\n },\n });\n}\n```\n\nArgument-free queries take only the reference:\n\n```tsx\nconst todos = useQuery(api.todos.list);\n```\n\nQueries with arguments require the second parameter. The generated reference supplies its type.\n`Query.skip` keeps the hook pending and opens no subscription. Changing from `Query.skip` to\narguments starts the query. Changing the arguments switches the subscription and returns pending\nuntil the new query has a result.\n\n## Handle the result\n\n`useQuery` first returns a pending `QueryResult`, then a value or application error.\n`Result.match` requires handlers for the states and declared errors in its type:\n\n```tsx\nreturn Result.match(todos, {\n pending: () => <p class=\"text-zinc-500\">Loading...</p>,\n value: (items) => (\n <ul>\n {items.map((todo) => (\n <li key={todo.id}>{todo.text}</li>\n ))}\n </ul>\n ),\n});\n```\n\nUse one `error` function when the UI treats all application errors alike. Use an object keyed by\n`_tag` when errors need different handling. `internalError` is optional. Without it, an\n`InternalServerError` is thrown to the nearest UI error boundary. See [Errors](errors.md).\n\n## Realtime behavior\n\nThe hook keeps its subscription until the component stops using it or its reference and arguments\nchange. Components using the same query and arguments share one browser subscription.\n\nAfter a successful mutation, Ignotum refreshes active queries whose recorded reads may have\nchanged. A document lookup responds to writes for that document. An indexed range responds to\nchanges inside its relevant range. A full table query responds to writes in that table.\n\nA lost connection does not change a settled query back to pending. The client keeps the latest\nresult, reconnects with an increasing delay capped at five seconds, then recreates active\nsubscriptions. A deployment change reloads the page.\n\nUse [Pagination](pagination.md) for paginated query definitions and `usePaginatedQuery`. Read\n[Guarantees](guarantees.md) for consistency, reconnect, and update behavior.\n\nNext: [Mutations](mutations.md)\n"],
|
|
34495
34949
|
["quickstart.md", "---\ntitle: Quickstart\ndescription: Create, understand, and deploy a small Ignotum app.\nsection: Start\nslug: quickstart\norder: 2\n---\n\n# Quickstart\n\nIgnotum requires Node.js 22.18 or newer. Create the generated counter app and start it:\n\n```sh\nnpx ignotum new my-app\ncd my-app\nnpx ignotum dev\n```\n\nOpen <http://127.0.0.1:3210>. The page shows a counter backed by an Ignotum database.\n\n## Find the app parts\n\nThe files used in this guide are:\n\n```text\nclient/\n index.tsx\nserver/\n counter.ts\n schema.ts\nshared/\n utils.ts\n_generated/\n```\n\n`server/schema.ts` defines stored data. `server/counter.ts` defines the query and mutation.\n`client/index.tsx` defines the Web app and calls those functions. `shared/utils.ts` is available to\nboth runtimes. Ignotum owns the files under `_generated`.\n\n## Define data\n\nThe generated schema has one table:\n\n```ts\nimport { defineSchema } from \"ignotum/server\";\n\nexport default defineSchema(({ table, values }) => ({\n counters: table({\n value: values.number(),\n }),\n}));\n```\n\nThe schema supplies runtime validation and the document types used by functions and Web code.\n\n## Define a query\n\n`server/counter.ts` exports `get`:\n\n```ts\nimport { mutation, query, values } from \"@/_generated/server.js\";\n\nexport const get = query({\n returns: values.number(),\n\n handler: function* (ctx) {\n const counters = yield* ctx.db.query(\"counters\").collect();\n return counters[0]?.value ?? 0;\n },\n});\n```\n\nCode generation gives this function the reference `api.counter.get`. The Web app subscribes to it\nwith `useQuery`:\n\n```tsx\nconst count = useQuery(api.counter.get);\n```\n\n`Result.match` renders the pending state and the returned value:\n\n```tsx\n{\n Result.match(count, {\n pending: () => <p>Loading...</p>,\n value: (value) => <p>{value}</p>,\n });\n}\n```\n\n## Define a mutation\n\nThe generated `increment` mutation either inserts the first counter document or updates the\nexisting one:\n\n```ts\nexport const increment = mutation({\n returns: values.number(),\n\n handler: function* (ctx) {\n const counters = yield* ctx.db.query(\"counters\").collect();\n const counter = counters[0];\n const value = (counter?.value ?? 0) + 1;\n\n if (counter === undefined) {\n yield* ctx.db.insert(\"counters\", { value });\n } else {\n yield* ctx.db.patch(\"counters\", counter.id, { value });\n }\n\n return value;\n },\n});\n```\n\nWeb code obtains a typed caller from the generated reference:\n\n```tsx\nconst increment = useMutation(api.counter.increment);\n\n<button type=\"button\" onClick={() => void increment()}>\n Increment\n</button>;\n```\n\nClick the button in one browser window. The mutation commits the new value, then the active query\nreceives an updated result. Open a second window to see both subscriptions follow the same data.\n\n## Deploy the app\n\nSign in with GitHub, then choose a globally available app slug:\n\n```sh\nnpx ignotum auth login\nnpx ignotum deploy --app my-app\n```\n\nThe command prints the hosted URL and links this directory to the hosted app. Later deployments use\nthe saved link:\n\n```sh\nnpx ignotum deploy\n```\n\nRead [Deploy an app](deploy-app.md) for login storage, slug rules, and deployment failure behavior.\n\nNext: [How Ignotum works](how-ignotum-works.md)\n"],
|
|
34496
34950
|
["reading-data.md", "---\ntitle: Reading data\ndescription: Read individual documents and table results.\nsection: Database\nslug: reading-data\norder: 13\n---\n\n# Reading data\n\nQuery and mutation handlers read documents through `ctx.db`. A read can name one document, scan a\ntable, or select a declared index. Ignotum does not turn an unindexed filter into a hidden table\nscan. Use an [index](indexes.md) for a filtered database read, or read the table and filter it when\nthe table is intentionally small.\n\n## One document\n\nUse `find` when absence is expected:\n\n```ts\nconst todo = yield * ctx.db.find(\"todos\", args.id);\n// Doc<\"todos\"> | undefined\n```\n\nUse `get` when absence is an application error path:\n\n```ts\nconst todo = yield * ctx.db.get(\"todos\", args.id);\n// Doc<\"todos\">\n```\n\nA missing `get` fails with `DocumentNotFound`. The error contains `table` and `id`. Catch it to\nrecover or map it to a public error. If it remains in the handler, it becomes part of that\nfunction's typed application errors.\n\nIDs are scoped to their tables at compile time. A `users` ID cannot be passed to a `todos` read.\n\n## A table\n\nStart a table query with `ctx.db.query`:\n\n```ts\nconst todos = yield * ctx.db.query(\"todos\").collect();\n```\n\nA table query is ordered by `createdAt`, then `id`, both ascending. Reverse both fields with\n`order(\"desc\")`:\n\n```ts\nconst newest = yield * ctx.db.query(\"todos\").order(\"desc\").take(20);\n```\n\nUse a table query when the function needs a table-wide result. Any write in that table can cause a\nsubscribed query that used the result to refresh.\n\n## Result methods\n\nFinish a table or index query with one method:\n\n| Method | Result |\n| ------------------- | ------------------------------------------------------------------------------ |\n| `collect()` | Every matching document, within hosted collection limits. |\n| `take(count)` | At most `count` documents. `count` may be 0 through 1,000. |\n| `first()` | The first document, or `undefined`. |\n| `unique()` | The only document, or `undefined`. More than one match is an internal failure. |\n| `paginate(options)` | One page and an opaque cursor. |\n\n`unique()` checks a result. It does not make the index unique and does not prevent duplicate field\nvalues. Use `first()` when multiple matches are valid.\n\nPrefer `take` or `first` when the function only needs a bounded result. `collect` is capped at\n1,000 documents when hosted and may hit byte limits first.\n\n## Reads inside a mutation\n\nA mutation sees writes it made earlier in the same handler, including through table and index\nqueries:\n\n```ts\nconst todo = yield * ctx.db.get(\"todos\", args.id);\nyield * ctx.db.patch(\"todos\", todo.id, { completed: !todo.completed });\nconst changed = yield * ctx.db.get(\"todos\", todo.id);\n```\n\nNo other mutation can interleave with those operations for the same app. See\n[Writing data](writing-data.md) and [Guarantees](guarantees.md).\n"],
|
|
34497
34951
|
["schema.md", "---\ntitle: Schema\ndescription: Define tables, fields, IDs, and stored document types.\nsection: Database\nslug: schema\norder: 11\n---\n\n# Schema\n\nDefine the data model in `server/schema.ts`. The keys returned from `defineSchema` are table\nnames, and each `table` call defines that table's fields:\n\n```ts\nimport { defineSchema } from \"ignotum/server\";\n\nexport default defineSchema(({ table, values }) => ({\n users: table({\n name: values.string(),\n }),\n todos: table({\n text: values.string(),\n completed: values.boolean(),\n ownerId: values.optional(values.id(\"users\")),\n }).index(\"by_completed\", [\"completed\"]),\n}));\n```\n\n## Field values\n\nRead [values](values.md) for the complete validator list and the TypeScript type produced by each\none. `values.id` only accepts a table declared in the same schema. Arrays and objects can be nested,\nand their contents can use any value validator.\n\n## System fields\n\nIgnotum adds three fields to every stored document:\n\n| Field | Type |\n| ----------- | ------------------------- |\n| `id` | The ID type for its table |\n| `createdAt` | `Date` |\n| `updatedAt` | `Date` |\n\nDo not declare these fields in a table. Do not pass them to `insert`, `patch`, or `replace`.\n\n`createdAt` is set when Ignotum inserts the document. `updatedAt` starts at the same time and\nadvances on every successful patch or replacement. Both are JavaScript `Date` values in function\nand Web code.\n\nChain `index` after a table to declare an ordered index. [Indexes](indexes.md) covers field rules,\nranges, and ordering.\n\n## Generated types\n\n`_generated/types.ts` exports the data model, document, and ID types:\n\n```ts\nimport type { DataModel, Doc, Id } from \"@/_generated/types.js\";\n\ntype Todo = Doc<\"todos\">;\ntype TodoId = Id<\"todos\">;\n```\n\n`Doc<\"todos\">` includes the fields from the `todos` table and its three system fields. An\n`Id<\"todos\">` cannot be passed where an `Id<\"users\">` is required.\n\n## Schema changes\n\nLocal development updates tables and indexes as the schema changes. Existing documents still have\nto match the current field validators when a function reads them.\n\nFor hosted apps, the first active deployment fixes table names, field names, and field validators.\nLater deployments may add, remove, rename, or reorder indexes. They may also change functions and\nWeb code. A deployment that changes the stored table or field schema is built and uploaded, but\nactivation fails with `SchemaIncompatible`, leaving the current deployment active.\n\nSchema migrations are not part of the current public API. Plan the first hosted schema with this\nrestriction in mind.\n\nNext: [Queries](queries.md)\n"],
|
|
34498
34952
|
["styling.md", "---\ntitle: Styling\ndescription: Style Web code with Tailwind CSS or imported CSS files.\nsection: Web\nslug: styling\norder: 43\n---\n\n# Styling\n\nIgnotum loads Tailwind CSS automatically. An app does not need a Tailwind configuration file or a\nframework stylesheet.\n\nUse utility classes through JSX's `class` attribute:\n\n```tsx\nfunction App() {\n return (\n <main class=\"mx-auto max-w-xl px-6 py-16\">\n <h1 class=\"text-2xl font-semibold text-zinc-950\">Todos</h1>\n </main>\n );\n}\n```\n\nCustom CSS is an ordinary client module. Give the file any name and import it from client code:\n\n```tsx\nimport \"./calendar.css\";\n```\n\nNext: [Public assets](public-assets.md)\n"],
|
|
34499
|
-
["values.md", "---\ntitle: Values\ndescription: Use runtime validators and their matching TypeScript types.\nsection: Database\nslug: values\norder: 12\n---\n\n# Values\n\nUse `values` validators to describe table fields, function arguments, return values, and application\nerrors. The same definition supplies a TypeScript type and a runtime check. Do not substitute a\nTypeScript-only interface where Ignotum asks for a validator.\n\n| Validator | TypeScript type | Notes |\n| ----------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------- |\n| `values.string()` | `string` | |\n| `values.number()` | `number` | Accepts finite JavaScript numbers, including integers. |\n| `values.integer()` | `number` | Accepts safe integers. |\n| `values.boolean()` | `boolean` | |\n| `values.date()` | `Date` | Accepts valid JavaScript dates. |\n| `values.file(options?)` | `FileValue` | Accepts configured image formats and a byte limit. See [Files](files.md). |\n| `values.null()` | `null` | Accepts only `null`. |\n| `values.literal(value)` | The exact type of `value` | Accepts one string, finite number, or boolean value. |\n| `values.literals(first, second, ...rest)` | A union of the supplied literal types | Requires at least two string, finite number, or boolean values. |\n| `values.id(\"todos\")` | `Id<\"todos\">` | Accepts an ID for a table declared in the same schema. IDs for different tables are different types. |\n| `values.doc(\"todos\")` | `Doc<\"todos\">` | Accepts a complete document, including its `id`, `createdAt`, and `updatedAt` system fields. |\n| `values.pagination()` | Pagination options | Defines the `pagination` argument used by `.paginate(...)`. |\n| `values.page(value)` | A page of `T` | Defines the return value of a paginated query. |\n| `values.optional(value)` | `T \\| undefined` | Makes an object or table field optional. The field may be omitted. |\n| `values.nullable(value)` | `T \\| null` | The value remains required unless it is also wrapped with `optional`. |\n| `values.array(value)` | `ReadonlyArray<T>` | Every item must match `value`. |\n| `values.object(fields)` | An object matching `fields` | Defines an object with known field names. |\n| `values.record(value)` | `Readonly<Record<string, T>>` | Defines an object with dynamic string keys whose values all match `value`. |\n| `values.union(...values)` | A union of the supplied types | Accepts a value matching any supplied validator. |\n| `values.never()` | `never` | No value can pass this validator. |\n| `values.error(\"Name\", fields)` | A tagged error object | Defines an application error whose `_tag` is the supplied name. |\n\n`T` means the TypeScript type produced by the wrapped validator.\n\nDates cross the Web and function boundary as `Date` instances. Ignotum rejects invalid dates.\n`values.number()` rejects `NaN`, `Infinity`, and `-Infinity`. Use `values.integer()` when fractional\nvalues should also be rejected.\n\n`values.file()` accepts JPEG, PNG, WebP, AVIF, and GIF by default. Pass `formats` to allow a\nnon-empty subset and `maxBytes` to set a positive limit up to 10 MiB. [Files](files.md) covers the\ncomplete file workflow and the places where file values are allowed.\n\n`values.doc`, `values.pagination`, and `values.page` are available on the schema-bound `values`\nexported by `_generated/server.ts`. They are not available while defining `server/schema.ts`.\n\n## Object transforms\n\nValidators created by `values.object(fields)` and `values.doc(\"table\")` support these chainable\nmethods:\n\n| Method | Result |\n| ------------------- | -------------------------------------------------------------------- |\n| `.pick(...keys)` | Keeps the listed fields. Every key must exist. |\n| `.omit(...keys)` | Removes the listed fields. Every key must exist. |\n| `.extend(fields)` | Adds fields. It rejects names that already exist. |\n| `.override(fields)` | Replaces validators for existing fields. It rejects new field names. |\n| `.partial()` | Makes every current field optional. |\n\nEach method returns a new validator and leaves the source unchanged. The next method works from the\ncurrent result. Added fields can be picked, omitted, or overridden immediately. An omitted name can\nbe added again with a different validator.\n\n```ts\nconst TodoInput = values.doc(\"todos\").omit(\"id\", \"createdAt\", \"updatedAt\").partial().extend({\n requestId: values.string(),\n});\n```\n\nUse `override` when changing an existing field. This makes replacements visible in the definition\nand prevents `extend` from silently weakening fields such as `id`:\n\n```ts\nconst EditableTodo = values.doc(\"todos\").override({\n title: values.optional(values.string()),\n});\n```\n\nThese methods are only available on fixed object validators. Arrays, records, unions, errors, and\nprimitive validators do not expose them. After transforming a document validator, its inferred type\nmatches the current fields in the chain rather than the complete document type.\n\nFixed object validators describe named fields. Use `values.record(value)` when keys are not known\nin advance.\n\n### Reuse embedded objects in the schema\n\nCreate a fixed object validator inside the `defineSchema` callback when several stored fields share\nan object shape. A transform can derive a stored variant without repeating its fields:\n\n```ts\ndefineSchema(({ table, values }) => {\n const Contact = values.object({\n email: values.string(),\n phone: values.string(),\n });\n\n return {\n users: table({\n contact: Contact,\n }),\n publicProfiles: table({\n contact: Contact.omit(\"phone\"),\n }),\n };\n});\n```\n\nThis pattern suits embedded objects stored by more than one table, including full and reduced\nversions of the same object. The base validator stays inside `defineSchema`, where `values.id` can\ncheck its table references against the completed schema.\n\n### Derive server function validators from documents\n\nUse the schema-bound `values` from `_generated/server.ts` when a function input or output follows a\nstored document. These validators know every table name and the complete document fields, including\n`id`, `createdAt`, and `updatedAt`.\n\nReusable server validators can live in an ignored server module such as `server/_validators.ts`:\n\n```ts\nimport { values } from \"@/_generated/server.js\";\n\nexport const TodoInput = values.doc(\"todos\").omit(\"id\", \"createdAt\", \"updatedAt\").partial();\n\nexport const PublicTodo = values.doc(\"todos\").omit(\"updatedAt\");\n```\n\nImport these validators into queries and mutations that need the same contract. This works well for\npatch inputs and document projections. Do not import generated validators into `server/schema.ts`.\nDocument validators depend on the schema, so importing them while defining that schema would create\na cycle.\n\n## Examples\n\n```ts\nconst TodoStatus = values.literals(\"pending\", \"completed\");\n\nconst Todo = values.doc(\"todos\");\n\nexport const list = query({\n returns: values.array(Todo),\n handler: function* (ctx) {\n return yield* ctx.db.query(\"todos\").collect();\n },\n});\n```\n\nUse the other validators to define reusable values that do not represent a stored document:\n\n```ts\nconst TodoInput = values.object({\n status: TodoStatus,\n scheduledAt: values.nullable(values.date()),\n scores: values.record(values.integer()),\n title: values.string(),\n});\n```\n\nUse `optional` when a field may be absent. Use `nullable` when a present field may contain `null`:\n\n```ts\nvalues.object({\n nickname: values.optional(values.string()),\n deletedAt: values.nullable(values.date()),\n});\n```\n\nWrapping a validator in both supports omission and an explicit `null`:\n\n```ts\nvalues.optional(values.nullable(values.date()));\n```\n"],
|
|
34953
|
+
["values.md", "---\ntitle: Values\ndescription: Use runtime validators and their matching TypeScript types.\nsection: Database\nslug: values\norder: 12\n---\n\n# Values\n\nUse `values` validators to describe table fields, function arguments, return values, and application\nerrors. The same definition supplies a TypeScript type and a runtime check. Do not substitute a\nTypeScript-only interface where Ignotum asks for a validator.\n\n| Validator | TypeScript type | Notes |\n| ----------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------- |\n| `values.string()` | `string` | |\n| `values.number()` | `number` | Accepts finite JavaScript numbers, including integers. |\n| `values.integer()` | `number` | Accepts safe integers. |\n| `values.boolean()` | `boolean` | |\n| `values.date()` | `Date` | Accepts valid JavaScript dates. |\n| `values.file(options?)` | `FileValue` | Accepts configured image formats and a byte limit. See [Files](files.md). |\n| `values.null()` | `null` | Accepts only `null`. |\n| `values.literal(value)` | The exact type of `value` | Accepts one string, finite number, or boolean value. |\n| `values.literals(first, second, ...rest)` | A union of the supplied literal types | Requires at least two string, finite number, or boolean values. |\n| `values.id(\"todos\")` | `Id<\"todos\">` | Accepts an ID for a table declared in the same schema. IDs for different tables are different types. |\n| `values.doc(\"todos\")` | `Doc<\"todos\">` | Accepts a complete document, including its `id`, `createdAt`, and `updatedAt` system fields. |\n| `values.pagination()` | Pagination options | Defines the `pagination` argument used by `.paginate(...)`. |\n| `values.page(value)` | A page of `T` | Defines the return value of a paginated query. |\n| `values.optional(value)` | `T \\| undefined` | Makes an object or table field optional. The field may be omitted. |\n| `values.nullable(value)` | `T \\| null` | The value remains required unless it is also wrapped with `optional`. |\n| `values.array(value)` | `ReadonlyArray<T>` | Every item must match `value`. |\n| `values.object(fields)` | An object matching `fields` | Defines an object with known field names. |\n| `values.record(value)` | `Readonly<Record<string, T>>` | Defines an object with dynamic string keys whose values all match `value`. |\n| `values.union(...values)` | A union of the supplied types | Accepts a value matching any supplied validator. |\n| `values.never()` | `never` | No value can pass this validator. |\n| `values.error(\"Name\", fields)` | A tagged error object | Defines an application error whose `_tag` is the supplied name. |\n\n`T` means the TypeScript type produced by the wrapped validator.\n\nEnvironment declarations also provide `values.secret()` and `values.secret(value)`. These produce\nredacted values that require `Secret.value(...)` for explicit access. Secret validators are not\navailable in schemas or function input and output contracts. See\n[Environment variables](environment-variables.md).\n\nDates cross the Web and function boundary as `Date` instances. Ignotum rejects invalid dates.\n`values.number()` rejects `NaN`, `Infinity`, and `-Infinity`. Use `values.integer()` when fractional\nvalues should also be rejected.\n\nMutation arguments accept objects with ordinary fields and arrays. Convert built-in collections\nsuch as `Map` and `Set` to objects or arrays, and convert `URL` instances to strings before calling\na mutation. Ignotum rejects these built-in objects instead of discarding their contents.\n\n`values.file()` accepts JPEG, PNG, WebP, AVIF, and GIF by default. Pass `formats` to allow a\nnon-empty subset and `maxBytes` to set a positive limit up to 10 MiB. [Files](files.md) covers the\ncomplete file workflow and the places where file values are allowed.\n\n`values.doc`, `values.pagination`, and `values.page` are available on the schema-bound `values`\nexported by `_generated/server.ts`. They are not available while defining `server/schema.ts`.\n\n## Object transforms\n\nValidators created by `values.object(fields)` and `values.doc(\"table\")` support these chainable\nmethods:\n\n| Method | Result |\n| ------------------- | -------------------------------------------------------------------- |\n| `.pick(...keys)` | Keeps the listed fields. Every key must exist. |\n| `.omit(...keys)` | Removes the listed fields. Every key must exist. |\n| `.extend(fields)` | Adds fields. It rejects names that already exist. |\n| `.override(fields)` | Replaces validators for existing fields. It rejects new field names. |\n| `.partial()` | Makes every current field optional. |\n\nEach method returns a new validator and leaves the source unchanged. The next method works from the\ncurrent result. Added fields can be picked, omitted, or overridden immediately. An omitted name can\nbe added again with a different validator.\n\n```ts\nconst TodoInput = values.doc(\"todos\").omit(\"id\", \"createdAt\", \"updatedAt\").partial().extend({\n requestId: values.string(),\n});\n```\n\nUse `override` when changing an existing field. This makes replacements visible in the definition\nand prevents `extend` from silently weakening fields such as `id`:\n\n```ts\nconst EditableTodo = values.doc(\"todos\").override({\n title: values.optional(values.string()),\n});\n```\n\nThese methods are only available on fixed object validators. Arrays, records, unions, errors, and\nprimitive validators do not expose them. After transforming a document validator, its inferred type\nmatches the current fields in the chain rather than the complete document type.\n\nFixed object validators describe named fields. Use `values.record(value)` when keys are not known\nin advance.\n\n### Reuse embedded objects in the schema\n\nCreate a fixed object validator inside the `defineSchema` callback when several stored fields share\nan object shape. A transform can derive a stored variant without repeating its fields:\n\n```ts\ndefineSchema(({ table, values }) => {\n const Contact = values.object({\n email: values.string(),\n phone: values.string(),\n });\n\n return {\n users: table({\n contact: Contact,\n }),\n publicProfiles: table({\n contact: Contact.omit(\"phone\"),\n }),\n };\n});\n```\n\nThis pattern suits embedded objects stored by more than one table, including full and reduced\nversions of the same object. The base validator stays inside `defineSchema`, where `values.id` can\ncheck its table references against the completed schema.\n\n### Derive server function validators from documents\n\nUse the schema-bound `values` from `_generated/server.ts` when a function input or output follows a\nstored document. These validators know every table name and the complete document fields, including\n`id`, `createdAt`, and `updatedAt`.\n\nReusable server validators can live in an ignored server module such as `server/_validators.ts`:\n\n```ts\nimport { values } from \"@/_generated/server.js\";\n\nexport const TodoInput = values.doc(\"todos\").omit(\"id\", \"createdAt\", \"updatedAt\").partial();\n\nexport const PublicTodo = values.doc(\"todos\").omit(\"updatedAt\");\n```\n\nImport these validators into queries and mutations that need the same contract. This works well for\npatch inputs and document projections. Do not import generated validators into `server/schema.ts`.\nDocument validators depend on the schema, so importing them while defining that schema would create\na cycle.\n\n## Examples\n\n```ts\nconst TodoStatus = values.literals(\"pending\", \"completed\");\n\nconst Todo = values.doc(\"todos\");\n\nexport const list = query({\n returns: values.array(Todo),\n handler: function* (ctx) {\n return yield* ctx.db.query(\"todos\").collect();\n },\n});\n```\n\nUse the other validators to define reusable values that do not represent a stored document:\n\n```ts\nconst TodoInput = values.object({\n status: TodoStatus,\n scheduledAt: values.nullable(values.date()),\n scores: values.record(values.integer()),\n title: values.string(),\n});\n```\n\nUse `optional` when a field may be absent. Use `nullable` when a present field may contain `null`:\n\n```ts\nvalues.object({\n nickname: values.optional(values.string()),\n deletedAt: values.nullable(values.date()),\n});\n```\n\nWrapping a validator in both supports omission and an explicit `null`:\n\n```ts\nvalues.optional(values.nullable(values.date()));\n```\n"],
|
|
34500
34954
|
["web.md", "---\ntitle: Web overview\nlabel: Overview\ndescription: Define the browser-facing part of an Ignotum app.\nsection: Web\nslug: web\norder: 40\n---\n\n# Web overview\n\nThe Web part of an Ignotum app lives in `client`. It renders the browser UI and calls queries and\nmutations through `_generated/api.ts`.\n\n`client/index.tsx` is the entry. It default-exports an `app(...)` definition with the page title and\nroot Preact component. Ignotum supplies the JSX runtime and Tailwind CSS.\n\n- [App entry](app-entry.md) documents `client/index.tsx` and the `app(...)` definition.\n- [Preact](preact.md) lists the UI exports and import rules.\n- [Styling](styling.md) covers Tailwind CSS and custom CSS.\n- [Public assets](public-assets.md) covers the favicon and files with fixed URL paths.\n- [Queries](queries.md) and [Mutations](mutations.md) cover the generated API and browser hooks.\n\nClient modules may import `_generated/api.ts`, `_generated/types.ts`, other client modules, shared\nmodules, and browser-compatible dependencies. They cannot import files under `server`,\n`_generated/server.ts`, `ignotum/server`, Node built-ins, or native add-ons.\n\nNext: [App entry](app-entry.md)\n"],
|
|
34501
34955
|
["writing-data.md", "---\ntitle: Writing data\ndescription: Insert, patch, replace, and delete documents in a mutation.\nsection: Database\nslug: writing-data\norder: 14\n---\n\n# Writing data\n\nOnly mutation handlers can write. Their `ctx.db` has four write methods and all methods from\n[Reading data](reading-data.md).\n\n## Insert\n\n`insert` creates a document and returns its table-specific ID:\n\n```ts\nconst id =\n yield *\n ctx.db.insert(\"todos\", {\n text: \"Learn Ignotum\",\n completed: false,\n });\n```\n\nSupply every required application field and omit optional fields that have no value. Ignotum sets\n`id`, `createdAt`, and `updatedAt`. Those system fields are not valid insert input.\n\n## Patch\n\n`patch` changes the supplied fields and leaves the rest alone:\n\n```ts\nyield * ctx.db.patch(\"todos\", args.id, { completed: true });\n```\n\nThe patch input is type-checked against the table, and every field is optional. A matching\ndocument keeps its `id` and `createdAt`; its `updatedAt` advances.\n\n## Replace\n\n`replace` supplies a new complete set of application fields:\n\n```ts\nyield *\n ctx.db.replace(\"todos\", args.id, {\n text: \"Build an app\",\n completed: false,\n });\n```\n\nEvery required field must be present. A matching document keeps its `id` and `createdAt`; its\n`updatedAt` advances.\n\n## Delete\n\n`delete` removes a document by ID:\n\n```ts\nyield * ctx.db.delete(\"todos\", args.id);\n```\n\n## Missing targets\n\n`patch`, `replace`, and `delete` fail with `DocumentNotFound` when the target document does not\nexist. The error contains the target `table` and `id`. Catch it directly or map it to a public\napplication error as described in [Errors](errors.md).\n\nUse `find` first when a missing target should be ignored:\n\n```ts\nconst todo = yield * ctx.db.find(\"todos\", args.id);\nif (todo !== undefined) yield * ctx.db.delete(\"todos\", args.id);\n```\n\n## Transaction behavior\n\nA mutation reads one logical state, sees its own earlier writes, and commits all successful writes\ntogether. Other mutations for the same app do not interleave with it.\n\n```ts\nexport const completeOldest = mutation({\n handler: function* (ctx) {\n const todo = yield* ctx.db\n .query(\"todos\")\n .index(\"by_completed\", (range) => range.eq(\"completed\", false))\n .first();\n\n if (todo !== undefined) {\n yield* ctx.db.patch(\"todos\", todo.id, { completed: true });\n }\n },\n});\n```\n\nThe commit happens only after the handler returns a valid success value. A declared application\nerror, thrown exception, invalid return, limit failure, or platform failure commits none of the\nmutation's writes.\n\nIgnotum validates stored fields and maintains declared indexes as part of the same atomic write.\nIf the mutation would exceed the hosted app storage quota, the whole mutation rolls back.\n\nSuccessful mutations cause active queries to refresh when their recorded reads may have changed.\nThe mutation result can reach its caller before every affected query has delivered its next\nsnapshot, so do not treat subscription rendering as part of the mutation promise. See\n[Guarantees](guarantees.md).\n"]
|
|
34502
34956
|
];
|
|
@@ -34986,12 +35440,17 @@ const main = () => {
|
|
|
34986
35440
|
};
|
|
34987
35441
|
//#endregion
|
|
34988
35442
|
//#region src/cli/bin.ts
|
|
34989
|
-
|
|
35443
|
+
const discoveryWorker = process$1.argv[2] === "__ignotum_discover_server";
|
|
35444
|
+
const writeDiscovery = (encoded) => {
|
|
35445
|
+
const bytes = new TextEncoder().encode(encoded);
|
|
35446
|
+
let offset = 0;
|
|
35447
|
+
while (offset < bytes.byteLength) offset += writeSync(3, bytes, offset, bytes.byteLength - offset);
|
|
35448
|
+
};
|
|
35449
|
+
if (discoveryWorker) {
|
|
34990
35450
|
const appDirectory = process$1.argv[3];
|
|
34991
35451
|
const moduleNames = process$1.argv[4];
|
|
34992
|
-
const outputPath = process$1.argv[5];
|
|
34993
35452
|
globalThis.fetch = () => Promise.reject(/* @__PURE__ */ new Error("Network access is disabled during builds."));
|
|
34994
|
-
(appDirectory === void 0 || moduleNames === void 0
|
|
35453
|
+
(appDirectory === void 0 || moduleNames === void 0 ? Effect.die("Invalid isolated discovery arguments.") : runServerDiscoveryWorker(appDirectory, moduleNames).pipe(Effect.tap((encoded) => Effect.sync(() => writeDiscovery(encoded))))).pipe(Effect.provide(layer$1), runMain);
|
|
34995
35454
|
} else main();
|
|
34996
35455
|
//#endregion
|
|
34997
35456
|
export {};
|