@hasna/mementos 0.14.68 → 0.14.69

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 (37) hide show
  1. package/dist/cli/__fixtures__/clean-fallback-stub-server.d.ts +2 -0
  2. package/dist/cli/__fixtures__/clean-fallback-stub-server.d.ts.map +1 -0
  3. package/dist/cli/commands/io-clean.d.ts.map +1 -1
  4. package/dist/cli/commands/memory-cmd-crud.d.ts.map +1 -1
  5. package/dist/cli/commands/storage.d.ts.map +1 -1
  6. package/dist/cli/global-options.d.ts +27 -0
  7. package/dist/cli/global-options.d.ts.map +1 -0
  8. package/dist/cli/index.js +502 -241
  9. package/dist/cli/register-all.d.ts +17 -0
  10. package/dist/cli/register-all.d.ts.map +1 -0
  11. package/dist/cli/startup-side-effects.d.ts +9 -0
  12. package/dist/cli/startup-side-effects.d.ts.map +1 -0
  13. package/dist/db/__fixtures__/fail-closed-stub-server.d.ts +2 -0
  14. package/dist/db/__fixtures__/fail-closed-stub-server.d.ts.map +1 -0
  15. package/dist/db/api-mode.d.ts +70 -2
  16. package/dist/db/api-mode.d.ts.map +1 -1
  17. package/dist/db/locks.d.ts.map +1 -1
  18. package/dist/db/memories.d.ts +24 -6
  19. package/dist/db/memories.d.ts.map +1 -1
  20. package/dist/db/session-jobs.d.ts.map +1 -1
  21. package/dist/db/store-backend.d.ts +33 -0
  22. package/dist/db/store-backend.d.ts.map +1 -0
  23. package/dist/index.js +80 -20
  24. package/dist/lib/enum-validation.d.ts +20 -0
  25. package/dist/lib/enum-validation.d.ts.map +1 -0
  26. package/dist/mcp/index.js +147 -30
  27. package/dist/server/helpers.d.ts +11 -0
  28. package/dist/server/helpers.d.ts.map +1 -1
  29. package/dist/server/index.d.ts.map +1 -1
  30. package/dist/server/index.js +185 -30
  31. package/dist/test-support/preload-local-store.d.ts +2 -0
  32. package/dist/test-support/preload-local-store.d.ts.map +1 -0
  33. package/dist/test-support/store-isolation.d.ts +83 -0
  34. package/dist/test-support/store-isolation.d.ts.map +1 -0
  35. package/dist/types/index.d.ts +8 -4
  36. package/dist/types/index.d.ts.map +1 -1
  37. package/package.json +1 -1
package/dist/mcp/index.js CHANGED
@@ -66,8 +66,19 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
66
66
  var __require = import.meta.require;
67
67
 
68
68
  // src/types/index.ts
