ignotum 0.0.13 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/cli/bin.mjs +369 -55
  2. package/dist/cli/bin.mjs.map +1 -1
  3. package/dist/runtime/client.d.ts +3 -3
  4. package/dist/runtime/client.js +24 -4
  5. package/dist/runtime/client.js.map +1 -1
  6. package/dist/runtime/descriptor-DCCz5AHd-_nhlnu8U.js +298 -0
  7. package/dist/runtime/descriptor-DCCz5AHd-_nhlnu8U.js.map +1 -0
  8. package/dist/runtime/id-E7JKqQ9r.d.ts +1 -0
  9. package/dist/runtime/index-DWU-7TIh.d.ts +185 -0
  10. package/dist/runtime/internal/host.d.ts +23 -5
  11. package/dist/runtime/internal/host.js +24 -4
  12. package/dist/runtime/internal/host.js.map +1 -1
  13. package/dist/runtime/internal/server.d.ts +1 -1
  14. package/dist/runtime/internal/server.js +1 -1
  15. package/dist/runtime/internal/types.d.ts +2 -1
  16. package/dist/runtime/internal/types.js +1 -1
  17. package/dist/runtime/{pagination-DnKg3dkI-r5ZUxBBx.d.ts → pagination-CpYCaRYD-C2Rpp4Cw.d.ts} +14 -4
  18. package/dist/runtime/pagination-E7JKqQ9r.d.ts +1 -0
  19. package/dist/runtime/{schema-1Zs03-iS.js → schema-DqogEydG.js} +38 -11
  20. package/dist/runtime/schema-DqogEydG.js.map +1 -0
  21. package/dist/runtime/server.d.ts +4 -3
  22. package/dist/runtime/server.js +1 -1
  23. package/dist/runtime/types-C1iDP2Yf-BnkfBOQf.d.ts +224 -0
  24. package/package.json +2 -2
  25. package/src/cli/agent-files.ts +4 -0
  26. package/src/cli/build/server.ts +35 -6
  27. package/src/cli/command.ts +25 -2
  28. package/src/cli/deploy.ts +34 -11
  29. package/src/client/sync.ts +1 -1
  30. package/src/dev-runtime/database.ts +77 -2
  31. package/src/dev-runtime/id.ts +19 -4
  32. package/src/dev-runtime/migrations.ts +9 -0
  33. package/src/internal/host.ts +1 -1
  34. package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js +0 -154
  35. package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js.map +0 -1
  36. package/dist/runtime/id-Dz0apuB3.d.ts +0 -1
  37. package/dist/runtime/index-D54flWtH.d.ts +0 -402
  38. package/dist/runtime/pagination-Dz0apuB3.d.ts +0 -1
  39. package/dist/runtime/schema-1Zs03-iS.js.map +0 -1
package/dist/cli/bin.mjs CHANGED
@@ -71,7 +71,7 @@ import * as RcRef from "effect/RcRef";
71
71
  import * as Terminal$1 from "effect/Terminal";
72
72
  import * as readline from "node:readline";
73
73
  import * as Runtime from "effect/Runtime";
74
- import { Array as Array$1, Brand, Cause, Clock, Config, ConfigProvider, Context, Crypto, DateTime, Deferred, Effect, Effectable, Encoding, Exit, 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
+ import { Array as Array$1, Brand, Cause, Clock, Config, ConfigProvider, Context, Crypto, DateTime, Deferred, Effect, Effectable, Encoding, Exit, FiberSet, FileSystem, Function as Function$1, HashMap, HashSet, Layer, ManagedRuntime, MutableHashMap, MutableHashSet, Option, Path as Path$1, Predicate, PubSub, Record, Redacted, Ref, Result, Schedule, Schema, SchemaGetter, Semaphore, Stream, String as String$1, Struct, Terminal } from "effect";
75
75
  import { Argument, CliError, Command, Flag, Prompt } from "effect/unstable/cli";
76
76
  import { customAlphabet } from "nanoid";
77
77
  import { createAuthClient } from "better-auth/client";
@@ -4518,7 +4518,7 @@ const access = /*#__PURE__*/ (() => {
4518
4518
  return nodeAccess(path, mode);
4519
4519
  };
4520
4520
  })();
