@hasna/mementos 0.14.88 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -9
- package/bun.lock +4 -65
- package/dist/cli/commands/info-history.d.ts.map +1 -1
- package/dist/cli/commands/info-stale.d.ts.map +1 -1
- package/dist/cli/commands/io-export.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-chain.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-crud.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-recall.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-search.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-tail.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-view.d.ts.map +1 -1
- package/dist/cli/commands/memory-cmd-when-to-use.d.ts.map +1 -1
- package/dist/cli/commands/storage.d.ts.map +1 -1
- package/dist/cli/commands/system-doctor.d.ts.map +1 -1
- package/dist/cli/commands/system-status.d.ts +43 -0
- package/dist/cli/commands/system-status.d.ts.map +1 -0
- package/dist/cli/commands/system.d.ts.map +1 -1
- package/dist/cli/helpers.d.ts.map +1 -1
- package/dist/cli/index.js +3397 -1954
- package/dist/cli/register-all.d.ts +1 -1
- package/dist/cli/register-all.d.ts.map +1 -1
- package/dist/db/api-mode.d.ts +135 -37
- package/dist/db/api-mode.d.ts.map +1 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/memories.d.ts.map +1 -1
- package/dist/db/store-backend.d.ts +8 -2
- package/dist/db/store-backend.d.ts.map +1 -1
- package/dist/diagnostics/historical-project-registration-receipt.js +85 -50
- package/dist/generated/storage-kit/backend.d.ts +12 -2
- package/dist/generated/storage-kit/backend.d.ts.map +1 -1
- package/dist/generated/storage-kit/index.d.ts +1 -1
- package/dist/generated/storage-kit/index.d.ts.map +1 -1
- package/dist/generated/storage-kit/migrations.d.ts +21 -0
- package/dist/generated/storage-kit/migrations.d.ts.map +1 -1
- package/dist/generated/storage-kit/pool.d.ts +2 -5
- package/dist/generated/storage-kit/pool.d.ts.map +1 -1
- package/dist/generated/storage-kit/query.d.ts +1 -1
- package/dist/generated/storage-kit/query.d.ts.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2811 -1285
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/env.d.ts +10 -0
- package/dist/lib/env.d.ts.map +1 -0
- package/dist/lib/gatherer.d.ts.map +1 -1
- package/dist/lib/local-opt-in.d.ts +114 -0
- package/dist/lib/local-opt-in.d.ts.map +1 -0
- package/dist/lib/paths.d.ts +9 -0
- package/dist/lib/paths.d.ts.map +1 -1
- package/dist/lib/redact.d.ts +70 -10
- package/dist/lib/redact.d.ts.map +1 -1
- package/dist/lib/reflection.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +2856 -1291
- package/dist/project-registration.js +273 -185
- package/dist/sdk/index.d.ts +173 -8
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +953 -12
- package/dist/server/auth.d.ts +2 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/helpers.d.ts +21 -7
- package/dist/server/helpers.d.ts.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2855 -1351
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +78 -129
- package/dist/test-support/store-isolation.d.ts +50 -11
- package/dist/test-support/store-isolation.d.ts.map +1 -1
- package/docker-entrypoint.sh +7 -0
- package/hasna.contract.json +17 -5
- package/package.json +5 -7
- package/postinstall.mjs +52 -1
- package/dashboard/bun.lock +0 -519
- package/dist/cli/brains.d.ts +0 -3
- package/dist/cli/brains.d.ts.map +0 -1
- package/dist/lib/retired-storage-mode.d.ts +0 -11
- package/dist/lib/retired-storage-mode.d.ts.map +0 -1
|
@@ -72,16 +72,7 @@ function serverDataBackendEnvKeys(name) {
|
|
|
72
72
|
databaseUrlKeys: [`HASNA_${token}_DATABASE_URL`, `${token}_DATABASE_URL`]
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
|
-
function
|
|
76
|
-
const token = envToken(name);
|
|
77
|
-
return [
|
|
78
|
-
`HASNA_${token}_STORAGE_MODE`,
|
|
79
|
-
`HASNA_${token}_MODE`,
|
|
80
|
-
`${token}_STORAGE_MODE`,
|
|
81
|
-
`${token}_MODE`
|
|
82
|
-
];
|
|
83
|
-
}
|
|
84
|
-
function firstEnv(env, keys) {
|
|
75
|
+
function firstDatabaseUrl(env, keys) {
|
|
85
76
|
for (const key of keys) {
|
|
86
77
|
const value = ownString(env, key)?.trim();
|
|
87
78
|
if (value)
|
|
@@ -89,23 +80,8 @@ function firstEnv(env, keys) {
|
|
|
89
80
|
}
|
|
90
81
|
return null;
|
|
91
82
|
}
|
|
92
|
-
function firstDefinedEnvKey(env, keys) {
|
|
93
|
-
for (const key of keys) {
|
|
94
|
-
if (Object.hasOwn(env, key) && env[key] !== undefined)
|
|
95
|
-
return key;
|
|
96
|
-
}
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
function assertNoLegacyStorageMode(name, env = process.env) {
|
|
100
|
-
const legacyKey = firstDefinedEnvKey(env, legacyModeKeys(name));
|
|
101
|
-
if (!legacyKey)
|
|
102
|
-
return;
|
|
103
|
-
const canonicalDatabaseUrl = serverDataBackendEnvKeys(name).databaseUrlKeys[0];
|
|
104
|
-
throw new Error(`${legacyKey} was removed. Delete the storage-mode variable; ` + `set ${canonicalDatabaseUrl} to select the postgresql server backend, ` + `or leave it unset for sqlite.`);
|
|
105
|
-
}
|
|
106
83
|
function resolveServerDataBackend(name, env = process.env) {
|
|
107
|
-
|
|
108
|
-
const databaseUrl = firstEnv(env, serverDataBackendEnvKeys(name).databaseUrlKeys);
|
|
84
|
+
const databaseUrl = firstDatabaseUrl(env, serverDataBackendEnvKeys(name).databaseUrlKeys);
|
|
109
85
|
if (!databaseUrl) {
|
|
110
86
|
return {
|
|
111
87
|
backend: "sqlite",
|
|
@@ -122,113 +98,69 @@ function resolveServerDataBackend(name, env = process.env) {
|
|
|
122
98
|
};
|
|
123
99
|
}
|
|
124
100
|
function resolveDatabaseUrl(name, env = process.env) {
|
|
125
|
-
|
|
126
|
-
const hit = firstEnv(env, serverDataBackendEnvKeys(name).databaseUrlKeys);
|
|
127
|
-
return hit?.value ?? null;
|
|
101
|
+
return firstDatabaseUrl(env, serverDataBackendEnvKeys(name).databaseUrlKeys)?.value ?? null;
|
|
128
102
|
}
|
|
129
103
|
var init_backend = () => {};
|
|
130
104
|
|
|
131
|
-
// src/lib/
|
|
132
|
-
|
|
133
|
-
for (const key of keys) {
|
|
134
|
-
if (Object.hasOwn(env, key) && env[key] !== undefined)
|
|
135
|
-
return key;
|
|
136
|
-
}
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
function assertNoLegacyStorageMode2(env = process.env) {
|
|
140
|
-
const legacyKey = firstDefinedEnvKey2(env, LEGACY_STORAGE_MODE_KEYS);
|
|
141
|
-
if (!legacyKey)
|
|
142
|
-
return;
|
|
143
|
-
throw new Error(`${legacyKey} was removed. Deployment modes no longer exist: delete the storage-mode variable. ` + `The client uses the local SQLite store, or the HTTP API selected by ` + `HASNA_MEMENTOS_API_URL + HASNA_MEMENTOS_API_KEY. ` + `On the server, set HASNA_MEMENTOS_DATABASE_URL to select the postgresql backend, ` + `or leave it unset for sqlite.`);
|
|
144
|
-
}
|
|
145
|
-
var LEGACY_STORAGE_MODE_KEYS;
|
|
146
|
-
var init_retired_storage_mode = __esm(() => {
|
|
147
|
-
LEGACY_STORAGE_MODE_KEYS = [
|
|
148
|
-
"HASNA_MEMENTOS_STORAGE_MODE",
|
|
149
|
-
"HASNA_MEMENTOS_MODE",
|
|
150
|
-
"MEMENTOS_STORAGE_MODE",
|
|
151
|
-
"MEMENTOS_MODE"
|
|
152
|
-
];
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// ../../node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
|
|
105
|
+
// src/lib/paths.ts
|
|
106
|
+
import { existsSync } from "fs";
|
|
156
107
|
import { homedir } from "os";
|
|
157
|
-
import { join } from "path";
|
|
158
|
-
|
|
108
|
+
import { join, resolve } from "path";
|
|
109
|
+
import { homedir as pathsResolverHomedir } from "os";
|
|
110
|
+
import { join as pathsResolverJoin } from "path";
|
|
111
|
+
function pathsResolverAssertApp(app) {
|
|
159
112
|
if (typeof app !== "string" || app.length === 0) {
|
|
160
113
|
throw new TypeError("paths: app must be a non-empty string");
|
|
161
114
|
}
|
|
162
|
-
if (!
|
|
115
|
+
if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
|
|
163
116
|
throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
|
|
164
117
|
}
|
|
165
118
|
}
|
|
166
|
-
function
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const value = envOf(options)[KIND_ENV[kind]];
|
|
171
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
172
|
-
}
|
|
173
|
-
function isMacOS(platform) {
|
|
174
|
-
return platform === "darwin";
|
|
119
|
+
function pathsResolverAssertKind(kind) {
|
|
120
|
+
if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
|
|
121
|
+
throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
|
|
122
|
+
}
|
|
175
123
|
}
|
|
176
|
-
function
|
|
177
|
-
|
|
178
|
-
|
|
124
|
+
function pathsResolverBaseDir(kind, options) {
|
|
125
|
+
pathsResolverAssertKind(kind);
|
|
126
|
+
const env = options.env ?? process.env;
|
|
127
|
+
const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
|
|
128
|
+
if (typeof override === "string" && override.length > 0)
|
|
179
129
|
return override;
|
|
180
|
-
const home = options.home ??
|
|
130
|
+
const home = options.home ?? pathsResolverHomedir();
|
|
181
131
|
const platform = options.platform ?? process.platform;
|
|
182
|
-
if (
|
|
132
|
+
if (platform === "darwin") {
|
|
183
133
|
switch (kind) {
|
|
184
|
-
case "config":
|
|
185
134
|
case "data":
|
|
186
|
-
return
|
|
135
|
+
return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
|
|
187
136
|
case "cache":
|
|
188
|
-
return
|
|
137
|
+
return pathsResolverJoin(home, "Library", "Caches", "Hasna");
|
|
189
138
|
case "state":
|
|
190
|
-
return
|
|
139
|
+
return pathsResolverJoin(home, "Library", "Logs", "Hasna");
|
|
191
140
|
}
|
|
192
141
|
}
|
|
193
142
|
switch (kind) {
|
|
194
|
-
case "config":
|
|
195
|
-
return join(home, ".config", "hasna");
|
|
196
143
|
case "data":
|
|
197
|
-
return
|
|
144
|
+
return pathsResolverJoin(home, ".local", "share", "hasna");
|
|
198
145
|
case "state":
|
|
199
|
-
return
|
|
146
|
+
return pathsResolverJoin(home, ".local", "state", "hasna");
|
|
200
147
|
case "cache":
|
|
201
|
-
return
|
|
148
|
+
return pathsResolverJoin(home, ".cache", "hasna");
|
|
202
149
|
}
|
|
203
150
|
}
|
|
204
|
-
function
|
|
205
|
-
|
|
206
|
-
const appSegment = options.internal === true ?
|
|
207
|
-
return
|
|
151
|
+
function pathsResolverResolve(kind, options) {
|
|
152
|
+
pathsResolverAssertApp(options.app);
|
|
153
|
+
const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
|
|
154
|
+
return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
|
|
208
155
|
}
|
|
209
156
|
function dataDir(options) {
|
|
210
|
-
return
|
|
157
|
+
return pathsResolverResolve("data", options);
|
|
211
158
|
}
|
|
212
|
-
var KIND_ENV, APP_SLUG_RE;
|
|
213
|
-
var init_dist = __esm(() => {
|
|
214
|
-
KIND_ENV = {
|
|
215
|
-
config: "HASNA_CONFIG_HOME",
|
|
216
|
-
data: "HASNA_DATA_HOME",
|
|
217
|
-
state: "HASNA_STATE_HOME",
|
|
218
|
-
cache: "HASNA_CACHE_HOME"
|
|
219
|
-
};
|
|
220
|
-
APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// src/lib/paths.ts
|
|
224
|
-
import { existsSync } from "fs";
|
|
225
|
-
import { homedir as homedir2 } from "os";
|
|
226
|
-
import { join as join2, resolve } from "path";
|
|
227
159
|
function effectiveHome() {
|
|
228
|
-
return process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
160
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir() || "/tmp";
|
|
229
161
|
}
|
|
230
162
|
function legacyDataRoot() {
|
|
231
|
-
return
|
|
163
|
+
return join(effectiveHome(), ".hasna", "mementos");
|
|
232
164
|
}
|
|
233
165
|
function resolverDataRoot() {
|
|
234
166
|
return dataDir({
|
|
@@ -240,7 +172,7 @@ function adoptResolverDataRoot(resolved, env = process.env) {
|
|
|
240
172
|
const dataOverride = env.HASNA_DATA_HOME;
|
|
241
173
|
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
242
174
|
return true;
|
|
243
|
-
return existsSync(
|
|
175
|
+
return existsSync(join(resolved, "mementos.db"));
|
|
244
176
|
}
|
|
245
177
|
function exactDataRoot() {
|
|
246
178
|
for (const key of ["HASNA_MEMENTOS_HOME", "MEMENTOS_HOME"]) {
|
|
@@ -257,14 +189,34 @@ function getDataRoot() {
|
|
|
257
189
|
const resolved = resolverDataRoot();
|
|
258
190
|
return adoptResolverDataRoot(resolved) ? resolve(resolved) : resolve(legacyDataRoot());
|
|
259
191
|
}
|
|
192
|
+
var PATHS_RESOLVER_KIND_ENV, PATHS_RESOLVER_APP_SLUG_RE;
|
|
260
193
|
var init_paths = __esm(() => {
|
|
261
|
-
|
|
194
|
+
PATHS_RESOLVER_KIND_ENV = {
|
|
195
|
+
data: "HASNA_DATA_HOME",
|
|
196
|
+
state: "HASNA_STATE_HOME",
|
|
197
|
+
cache: "HASNA_CACHE_HOME"
|
|
198
|
+
};
|
|
199
|
+
PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// src/lib/env.ts
|
|
203
|
+
var alias = (canonical, legacy) => process.env[canonical] ?? process.env[legacy], env;
|
|
204
|
+
var init_env = __esm(() => {
|
|
205
|
+
env = {
|
|
206
|
+
apiKey: () => alias("HASNA_MEMENTOS_API_KEY", "MEMENTOS_API_KEY"),
|
|
207
|
+
defaultScope: () => alias("HASNA_MEMENTOS_DEFAULT_SCOPE", "MEMENTOS_DEFAULT_SCOPE"),
|
|
208
|
+
defaultCategory: () => alias("HASNA_MEMENTOS_DEFAULT_CATEGORY", "MEMENTOS_DEFAULT_CATEGORY"),
|
|
209
|
+
defaultImportance: () => alias("HASNA_MEMENTOS_DEFAULT_IMPORTANCE", "MEMENTOS_DEFAULT_IMPORTANCE"),
|
|
210
|
+
pgsyncQueryTimeoutMs: () => alias("HASNA_MEMENTOS_PGSYNC_QUERY_TIMEOUT_MS", "MEMENTOS_PGSYNC_QUERY_TIMEOUT_MS"),
|
|
211
|
+
reflectProvider: () => alias("HASNA_MEMENTOS_REFLECT_PROVIDER", "MEMENTOS_REFLECT_PROVIDER"),
|
|
212
|
+
reflectModel: () => alias("HASNA_MEMENTOS_REFLECT_MODEL", "MEMENTOS_REFLECT_MODEL")
|
|
213
|
+
};
|
|
262
214
|
});
|
|
263
215
|
|
|
264
216
|
// src/storage.ts
|
|
265
217
|
import { Database } from "bun:sqlite";
|
|
266
218
|
import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
267
|
-
import { join as
|
|
219
|
+
import { join as join2 } from "path";
|
|
268
220
|
import { fileURLToPath } from "url";
|
|
269
221
|
import { Worker } from "worker_threads";
|
|
270
222
|
import pg from "pg";
|
|
@@ -289,6 +241,7 @@ class SqliteAdapter {
|
|
|
289
241
|
db;
|
|
290
242
|
constructor(path) {
|
|
291
243
|
this.db = new Database(path, { create: true });
|
|
244
|
+
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
292
245
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
293
246
|
this.db.exec("PRAGMA foreign_keys = ON");
|
|
294
247
|
}
|
|
@@ -539,9 +492,9 @@ function getConfigPath() {
|
|
|
539
492
|
return STORAGE_CONFIG_PATH;
|
|
540
493
|
}
|
|
541
494
|
function getStorageDatabaseEnv() {
|
|
542
|
-
for (const
|
|
543
|
-
if (readEnv(
|
|
544
|
-
return
|
|
495
|
+
for (const env2 of DATABASE_ENV_NAMES) {
|
|
496
|
+
if (readEnv(env2.name))
|
|
497
|
+
return env2;
|
|
545
498
|
}
|
|
546
499
|
return null;
|
|
547
500
|
}
|
|
@@ -551,20 +504,19 @@ function getStorageEnvName(key) {
|
|
|
551
504
|
return readEnv(canonical) || !readEnv(fallback) ? canonical : fallback;
|
|
552
505
|
}
|
|
553
506
|
function getStorageDatabaseUrl() {
|
|
554
|
-
const
|
|
555
|
-
return
|
|
507
|
+
const env2 = getStorageDatabaseEnv();
|
|
508
|
+
return env2 ? readEnv(env2.name) : null;
|
|
556
509
|
}
|
|
557
510
|
function getStorageDatabaseEnvName() {
|
|
558
511
|
return getStorageEnvName("databaseUrl");
|
|
559
512
|
}
|
|
560
|
-
function getStorageBackend(
|
|
561
|
-
return resolveServerDataBackend("mementos",
|
|
513
|
+
function getStorageBackend(env2 = process.env) {
|
|
514
|
+
return resolveServerDataBackend("mementos", env2).backend;
|
|
562
515
|
}
|
|
563
|
-
function getStorageBackendDatabaseUrl(
|
|
564
|
-
return resolveDatabaseUrl("mementos",
|
|
516
|
+
function getStorageBackendDatabaseUrl(env2 = process.env) {
|
|
517
|
+
return resolveDatabaseUrl("mementos", env2);
|
|
565
518
|
}
|
|
566
519
|
function getStorageConfig() {
|
|
567
|
-
assertNoLegacyStorageMode2();
|
|
568
520
|
const fileConfig = readConfigFile();
|
|
569
521
|
const merged = {
|
|
570
522
|
...DEFAULT_STORAGE_CONFIG,
|
|
@@ -651,14 +603,14 @@ function storageEnvStatus(key) {
|
|
|
651
603
|
};
|
|
652
604
|
}
|
|
653
605
|
function postgresBackendStatus(config) {
|
|
654
|
-
const
|
|
655
|
-
const envUrl =
|
|
656
|
-
if (
|
|
606
|
+
const env2 = getStorageDatabaseEnv();
|
|
607
|
+
const envUrl = env2 ? readEnv(env2.name) : null;
|
|
608
|
+
if (env2 && envUrl) {
|
|
657
609
|
const validation = validatePostgresConnectionString(envUrl);
|
|
658
610
|
return {
|
|
659
611
|
configured: validation.ok,
|
|
660
612
|
source: "env",
|
|
661
|
-
env_name:
|
|
613
|
+
env_name: env2.name,
|
|
662
614
|
redacted_url: validation.redacted_url,
|
|
663
615
|
missing: [],
|
|
664
616
|
issues: validation.issues,
|
|
@@ -704,7 +656,6 @@ function getSafeStorageConfigSummary(config = getStorageConfig()) {
|
|
|
704
656
|
};
|
|
705
657
|
}
|
|
706
658
|
function getStorageStatus() {
|
|
707
|
-
assertNoLegacyStorageMode2();
|
|
708
659
|
const config = getStorageConfig();
|
|
709
660
|
const backend = getStorageBackend();
|
|
710
661
|
const postgresRequested = backend === "postgresql";
|
|
@@ -814,14 +765,12 @@ function resolveConfiguredConnectionString(dbName) {
|
|
|
814
765
|
return `postgres://${username}:${encodeURIComponent(password)}@${host}:${port}/${dbName}${sslParam}`;
|
|
815
766
|
}
|
|
816
767
|
function getStorageConnectionString(dbName = "mementos") {
|
|
817
|
-
assertNoLegacyStorageMode2();
|
|
818
768
|
if (!isServerContext()) {
|
|
819
769
|
throw new Error("Refusing to construct an RDS Postgres DSN outside the mementos-serve server. " + "The raw database DSN is NEVER distributed to client machines. " + "Clients must use the HTTP API: set HASNA_MEMENTOS_API_URL and " + "HASNA_MEMENTOS_API_KEY (and unset HASNA_MEMENTOS_DATABASE_URL).");
|
|
820
770
|
}
|
|
821
771
|
return resolveConfiguredConnectionString(dbName);
|
|
822
772
|
}
|
|
823
773
|
function getStorageConnectionStringForOperator(dbName = "mementos") {
|
|
824
|
-
assertNoLegacyStorageMode2();
|
|
825
774
|
return resolveConfiguredConnectionString(dbName);
|
|
826
775
|
}
|
|
827
776
|
function isSyncExcludedTable(table) {
|
|
@@ -992,8 +941,8 @@ CREATE TABLE IF NOT EXISTS _sync_meta (
|
|
|
992
941
|
)`;
|
|
993
942
|
var init_storage = __esm(() => {
|
|
994
943
|
init_backend();
|
|
995
|
-
init_retired_storage_mode();
|
|
996
944
|
init_paths();
|
|
945
|
+
init_env();
|
|
997
946
|
PgSyncPool = class PgSyncPool {
|
|
998
947
|
worker;
|
|
999
948
|
status;
|
|
@@ -1003,7 +952,7 @@ var init_storage = __esm(() => {
|
|
|
1003
952
|
generation = 0;
|
|
1004
953
|
static DATA_BYTES = 128 * 1024 * 1024;
|
|
1005
954
|
static queryTimeoutMs() {
|
|
1006
|
-
const raw =
|
|
955
|
+
const raw = env.pgsyncQueryTimeoutMs()?.trim();
|
|
1007
956
|
const parsed = raw ? Number(raw) : Number.NaN;
|
|
1008
957
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : 60000;
|
|
1009
958
|
}
|
|
@@ -1011,9 +960,9 @@ var init_storage = __esm(() => {
|
|
|
1011
960
|
const ext = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
1012
961
|
const here = fileURLToPath(new URL(".", import.meta.url));
|
|
1013
962
|
const candidates = [
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
963
|
+
join2(here, `pg-sync-worker${ext}`),
|
|
964
|
+
join2(here, "..", `pg-sync-worker${ext}`),
|
|
965
|
+
join2(here, "..", "..", `pg-sync-worker${ext}`)
|
|
1017
966
|
];
|
|
1018
967
|
for (const candidate of candidates) {
|
|
1019
968
|
if (existsSync2(candidate))
|
|
@@ -1119,8 +1068,8 @@ var init_storage = __esm(() => {
|
|
|
1119
1068
|
schedule_minutes: 0
|
|
1120
1069
|
}
|
|
1121
1070
|
};
|
|
1122
|
-
STORAGE_CONFIG_DIR =
|
|
1123
|
-
STORAGE_CONFIG_PATH =
|
|
1071
|
+
STORAGE_CONFIG_DIR = join2(LOCAL_DATA_DIR, "storage");
|
|
1072
|
+
STORAGE_CONFIG_PATH = join2(STORAGE_CONFIG_DIR, "config.json");
|
|
1124
1073
|
DATABASE_ENV_NAMES = [
|
|
1125
1074
|
{ name: MEMENTOS_STORAGE_ENV.databaseUrl, deprecated: false },
|
|
1126
1075
|
{ name: MEMENTOS_STORAGE_FALLBACK_ENV.databaseUrl, deprecated: false }
|
|
@@ -1144,28 +1093,96 @@ var init_storage = __esm(() => {
|
|
|
1144
1093
|
];
|
|
1145
1094
|
});
|
|
1146
1095
|
|
|
1096
|
+
// src/lib/local-opt-in.ts
|
|
1097
|
+
import {
|
|
1098
|
+
clientTransportEnvKeys,
|
|
1099
|
+
credentialOverrideEnvKey,
|
|
1100
|
+
credentialPointerEnvKey,
|
|
1101
|
+
CREDENTIAL_PROFILE_ENV_KEY
|
|
1102
|
+
} from "@hasna/contracts/client";
|
|
1103
|
+
function isMementosLocalOptIn(env2 = process.env) {
|
|
1104
|
+
return MEMENTOS_LOCAL_OPT_IN_ENV_KEYS.some((key) => (env2[key] ?? "").trim() !== "");
|
|
1105
|
+
}
|
|
1106
|
+
function hasExplicitLocalDbPath(env2 = process.env) {
|
|
1107
|
+
return MEMENTOS_DB_PATH_ENV_KEYS.some((key) => (env2[key] ?? "").trim() !== "");
|
|
1108
|
+
}
|
|
1109
|
+
function mementosAuthorityEnvKeys() {
|
|
1110
|
+
const keys = clientTransportEnvKeys("mementos");
|
|
1111
|
+
return [
|
|
1112
|
+
...keys.apiUrlKeys,
|
|
1113
|
+
...keys.apiKeyKeys,
|
|
1114
|
+
credentialOverrideEnvKey("mementos"),
|
|
1115
|
+
credentialPointerEnvKey("mementos"),
|
|
1116
|
+
CREDENTIAL_PROFILE_ENV_KEY
|
|
1117
|
+
];
|
|
1118
|
+
}
|
|
1119
|
+
function hasMementosEnvAuthorityIntent(env2 = process.env) {
|
|
1120
|
+
return mementosAuthorityEnvKeys().some((key) => (env2[key] ?? "").trim() !== "");
|
|
1121
|
+
}
|
|
1122
|
+
function selectsMementosLocalStore(env2 = process.env) {
|
|
1123
|
+
if (hasExplicitLocalDbPath(env2))
|
|
1124
|
+
return true;
|
|
1125
|
+
return !hasMementosEnvAuthorityIntent(env2) && isMementosLocalOptIn(env2);
|
|
1126
|
+
}
|
|
1127
|
+
function mementosResolverEnv(env2) {
|
|
1128
|
+
const blanks = mementosAuthorityEnvKeys().filter((key) => (key in env2) && (env2[key] ?? "").trim() === "");
|
|
1129
|
+
if (blanks.length === 0)
|
|
1130
|
+
return env2;
|
|
1131
|
+
const next = { ...env2 };
|
|
1132
|
+
for (const key of blanks)
|
|
1133
|
+
delete next[key];
|
|
1134
|
+
return next;
|
|
1135
|
+
}
|
|
1136
|
+
function isAmbientMementosEnv(env2) {
|
|
1137
|
+
if (typeof process !== "undefined" && env2 === process.env)
|
|
1138
|
+
return true;
|
|
1139
|
+
return env2[CONTRACTS_AMBIENT_ENVIRONMENT] === true;
|
|
1140
|
+
}
|
|
1141
|
+
function mementosResolverInputs(env2, credentials = {}) {
|
|
1142
|
+
const normalised = mementosResolverEnv(env2);
|
|
1143
|
+
if (normalised === env2)
|
|
1144
|
+
return { env: normalised, credentials };
|
|
1145
|
+
const keychain = { ...credentials.keychain };
|
|
1146
|
+
if (keychain.enabled === undefined && keychain.run === undefined) {
|
|
1147
|
+
keychain.enabled = isAmbientMementosEnv(env2);
|
|
1148
|
+
}
|
|
1149
|
+
return { env: normalised, credentials: { ...credentials, keychain } };
|
|
1150
|
+
}
|
|
1151
|
+
var MEMENTOS_LOCAL_OPT_IN_ENV_KEYS, MEMENTOS_DB_PATH_ENV_KEYS, CONTRACTS_AMBIENT_ENVIRONMENT;
|
|
1152
|
+
var init_local_opt_in = __esm(() => {
|
|
1153
|
+
MEMENTOS_LOCAL_OPT_IN_ENV_KEYS = ["HASNA_MEMENTOS_LOCAL", "MEMENTOS_LOCAL"];
|
|
1154
|
+
MEMENTOS_DB_PATH_ENV_KEYS = ["HASNA_MEMENTOS_DB_PATH", "MEMENTOS_DB_PATH"];
|
|
1155
|
+
CONTRACTS_AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1147
1158
|
// src/db/api-mode.ts
|
|
1148
1159
|
import { tmpdir } from "os";
|
|
1149
|
-
import { join as
|
|
1160
|
+
import { join as join3 } from "path";
|
|
1150
1161
|
import { writeFileSync as writeFileSync2, unlinkSync } from "fs";
|
|
1151
1162
|
import { randomUUID } from "crypto";
|
|
1152
|
-
|
|
1163
|
+
import {
|
|
1164
|
+
clientTransportEnvKeys as clientTransportEnvKeys2,
|
|
1165
|
+
resolveClientTransport,
|
|
1166
|
+
resolveCredential,
|
|
1167
|
+
toV1BaseUrl,
|
|
1168
|
+
ClientTransportConfigurationError
|
|
1169
|
+
} from "@hasna/contracts/client";
|
|
1170
|
+
function firstEnv(keys, env2 = process.env) {
|
|
1153
1171
|
for (const k of keys) {
|
|
1154
|
-
const v =
|
|
1172
|
+
const v = env2[k]?.trim();
|
|
1155
1173
|
if (v)
|
|
1156
1174
|
return v;
|
|
1157
1175
|
}
|
|
1158
1176
|
return;
|
|
1159
1177
|
}
|
|
1160
|
-
function
|
|
1161
|
-
|
|
1162
|
-
if (process.env[k]?.trim())
|
|
1163
|
-
return k;
|
|
1164
|
-
}
|
|
1165
|
-
return null;
|
|
1178
|
+
function hasDatabaseUrl(env2 = process.env) {
|
|
1179
|
+
return Boolean(firstEnv(DATABASE_URL_ENV_KEYS, env2));
|
|
1166
1180
|
}
|
|
1167
|
-
function
|
|
1168
|
-
|
|
1181
|
+
function assertCleanResolvedBase(baseUrl) {
|
|
1182
|
+
if (/[?#]/.test(baseUrl)) {
|
|
1183
|
+
throw new Error("mementos base URL must not contain userinfo, query, or fragment data");
|
|
1184
|
+
}
|
|
1185
|
+
return baseUrl;
|
|
1169
1186
|
}
|
|
1170
1187
|
function isLoopbackHost(rawHost) {
|
|
1171
1188
|
const host = rawHost.replace(/^\[/, "").replace(/\]$/, "").toLowerCase();
|
|
@@ -1186,50 +1203,61 @@ function assertRequestAllowedUnderTest(baseUrl) {
|
|
|
1186
1203
|
return;
|
|
1187
1204
|
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.
|
|
1188
1205
|
` + ` host : ${host || "(unparseable base URL)"}
|
|
1189
|
-
` +
|
|
1206
|
+
` + " 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.
|
|
1190
1207
|
` + " fix : build the child/process env via src/test-support/store-isolation.ts, or point the " + `suite at a loopback stub.
|
|
1191
1208
|
` + ` override : set ${ALLOW_REMOTE_API_IN_TESTS_ENV}=1 only for a test that must reach a remote endpoint.`);
|
|
1192
1209
|
}
|
|
1193
|
-
function
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
return base;
|
|
1197
|
-
return `${base}/v1`;
|
|
1198
|
-
}
|
|
1199
|
-
function assertUnambiguousStoreEnv(env = process.env) {
|
|
1200
|
-
assertNoLegacyStorageMode2(env);
|
|
1201
|
-
if (firstEnvKey(DB_PATH_ENV_KEYS))
|
|
1202
|
-
return;
|
|
1203
|
-
if (hasDatabaseUrl())
|
|
1204
|
-
return;
|
|
1205
|
-
const urlKey = firstEnvKey(API_URL_ENV_KEYS);
|
|
1206
|
-
const keyKey = firstEnvKey(API_KEY_ENV_KEYS);
|
|
1207
|
-
if (urlKey && !keyKey) {
|
|
1208
|
-
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.`);
|
|
1209
|
-
}
|
|
1210
|
-
if (keyKey && !urlKey) {
|
|
1211
|
-
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.`);
|
|
1212
|
-
}
|
|
1210
|
+
function toStoreConfigError(error) {
|
|
1211
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1212
|
+
return new MementosStoreConfigError(`${message} Local SQLite is opt-in only (${DB_PATH_ENV_KEYS[0]}, or ${MEMENTOS_LOCAL_OPT_IN_ENV_KEYS[0]} = 1) and is disabled by default \u2014 failing closed`);
|
|
1213
1213
|
}
|
|
1214
|
-
function getApiConfig() {
|
|
1215
|
-
|
|
1216
|
-
if (firstEnvKey(DB_PATH_ENV_KEYS))
|
|
1214
|
+
function getApiConfig(env2 = process.env, options = {}) {
|
|
1215
|
+
if (hasDatabaseUrl(env2))
|
|
1217
1216
|
return null;
|
|
1218
|
-
|
|
1219
|
-
const apiKey = firstEnv2(API_KEY_ENV_KEYS);
|
|
1220
|
-
if (!rawBase || !apiKey)
|
|
1217
|
+
if (selectsMementosLocalStore(env2))
|
|
1221
1218
|
return null;
|
|
1222
|
-
|
|
1219
|
+
const inputs = mementosResolverInputs(env2, options.credentials);
|
|
1220
|
+
let credential;
|
|
1221
|
+
try {
|
|
1222
|
+
credential = resolveCredential("mementos", inputs.env, inputs.credentials);
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
throw toStoreConfigError(error);
|
|
1225
|
+
}
|
|
1226
|
+
let resolution;
|
|
1227
|
+
try {
|
|
1228
|
+
resolution = resolveClientTransport("mementos", inputs.env, {
|
|
1229
|
+
credentials: credential ? { ...inputs.credentials, apiKey: credential.apiKey } : inputs.credentials
|
|
1230
|
+
});
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
if (error instanceof ClientTransportConfigurationError && credential === null && /is not set and no API key could be resolved/.test(error.message)) {
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
throw toStoreConfigError(error);
|
|
1236
|
+
}
|
|
1237
|
+
return { baseUrl: assertCleanResolvedBase(resolution.baseUrl), apiKey: credential.apiKey };
|
|
1223
1238
|
}
|
|
1224
1239
|
function isApiMode() {
|
|
1225
1240
|
if (hasDatabaseUrl())
|
|
1226
1241
|
return false;
|
|
1227
1242
|
return getApiConfig() !== null;
|
|
1228
1243
|
}
|
|
1244
|
+
function assertClientStoreConfigured(env2 = process.env, options = {}) {
|
|
1245
|
+
if (selectsMementosLocalStore(env2))
|
|
1246
|
+
return;
|
|
1247
|
+
if (hasDatabaseUrl(env2))
|
|
1248
|
+
return;
|
|
1249
|
+
const config = getApiConfig(env2, options);
|
|
1250
|
+
if (config !== null)
|
|
1251
|
+
return;
|
|
1252
|
+
throw new MementosStoreConfigError(unconfiguredStoreMessage());
|
|
1253
|
+
}
|
|
1254
|
+
function unconfiguredStoreMessage() {
|
|
1255
|
+
return "mementos is not configured to reach any memory store, and will NOT fall back to the " + "on-box SQLite store (~/.hasna/mementos/mementos.db). " + "No credential could be resolved from the Keychain item hasna.credentials.mementos.api-key " + "(macOS only), ~/.hasna/mementos/config/credentials, or " + `${API_KEY_ENV_KEYS[0]}; the authority would be the fleet gateway ${"https://api.hasna.com/mementos"} ` + `(or ${API_URL_ENV_KEYS[0]} if set). ` + `Set ${API_URL_ENV_KEYS[0]} and ${API_KEY_ENV_KEYS[0]} (the resolver also accepts the legacy ` + `${API_URL_ENV_KEYS[1]} / ${API_KEY_ENV_KEYS[1]} aliases for one release) to use the fleet memory ` + `API, or opt into an explicit local SQLite file with ${DB_PATH_ENV_KEYS[0]} / ` + `${DB_PATH_ENV_KEYS[1]} (or ${MEMENTOS_LOCAL_OPT_IN_ENV_KEYS[0]}=1).`;
|
|
1256
|
+
}
|
|
1229
1257
|
function apiRequestRaw(method, path, body) {
|
|
1230
1258
|
const cfg = getApiConfig();
|
|
1231
1259
|
if (!cfg)
|
|
1232
|
-
throw new Error("api-mode: not configured (
|
|
1260
|
+
throw new Error("api-mode: not configured (no Hasna mementos credential resolved)");
|
|
1233
1261
|
assertRequestAllowedUnderTest(cfg.baseUrl);
|
|
1234
1262
|
const url = `${cfg.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
1235
1263
|
const hasBody = body !== undefined && body !== null;
|
|
@@ -1256,7 +1284,7 @@ x-api-key: ${cfg.apiKey}
|
|
|
1256
1284
|
];
|
|
1257
1285
|
let bodyFile;
|
|
1258
1286
|
if (hasBody) {
|
|
1259
|
-
bodyFile =
|
|
1287
|
+
bodyFile = join3(tmpdir(), `mem-req-${process.pid}-${randomUUID()}.json`);
|
|
1260
1288
|
writeFileSync2(bodyFile, JSON.stringify(body), { mode: 384 });
|
|
1261
1289
|
args.push("--data-binary", `@${bodyFile}`);
|
|
1262
1290
|
}
|
|
@@ -1344,13 +1372,13 @@ function toQuery(params) {
|
|
|
1344
1372
|
const s = sp.toString();
|
|
1345
1373
|
return s ? `?${s}` : "";
|
|
1346
1374
|
}
|
|
1347
|
-
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",
|
|
1375
|
+
var API_URL_ENV_KEYS, API_KEY_ENV_KEYS, DATABASE_URL_ENV_KEYS, DB_PATH_ENV_KEYS, ALLOW_REMOTE_API_IN_TESTS_ENV = "MEMENTOS_ALLOW_REMOTE_API_IN_TESTS", MementosStoreConfigError, ApiRequestError, DEFAULT_TIMEOUT_S = "45";
|
|
1348
1376
|
var init_api_mode = __esm(() => {
|
|
1349
|
-
|
|
1350
|
-
API_URL_ENV_KEYS =
|
|
1351
|
-
API_KEY_ENV_KEYS =
|
|
1377
|
+
init_local_opt_in();
|
|
1378
|
+
API_URL_ENV_KEYS = clientTransportEnvKeys2("mementos").apiUrlKeys;
|
|
1379
|
+
API_KEY_ENV_KEYS = clientTransportEnvKeys2("mementos").apiKeyKeys;
|
|
1352
1380
|
DATABASE_URL_ENV_KEYS = ["HASNA_MEMENTOS_DATABASE_URL", "MEMENTOS_DATABASE_URL"];
|
|
1353
|
-
DB_PATH_ENV_KEYS =
|
|
1381
|
+
DB_PATH_ENV_KEYS = MEMENTOS_DB_PATH_ENV_KEYS;
|
|
1354
1382
|
MementosStoreConfigError = class MementosStoreConfigError extends Error {
|
|
1355
1383
|
code = "MEMENTOS_STORE_CONFIG";
|
|
1356
1384
|
constructor(message) {
|
|
@@ -2777,7 +2805,7 @@ __export(exports_database, {
|
|
|
2777
2805
|
closeDatabase: () => closeDatabase
|
|
2778
2806
|
});
|
|
2779
2807
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, cpSync } from "fs";
|
|
2780
|
-
import { dirname, join as
|
|
2808
|
+
import { dirname, join as join4, resolve as resolve2 } from "path";
|
|
2781
2809
|
function isInMemoryDb(path) {
|
|
2782
2810
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
2783
2811
|
}
|
|
@@ -2786,7 +2814,7 @@ function findNearestMementosDb(startDir) {
|
|
|
2786
2814
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
2787
2815
|
const legacyHomeDb = resolve2(home, ".mementos", "mementos.db");
|
|
2788
2816
|
while (true) {
|
|
2789
|
-
const candidate =
|
|
2817
|
+
const candidate = join4(dir, ".mementos", "mementos.db");
|
|
2790
2818
|
if (existsSync3(candidate) && resolve2(candidate) !== legacyHomeDb)
|
|
2791
2819
|
return candidate;
|
|
2792
2820
|
const parent = dirname(dir);
|
|
@@ -2799,7 +2827,7 @@ function findNearestMementosDb(startDir) {
|
|
|
2799
2827
|
function findGitRoot(startDir) {
|
|
2800
2828
|
let dir = resolve2(startDir);
|
|
2801
2829
|
while (true) {
|
|
2802
|
-
if (existsSync3(
|
|
2830
|
+
if (existsSync3(join4(dir, ".git")))
|
|
2803
2831
|
return dir;
|
|
2804
2832
|
const parent = dirname(dir);
|
|
2805
2833
|
if (parent === dir)
|
|
@@ -2809,9 +2837,11 @@ function findGitRoot(startDir) {
|
|
|
2809
2837
|
return null;
|
|
2810
2838
|
}
|
|
2811
2839
|
function migrateGlobalDir() {
|
|
2840
|
+
if (!selectsMementosLocalStore())
|
|
2841
|
+
return;
|
|
2812
2842
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
2813
2843
|
const newDir = getDataRoot();
|
|
2814
|
-
const oldDir =
|
|
2844
|
+
const oldDir = join4(home, ".mementos");
|
|
2815
2845
|
if (!existsSync3(newDir) && existsSync3(oldDir)) {
|
|
2816
2846
|
mkdirSync2(dirname(newDir), { recursive: true });
|
|
2817
2847
|
cpSync(oldDir, newDir, { recursive: true });
|
|
@@ -2830,11 +2860,11 @@ function getDbPath() {
|
|
|
2830
2860
|
if (process.env["MEMENTOS_DB_SCOPE"] === "project") {
|
|
2831
2861
|
const gitRoot = findGitRoot(cwd);
|
|
2832
2862
|
if (gitRoot) {
|
|
2833
|
-
return
|
|
2863
|
+
return join4(gitRoot, ".mementos", "mementos.db");
|
|
2834
2864
|
}
|
|
2835
2865
|
}
|
|
2836
2866
|
migrateGlobalDir();
|
|
2837
|
-
return
|
|
2867
|
+
return join4(getDataRoot(), "mementos.db");
|
|
2838
2868
|
}
|
|
2839
2869
|
function ensureDir(filePath) {
|
|
2840
2870
|
if (isInMemoryDb(filePath))
|
|
@@ -2870,6 +2900,9 @@ function getDatabase(dbPath) {
|
|
|
2870
2900
|
throw new Error("REFUSING-UNPINNED-TEST-OPEN: a test process tried to open the default local " + `SQLite store (${getDbPath()}) with no explicit dbPath argument and no ` + `${DB_PATH_ENV_KEYS.join("/")} set. Pass an explicit dbPath (e.g. ":memory:" ` + "or a scratch file), or set one of those env keys to a scratch path, before " + "calling getDatabase(). This guard exists because an unpinned test process " + "would otherwise silently read and write the real, shared, on-disk memory " + "store (todos 57b8b8c5).");
|
|
2871
2901
|
}
|
|
2872
2902
|
}
|
|
2903
|
+
if (process.env["NODE_ENV"] !== "test" && !isServerContext()) {
|
|
2904
|
+
assertClientStoreConfigured();
|
|
2905
|
+
}
|
|
2873
2906
|
}
|
|
2874
2907
|
const path = dbPath || getDbPath();
|
|
2875
2908
|
if (_db) {
|
|
@@ -2882,7 +2915,6 @@ function getDatabase(dbPath) {
|
|
|
2882
2915
|
ensureDir(path);
|
|
2883
2916
|
_db = new SqliteAdapter(path);
|
|
2884
2917
|
_db.run("PRAGMA journal_mode = WAL");
|
|
2885
|
-
_db.run("PRAGMA busy_timeout = 5000");
|
|
2886
2918
|
_db.run("PRAGMA foreign_keys = ON");
|
|
2887
2919
|
_db.run("PRAGMA wal_autocheckpoint = 100");
|
|
2888
2920
|
runMigrations(_db);
|
|
@@ -2979,6 +3011,7 @@ var init_database = __esm(() => {
|
|
|
2979
3011
|
init_api_mode();
|
|
2980
3012
|
init_migrations();
|
|
2981
3013
|
init_paths();
|
|
3014
|
+
init_local_opt_in();
|
|
2982
3015
|
ALLOWED_TABLES = new Set([
|
|
2983
3016
|
"memories",
|
|
2984
3017
|
"agents",
|
|
@@ -3007,13 +3040,13 @@ import { createHash } from "crypto";
|
|
|
3007
3040
|
|
|
3008
3041
|
// src/lib/package-version.ts
|
|
3009
3042
|
import { readFileSync as readFileSync2 } from "fs";
|
|
3010
|
-
import { dirname as dirname2, join as
|
|
3043
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
3011
3044
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3012
3045
|
function getMementosPackageVersion() {
|
|
3013
3046
|
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
3014
3047
|
for (const candidate of [
|
|
3015
|
-
|
|
3016
|
-
|
|
3048
|
+
join5(here, "..", "..", "package.json"),
|
|
3049
|
+
join5(here, "..", "package.json")
|
|
3017
3050
|
]) {
|
|
3018
3051
|
try {
|
|
3019
3052
|
const parsed = JSON.parse(readFileSync2(candidate, "utf8"));
|
|
@@ -5200,6 +5233,17 @@ var SECRET_PATTERNS = [
|
|
|
5200
5233
|
{ name: "jwt", pattern: /eyJ[a-zA-Z0-9_-]{10,}\.eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/g },
|
|
5201
5234
|
{ name: "hex_secret", pattern: /(?<=(?:key|token|secret|password|hash)\s*[:=]\s*["']?)[0-9a-f]{32,}(?=["']?)/gi }
|
|
5202
5235
|
];
|
|
5236
|
+
var KEY_TAG_SECRET_PATTERNS = SECRET_PATTERNS.filter((p) => new Set([
|
|
5237
|
+
"openai_key",
|
|
5238
|
+
"anthropic_key",
|
|
5239
|
+
"aws_key",
|
|
5240
|
+
"github_token",
|
|
5241
|
+
"github_oauth",
|
|
5242
|
+
"npm_token",
|
|
5243
|
+
"stripe_key",
|
|
5244
|
+
"slack_token",
|
|
5245
|
+
"jwt"
|
|
5246
|
+
]).has(p.name));
|
|
5203
5247
|
function redactSecrets(text) {
|
|
5204
5248
|
let result = text;
|
|
5205
5249
|
for (const { pattern } of SECRET_PATTERNS) {
|
|
@@ -5208,6 +5252,14 @@ function redactSecrets(text) {
|
|
|
5208
5252
|
}
|
|
5209
5253
|
return result;
|
|
5210
5254
|
}
|
|
5255
|
+
function redactCredentialKey(text) {
|
|
5256
|
+
let result = text;
|
|
5257
|
+
for (const { pattern } of KEY_TAG_SECRET_PATTERNS) {
|
|
5258
|
+
pattern.lastIndex = 0;
|
|
5259
|
+
result = result.replace(pattern, REDACTED);
|
|
5260
|
+
}
|
|
5261
|
+
return result;
|
|
5262
|
+
}
|
|
5211
5263
|
function containsSecrets(text) {
|
|
5212
5264
|
for (const { pattern } of SECRET_PATTERNS) {
|
|
5213
5265
|
pattern.lastIndex = 0;
|
|
@@ -5216,6 +5268,40 @@ function containsSecrets(text) {
|
|
|
5216
5268
|
}
|
|
5217
5269
|
return false;
|
|
5218
5270
|
}
|
|
5271
|
+
function redactValueTree(value) {
|
|
5272
|
+
if (typeof value === "string")
|
|
5273
|
+
return redactSecrets(value);
|
|
5274
|
+
if (Array.isArray(value))
|
|
5275
|
+
return value.map(redactValueTree);
|
|
5276
|
+
if (value !== null && typeof value === "object") {
|
|
5277
|
+
const out = {};
|
|
5278
|
+
for (const [k, v] of Object.entries(value)) {
|
|
5279
|
+
out[k] = redactValueTree(v);
|
|
5280
|
+
}
|
|
5281
|
+
return out;
|
|
5282
|
+
}
|
|
5283
|
+
return value;
|
|
5284
|
+
}
|
|
5285
|
+
function redactMemoryWriteInput(input) {
|
|
5286
|
+
const out = { ...input };
|
|
5287
|
+
if (typeof out.key === "string")
|
|
5288
|
+
out.key = redactCredentialKey(out.key);
|
|
5289
|
+
if (typeof out.value === "string")
|
|
5290
|
+
out.value = redactSecrets(out.value);
|
|
5291
|
+
if (out.summary !== undefined && out.summary !== null) {
|
|
5292
|
+
out.summary = redactSecrets(String(out.summary));
|
|
5293
|
+
}
|
|
5294
|
+
if (Array.isArray(out.tags)) {
|
|
5295
|
+
out.tags = out.tags.map((t) => redactCredentialKey(String(t)));
|
|
5296
|
+
}
|
|
5297
|
+
if (typeof out.when_to_use === "string") {
|
|
5298
|
+
out.when_to_use = redactSecrets(out.when_to_use);
|
|
5299
|
+
}
|
|
5300
|
+
if (out.metadata !== undefined) {
|
|
5301
|
+
out.metadata = redactValueTree(out.metadata);
|
|
5302
|
+
}
|
|
5303
|
+
return out;
|
|
5304
|
+
}
|
|
5219
5305
|
|
|
5220
5306
|
// src/lib/hooks.ts
|
|
5221
5307
|
var _idCounter = 0;
|
|
@@ -5528,6 +5614,7 @@ function createMemory(input, dedupeMode = "merge", db) {
|
|
|
5528
5614
|
if (reservedViolation) {
|
|
5529
5615
|
throw new Error(reservedViolation);
|
|
5530
5616
|
}
|
|
5617
|
+
input = redactMemoryWriteInput(input);
|
|
5531
5618
|
if (!db && isApiMode()) {
|
|
5532
5619
|
const { status, data } = apiJson("POST", "/memories", { ...input, dedupe: dedupeMode });
|
|
5533
5620
|
if (!data || !data.id) {
|
|
@@ -5985,6 +6072,7 @@ function listMemoriesBounded(filter = {}, target, db) {
|
|
|
5985
6072
|
};
|
|
5986
6073
|
}
|
|
5987
6074
|
function updateMemory(id, input, db) {
|
|
6075
|
+
input = redactMemoryWriteInput(input);
|
|
5988
6076
|
if (!db && isApiMode()) {
|
|
5989
6077
|
const { status, data } = apiJson("PATCH", `/memories/${encodeURIComponent(id)}`, input, { allow404: true });
|
|
5990
6078
|
if (status === 404)
|