69
- var AgentConflictError, EntityNotFoundError, MemoryNotFoundError, DuplicateMemoryError, InvalidScopeError, VersionConflictError, MemoryConflictError;
69
+ var MEMORY_SCOPES, MEMORY_CATEGORIES, MEMORY_SOURCES, MEMORY_STATUSES, AgentConflictError, EntityNotFoundError, MemoryNotFoundError, DuplicateMemoryError, InvalidScopeError, VersionConflictError, MemoryConflictError;
70
70
  var init_types = __esm(() => {
71
+ MEMORY_SCOPES = ["global", "shared", "private", "working"];
72
+ MEMORY_CATEGORIES = [
73
+ "preference",
74
+ "fact",
75
+ "knowledge",
76
+ "history",
77
+ "procedural",
78
+ "resource"
79
+ ];
80
+ MEMORY_SOURCES = ["user", "agent", "system", "auto", "imported"];
81
+ MEMORY_STATUSES = ["active", "archived", "expired"];
71
82
  AgentConflictError = class AgentConflictError extends Error {
72
83
  conflict = true;
73
84
  existing_id;
@@ -996,7 +1007,7 @@ import { tmpdir } from "os";
996
1007
  import { join as join2 } from "path";
997
1008
  import { writeFileSync as writeFileSync2, unlinkSync } from "fs";
998
1009
  import { randomUUID } from "crypto";
999
- function firstEnv(...keys) {
1010
+ function firstEnv(keys) {
1000
1011
  for (const k of keys) {
1001
1012
  const v = process.env[k]?.trim();
1002
1013
  if (v)
@@ -1004,8 +1015,38 @@ function firstEnv(...keys) {
1004
1015
  }
1005
1016
  return;
1006
1017
  }
1018
+ function firstEnvKey(keys) {
1019
+ for (const k of keys) {
1020
+ if (process.env[k]?.trim())
1021
+ return k;
1022
+ }
1023
+ return null;
1024
+ }
1007
1025
  function hasDatabaseUrl() {
1008
- return Boolean(firstEnv("HASNA_MEMENTOS_DATABASE_URL", "MEMENTOS_DATABASE_URL"));
1026
+ return Boolean(firstEnv(DATABASE_URL_ENV_KEYS));
1027
+ }
1028
+ function isLoopbackHost(rawHost) {
1029
+ const host = rawHost.replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
1030
+ return host === "localhost" || host === "::1" || /^127\./.test(host);
1031
+ }
1032
+ function assertRequestAllowedUnderTest(baseUrl) {
1033
+ if (process.env["NODE_ENV"] !== "test")
1034
+ return;
1035
+ if (process.env[ALLOW_REMOTE_API_IN_TESTS_ENV]?.trim())
1036
+ return;
1037
+ let host;
1038
+ try {
1039
+ host = new URL(baseUrl).hostname;
1040
+ } catch {
1041
+ host = "";
1042
+ }
1043
+ if (host && isLoopbackHost(host))
1044
+ return;
1045
+ throw new Error("api-mode: REFUSING to make a cloud request from a test process \u2014 this would write to or read " + "from the SHARED PRODUCTION memory store, where test fixtures are indistinguishable from real " + `memories.
1046
+ ` + ` host : ${host || "(unparseable base URL)"}
1047
+ ` + ` how this happens: a selector set at module scope (after the bun test preload ran), or \`bun test\` ` + `invoked from a directory with no bunfig.toml so the preload never loaded.
1048
+ ` + " fix : build the child/process env via src/test-support/store-isolation.ts, or point the " + `suite at a loopback stub.
1049
+ ` + ` override : set ${ALLOW_REMOTE_API_IN_TESTS_ENV}=1 only for a test that must reach a remote endpoint.`);
1009
1050
  }
1010
1051
  function normalizeBase(raw) {
1011
1052
  let base = raw.trim().replace(/\/+$/, "");
@@ -1013,9 +1054,24 @@ function normalizeBase(raw) {
1013
1054
  return base;
1014
1055
  return `${base}/v1`;
1015
1056
  }
1057
+ function assertUnambiguousStoreEnv() {
1058
+ if (firstEnvKey(DB_PATH_ENV_KEYS))
1059
+ return;
1060
+ if (hasDatabaseUrl())
1061
+ return;
1062
+ const urlKey = firstEnvKey(API_URL_ENV_KEYS);
1063
+ const keyKey = firstEnvKey(API_KEY_ENV_KEYS);
1064
+ if (urlKey && !keyKey) {
1065
+ throw new MementosStoreConfigError(`${urlKey} points at the cloud memory store but ${API_KEY_ENV_KEYS[0]} is not set. ` + `Refusing to serve the on-box SQLite store in its place, because it holds a different ` + `dataset. Set ${API_KEY_ENV_KEYS[0]} to reach the cloud store. If you meant to use the ` + `on-box SQLite store, unset ${urlKey} or set ${DB_PATH_ENV_KEYS[0]} explicitly.`);
1066
+ }
1067
+ if (keyKey && !urlKey) {
1068
+ throw new MementosStoreConfigError(`${keyKey} is set but ${API_URL_ENV_KEYS[0]} is not, so the cloud memory store cannot be ` + `reached. Refusing to serve the on-box SQLite store in its place, because it holds a ` + `different dataset. Set ${API_URL_ENV_KEYS[0]} to reach the cloud store. If you meant to ` + `use the on-box SQLite store, unset ${keyKey} or set ${DB_PATH_ENV_KEYS[0]} explicitly.`);
1069
+ }
1070
+ }
1016
1071
  function getApiConfig() {
1017
- const rawBase = firstEnv("HASNA_MEMENTOS_API_URL", "MEMENTOS_API_URL");
1018
- const apiKey = firstEnv("HASNA_MEMENTOS_API_KEY", "MEMENTOS_API_KEY");
1072
+ assertUnambiguousStoreEnv();
1073
+ const rawBase = firstEnv(API_URL_ENV_KEYS);
1074
+ const apiKey = firstEnv(API_KEY_ENV_KEYS);
1019
1075
  if (!rawBase || !apiKey)
1020
1076
  return null;
1021
1077
  return { baseUrl: normalizeBase(rawBase), apiKey };
@@ -1029,6 +1085,7 @@ function apiRequestRaw(method, path, body) {
1029
1085
  const cfg = getApiConfig();
1030
1086
  if (!cfg)
1031
1087
  throw new Error("api-mode: not configured (HASNA_MEMENTOS_API_URL / HASNA_MEMENTOS_API_KEY)");
1088
+ assertRequestAllowedUnderTest(cfg.baseUrl);
1032
1089
  const url = `${cfg.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
1033
1090
  const hasBody = body !== undefined && body !== null;
1034
1091
  const timeout = process.env["HASNA_MEMENTOS_API_TIMEOUT"] || DEFAULT_TIMEOUT_S;
@@ -1095,13 +1152,13 @@ x-api-key: ${cfg.apiKey}
1095
1152
  }
1096
1153
  return { status, body: respBody };
1097
1154
  }
1098
- function apiJson(method, path, body) {
1155
+ function apiJson(method, path, body, options) {
1099
1156
  const raw = apiRequestRaw(method, path, body);
1100
1157
  if (raw.status >= 200 && raw.status < 300) {
1101
1158
  const data = raw.body.trim() ? JSON.parse(raw.body) : undefined;
1102
1159
  return { status: raw.status, data };
1103
1160
  }
1104
- if (raw.status === 404) {
1161
+ if (raw.status === 404 && options?.allow404) {
1105
1162
  return { status: 404, data: undefined };
1106
1163
  }
1107
1164
  let msg = `mementos cloud ${method} ${path} \u2192 ${raw.status}`;
@@ -1133,8 +1190,19 @@ function toQuery(params) {
1133
1190
  const s = sp.toString();
1134
1191
  return s ? `?${s}` : "";
1135
1192
  }
1136
- var ApiRequestError, DEFAULT_TIMEOUT_S = "45";
1193
+ var API_URL_ENV_KEYS, API_KEY_ENV_KEYS, DATABASE_URL_ENV_KEYS, ALLOW_REMOTE_API_IN_TESTS_ENV = "MEMENTOS_ALLOW_REMOTE_API_IN_TESTS", DB_PATH_ENV_KEYS, MementosStoreConfigError, ApiRequestError, DEFAULT_TIMEOUT_S = "45";
1137
1194
  var init_api_mode = __esm(() => {
1195
+ API_URL_ENV_KEYS = ["HASNA_MEMENTOS_API_URL", "MEMENTOS_API_URL"];
1196
+ API_KEY_ENV_KEYS = ["HASNA_MEMENTOS_API_KEY", "MEMENTOS_API_KEY"];
1197
+ DATABASE_URL_ENV_KEYS = ["HASNA_MEMENTOS_DATABASE_URL", "MEMENTOS_DATABASE_URL"];
1198
+ DB_PATH_ENV_KEYS = ["HASNA_MEMENTOS_DB_PATH", "MEMENTOS_DB_PATH"];
1199
+ MementosStoreConfigError = class MementosStoreConfigError extends Error {
1200
+ code = "MEMENTOS_STORE_CONFIG";
1201
+ constructor(message) {
1202
+ super(message);
1203
+ this.name = "MementosStoreConfigError";
1204
+ }
1205
+ };
1138
1206
  ApiRequestError = class ApiRequestError extends Error {
1139
1207
  status;
1140
1208
  body;
@@ -2379,6 +2447,41 @@ var init_redact = __esm(() => {
2379
2447
  ];
2380
2448
  });
2381
2449
 
2450
+ // src/lib/enum-validation.ts
2451
+ function formatEnumViolation(v) {
2452
+ return `Invalid ${v.field}: "${v.value}". Allowed values: ${v.allowed.join(", ")}.`;
2453
+ }
2454
+ function validateEnumField(field, value) {
2455
+ const allowed = ENUM_FIELDS[field];
2456
+ if (!allowed)
2457
+ return null;
2458
+ if (value === undefined || value === null || value === "")
2459
+ return null;
2460
+ if (typeof value === "string" && allowed.includes(value))
2461
+ return null;
2462
+ return { field, value: String(value), allowed };
2463
+ }
2464
+ function validateMemoryEnums(input) {
2465
+ for (const field of Object.keys(ENUM_FIELDS)) {
2466
+ if (!(field in input))
2467
+ continue;
2468
+ const violation = validateEnumField(field, input[field]);
2469
+ if (violation)
2470
+ return violation;
2471
+ }
2472
+ return null;
2473
+ }
2474
+ var ENUM_FIELDS;
2475
+ var init_enum_validation = __esm(() => {
2476
+ init_types();
2477
+ ENUM_FIELDS = {
2478
+ category: MEMORY_CATEGORIES,
2479
+ scope: MEMORY_SCOPES,
2480
+ source: MEMORY_SOURCES,
2481
+ status: MEMORY_STATUSES
2482
+ };
2483
+ });
2484
+
2382
2485
  // src/lib/hooks.ts
2383
2486
  function generateHookId() {
2384
2487
  return `hook_${++_idCounter}_${Date.now().toString(36)}`;
@@ -2692,7 +2795,10 @@ function parseMemoryRow(row) {
2692
2795
  }
2693
2796
  function createMemory(input, dedupeMode = "merge", db) {
2694
2797
  if (!db && isApiMode()) {
2695
- const { data } = apiJson("POST", "/memories", { ...input, dedupe: dedupeMode });
2798
+ const { status, data } = apiJson("POST", "/memories", { ...input, dedupe: dedupeMode });
2799
+ if (!data || !data.id) {
2800
+ throw new ApiRequestError(`mementos cloud POST /memories \u2192 ${status} but no memory was returned; the write did not persist (key: ${input.key})`, status, "");
2801
+ }
2696
2802
  return data;
2697
2803
  }
2698
2804
  const d = db || getDatabase();
@@ -2831,16 +2937,25 @@ function bulkUpsertMemories(memories, db) {
2831
2937
  const d = db || getDatabase();
2832
2938
  let inserted = 0;
2833
2939
  let skipped = 0;
2940
+ let rejected = 0;
2834
2941
  const errors = [];
2835
- const insert = d.prepare(`INSERT OR IGNORE INTO memories (id, key, value, category, scope, summary, tags, importance, source, status, pinned, agent_id, project_id, session_id, machine_id, namespace, created_by_agent, when_to_use, sequence_group, sequence_order, metadata, access_count, version, expires_at, valid_from, valid_until, ingested_at, created_at, updated_at)
2836
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
2942
+ const insert = d.prepare(`INSERT INTO memories (id, key, value, category, scope, summary, tags, importance, source, status, pinned, agent_id, project_id, session_id, machine_id, namespace, created_by_agent, when_to_use, sequence_group, sequence_order, metadata, access_count, version, expires_at, valid_from, valid_until, ingested_at, created_at, updated_at)
2943
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2944
+ ON CONFLICT DO NOTHING`);
2837
2945
  const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
2838
2946
  for (const mem of memories) {
2839
2947
  const key = mem["key"];
2840
2948
  const id = mem["id"] || uuid();
2841
2949
  try {
2842
2950
  if (!key) {
2843
- errors.push(`skipped row without key (id=${id})`);
2951
+ rejected++;
2952
+ errors.push(`rejected row without key (id=${id})`);
2953
+ continue;
2954
+ }
2955
+ const violation = validateMemoryEnums(mem);
2956
+ if (violation) {
2957
+ rejected++;
2958
+ errors.push(`Rejected "${key}": ${formatEnumViolation(violation)}`);
2844
2959
  continue;
2845
2960
  }
2846
2961
  const timestamp = now();
@@ -2889,10 +3004,11 @@ function bulkUpsertMemories(memories, db) {
2889
3004
  skipped++;
2890
3005
  }
2891
3006
  } catch (e) {
3007
+ rejected++;
2892
3008
  errors.push(`Failed "${String(key)}": ${e instanceof Error ? e.message : String(e)}`);
2893
3009
  }
2894
3010
  }
2895
- return { inserted, skipped, errors, total: memories.length };
3011
+ return { inserted, skipped, rejected, errors, total: memories.length };
2896
3012
  }
2897
3013
  function ensureMemoryReferences(d, input) {
2898
3014
  const t = now();
@@ -2920,7 +3036,7 @@ function listMemoriesByKey(key, db) {
2920
3036
  }
2921
3037
  function getMemory(id, db) {
2922
3038
  if (!db && isApiMode()) {
2923
- const { status, data } = apiJson("GET", `/memories/${encodeURIComponent(id)}`);
3039
+ const { status, data } = apiJson("GET", `/memories/${encodeURIComponent(id)}`, undefined, { allow404: true });
2924
3040
  return status === 404 ? null : data ?? null;
2925
3041
  }
2926
3042
  const d = db || getDatabase();
@@ -3253,7 +3369,7 @@ function getMemoryEmbeddings(ids, db) {
3253
3369
  }
3254
3370
  function updateMemory(id, input, db) {
3255
3371
  if (!db && isApiMode()) {
3256
- const { status, data } = apiJson("PATCH", `/memories/${encodeURIComponent(id)}`, input);
3372
+ const { status, data } = apiJson("PATCH", `/memories/${encodeURIComponent(id)}`, input, { allow404: true });
3257
3373
  if (status === 404)
3258
3374
  throw new MemoryNotFoundError(id);
3259
3375
  return data;
@@ -3361,7 +3477,7 @@ function updateMemory(id, input, db) {
3361
3477
  }
3362
3478
  function deleteMemory(id, db) {
3363
3479
  if (!db && isApiMode()) {
3364
- const { status } = apiJson("DELETE", `/memories/${encodeURIComponent(id)}`);
3480
+ const { status } = apiJson("DELETE", `/memories/${encodeURIComponent(id)}`, undefined, { allow404: true });
3365
3481
  return status !== 404;
3366
3482
  }
3367
3483
  const d = db || getDatabase();
@@ -3513,6 +3629,7 @@ var init_memories = __esm(() => {
3513
3629
  init_types();
3514
3630
  init_database();
3515
3631
  init_redact();
3632
+ init_enum_validation();
3516
3633
  init_hooks();
3517
3634
  init_poisoning();
3518
3635
  init_entity_memories();
@@ -3660,7 +3777,7 @@ function createWebhookHook(input, db) {
3660
3777
  }
3661
3778
  function getWebhookHook(id, db) {
3662
3779
  if (!db && isApiMode()) {
3663
- const { status, data } = apiJson("GET", `/webhooks/${encodeURIComponent(id)}`);
3780
+ const { status, data } = apiJson("GET", `/webhooks/${encodeURIComponent(id)}`, undefined, { allow404: true });
3664
3781
  if (status === 404 || !data)
3665
3782
  return null;
3666
3783
  return data;
@@ -3696,7 +3813,7 @@ function updateWebhookHook(id, updates, db) {
3696
3813
  enabled: updates.enabled,
3697
3814
  priority: updates.priority,
3698
3815
  description: updates.description
3699
- });
3816
+ }, { allow404: true });
3700
3817
  if (status === 404 || !data)
3701
3818
  return null;
3702
3819
  return data;
@@ -3727,7 +3844,7 @@ function updateWebhookHook(id, updates, db) {
3727
3844
  }
3728
3845
  function deleteWebhookHook(id, db) {
3729
3846
  if (!db && isApiMode()) {
3730
- const { status } = apiJson("DELETE", `/webhooks/${encodeURIComponent(id)}`);
3847
+ const { status } = apiJson("DELETE", `/webhooks/${encodeURIComponent(id)}`, undefined, { allow404: true });
3731
3848
  return status === 204 || status === 200;
3732
3849
  }
3733
3850
  const d = db || getDatabase();
@@ -3825,7 +3942,7 @@ function createEntity(input, db) {
3825
3942
  }
3826
3943
  function getEntity(id, db) {
3827
3944
  if (!db && isApiMode()) {
3828
- const { status, data } = apiJson("GET", `/entities/${encodeURIComponent(id)}`);
3945
+ const { status, data } = apiJson("GET", `/entities/${encodeURIComponent(id)}`, undefined, { allow404: true });
3829
3946
  if (status === 404 || !data)
3830
3947
  throw new EntityNotFoundError(id);
3831
3948
  return data;
@@ -3908,7 +4025,7 @@ function listEntities(filter = {}, db) {
3908
4025
  }
3909
4026
  function updateEntity(id, input, db) {
3910
4027
  if (!db && isApiMode()) {
3911
- const { status, data } = apiJson("PATCH", `/entities/${encodeURIComponent(id)}`, input);
4028
+ const { status, data } = apiJson("PATCH", `/entities/${encodeURIComponent(id)}`, input, { allow404: true });
3912
4029
  if (status === 404 || !data)
3913
4030
  throw new EntityNotFoundError(id);
3914
4031
  return data;
@@ -3941,7 +4058,7 @@ function updateEntity(id, input, db) {
3941
4058
  }
3942
4059
  function deleteEntity(id, db) {
3943
4060
  if (!db && isApiMode()) {
3944
- const { status } = apiJson("DELETE", `/entities/${encodeURIComponent(id)}`);
4061
+ const { status } = apiJson("DELETE", `/entities/${encodeURIComponent(id)}`, undefined, { allow404: true });
3945
4062
  if (status === 404)
3946
4063
  throw new EntityNotFoundError(id);
3947
4064
  return;
@@ -4920,7 +5037,7 @@ function createRelation(input, db) {
4920
5037
  }
4921
5038
  function getRelation(id, db) {
4922
5039
  if (!db && isApiMode()) {
4923
- const { status, data } = apiJson("GET", `/relations/${encodeURIComponent(id)}`);
5040
+ const { status, data } = apiJson("GET", `/relations/${encodeURIComponent(id)}`, undefined, { allow404: true });
4924
5041
  if (status === 404 || !data)
4925
5042
  throw new Error(`Relation not found: ${id}`);
4926
5043
  return data;
@@ -4965,7 +5082,7 @@ function listRelations(filter, db) {
4965
5082
  }
4966
5083
  function deleteRelation(id, db) {
4967
5084
  if (!db && isApiMode()) {
4968
- const { status } = apiJson("DELETE", `/relations/${encodeURIComponent(id)}`);
5085
+ const { status } = apiJson("DELETE", `/relations/${encodeURIComponent(id)}`, undefined, { allow404: true });
4969
5086
  if (status === 404)
4970
5087
  throw new Error(`Relation not found: ${id}`);
4971
5088
  return;
@@ -55581,7 +55698,7 @@ function registerAgent(name, sessionId, description, role, projectId, db) {
55581
55698
  }
55582
55699
  function getAgent(idOrName, db) {
55583
55700
  if (!db && isApiMode()) {
55584
- const { status, data } = apiJson("GET", `/agents/${encodeURIComponent(idOrName)}`);
55701
+ const { status, data } = apiJson("GET", `/agents/${encodeURIComponent(idOrName)}`, undefined, { allow404: true });
55585
55702
  if (status === 404 || !data)
55586
55703
  return null;
55587
55704
  return data;
@@ -55634,7 +55751,7 @@ function listAgentsByProject(projectId, db) {
55634
55751
  }
55635
55752
  function updateAgent(id, updates, db) {
55636
55753
  if (!db && isApiMode()) {
55637
- const { status, data } = apiJson("PATCH", `/agents/${encodeURIComponent(id)}`, updates);
55754
+ const { status, data } = apiJson("PATCH", `/agents/${encodeURIComponent(id)}`, updates, { allow404: true });
55638
55755
  if (status === 404 || !data)
55639
55756
  return null;
55640
55757
  return data;
@@ -55719,7 +55836,7 @@ function registerProject(name, path, description, memoryPrefix, db) {
55719
55836
  }
55720
55837
  function getProject(idOrPath, db) {
55721
55838
  if (!db && isApiMode()) {
55722
- const { status, data } = apiJson("GET", `/projects/${encodeURIComponent(idOrPath)}`);
55839
+ const { status, data } = apiJson("GET", `/projects/${encodeURIComponent(idOrPath)}`, undefined, { allow404: true });
55723
55840
  if (status === 404 || !data)
55724
55841
  return null;
55725
55842
  return data;
@@ -58856,7 +58973,7 @@ function createSubscription(input, db) {
58856
58973
  }
58857
58974
  function deleteSubscription(id, db) {
58858
58975
  if (!db && isApiMode()) {
58859
- const { status } = apiJson("DELETE", `/subscriptions/${encodeURIComponent(id)}`);
58976
+ const { status } = apiJson("DELETE", `/subscriptions/${encodeURIComponent(id)}`, undefined, { allow404: true });
58860
58977
  return status !== 404;
58861
58978
  }
58862
58979
  const d = db || getDatabase();
@@ -60772,7 +60889,7 @@ function acquireLock(agentId, resourceType, resourceId, lockType = "exclusive",
60772
60889
  }
60773
60890
  function releaseLock(lockId, agentId, db) {
60774
60891
  if (!db && isApiMode()) {
60775
- const { status } = apiJson("DELETE", `/locks/${encodeURIComponent(lockId)}`, { agent_id: agentId });
60892
+ const { status } = apiJson("DELETE", `/locks/${encodeURIComponent(lockId)}`, { agent_id: agentId }, { allow404: true });
60776
60893
  return status !== 404;
60777
60894
  }
60778
60895
  const d = db || getDatabase();
@@ -62517,7 +62634,7 @@ function createSessionJob(input, db) {
62517
62634
  }
62518
62635
  function getSessionJob(id, db) {
62519
62636
  if (!db && isApiMode()) {
62520
- const { status, data } = apiJson("GET", `/sessions/jobs/${encodeURIComponent(id)}`);
62637
+ const { status, data } = apiJson("GET", `/sessions/jobs/${encodeURIComponent(id)}`, undefined, { allow404: true });
62521
62638
  if (status === 404 || !data)
62522
62639
  return null;
62523
62640
  return data;
@@ -2,6 +2,17 @@ export declare const CORS_HEADERS: Record<string, string>;
2
2
  export declare const MIME_TYPES: Record<string, string>;
3
3
  export declare function json(data: unknown, status?: number): Response;
4
4
  export declare function errorResponse(message: string, status: number, details?: unknown): Response;
5
+ /**
6
+ * Classify a thrown error as a database constraint violation — i.e. the caller
7
+ * sent a value the schema refuses (bad enum, missing FK, duplicate unique key).
8
+ * Returns an actionable message for a 400, or `null` when the error is a
9
+ * genuine server fault that must stay a 500.
10
+ *
11
+ * Defence in depth behind the per-route validators: a column whose CHECK is not
12
+ * yet mirrored by a validator still gets reported as a client error rather than
13
+ * as an opaque outage.
14
+ */
15
+ export declare function describeConstraintViolation(e: unknown): string | null;
5
16
  export declare function readJson(req: Request): Promise<unknown>;
6
17
  export declare function getCorsHeaders(req?: Request): Record<string, string>;
7
18
  export declare function authenticateRequest(req: Request): Response | null;
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/server/helpers.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAK/C,CAAC;AAMF,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAU7C,CAAC;AAMF,wBAAgB,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,SAAM,GAAG,QAAQ,CAK1D;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,OAAO,GAChB,QAAQ,CAIV;AAKD,wBAAsB,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAU7D;AAMD,wBAAgB,cAAc,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWpE;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,CAqBjE;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMhE;AAMD,wBAAgB,mBAAmB,IAAI,MAAM,CAiB5C;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAMjE"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/server/helpers.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAK/C,CAAC;AAMF,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAU7C,CAAC;AAMF,wBAAgB,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,SAAM,GAAG,QAAQ,CAK1D;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,OAAO,GAChB,QAAQ,CAIV;AAED;;;;;;;;;GASG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAWrE;AAKD,wBAAsB,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAU7D;AAMD,wBAAgB,cAAc,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWpE;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,CAqBjE;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMhE;AAMD,wBAAgB,mBAAmB,IAAI,MAAM,CAiB5C;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAMjE"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AA8CH,OAAO,sBAAsB,CAAC;AAC9B,OAAO,oBAAoB,CAAC;AAC5B,OAAO,sBAAsB,CAAC;AAC9B,OAAO,sBAAsB,CAAC;AAC9B,OAAO,mBAAmB,CAAC;AAC3B,OAAO,oBAAoB,CAAC;AA0F5B,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CA8K9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AA8CH,OAAO,sBAAsB,CAAC;AAC9B,OAAO,oBAAoB,CAAC;AAC5B,OAAO,sBAAsB,CAAC;AAC9B,OAAO,sBAAsB,CAAC;AAC9B,OAAO,mBAAmB,CAAC;AAC3B,OAAO,oBAAoB,CAAC;AA0F5B,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAsL9C"}