4521
- const copy = /*#__PURE__*/ (() => {
4521
+ const copy$1 = /*#__PURE__*/ (() => {
4522
4522
  const nodeCp = /*#__PURE__*/ effectify(NFS.cp, /*#__PURE__*/ handleErrnoException("FileSystem", "copy"), /*#__PURE__*/ handleBadArgument("copy"));
4523
4523
  return (fromPath, toPath, options) => nodeCp(fromPath, toPath, {
4524
4524
  force: options?.overwrite ?? false,
@@ -4820,7 +4820,7 @@ const makeFileSystem = /*#__PURE__*/ Effect$1.map(/*#__PURE__*/ Effect$1.service
4820
4820
  access,
4821
4821
  chmod,
4822
4822
  chown,
4823
- copy,
4823
+ copy: copy$1,
4824
4824
  copyFile,
4825
4825
  glob,
4826
4826
  link,
@@ -29945,7 +29945,151 @@ var FunctionUnavailable = class extends Schema.TaggedError()("FunctionUnavailabl
29945
29945
  message: Schema.String
29946
29946
  }) {};
29947
29947
  //#endregion
29948
- //#region ../contracts/dist/descriptor-Cyo9FP9n.js
29948
+ //#region ../contracts/dist/descriptor-DCCz5AHd.js
29949
+ const ValueEvolutionTypeId = Symbol.for("ignotum/schema/ValueEvolution");
29950
+ const evolutionRules = (value) => value[ValueEvolutionTypeId] ?? {};
29951
+ const codec = (value) => Schema.toCodecJson(value);
29952
+ const encode = (schema, value) => Schema.decodeUnknownSync(Schema.Json)(Schema.encodeUnknownSync(codec(schema), { onExcessProperty: "error" })(value));
29953
+ const decode = (schema, value) => Schema.decodeUnknownSync(codec(schema), { onExcessProperty: "error" })(value);
29954
+ const copy = (value) => Object.create(Object.getPrototypeOf(value), Object.getOwnPropertyDescriptors(value));
29955
+ const isValueObject = Schema.is(Schema.Record(Schema.String, Schema.Unknown));
29956
+ const attachEvolution = (value, rules = {}) => {
29957
+ Object.defineProperty(value, ValueEvolutionTypeId, {
29958
+ configurable: true,
29959
+ value: rules
29960
+ });
29961
+ return Object.assign(value, {
29962
+ default(defaultValue) {
29963
+ if (getValueDescriptor(value)?.type === "optional") throw new Error("Defaults require a non-optional validator. A default already allows omission on creation.");
29964
+ const defaultJson = JSON.stringify(encode(value, defaultValue));
29965
+ const next = copy(value);
29966
+ const constructor = Schema.withConstructorDefault(Effect.sync(() => decode(value, JSON.parse(defaultJson))))(value);
29967
+ Object.defineProperty(next, "ast", {
29968
+ value: constructor.ast,
29969
+ configurable: true
29970
+ });
29971
+ return attachEvolution(next, {
29972
+ ...rules,
29973
+ defaultJson
29974
+ });
29975
+ },
29976
+ renameFrom(name) {
29977
+ if (name.length === 0 || [
29978
+ "id",
29979
+ "createdAt",
29980
+ "updatedAt"
29981
+ ].includes(name)) throw new Error(`Cannot rename reserved or empty field '${name}'.`);
29982
+ return attachEvolution(copy(value), {
29983
+ ...rules,
29984
+ renameFrom: name
29985
+ });
29986
+ },
29987
+ from(previous, convert) {
29988
+ if (getValueDescriptor(previous) === void 0) throw new Error("Historical values must use an Ignotum values validator.");
29989
+ return attachEvolution(copy(value), {
29990
+ ...rules,
29991
+ previous: [...rules.previous ?? [], {
29992
+ schema: previous,
29993
+ convert
29994
+ }]
29995
+ });
29996
+ }
29997
+ });
29998
+ };
29999
+ const withValueChildren = (value, children) => {
30000
+ attachEvolution(value, {
30001
+ ...evolutionRules(value),
30002
+ children
30003
+ });
30004
+ return value;
30005
+ };
30006
+ const validateFieldRenames = (fields) => {
30007
+ const sources = /* @__PURE__ */ new Set();
30008
+ for (const [name, field] of Object.entries(fields)) {
30009
+ const source = evolutionRules(field).renameFrom;
30010
+ if (source === void 0) continue;
30011
+ if (source === name || Object.hasOwn(fields, source) || sources.has(source)) throw new Error(`Rename source '${source}' conflicts with another field.`);
30012
+ sources.add(source);
30013
+ }
30014
+ };
30015
+ const mapChildren = (schema, value, visit, rename) => {
30016
+ const children = evolutionRules(schema).children;
30017
+ if (children === void 0) return value;
30018
+ switch (children.type) {
30019
+ case "object": {
30020
+ if (!isValueObject(value)) return value;
30021
+ const result = { ...value };
30022
+ for (const [name, field] of Object.entries(children.fields)) {
30023
+ const source = rename ? evolutionRules(field).renameFrom : void 0;
30024
+ if (source !== void 0 && Object.hasOwn(result, source)) {
30025
+ if (Object.hasOwn(result, name)) throw new Error(`Both '${name}' and its rename source '${source}' are present.`);
30026
+ Object.defineProperty(result, name, {
30027
+ value: result[source],
30028
+ enumerable: true,
30029
+ configurable: true,
30030
+ writable: true
30031
+ });
30032
+ delete result[source];
30033
+ }
30034
+ const next = visit(field, Object.hasOwn(result, name) ? result[name] : void 0);
30035
+ if (next !== void 0) Object.defineProperty(result, name, {
30036
+ value: next,
30037
+ enumerable: true,
30038
+ configurable: true,
30039
+ writable: true
30040
+ });
30041
+ }
30042
+ return result;
30043
+ }
30044
+ case "array": return Array.isArray(value) ? value.map((item) => visit(children.value, item)) : value;
30045
+ case "record": return isValueObject(value) ? Object.fromEntries(Object.entries(value).map(([key, item]) => [key, visit(children.value, item)])) : value;
30046
+ case "nullable": return value === null ? value : visit(children.value, value);
30047
+ case "optional": return value === void 0 ? value : visit(children.value, value);
30048
+ case "union":
30049
+ for (const member of children.members) try {
30050
+ const result = visit(member, value);
30051
+ if (rename) decode(member, result);
30052
+ else encode(member, result);
30053
+ return result;
30054
+ } catch {}
30055
+ return value;
30056
+ }
30057
+ };
30058
+ /** Apply creation defaults to decoded values, without accepting historical representations. */
30059
+ const applyValueDefaults = (schema, value) => {
30060
+ const rules = evolutionRules(schema);
30061
+ if (value === void 0 && rules.defaultJson !== void 0) return decode(schema, JSON.parse(rules.defaultJson));
30062
+ return mapChildren(schema, value, applyValueDefaults, false);
30063
+ };
30064
+ /** Convert a stored JSON value to the current representation, preserving already valid values. */
30065
+ const evolveStoredValue = (schema, value) => {
30066
+ try {
30067
+ decode(schema, value);
30068
+ return value;
30069
+ } catch {}
30070
+ const rules = evolutionRules(schema);
30071
+ if (value === void 0 && rules.defaultJson !== void 0) return JSON.parse(rules.defaultJson);
30072
+ let failure;
30073
+ try {
30074
+ const next = mapChildren(schema, value, evolveStoredValue, true);
30075
+ decode(schema, next);
30076
+ return next;
30077
+ } catch (cause) {
30078
+ failure = cause;
30079
+ }
30080
+ const matches = [];
30081
+ for (const previous of rules.previous ?? []) try {
30082
+ matches.push({
30083
+ previous,
30084
+ value: decode(previous.schema, value)
30085
+ });
30086
+ } catch {}
30087
+ if (matches.length > 1) throw new Error("More than one historical validator matches this value.");
30088
+ const match = matches[0];
30089
+ if (match === void 0) throw failure;
30090
+ return encode(schema, applyValueDefaults(schema, match.previous.convert(match.value)));
30091
+ };
30092
+ const evolveStoredFields = (schema, value) => Schema.decodeUnknownSync(Schema.JsonObject)(evolveStoredValue(schema, value));
29949
30093
  const LiteralValue = Schema.Union([
29950
30094
  Schema.String,
29951
30095
  Schema.Finite,
@@ -30029,7 +30173,7 @@ const attachValueDescriptor = Function$1.dual(2, (value, descriptor) => {
30029
30173
  value: descriptor,
30030
30174
  writable: false
30031
30175
  });
30032
- return value;
30176
+ return attachEvolution(value);
30033
30177
  });
30034
30178
  const getValueDescriptor = (value) => {
30035
30179
  if (!Predicate.hasProperty(value, ValueDescriptorTypeId)) return void 0;
@@ -30205,7 +30349,7 @@ const ServerBuildManifestV2 = Schema.Struct({
30205
30349
  schema: ArtifactReference,
30206
30350
  functions: Schema.Array(ServerFunctionArtifact)
30207
30351
  });
30208
- const ServerBuildManifest = upgrade(upgrade(initial(ServerBuildManifestV1), ServerBuildManifestV2, (manifest) => ({
30352
+ const ServerBuildManifest = upgrade(upgrade(upgrade(initial(ServerBuildManifestV1), ServerBuildManifestV2, (manifest) => ({
30209
30353
  formatVersion: 2,
30210
30354
  schema: manifest.schema,
30211
30355
  functions: manifest.functions
@@ -30218,6 +30362,18 @@ const ServerBuildManifest = upgrade(upgrade(initial(ServerBuildManifestV1), Serv
30218
30362
  formatVersion: 3,
30219
30363
  schema: manifest.schema,
30220
30364
  functions: manifest.functions
30365
+ })), Schema.Struct({
30366
+ formatVersion: Schema.Literal(4),
30367
+ schema: ArtifactReference,
30368
+ environment: Schema.optional(ServerEnvironmentArtifact),
30369
+ functions: Schema.Array(ServerFunctionArtifact),
30370
+ schemaEvolution: Schema.optional(Schema.Struct({
30371
+ bundle: ArtifactReference,
30372
+ sourceMap: ArtifactReference
30373
+ }))
30374
+ }), (manifest) => ({
30375
+ ...manifest,
30376
+ formatVersion: 4
30221
30377
  }));
30222
30378
  const deploymentInventoryPath = ArtifactPath.make("inventory.json");
30223
30379
  const clientManifestPath = ArtifactPath.make("client/manifest.json");
@@ -30273,7 +30429,8 @@ Schema.Struct({
30273
30429
  previousDeploymentId: Schema.NullOr(DeploymentId),
30274
30430
  previousGeneration: DeploymentGeneration,
30275
30431
  deploymentId: DeploymentId,
30276
- generation: DeploymentGeneration
30432
+ generation: DeploymentGeneration,
30433
+ schemaPreparation: Schema.optional(Schema.Boolean)
30277
30434
  });
30278
30435
  const CreateAppRequest = Schema.Struct({ slug: AppSlug });
30279
30436
  const BeginDeploymentRequest = Schema.Struct({ inventory: DeploymentInventory });
@@ -30335,7 +30492,7 @@ var IdGenerator = class IdGenerator extends Context.Service()("@ignotum/shared/i
30335
30492
  };
30336
30493
  //#endregion
30337
30494
  //#region package.json
30338
- var version = "0.0.13";
30495
+ var version = "0.0.14";
30339
30496
  //#endregion
30340
30497
  //#region src/cli/codegen.ts
30341
30498
  const generatedHeader = "// Generated by `ignotum codegen`. Do not edit.";
@@ -30854,15 +31011,19 @@ const requireDescriptor = (value) => {
30854
31011
  return descriptor;
30855
31012
  };
30856
31013
  const array = (value) => {
30857
- return attachValueDescriptor(Schema.Array(value), {
31014
+ return withValueChildren(attachValueDescriptor(Schema.Array(value), {
30858
31015
  type: "array",
30859
31016
  value: requireDescriptor(value)
31017
+ }), {
31018
+ type: "array",
31019
+ value
30860
31020
  });
30861
31021
  };
30862
31022
  const assertFieldsExist = (fields, keys, operation) => {
30863
31023
  for (const key of keys) if (!Object.hasOwn(fields, key)) throw new Error(`Cannot ${operation} unknown field '${String(key)}'.`);
30864
31024
  };
30865
31025
  const fixedObject = (fields) => {
31026
+ validateFieldRenames(fields);
30866
31027
  const pick = (...keys) => {
30867
31028
  assertFieldsExist(fields, keys, "pick");
30868
31029
  return fixedObject(Struct.pick(fields, keys));
@@ -30892,9 +31053,12 @@ const fixedObject = (fields) => {
30892
31053
  }
30893
31054
  return fixedObject(partialFields);
30894
31055
  };
30895
- const schema = attachValueDescriptor(Schema.Struct(fields), {
31056
+ const schema = withValueChildren(attachValueDescriptor(Schema.Struct(fields), {
30896
31057
  type: "object",
30897
31058
  fields: descriptorFields(fields)
31059
+ }), {
31060
+ type: "object",
31061
+ fields
30898
31062
  });
30899
31063
  return Object.assign(schema, {
30900
31064
  extend,
@@ -30906,27 +31070,39 @@ const fixedObject = (fields) => {
30906
31070
  };
30907
31071
  const object = (fields) => fixedObject(fields);
30908
31072
  const optional = (value) => {
30909
- return attachValueDescriptor(Schema.optional(value), {
31073
+ return withValueChildren(attachValueDescriptor(Schema.optional(value), {
30910
31074
  type: "optional",
30911
31075
  value: requireDescriptor(value)
31076
+ }), {
31077
+ type: "optional",
31078
+ value
30912
31079
  });
30913
31080
  };
30914
31081
  const nullable = (value) => {
30915
- return attachValueDescriptor(Schema.NullOr(value), {
31082
+ return withValueChildren(attachValueDescriptor(Schema.NullOr(value), {
30916
31083
  type: "nullable",
30917
31084
  value: requireDescriptor(value)
31085
+ }), {
31086
+ type: "nullable",
31087
+ value
30918
31088
  });
30919
31089
  };
30920
31090
  const record = (value) => {
30921
- return attachValueDescriptor(Schema.Record(Schema.String, value), {
31091
+ return withValueChildren(attachValueDescriptor(Schema.Record(Schema.String, value), {
30922
31092
  type: "record",
30923
31093
  value: requireDescriptor(value)
31094
+ }), {
31095
+ type: "record",
31096
+ value
30924
31097
  });
30925
31098
  };
30926
31099
  const union = (...members) => {
30927
- return attachValueDescriptor(Schema.Union(members), {
31100
+ return withValueChildren(attachValueDescriptor(Schema.Union(members), {
30928
31101
  type: "union",
30929
31102
  members: members.map(requireDescriptor)
31103
+ }), {
31104
+ type: "union",
31105
+ members
30930
31106
  });
30931
31107
  };
30932
31108
  Redacted.isRedacted, Redacted.value;
@@ -31209,6 +31385,13 @@ const validateManifest = Effect.fn("Deployment.validateManifest")(function* (man
31209
31385
  references.push(manifest.environment.artifact);
31210
31386
  referencedPaths.add(serverEnvironmentPath);
31211
31387
  }
31388
+ if (manifest.schemaEvolution !== void 0) {
31389
+ const { bundle, sourceMap } = manifest.schemaEvolution;
31390
+ if (!bundle.path.startsWith("server/functions/") || !bundle.path.endsWith(".mjs") || sourceMap.path !== `${bundle.path}.map`) return yield* invalid$3("The schema conversion bundle or its source map path is invalid.");
31391
+ references.push(bundle, sourceMap);
31392
+ referencedPaths.add(bundle.path);
31393
+ referencedPaths.add(sourceMap.path);
31394
+ }
31212
31395
  for (const definition of manifest.functions) {
31213
31396
  if (addresses.has(definition.address)) return yield* invalid$3(`Duplicate function address '${definition.address}'.`);
31214
31397
  addresses.add(definition.address);
@@ -31368,18 +31551,40 @@ const makeSchemaSnapshot = (schema) => {
31368
31551
  };
31369
31552
  };
31370
31553
  const encodeSchemaSnapshot = (snapshot) => `${encodeCanonical(SchemaSnapshot, snapshot)}\n`;
31371
- Function$1.dual(2, (previous, next) => previous === void 0 || encodeCanonical(SchemaSnapshot, {
31372
- ...previous,
31373
- tables: previous.tables.map((table) => ({
31374
- ...table,
31375
- indexes: []
31376
- }))
31377
- }) === encodeCanonical(SchemaSnapshot, {
31378
- ...next,
31379
- tables: next.tables.map((table) => ({
31380
- ...table,
31381
- indexes: []
31382
- }))
31554
+ const acceptsPreviousValue = (previous, next) => {
31555
+ if (encodeCanonical(ValueDescriptor, previous) === encodeCanonical(ValueDescriptor, next)) return true;
31556
+ if (previous.type === "never") return true;
31557
+ if (next.type === "optional") return acceptsPreviousValue(previous.type === "optional" ? previous.value : previous, next.value);
31558
+ if (previous.type === "optional") return false;
31559
+ if (next.type === "nullable") return previous.type === "null" || acceptsPreviousValue(previous.type === "nullable" ? previous.value : previous, next.value);
31560
+ if (previous.type === "nullable") return acceptsPreviousValue({ type: "null" }, next) && acceptsPreviousValue(previous.value, next);
31561
+ if (previous.type === "union") return previous.members.every((member) => acceptsPreviousValue(member, next));
31562
+ if (next.type === "union") return next.members.some((member) => acceptsPreviousValue(previous, member));
31563
+ if (previous.type === "integer" && next.type === "number") return true;
31564
+ if (previous.type === "userId" && next.type === "string") return true;
31565
+ if (previous.type === "literals") return previous.values.every((value) => acceptsPreviousValue({
31566
+ type: "literal",
31567
+ value
31568
+ }, next));
31569
+ if (previous.type === "literal") {
31570
+ if (next.type === "literals") return next.values.includes(previous.value);
31571
+ if (next.type === "string") return Predicate.isString(previous.value);
31572
+ if (next.type === "boolean") return Predicate.isBoolean(previous.value);
31573
+ if (next.type === "number") return Predicate.isNumber(previous.value);
31574
+ if (next.type === "integer") return Predicate.isNumber(previous.value) && Number.isInteger(previous.value);
31575
+ }
31576
+ if (previous.type === "file" && next.type === "file") return previous.maxBytes <= next.maxBytes && previous.formats.every((format) => next.formats.includes(format));
31577
+ if (previous.type === "array" && next.type === "array" || previous.type === "record" && next.type === "record") return acceptsPreviousValue(previous.value, next.value);
31578
+ if (previous.type === "object" && next.type === "object") return acceptsPreviousFields(previous.fields, next.fields);
31579
+ return false;
31580
+ };
31581
+ const acceptsPreviousFields = (previous, next) => previous.every((field) => {
31582
+ const target = next.find((candidate) => candidate.name === field.name);
31583
+ return target !== void 0 && acceptsPreviousValue(field.value, target.value);
31584
+ }) && next.every((field) => previous.some((candidate) => candidate.name === field.name) || field.value.type === "optional");
31585
+ Function$1.dual(2, (previous, next) => previous === void 0 || previous.tables.every((table) => {
31586
+ const target = next.tables.find((candidate) => candidate.name === table.name);
31587
+ return target !== void 0 && acceptsPreviousFields(table.fields, target.fields);
31383
31588
  }));
31384
31589
  const AppConfiguration = initial(Schema.Struct({
31385
31590
  formatVersion: Schema.Literal(1),
@@ -32413,16 +32618,22 @@ const discoverServerFunctions = Effect.fn("Deploy.discoverServerFunctionsIsolate
32413
32618
  })));
32414
32619
  }));
32415
32620
  });
32416
- const serverFunctionEntryPlugin = (definition) => {
32621
+ const serverFunctionEntryPlugin = (definition, schemaPath) => {
32417
32622
  const entryId = `virtual:ignotum/server-function/${definition.address}`;
32418
32623
  const resolvedEntryId = `\0${entryId}`;
32419
- const source = `import { call } from ${encodeJavaScriptString(hostModuleId)}
32624
+ const source = schemaPath === void 0 ? `import { call } from ${encodeJavaScriptString(hostModuleId)}
32420
32625
  import { invoke } from "ignotum/internal/host"
32421
32626
  import { ${definition.exportName} as definition } from ${encodeJavaScriptString(normalizePath(definition.modulePath))}
32422
32627
 
32423
32628
  export default async function handler(encodedRequest, encodedEnvironment) {
32424
32629
  return invoke(definition, JSON.parse(encodedRequest), call, JSON.parse(encodedEnvironment))
32425
32630
  }
32631
+ ` : `import { evolve } from "ignotum/internal/host"
32632
+ import schema from ${encodeJavaScriptString(normalizePath(schemaPath))}
32633
+
32634
+ export default function handler(encodedRequest) {
32635
+ return evolve(schema, encodedRequest)
32636
+ }
32426
32637
  `;
32427
32638
  return {
32428
32639
  name: "ignotum:server-function-entry",
@@ -32430,7 +32641,7 @@ export default async function handler(encodedRequest, encodedEnvironment) {
32430
32641
  resolveId: (id) => id === entryId ? resolvedEntryId : void 0
32431
32642
  };
32432
32643
  };
32433
- const buildServerFunction = Effect.fn("Deploy.buildServerFunction")(function* (appDirectory, serverOutputDirectory, conditions, definition) {
32644
+ const buildServerFunction = Effect.fn("Deploy.buildServerFunction")(function* (appDirectory, serverOutputDirectory, conditions, definition, schemaPath) {
32434
32645
  const fileSystem = yield* FileSystem.FileSystem;
32435
32646
  const path = yield* Path$1.Path;
32436
32647
  const functionDirectory = path.join(serverOutputDirectory, "functions", definition.moduleName);
@@ -32463,7 +32674,7 @@ const buildServerFunction = Effect.fn("Deploy.buildServerFunction")(function* (a
32463
32674
  configFile: false,
32464
32675
  logLevel: "warn",
32465
32676
  mode: "production",
32466
- plugins: [boundaries, serverFunctionEntryPlugin(definition)],
32677
+ plugins: [boundaries, serverFunctionEntryPlugin(definition, schemaPath)],
32467
32678
  publicDir: false,
32468
32679
  resolve: {
32469
32680
  conditions: [...conditions],
@@ -32476,7 +32687,7 @@ const buildServerFunction = Effect.fn("Deploy.buildServerFunction")(function* (a
32476
32687
  if (entries.length !== 1) return yield* InvalidBuildOutput.make({ message: `${definition.address} did not emit exactly one entry chunk.` });
32477
32688
  const entry = entries[0];
32478
32689
  if (entry === void 0) return yield* InvalidBuildOutput.make({ message: `${definition.address} did not emit an entry chunk.` });
32479
- if (entry.dynamicImports.length > 0 || entry.imports.length !== 1 || entry.imports[0] !== hostModuleId || !entry.exports.includes("default")) return yield* InvalidBuildOutput.make({ message: `${definition.address} is not a self-contained default-exported handler. Imports: ${entry.imports.join(", ") || "none"}; dynamic imports: ${entry.dynamicImports.join(", ") || "none"}; exports: ${entry.exports.join(", ") || "none"}.` });
32690
+ if (entry.dynamicImports.length > 0 || (schemaPath === void 0 ? entry.imports.length !== 1 || entry.imports[0] !== hostModuleId : entry.imports.length !== 0) || !entry.exports.includes("default")) return yield* InvalidBuildOutput.make({ message: `${definition.address} is not a self-contained default-exported handler. Imports: ${entry.imports.join(", ") || "none"}; dynamic imports: ${entry.dynamicImports.join(", ") || "none"}; exports: ${entry.exports.join(", ") || "none"}.` });
32480
32691
  const relativeFile = normalizePath(path.join("functions", definition.moduleName, entry.fileName));
32481
32692
  const relativeSourceMap = `${relativeFile}.map`;
32482
32693
  const absoluteFile = path.join(serverOutputDirectory, relativeFile);
@@ -32520,8 +32731,20 @@ const buildServer = Effect.fn("Deploy.buildServer")(function* (appDirectory, out
32520
32731
  const snapshotBytes = utf8Bytes(encodedSnapshot);
32521
32732
  yield* fileSystem.writeFile(path.join(outputDirectory, "schema.json"), snapshotBytes);
32522
32733
  const functions = yield* Effect.forEach(discovered.functions, (definition) => buildServerFunction(appDirectory, outputDirectory, conditions, definition), { concurrency: 4 });
32734
+ const schemaPath = path.join(appDirectory, "server", "schema.ts");
32735
+ const schemaEvolution = yield* buildServerFunction(appDirectory, outputDirectory, conditions, {
32736
+ address: FunctionAddress.make("api._schema.evolve"),
32737
+ exportName: "evolve",
32738
+ kind: "Mutation",
32739
+ moduleName: "_schema",
32740
+ modulePath: schemaPath
32741
+ }, schemaPath);
32523
32742
  const manifest = {
32524
- formatVersion: 3,
32743
+ formatVersion: 4,
32744
+ schemaEvolution: {
32745
+ bundle: schemaEvolution.bundle,
32746
+ sourceMap: schemaEvolution.sourceMap
32747
+ },
32525
32748
  environment: {
32526
32749
  artifact: yield* artifactReference(serverEnvironmentPath, environmentBytes),
32527
32750
  fields: discovered.environment.fields
@@ -32559,6 +32782,11 @@ var DeploymentActivationUnconfirmed = class extends Schema.TaggedError()("Deploy
32559
32782
  deploymentId: DeploymentId,
32560
32783
  message: Schema.String
32561
32784
  }) {};
32785
+ const activationError = (deploymentId, cause) => cause._tag === "ControlClientUnavailable" || cause.status >= 500 ? DeploymentActivationUnconfirmed.make({
32786
+ cause,
32787
+ deploymentId,
32788
+ message: `Deployment ${deploymentId} is ready, but activation could not be confirmed. Run ignotum deploy --resume ${deploymentId} to finish activation.`
32789
+ }) : cause;
32562
32790
  const replaceBuildDirectory = Effect.fn("Deploy.replaceBuildDirectory")(function* (stateDirectory, stagingDirectory, buildDirectory) {
32563
32791
  const fileSystem = yield* FileSystem.FileSystem;
32564
32792
  if (!(yield* fileSystem.exists(buildDirectory))) {
@@ -32641,11 +32869,7 @@ const uploadDeployment = Effect.fn("Deploy.upload")(function* (configuration, bu
32641
32869
  deploymentId: deployment.id,
32642
32870
  message: `Deployment ${deployment.id} could not be finalized. The active deployment was not changed.`
32643
32871
  }) }));
32644
- yield* control.activateDeployment(configuration.appId, deployment.id).pipe(Effect.catchTags({ ControlClientUnavailable: (cause) => DeploymentActivationUnconfirmed.make({
32645
- cause,
32646
- deploymentId: deployment.id,
32647
- message: `Deployment ${deployment.id} is ready, but activation could not be confirmed.`
32648
- }) }));
32872
+ yield* control.activateDeployment(configuration.appId, deployment.id).pipe(Effect.mapError((cause) => activationError(deployment.id, cause)));
32649
32873
  return deployment.id;
32650
32874
  });
32651
32875
  const deploy$1 = Effect.fn("Deploy.run")(function* (appDirectory, requestedSlug) {
@@ -32658,6 +32882,14 @@ const deploy$1 = Effect.fn("Deploy.run")(function* (appDirectory, requestedSlug)
32658
32882
  deploymentId
32659
32883
  };
32660
32884
  });
32885
+ const resumeDeployment = Effect.fn("Deploy.resume")(function* (appDirectory, deploymentId, requestedSlug) {
32886
+ const configuration = yield* configureApp(appDirectory, requestedSlug);
32887
+ yield* (yield* ControlClient).activateDeployment(configuration.appId, deploymentId).pipe(Effect.mapError((cause) => activationError(deploymentId, cause)));
32888
+ return {
32889
+ appUrl: appUrl(configuration.slug),
32890
+ deploymentId
32891
+ };
32892
+ });
32661
32893
  //#endregion
32662
32894
  //#region src/dev-runtime/dev-database.ts
32663
32895
  const databaseFileNames = [
@@ -33260,9 +33492,11 @@ const LocalCompletion = Schema.Struct({
33260
33492
  });
33261
33493
  const projection = (user, share) => ({
33262
33494
  id: user.id,
33263
- name: share.includes("name") ? user.name : void 0,
33264
- email: share.includes("email") ? user.email : void 0,
33265
- image: share.includes("image") ? user.image : void 0
33495
+ ...Record.filter({
33496
+ name: share.includes("name") ? user.name : void 0,
33497
+ email: share.includes("email") ? user.email : void 0,
33498
+ image: share.includes("image") ? user.image : void 0
33499
+ }, Predicate.isNotUndefined)
33266
33500
  });
33267
33501
  var LocalId = class LocalId extends Context.Service()("ignotum/dev-runtime/id/LocalId") {
33268
33502
  static layer = (directory) => Layer.effect(LocalId, Effect.gen(function* () {
@@ -33563,6 +33797,7 @@ var FunctionRuntime = class extends Context.Service()("@ignotum/runtime/function
33563
33797
  //#endregion
33564
33798
  //#region ../contracts/dist/runtime/transport.js
33565
33799
  const runtimeInvocationPath = "/v1/invoke";
33800
+ const runtimeConsolePath = "/v1/console/read";
33566
33801
  const runtimeRevisionPath = "/v1/revision";
33567
33802
  const runtimeIndexPreparePath = "/v1/indexes/prepare";
33568
33803
  const runtimeIndexCommitPath = "/v1/indexes/commit";
@@ -33573,6 +33808,7 @@ const appSyncPath = `${ignotumPathPrefix$1}/v1/sync`;
33573
33808
  const fileUploadUrlPrefix = `${ignotumPathPrefix$1}/v1/files/upload/`;
33574
33809
  const fileGrantUrlPrefix = `${ignotumPathPrefix$1}/v1/files/grant/`;
33575
33810
  const RuntimeRequestPath = Schema.Literals([
33811
+ runtimeConsolePath,
33576
33812
  runtimeInvocationPath,
33577
33813
  runtimeRevisionPath,
33578
33814
  runtimeIndexPreparePath,
@@ -33600,9 +33836,13 @@ Schema.Struct({
33600
33836
  });
33601
33837
  Schema.Struct({
33602
33838
  appId: AppId,
33603
- deploymentId: DeploymentId
33839
+ deploymentId: DeploymentId,
33840
+ generation: Schema.optional(Schema.Natural)
33604
33841
  });
33605
- Schema.Struct({ completed: Schema.Literal(true) });
33842
+ Schema.Union([Schema.Struct({ completed: Schema.Literal(true) }), Schema.Struct({
33843
+ completed: Schema.Literal(false),
33844
+ message: Schema.String
33845
+ })]);
33606
33846
  const RuntimeErrorCode = Schema.Literals([
33607
33847
  "InvalidRequest",
33608
33848
  "InvalidSignature",
@@ -33705,10 +33945,17 @@ const addApplicationFiles = Effect.gen(function* () {
33705
33945
  ) STRICT
33706
33946
  `;
33707
33947
  });
33948
+ const addSchemaState = Effect.gen(function* () {
33949
+ yield* (yield* Client.SqlClient)`CREATE TABLE schema_state (
33950
+ singleton INTEGER PRIMARY KEY NOT NULL CHECK (singleton = 1),
33951
+ snapshot TEXT NOT NULL CHECK (json_valid(snapshot))
33952
+ ) STRICT`;
33953
+ });
33708
33954
  const developmentMigrationLoader = Migrator.fromRecord({
33709
33955
  "0001_initial_development_schema": initialDevelopmentSchema,
33710
33956
  "0002_application_indexes": addDevelopmentIndexes,
33711
- "0003_application_files": addApplicationFiles
33957
+ "0003_application_files": addApplicationFiles,
33958
+ "0004_schema_state": addSchemaState
33712
33959
  });
33713
33960
  var DevelopmentDatabase = class DevelopmentDatabase extends Context.Service()("ignotum/dev-runtime/migrations/DevelopmentDatabase") {
33714
33961
  static layer = Layer.effect(DevelopmentDatabase, Migrator.make({})({
@@ -33792,7 +34039,11 @@ const tableFor = Effect.fn("LocalDatabase.tableFor")(function* (schema, tableNam
33792
34039
  });
33793
34040
  const fieldsCodec = (table) => Schema.fromJsonString(Schema.make(Schema.toCodecJson(table.schema).ast));
33794
34041
  const encodeFields = Effect.fn("LocalDatabase.encodeFields")(function* (table, tableName, value, operation, id) {
33795
- return yield* Schema.encodeEffect(fieldsCodec(table), { onExcessProperty: "error" })(value).pipe(Effect.mapError((cause) => schemaMismatch(tableName, id, operation, cause)));
34042
+ const normalized = yield* Effect.try({
34043
+ try: () => operation === "patch" ? value : applyValueDefaults(table.schema, value),
34044
+ catch: (cause) => schemaMismatch(tableName, id, operation, cause)
34045
+ });
34046
+ return yield* Schema.encodeUnknownEffect(fieldsCodec(table), { onExcessProperty: "error" })(normalized).pipe(Effect.mapError((cause) => schemaMismatch(tableName, id, operation, cause)));
33796
34047
  });
33797
34048
  const decodeFields = Effect.fn("LocalDatabase.decodeFields")(function* (table, tableName, row, operation) {
33798
34049
  return yield* Schema.decodeEffect(fieldsCodec(table), { onExcessProperty: "error" })(row.fields).pipe(Effect.mapError((cause) => schemaMismatch(tableName, row.id, operation, cause)));
@@ -33961,6 +34212,54 @@ var LocalDatabase = class LocalDatabase extends Context.Service()("ignotum/dev-r
33961
34212
  yield* Effect.forEach(stored.filter((index) => !expected.has(index.identity)), (index) => sql`DELETE FROM indexes WHERE id = ${index.id}`);
33962
34213
  }
33963
34214
  });
34215
+ const prepareSchema = Effect.fn("LocalDatabase.prepareSchema")(function* (schema) {
34216
+ const definitions = schema[SchemaDefinitionTypeId];
34217
+ const snapshot = encodeCanonical(SchemaSnapshot, {
34218
+ formatVersion: 2,
34219
+ tables: Object.entries(definitions).sort(([left], [right]) => left.localeCompare(right)).map(([name, definition]) => ({
34220
+ name,
34221
+ fields: descriptorFields(definition.fields),
34222
+ indexes: Object.values(definition.indexes).sort((left, right) => left.name.localeCompare(right.name))
34223
+ }))
34224
+ });
34225
+ const current = yield* sql`SELECT snapshot FROM schema_state WHERE singleton = 1`;
34226
+ if (current[0]?.snapshot === snapshot) return;
34227
+ let changedData = false;
34228
+ const storedTables = yield* sql`SELECT id, name FROM tables`;
34229
+ for (const stored of storedTables) {
34230
+ if (Object.hasOwn(definitions, stored.name)) continue;
34231
+ if ((yield* sql`SELECT 1 FROM documents WHERE tableId = ${stored.id} LIMIT 1`).length > 0) return yield* Effect.die(`Cannot remove non-empty table '${stored.name}'.`);
34232
+ yield* sql`DELETE FROM tables WHERE id = ${stored.id}`;
34233
+ }
34234
+ for (const [name, definition] of Object.entries(definitions)) {
34235
+ const table = yield* resolveTable(schema, name);
34236
+ let cursor = "";
34237
+ let changed = false;
34238
+ while (true) {
34239
+ const rows = yield* sql`
34240
+ SELECT id, fields FROM documents WHERE tableId = ${table.id} AND id > ${cursor}
34241
+ ORDER BY id LIMIT 100
34242
+ `;
34243
+ if (rows.length === 0) break;
34244
+ for (const row of rows) {
34245
+ const fields = yield* Effect.try({
34246
+ try: () => JSON.stringify(evolveStoredFields(definition.schema, JSON.parse(row.fields))),
34247
+ catch: () => /* @__PURE__ */ new Error(`Document '${row.id}' in '${name}' does not match the new schema or its conversion rules.`)
34248
+ }).pipe(Effect.orDie);
34249
+ if (fields !== row.fields) {
34250
+ yield* sql`UPDATE documents SET fields = ${fields} WHERE tableId = ${table.id} AND id = ${row.id}`;
34251
+ changed = true;
34252
+ changedData = true;
34253
+ }
34254
+ cursor = row.id;
34255
+ }
34256
+ }
34257
+ if (changed) yield* sql`DELETE FROM indexes WHERE tableId = ${table.id}`;
34258
+ }
34259
+ yield* sql`INSERT INTO schema_state (singleton, snapshot) VALUES (1, ${snapshot})
34260
+ ON CONFLICT (singleton) DO UPDATE SET snapshot = excluded.snapshot`;
34261
+ if (current.length > 0 || changedData) yield* advanceRevision(void 0);
34262
+ });
33964
34263
  const indexKeys = Effect.fn("LocalDatabase.indexKeys")(function* (table, row) {
33965
34264
  return (yield* resolvedIndexes(table)).map((index) => ({
33966
34265
  index,
@@ -34281,6 +34580,7 @@ var LocalDatabase = class LocalDatabase extends Context.Service()("ignotum/dev-r
34281
34580
  });
34282
34581
  };
34283
34582
  const trackedQueryTransaction = (schema, use) => sql.withTransaction(Effect.gen(function* () {
34583
+ yield* prepareSchema(schema).pipe(Effect.orDie);
34284
34584
  yield* prepareIndexes(schema).pipe(Effect.orDie);
34285
34585
  const dependencies = makeDependencyRecorder();
34286
34586
  const value = yield* use(Object.freeze({ db: makeReader(schema, dependencies) }));
@@ -34292,6 +34592,7 @@ var LocalDatabase = class LocalDatabase extends Context.Service()("ignotum/dev-r
34292
34592
  };
34293
34593
  }));
34294
34594
  const trackedMutationTransaction = (schema, use) => sql.withTransaction(Effect.gen(function* () {
34595
+ yield* prepareSchema(schema).pipe(Effect.orDie);
34295
34596
  yield* prepareIndexes(schema).pipe(Effect.orDie);
34296
34597
  const invalidations = makeDependencyRecorder();
34297
34598
  const value = yield* use(Object.freeze({ db: makeWriter(schema, invalidations) }));
@@ -35328,16 +35629,16 @@ const dev$1 = Effect.fn("Dev.run")(function* (options) {
35328
35629
  var authentication_default = "---\ntitle: Ignotum ID\ndescription: Sign users in and read their identity in queries and mutations.\nsection: Authentication\nslug: authentication\norder: 40\n---\n\n# Ignotum ID\n\nIgnotum ID lets people sign in to your app with Google. It is available in every app without an\nauthentication configuration file. The account people use in your app is separate from the\nIgnotum account you use to deploy it.\n\n## Add sign-in to your app\n\nUse `useId` from `ignotum/client`:\n\n```tsx\nimport { useId } from \"ignotum/client\";\n\nexport function Account() {\n const id = useId();\n\n if (id.status === \"pending\") return <p>Loading account...</p>;\n if (id.status === \"error\") return <p>{id.error}</p>;\n if (id.status === \"signedOut\") {\n return <button onClick={() => id.signIn()}>Sign in</button>;\n }\n\n return (\n <div>\n <p>{id.user.name ?? \"Signed in\"}</p>\n <button onClick={() => id.signOut()}>Sign out</button>\n </div>\n );\n}\n```\n\n`signIn()` opens Ignotum ID and returns to the current page when the person finishes or cancels.\nPass `returnTo: \"/account\"` to choose another page in your app. The destination must be a relative\npath within the app. The action's promise reports whether the redirect started. After returning,\n`id.outcome` reports `SignedIn`, `ProfileUpdated`, or `Cancelled` when available.\n\nSign-out ends this app's session. People can manage all their connected apps and sign out of their\nother sessions from the Ignotum ID account page.\n\n## Read the current user\n\nQueries and mutations receive `ctx.id`. Use `current()` when signed-out visitors are allowed:\n\n```ts\nconst user = yield * ctx.id.current();\nif (user === null) return [];\n```\n\nUse `require()` when the function needs a signed-in user:\n\n```ts\nconst user = yield * ctx.id.require();\n```\n\n`require()` returns the application error `IdRequired` when the visitor is signed out. Handle it\nwith `Result.match` as you would another application error.\n\nThe returned user has an `id` and optional `name`, `email`, and `image` fields. The same person has\na stable ID within your app, including after disconnecting and signing in again. Their ID differs\nbetween apps. Use the ID for ownership checks. Names and email addresses can change and should\nnot be used as ownership keys.\n\nIgnotum updates queries that read `ctx.id` when the user's shared profile changes. After sign-out\nor a loss of session verification, results that depend on identity return to pending until they\ncan be loaded for the current visitor.\n\n## Store ownership\n\nUse `values.userId()` for an Ignotum ID user reference:\n\n```ts\nimport { defineSchema } from \"ignotum/server\";\n\nexport default defineSchema(({ table, values }) => ({\n todos: table({\n ownerId: values.userId(),\n text: values.string(),\n }).index(\"by_owner\", [\"ownerId\"]),\n}));\n```\n\nSet the owner from `ctx.id.require()` in your mutation. Do not trust an owner ID supplied by Web\ncode. Check ownership before returning, updating, or deleting a private document. Signing in\nidentifies the caller; your functions decide which documents that caller may access.\n\nThe `User` and `UserId` types are exported from `ignotum/server` and `ignotum/client`.\n\nNext: [Profile sharing](profile-sharing.md)\n";
35329
35630
  //#endregion
35330
35631
  //#region ../../docs/user/profile-sharing.md?raw
35331
- var profile_sharing_default = "---\ntitle: Profile sharing\ndescription: Request optional profile information and handle changes to sharing.\nsection: Authentication\nslug: profile-sharing\norder: 41\n---\n\n# Profile sharing\n\nSigning in gives your app a user ID. Profile information is optional. Request the fields your app\nneeds when starting sign-in:\n\n```tsx\nconst id = useId();\nawait id.signIn({ profile: [\"name\", \"image\"] });\n```\n\nPeople choose which requested fields to share. A person can finish sign-in without sharing any of\nthem. Always handle absent fields in `id.user` and in the user returned by `ctx.id`.\n\nA signed-in person can share more information later:\n\n```tsx\nawait id.requestProfile([\"email\"], { returnTo: \"/settings\" });\n```\n\nThis request keeps the current app account. Cancelling it leaves existing sharing in place.\nRequesting profile information while signed out fails; start with `signIn()` instead.\n\nPeople can change their name and image, withdraw sharing, or disconnect your app from the Ignotum\nID account page. Your active queries and `useId` update when those changes reach the app. Keep\nprofile fields optional in the UI and use `user.id` for stored references.\n\nA shared email comes from the person's verified Google account. A shared image is a URL you can\nuse in an image element. Treat that URL as temporary and read it from the current profile instead\nof storing it as a permanent address.\n\nNext: [Test authentication locally](local-authentication.md)\n";
35632
+ var profile_sharing_default = "---\ntitle: Profile sharing\ndescription: Request optional profile information and handle changes to sharing.\nsection: Authentication\nslug: profile-sharing\norder: 41\n---\n\n# Profile sharing\n\nSigning in gives your app a user ID. Profile information is optional. Request the fields your app\nneeds when starting sign-in:\n\n```tsx\nconst id = useId();\nawait id.signIn({ profile: [\"name\", \"image\"] });\n```\n\nRequested fields are selected by default on the Ignotum ID continue page. People can uncheck\nnew fields before continuing and change sharing later from their ID account page. A person can\nfinish sign-in without sharing any of them. Always handle absent fields in `id.user` and in the\nuser returned by `ctx.id`.\n\nA signed-in person can share more information later:\n\n```tsx\nawait id.requestProfile([\"email\"], { returnTo: \"/settings\" });\n```\n\nThis request keeps the current app account. Cancelling it leaves existing sharing in place.\nRequesting profile information while signed out fails; start with `signIn()` instead.\n\nPeople can change their name and image, withdraw sharing, or disconnect your app from the Ignotum\nID account page. Your active queries and `useId` update when those changes reach the app. Keep\nprofile fields optional in the UI and use `user.id` for stored references.\n\nA shared email comes from the person's verified Google account. A shared image is a URL you can\nuse in an image element. Treat that URL as temporary and read it from the current profile instead\nof storing it as a permanent address.\n\nNext: [Test authentication locally](local-authentication.md)\n";
35332
35633
  //#endregion
35333
35634
  //#region ../../docs/user/local-authentication.md?raw
35334
- var local_authentication_default = "---\ntitle: Test locally\ndescription: Try signed-in and signed-out behavior with local usernames.\nsection: Authentication\nslug: local-authentication\norder: 42\n---\n\n# Test authentication locally\n\nRun `npx ignotum dev` and use your app's sign-in button. The local sign-in page lets you choose a\nusername such as `john`, enter a profile, and select which requested fields to share. You do not\nneed a Google account or provider credentials.\n\nLocal usernames are readable user IDs. Use lowercase letters, digits, underscores, or hyphens,\nstarting with a letter. Each browser tab keeps its own selected user, so you can test two people\nusing the app at once. Signing out affects that tab.\n\nUse `ctx.id.current()`, `ctx.id.require()`, and `useId()` in the same way locally and after\ndeployment. Test signed-out visitors, two different users, and a user who declines optional\nprofile sharing. Production user IDs are opaque; do not make application behavior depend on the\nshape of a local username.\n";
35635
+ var local_authentication_default = "---\ntitle: Test locally\ndescription: Try signed-in and signed-out behavior with local usernames.\nsection: Authentication\nslug: local-authentication\norder: 42\n---\n\n# Test authentication locally\n\nRun `npx ignotum dev` and use your app's sign-in button. The local sign-in page lets you choose a\nusername such as `john`, enter a profile, and select which requested fields to share. You do not\nneed a Google account or provider credentials.\n\nProfile fields you leave blank or do not share are omitted from the user returned to your app.\nYou can sign in with just a username.\n\nLocal usernames are readable user IDs. Use lowercase letters, digits, underscores, or hyphens,\nstarting with a letter. Each browser tab keeps its own selected user, so you can test two people\nusing the app at once. Signing out affects that tab.\n\nUse `ctx.id.current()`, `ctx.id.require()`, and `useId()` in the same way locally and after\ndeployment. Test signed-out visitors, two different users, and a user who declines optional\nprofile sharing. Production user IDs are opaque; do not make application behavior depend on the\nshape of a local username.\n";
35335
35636
  //#endregion
35336
35637
  //#region ../../docs/agent/AGENTS.md?raw
35337
35638
  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";
35338
35639
  //#endregion
35339
35640
  //#region ../../docs/agent/skills/ignotum/SKILL.md?raw
35340
- 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";
35641
+ 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 stored fields, read [Migrations](references/migrations.md). Changes that preserve\nvalid documents work automatically. Use `.default()`, `.renameFrom()`, and `.from()` in\n`server/schema.ts` for missing values, renamed fields, and historical representations. Conversions\nmust be synchronous and deterministic. Use application mutations for changes that depend on other\ndocuments or external data, or change the meaning of values that already satisfy the validator.\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";
35341
35642
  //#endregion
35342
35643
  //#region src/cli/agent-files.ts
35343
35644
  const references = [
@@ -35346,11 +35647,12 @@ const references = [
35346
35647
  ["local-authentication.md", local_authentication_default],
35347
35648
  ["api.md", "---\ntitle: HTTP API\ndescription: Use the authenticated API at api.ignotum.cloud.\nsection: Reference\nslug: api\norder: 73\n---\n\n# HTTP API\n\nThe public API at `https://api.ignotum.cloud` creates apps and manages immutable deployments. The\n`ignotum deploy` command is the normal client for these endpoints. Use the HTTP API directly when\nbuilding deployment tooling or checking an app after an interrupted command.\n\nThe API does not expose application queries and mutations over HTTP. Deployed clients call those\nfunctions through the generated client API.\n\n## Authentication\n\nEvery route except `GET /health` requires a bearer token:\n\n```http\nAuthorization: Bearer <IGNOTUM_SESSION_TOKEN>\n```\n\n`ignotum auth login` obtains this revocable session through the device authorization flow. Treat it\nas an account credential. Do not include it in client code or send it from a deployed app. The API\ndoes not enable browser CORS, so call it from a CLI, CI job, or other trusted server environment.\n\nJSON requests require `Content-Type: application/json`. A JSON body may be at most 1 MiB. Responses\nuse JSON unless a route is not found.\n\n## Common values\n\nIgnotum IDs are opaque strings. Their prefixes identify the kind:\n\n```text\napp_000000000000000000000001\ndep_000000000000000000000001\nteam_000000000000000000000001\n```\n\nEach prefix is followed by 24 lowercase letters or digits. Do not parse or generate these IDs.\n\nTimestamps such as `createdAt` and `readyAt` are Unix time in milliseconds.\n\nAn app object has this shape:\n\n```json\n{\n \"id\": \"app_000000000000000000000001\",\n \"teamId\": \"team_000000000000000000000001\",\n \"slug\": \"team-todos\",\n \"activeDeploymentId\": \"dep_000000000000000000000001\",\n \"deploymentGeneration\": 3,\n \"createdAt\": 1787925600000\n}\n```\n\n`activeDeploymentId` is `null` before the first activation. `deploymentGeneration` starts at 0 and\nincreases when a deployment becomes active.\n\n## Health\n\n```http\nGET /health\n```\n\nA healthy API returns `200`:\n\n```json\n{ \"service\": \"api\", \"status\": \"ok\" }\n```\n\nAn infrastructure check failure returns `503` with `status` set to `unavailable`. This route does\nnot require authentication.\n\n## Create an app\n\n```http\nPOST /v1/apps\nContent-Type: application/json\nAuthorization: Bearer <token>\n\n{ \"slug\": \"team-todos\" }\n```\n\nA slug is 1 through 63 lowercase letters, digits, or hyphens. It must start and end with a letter\nor digit. A successful request returns `201` and the new app object.\n\nThis operation is not idempotent. A repeated request for the same slug returns `SlugUnavailable`.\n\n## Get an app\n\nLook up an app by ID:\n\n```http\nGET /v1/apps/app_000000000000000000000001\nAuthorization: Bearer <token>\n```\n\nOr by slug:\n\n```http\nGET /v1/apps/by-slug/team-todos\nAuthorization: Bearer <token>\n```\n\nBoth routes return `200` and the same app shape. The authenticated account must own the app. The\nAPI currently has no route to list apps, rename a slug, or delete an app.\n\n## Deployment flow\n\nA deployment uses four steps:\n\n1. Begin with an inventory.\n2. Upload the canonical `inventory.json` and every file listed by it.\n3. Finalize the deployment.\n4. Activate the ready deployment.\n\nThe deployment is inactive until the last step succeeds.\n\n### Inventory\n\nThe begin request contains a version 1 inventory:\n\n```json\n{\n \"formatVersion\": 1,\n \"files\": [\n {\n \"path\": \"client/shell.html\",\n \"size\": 128,\n \"sha256\": \"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\n \"kind\": \"ClientShell\",\n \"contentType\": \"text/html; charset=utf-8\"\n }\n ]\n}\n```\n\nEach file entry has:\n\n| Field | Meaning |\n| ----------------- | ---------------------------------------------------------------------- |\n| `path` | Relative artifact path with `/` separators and no `.` or `..` segment. |\n| `size` | Byte length as a non-negative integer. |\n| `sha256` | Lowercase 64-character SHA-256 digest. |\n| `kind` | The artifact role listed below. |\n| `contentType` | MIME type stored with the file. |\n| `contentEncoding` | Optional encoding metadata. |\n\nThe defined kinds are `ClientAsset`, `ClientDocument`, `ClientManifest`, `ClientPublicFile`,\n`ClientShell`, `FunctionBundle`, `ServerManifest`, `SourceMap`, and `SchemaSnapshot`.\n\nFiles must be sorted by `path`. The inventory must contain `client/manifest.json`,\n`client/shell.html`, `server/manifest.json`, and `server/schema.json`. Paths and kinds must agree,\nand every manifest reference must match the inventory. `inventory.json` is not listed because it\ncannot contain its own hash.\n\nThe byte limits are in [Limits](limits.md). The public `ignotum` package does not expose an artifact\nbuilder or a build-only command. The formats below let deployment tooling inspect and transport an\nartifact emitted by the CLI. Building function bundles with another compiler is not a supported\npublic extension point.\n\nCanonical `inventory.json` is UTF-8 JSON with object keys sorted lexicographically, array order\npreserved, no insignificant whitespace, and one trailing line feed. Its upload headers contain the\nbyte length and SHA-256 digest of those exact bytes.\n\n### Client manifest\n\n`client/manifest.json` connects hosted URL paths to client files:\n\n```json\n{\n \"formatVersion\": 1,\n \"shell\": {\n \"path\": \"client/shell.html\",\n \"size\": 128,\n \"sha256\": \"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\"\n },\n \"routes\": [\n {\n \"pathname\": \"/manual.pdf\",\n \"artifact\": {\n \"path\": \"client/routes/manual.pdf\",\n \"size\": 4096,\n \"sha256\": \"abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789\"\n }\n }\n ]\n}\n```\n\nThe shell reference must point to `client/shell.html`. Routes must be sorted by `pathname`. A\npathname starts with `/`, uses only letters, digits, `.`, `_`, `~`, `-`, and `/`, and cannot claim\n`/_ignotum`. Each route points to a matching `ClientDocument` or `ClientPublicFile` inventory entry.\nAn artifact can have only one route, and every document or public-file entry must have one.\n\nFiles under `client/assets/` use `ClientAsset`. Files under `client/routes/` use `ClientDocument`\nwhen their name ends in `.html`; AVIF, GIF, ICO, JPEG, PNG, WebP, and PDF files use\n`ClientPublicFile`. The four fixed client paths use their corresponding fixed kinds.\n\n### Server manifest\n\n`server/manifest.json` lists every callable function and the schema snapshot:\n\n```json\n{\n \"formatVersion\": 1,\n \"schema\": {\n \"path\": \"server/schema.json\",\n \"size\": 512,\n \"sha256\": \"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\"\n },\n \"functions\": [\n {\n \"address\": \"api.todos.list\",\n \"kind\": \"Query\",\n \"bundle\": {\n \"path\": \"server/functions/todos/list-a1b2c3.mjs\",\n \"size\": 2048,\n \"sha256\": \"abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789\"\n },\n \"sourceMap\": {\n \"path\": \"server/functions/todos/list-a1b2c3.mjs.map\",\n \"size\": 1024,\n \"sha256\": \"123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0\"\n }\n }\n ]\n}\n```\n\nFunction addresses have the form `api.<module>.<export>`, where both names are TypeScript\nidentifiers. `kind` is `Query` or `Mutation`. Addresses and referenced paths must be unique. A\nbundle ends in `.mjs`; its source map is the same path followed by `.map`. Every `server/` file is\nthe manifest, schema, or one referenced bundle or source map.\n\n### Schema snapshot\n\n`server/schema.json` is version 2:\n\n```json\n{\n \"formatVersion\": 2,\n \"tables\": [\n {\n \"name\": \"todos\",\n \"fields\": [\n { \"name\": \"completed\", \"value\": { \"type\": \"boolean\" } },\n { \"name\": \"text\", \"value\": { \"type\": \"string\" } }\n ],\n \"indexes\": [{ \"name\": \"by_completed\", \"fields\": [\"completed\"] }]\n }\n ]\n}\n```\n\nSort tables and fields by name. Sort indexes by name and preserve each index's declared field\norder. A value descriptor uses one of these recursive forms:\n\n```text\n{ \"type\": \"boolean\" | \"date\" | \"integer\" | \"never\" | \"null\" | \"number\" | \"string\" }\n{ \"type\": \"id\", \"table\": string }\n{ \"type\": \"literal\", \"value\": string | finite number | boolean }\n{ \"type\": \"literals\", \"values\": Array<string | finite number | boolean> }\n{ \"type\": \"array\" | \"nullable\" | \"optional\" | \"record\", \"value\": descriptor }\n{ \"type\": \"union\", \"members\": Array<descriptor> }\n{ \"type\": \"object\", \"fields\": Array<{ \"name\": string, \"value\": descriptor }> }\n{ \"type\": \"error\", \"tag\": string, \"fields\": Array<{ \"name\": string, \"value\": descriptor }> }\n```\n\nSort object and error fields by name. This snapshot must describe the same schema used to build the\nfunction bundles. After the first activation, table and field descriptors must match the active\nsnapshot exactly; only the `indexes` arrays may change.\n\n### Begin\n\n```http\nPOST /v1/apps/{appId}/deployments\nContent-Type: application/json\nAuthorization: Bearer <token>\n\n{ \"inventory\": { \"formatVersion\": 1, \"files\": [] } }\n```\n\nThe abbreviated body above shows the envelope only. Send a complete valid inventory. Success\nreturns `201` and a deployment object:\n\n```json\n{\n \"id\": \"dep_000000000000000000000002\",\n \"appId\": \"app_000000000000000000000001\",\n \"status\": \"Uploading\",\n \"inventory\": { \"formatVersion\": 1, \"files\": [] },\n \"createdAt\": 1787925601000,\n \"readyAt\": null\n}\n```\n\nThe response repeats the complete accepted inventory. This operation is not idempotent. Repeating\nit creates another deployment.\n\n### Upload a file\n\nUpload each path as one percent-encoded route component. For example,\n`client/shell.html` becomes `client%2Fshell.html`:\n\n```http\nPUT /v1/apps/{appId}/deployments/{deploymentId}/files/client%2Fshell.html\nAuthorization: Bearer <token>\nContent-Length: 128\nX-Ignotum-Sha256: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\n\n<128 raw bytes>\n```\n\n`Content-Length` and `X-Ignotum-Sha256` must match the accepted inventory entry. The body is the raw\nfile, not JSON. Upload the canonical `inventory.json` through the same route even though it is not\nin the `files` array.\n\nA new file returns:\n\n```json\n{ \"status\": \"Stored\" }\n```\n\nUploading the same path, bytes, and metadata again is safe and returns `AlreadyStored`. Different\ncontent at an existing path returns `DeploymentFileConflict`; deployment files are immutable.\n\n### Finalize\n\n```http\nPOST /v1/apps/{appId}/deployments/{deploymentId}/finalize\nAuthorization: Bearer <token>\n```\n\nThe request has no body. Finalization checks that every expected file exists, no extra file exists,\nall metadata matches, and the client and server manifests are valid. Success returns `200` with the\ndeployment status changed to `Ready` and `readyAt` set.\n\nFinalizing an already ready deployment is safe and returns its current record.\n\n### Activate\n\n```http\nPOST /v1/apps/{appId}/deployments/{deploymentId}/activate\nAuthorization: Bearer <token>\n```\n\nThe request has no body. Only a ready, unexpired deployment can be activated. Ignotum rejects a\nstored-schema change but permits index changes. See\n[Deployment model](deployment-model.md#schema-compatibility).\n\nSuccess returns `200` with the updated app object. Activating its current deployment again is safe.\nActivation reloads connected app clients.\n\n## Errors\n\nEvery recognized API failure has this body:\n\n```json\n{\n \"code\": \"SlugUnavailable\",\n \"message\": \"The app slug is unavailable.\"\n}\n```\n\nTreat `code` as the machine-readable value. Messages may change.\n\n| Status | Codes |\n| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `400` | `InvalidRequest`, `DeploymentFileUnexpected` |\n| `401` | `Unauthorized` |\n| `403` | `AppAccessDenied` |\n| `404` | `AppNotFound`, `DeploymentNotFound` |\n| `409` | `SlugUnavailable`, `DeploymentNotUploading`, `DeploymentFileConflict`, `DeploymentIncomplete`, `DeploymentNotReady`, `SchemaIncompatible`, `ActivationConflict` |\n| `503` | `ApiUnavailable` |\n\n`ApiUnavailable` does not prove whether a state-changing request reached its final step. Read the\napp before deciding how to recover from an unconfirmed activation. Do not automatically retry\n`POST /v1/apps` or the begin-deployment request because either may create a second resource.\n"],
35348
35649
  ["app-entry.md", "---\ntitle: App entry\ndescription: Define the Web title and root component in client/index.tsx.\nsection: Web\nslug: app-entry\norder: 41\n---\n\n# App entry\n\nThe required `client/index.tsx` file names the page and supplies its root component:\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\nKeep the entry in this form:\n\n- Import `app` by name from `ignotum/client`, without an alias.\n- Default-export one direct `app(...)` call.\n- Pass one object with exactly `title` and `component`.\n- Write both keys as unquoted properties, without a spread.\n- Use a non-empty quoted string literal for `title`.\n\nThe component can import and render other client modules.\n\nIgnotum does not use an `App.tsx` entry convention. The entry is `client/index.tsx`, and its default\nexport is the `app(...)` definition.\n\nNext: [Preact](preact.md)\n"],
35349
- ["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"],
35350
- ["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"],
35351
- ["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"],
35352
- ["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"],
35353
- ["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"],
35650
+ ["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\nUse `ignotum deploy --resume <deployment-id>` to finish an interrupted activation using the\nalready uploaded deployment. This skips the build and upload. See [Migrations](migrations.md)\nfor schema changes and recovery.\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"],
35651
+ ["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, and IDs.\n- [Migrations](migrations.md) covers changes to schemas and stored data.\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"],
35652
+ ["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\nBuild, upload, and schema validation failures leave the current deployment active. If activation\ncannot be confirmed, use `npx ignotum deploy --resume <deployment-id>` to finish it. See\n[Deployment model](deployment-model.md) for activation, schema compatibility, and interrupted\ndeployments.\n\nNext: [Deployment model](deployment-model.md)\n"],
35653
+ ["console.md", "---\ntitle: Console\nlabel: Console\ndescription: Browse live data, connected IDs, and deployments for your apps.\nsection: Deployments\nslug: console\norder: 54\n---\n\n# Console\n\nOpen [Ignotum Console](https://console.ignotum.cloud) and sign in with the GitHub account you use\nfor Ignotum. Select an app to open its Data, Deployments, and ID tabs.\n\n## Data\n\nChoose a table in the sidebar to browse its documents. Empty tables appear too. Columns show\n`id` first, followed by your top-level fields in alphabetical order, then `updatedAt` and\n`createdAt`. Dates use your browser's local time.\n\nExpand objects and arrays inside a cell to inspect their contents. Hover over or focus a document\nreference to preview the referenced document. An ID reference shows the profile that person\ncurrently shares with this app. Select a file thumbnail or filename to open a larger image preview.\n\nData updates as your app changes. You can browse pages of documents, but you cannot edit or delete\nthem in Console.\n\n## Deployments\n\nDeployments appear newest first. The current deployment has a **Current** badge.\n\n- **Activate** publishes a ready deployment that has never been active.\n- **Rollback** returns to a ready deployment that was active before.\n\nConfirm the change to make the selected deployment live. The same compatibility checks apply as\nwhen you deploy from the command line. A rollback keeps your app's current data; it does not\nrestore an earlier copy of the database. See [Deployment model](deployment-model.md).\n\nOlder deployments disappear when they are no longer retained. See [Limits](limits.md).\n\n## ID\n\nThe ID tab lists people currently connected to this app. It shows their app ID, shared image,\nname, email, and connection date. Fields they have not shared stay blank.\n\nConnections remain visible when someone signs out. If they disconnect the app, their row\ndisappears. Removing a shared profile field clears it from the table and any open profile preview.\nThese changes update live. You cannot change profiles or connections in Console.\n\nIf the connection drops, live views clear while Console reconnects. Sign in again if your platform\nsession has expired.\n"],
35654
+ ["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\nSchema changes are validated against existing documents before activation. Changes that preserve\nvalid data work automatically; defaults, renames, and conversions handle changes to stored values.\nSee [Migrations](migrations.md).\n\nApp operations pause during schema preparation and activation. Data and indexes are ready before\nthe new deployment starts serving requests. A validation or conversion failure leaves stored data\nunchanged and resumes the current deployment.\n\n## Failed and interrupted deployments\n\nA build, upload, validation, or conversion failure leaves the current deployment active.\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, the app may remain\npaused. Finish the same activation with `ignotum deploy --resume <deployment-id>`. This uses the\nuploaded code and environment without rebuilding. You can inspect the app's `activeDeploymentId`\nthrough the [HTTP API](api.md).\n\nSee [Guarantees](guarantees.md) for the activation contract and [Limits](limits.md) for deployment\nretention.\n"],
35655
+ ["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- [Console](console.md) lets you browse live data, connected IDs, and deployment history.\n- [Limits](limits.md) lists deployment size and retention limits.\n\nNext: [Deploy an app](deploy-app.md)\n"],
35354
35656
  ["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"],
35355
35657
  ["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"],
35356
35658
  ["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"],
@@ -35362,6 +35664,7 @@ const references = [
35362
35664
  ["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"],
35363
35665
  ["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. When the app has no free slot, a new mutation call\nrejects with `ResourceLimitExceeded` so its promise does not stay pending.\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"],
35364
35666
  ["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\nSee [Test authentication locally](local-authentication.md) to try different users without a Google\naccount.\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"],
35667
+ ["migrations.md", "---\ntitle: Migrations\ndescription: Evolve stored data with automatic schema changes, defaults, renames, and conversions.\nsection: Database\nslug: migrations\norder: 12\n---\n\n# Migrations\n\nChange your schema in `server/schema.ts` and deploy it with your updated functions and Web code.\nIgnotum checks existing documents before activating the deployment. Changes that leave every\nstored document valid need no conversion code.\n\nFor example, you can add a table, add an optional field, widen a validator, or change indexes.\nTightening a validator also works when all existing values already satisfy it. Removing a table\nrequires it to be empty. Removing a field requires it to be absent from existing documents or\nexplicitly removed by a document conversion.\n\n## Add a field with a default\n\nUse `.default(value)` when a missing field should receive a value:\n\n```ts\nimport { defineSchema } from \"ignotum/server\";\n\nexport default defineSchema(({ table, values }) => ({\n todos: table({\n text: values.string(),\n completed: values.boolean(),\n priority: values.integer().default(0),\n }),\n}));\n```\n\nExisting documents without `priority` receive `0`. New inserts and replacements may omit it;\nreads always return a number. A default fills a missing value. It does not replace `null`, repair\nan invalid value, or overwrite an existing value. Omitting a field from a patch leaves it unchanged.\n\nUse a required validator with `.default()`, rather than wrapping it in `values.optional()`.\nDefaults must themselves satisfy the validator.\n\n## Rename and convert a field\n\nUse `.renameFrom()` to preserve a field's value under a new name. Add `.from()` when its old\nrepresentation also needs to change:\n\n```ts\nimport { defineSchema } from \"ignotum/server\";\n\nexport default defineSchema(({ table, values }) => ({\n todos: table({\n title: values.string().renameFrom(\"text\"),\n status: values\n .literals(\"open\", \"done\")\n .renameFrom(\"completed\")\n .from(values.boolean(), (completed) => (completed ? \"done\" : \"open\")),\n priority: values.integer().default(0),\n }).index(\"by_status\", [\"status\"]),\n}));\n```\n\nA stored `{ text: \"Buy milk\", completed: false }` becomes\n`{ title: \"Buy milk\", status: \"open\", priority: 0 }`. The document keeps its ID, creation time,\nand last update time. Indexes reflect the converted fields.\n\nThe first argument to `.from()` describes an older value. The callback receives that value and\nreturns its current representation. The result must satisfy the current validator. Values already\nvalid under the current validator are preserved, so a conversion does not run again on every deploy.\nOrdinary writes accept only the current representation.\n\nThe same helpers work inside objects, arrays, and records. A rename cannot reuse another current\nfield's name. If a document contains both a renamed field and its old name, deployment fails rather\nthan choosing which value to keep.\n\n## Reshape a document\n\nUse a table conversion when a change depends on several fields, or when you need to remove a field:\n\n```ts\nimport { defineSchema } from \"ignotum/server\";\n\nexport default defineSchema(({ table, values }) => ({\n users: table({ name: values.string() }).from(\n values.object({ firstName: values.string(), lastName: values.string() }),\n (previous) => ({ name: `${previous.firstName} ${previous.lastName}` }),\n ),\n}));\n```\n\nReturn the complete current set of application fields. Do not include `id`, `createdAt`, or\n`updatedAt`. Use `.from()` on `values.object()` to reshape an embedded object in the same way.\n\nYou can keep several `.from()` alternatives for different historical representations. Each\nalternative must convert directly to the current shape. If multiple historical validators match\na value, the migration fails. Keep the rules needed by every environment you still deploy to,\nincluding environments that have skipped releases.\n\n## Write conversion callbacks\n\nWrite synchronous, deterministic callbacks that depend only on their input. They may be retried.\nDo not read the clock, generate random values, call the database, read environment variables, or\nperform network requests. Each converted document must stay within the normal document size limit,\nand conversions have execution limits.\n\nThese helpers handle changes within a document. Use application mutations for changes that need\nother documents, external data, or a different meaning for values that already pass the current\nvalidator. A conversion also cannot attach a new file or change a file's metadata; use the file\nand mutation APIs for those operations.\n\nFor example, changing what a string means does not make existing strings invalid, and changing an\nID's target table does not move documents between tables. Plan those changes explicitly in your\napplication.\n\n## Deployment and recovery\n\nIgnotum pauses app operations while it validates the new schema and converts stored data. The new\ndeployment starts serving requests only after the data and indexes are ready. Connected clients\nreload for the new deployment.\n\nIf validation or conversion fails, the current deployment resumes and stored data remains unchanged.\nThe error identifies the document or schema change that needs attention. Update the schema or\nconversion and deploy again.\n\nIf activation is interrupted and its outcome cannot be confirmed, the app may remain paused. Use\nthe deployment ID printed by the CLI to finish the same activation:\n\n```sh\nnpx ignotum deploy --resume dep_your_deployment_id\n```\n\nResuming uses the already uploaded deployment, including its original environment values. It does\nnot rebuild local source. Reactivating older code must also satisfy the current stored data; Ignotum\ndoes not automatically reverse conversions.\n\n## Local development\n\nLocal development applies the same schema helpers to your local data when the updated schema is\nused. A failed migration leaves local documents unchanged. Exercise conversions with representative\nold data before deploying, including missing values and every historical representation you support.\n\nNext: [Queries](queries.md)\n"],
35365
35668
  ["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. Duplicate or delayed\nresponses for a completed call do not interrupt live queries or other mutation calls.\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"],
35366
35669
  ["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"],
35367
35670
  ["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 | `useElementId`, `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\n`useElementId` is Preact's hook for generating element IDs. `useId` reads the current\n[Ignotum ID account](authentication.md).\n"],
@@ -35370,9 +35673,9 @@ const references = [
35370
35673
  ["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 keeps settled public queries visible. Queries that read `ctx.id` return to\npending if their session can no longer be verified. 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"],
35371
35674
  ["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"],
35372
35675
  ["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"],
35373
- ["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"],
35676
+ ["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\nIgnotum validates existing data when you change the schema. Add tables and optional fields or widen\nvalidators without conversion code. Use defaults, renames, and conversions for changes to stored\nvalues. Read [Migrations](migrations.md) for examples and deployment behavior.\n\nNext: [Migrations](migrations.md)\n"],
35374
35677
  ["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"],
35375
- ["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.userId()` | `UserId` | An app user ID returned by `ctx.id`. See [Authentication](authentication.md). |\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"],
35678
+ ["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.userId()` | `UserId` | An app user ID returned by `ctx.id`. See [Authentication](authentication.md). |\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\n## Schema evolution helpers\n\nUse `.default(value)` to fill an omitted field on creation and during migration, `.renameFrom(name)`\nto preserve a renamed field, and `.from(previousValidator, convert)` to convert a historical value.\nRead [Migrations](migrations.md) for examples, nested values, and document conversions.\n"],
35376
35679
  ["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"],
35377
35680
  ["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"]
35378
35681
  ];
@@ -35804,9 +36107,20 @@ const dev = Command.make("dev", {
35804
36107
  userMessage: error.message
35805
36108
  })));
35806
36109
  })).pipe(Command.withDescription("Run the local Ignotum development server."), Command.withSubcommands([devDatabase]));
35807
- const deploy = Command.make("deploy", { app: Flag.string("app").pipe(Flag.withDescription("Create or link this app slug on the first deploy."), Flag.withSchema(AppSlug), Flag.optional) }, Effect.fn("deploy")(function* ({ app }) {
36110
+ const deploy = Command.make("deploy", {
36111
+ resume: Flag.string("resume").pipe(Flag.withDescription("Finish activation of an already uploaded deployment."), Flag.withSchema(DeploymentId), Flag.optional),
36112
+ app: Flag.string("app").pipe(Flag.withDescription("Create or link this app slug on the first deploy."), Flag.withSchema(AppSlug), Flag.optional)
36113
+ }, Effect.fn("deploy")(function* ({ app, resume }) {
35808
36114
  const path = yield* Path$1.Path;
35809
36115
  const terminal = yield* Terminal.Terminal;
36116
+ if (Option.isSome(resume)) {
36117
+ const result = yield* resumeDeployment(path.resolve("."), resume.value, Option.getOrUndefined(app)).pipe(Effect.provide(ControlClient.layer), Effect.mapError((error) => CliError.UserError.make({
36118
+ cause: error,
36119
+ userMessage: error.message
36120
+ })));
36121
+ yield* terminal.display(`Deployment activated.\n\nApp: ${result.appUrl}\nDeployment: ${result.deploymentId}\n`);
36122
+ return;
36123
+ }
35810
36124
  const result = yield* deploy$1(path.resolve("."), Option.getOrUndefined(app)).pipe(Effect.provide(ControlClient.layer), Effect.mapError((error) => CliError.UserError.make({
35811
36125
  cause: error,
35812
36126
  userMessage: error.message