nexus-ai-pro 2.0.0 → 2.2.0

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 (282) hide show
  1. package/API_STABILITY.md +65 -0
  2. package/CHANGELOG.md +267 -1
  3. package/MIGRATING.md +1 -1
  4. package/README.md +17 -8
  5. package/dist/agent/create-agent.d.ts +9 -1
  6. package/dist/agent/create-agent.js +40 -1
  7. package/dist/cache/memory-cache.d.ts +8 -0
  8. package/dist/cache/memory-cache.js +9 -0
  9. package/dist/cache/semantic-cache.d.ts +6 -3
  10. package/dist/cache/semantic-cache.js +5 -2
  11. package/dist/cli/args.js +3 -0
  12. package/dist/cli/db.d.ts +16 -4
  13. package/dist/cli/db.js +169 -22
  14. package/dist/cli/doctor.d.ts +17 -0
  15. package/dist/cli/doctor.js +53 -0
  16. package/dist/cli/graph.d.ts +9 -0
  17. package/dist/cli/graph.js +47 -0
  18. package/dist/cli.js +21 -2
  19. package/dist/core/lifecycle.js +11 -6
  20. package/dist/core/nexus.d.ts +12 -2
  21. package/dist/core/nexus.js +106 -23
  22. package/dist/doctor/index.d.ts +90 -0
  23. package/dist/doctor/index.js +237 -0
  24. package/dist/evals/judge.d.ts +11 -0
  25. package/dist/evals/judge.js +33 -0
  26. package/dist/evaluate/index.d.ts +1 -0
  27. package/dist/evaluate/index.js +1 -0
  28. package/dist/evaluate/provenance.d.ts +22 -0
  29. package/dist/evaluate/provenance.js +10 -0
  30. package/dist/evaluate/run.js +20 -4
  31. package/dist/graph/durability.d.ts +27 -0
  32. package/dist/graph/durability.js +66 -0
  33. package/dist/graph/errors.d.ts +30 -4
  34. package/dist/graph/errors.js +18 -2
  35. package/dist/graph/event-stream.d.ts +75 -0
  36. package/dist/graph/event-stream.js +289 -0
  37. package/dist/graph/functional.d.ts +80 -7
  38. package/dist/graph/functional.js +138 -35
  39. package/dist/graph/graph.d.ts +56 -8
  40. package/dist/graph/graph.js +331 -49
  41. package/dist/graph/index.d.ts +4 -2
  42. package/dist/graph/index.js +3 -1
  43. package/dist/graph/lint.d.ts +57 -0
  44. package/dist/graph/lint.js +141 -0
  45. package/dist/graph/run-control.d.ts +28 -0
  46. package/dist/graph/run-control.js +26 -0
  47. package/dist/images/evals.js +4 -2
  48. package/dist/operations/adapters.d.ts +52 -4
  49. package/dist/operations/adapters.js +194 -7
  50. package/dist/operations/runner.js +14 -15
  51. package/dist/operations/stats.js +8 -1
  52. package/dist/operations/store.d.ts +5 -1
  53. package/dist/operations/store.js +7 -0
  54. package/dist/ops/circuit-breaker.d.ts +52 -0
  55. package/dist/ops/circuit-breaker.js +4 -0
  56. package/dist/ops/circuit-store.d.ts +13 -2
  57. package/dist/ops/circuit-store.js +68 -0
  58. package/dist/ops/circuit-sync.d.ts +17 -0
  59. package/dist/ops/circuit-sync.js +86 -0
  60. package/dist/ops/health.d.ts +24 -0
  61. package/dist/ops/health.js +38 -10
  62. package/dist/ops/index.d.ts +1 -1
  63. package/dist/ops/index.js +1 -1
  64. package/dist/ops/rate-limit-adapters.d.ts +23 -0
  65. package/dist/ops/rate-limit-adapters.js +34 -0
  66. package/dist/ops/rate-limiter.d.ts +14 -2
  67. package/dist/ops/rate-limiter.js +31 -0
  68. package/dist/postgres/circuits.d.ts +21 -4
  69. package/dist/postgres/circuits.js +65 -4
  70. package/dist/postgres/client.d.ts +0 -2
  71. package/dist/postgres/client.js +0 -4
  72. package/dist/postgres/evaluate.d.ts +16 -4
  73. package/dist/postgres/evaluate.js +17 -5
  74. package/dist/postgres/index.d.ts +32 -11
  75. package/dist/postgres/index.js +65 -31
  76. package/dist/postgres/migrations.d.ts +60 -0
  77. package/dist/postgres/migrations.js +121 -0
  78. package/dist/postgres/operations.d.ts +14 -3
  79. package/dist/postgres/operations.js +45 -15
  80. package/dist/postgres/prompts.d.ts +11 -2
  81. package/dist/postgres/prompts.js +14 -3
  82. package/dist/postgres/rollups.d.ts +35 -0
  83. package/dist/postgres/rollups.js +129 -0
  84. package/dist/postgres/store.d.ts +11 -2
  85. package/dist/postgres/store.js +14 -3
  86. package/dist/postgres/traces.d.ts +13 -3
  87. package/dist/postgres/traces.js +25 -3
  88. package/dist/postgres/vectors.d.ts +11 -2
  89. package/dist/postgres/vectors.js +14 -3
  90. package/dist/router/auto-router.d.ts +34 -7
  91. package/dist/router/auto-router.js +201 -68
  92. package/dist/server/assistant.d.ts +44 -0
  93. package/dist/server/assistant.js +39 -0
  94. package/dist/server/errors.d.ts +12 -0
  95. package/dist/server/errors.js +7 -0
  96. package/dist/server/index.d.ts +1 -1
  97. package/dist/server/index.js +1 -1
  98. package/dist/server/runs.d.ts +28 -1
  99. package/dist/server/runs.js +110 -14
  100. package/dist/sqlite/index.d.ts +16 -3
  101. package/dist/sqlite/index.js +16 -3
  102. package/dist/sqlite/migrations.d.ts +26 -0
  103. package/dist/sqlite/migrations.js +63 -0
  104. package/dist/sqlite/operations.d.ts +14 -3
  105. package/dist/sqlite/operations.js +42 -15
  106. package/dist/sqlite/store.d.ts +9 -2
  107. package/dist/sqlite/store.js +13 -3
  108. package/dist/sqlite/vectors.d.ts +11 -2
  109. package/dist/sqlite/vectors.js +13 -2
  110. package/dist/store/tenant.d.ts +12 -0
  111. package/dist/store/tenant.js +29 -0
  112. package/dist/tenancy/index.d.ts +113 -0
  113. package/dist/tenancy/index.js +233 -0
  114. package/dist/testing/record.d.ts +42 -0
  115. package/dist/testing/record.js +57 -5
  116. package/dist/tracing/index.d.ts +3 -3
  117. package/dist/tracing/index.js +2 -2
  118. package/dist/tracing/instrument.js +30 -0
  119. package/dist/tracing/otlp.d.ts +91 -0
  120. package/dist/tracing/otlp.js +124 -0
  121. package/dist/tracing/rollups.d.ts +100 -0
  122. package/dist/tracing/rollups.js +139 -0
  123. package/dist/tracing/stores.d.ts +17 -0
  124. package/dist/tracing/stores.js +23 -0
  125. package/dist/tracing/tracer.d.ts +50 -4
  126. package/dist/tracing/tracer.js +85 -6
  127. package/dist/types/config.d.ts +33 -2
  128. package/dist/types/evaluate.d.ts +37 -0
  129. package/dist/types/graph.d.ts +354 -9
  130. package/dist/types/graph.js +3 -1
  131. package/dist/types/messages.d.ts +5 -0
  132. package/dist/types/operations.d.ts +22 -2
  133. package/dist/types/response.d.ts +20 -0
  134. package/dist/types/server.d.ts +18 -0
  135. package/dist/types/tracing.d.ts +11 -0
  136. package/dist/utils/schema-migrations.d.ts +97 -0
  137. package/dist/utils/schema-migrations.js +91 -0
  138. package/dist/utils/signals.d.ts +17 -0
  139. package/dist/utils/signals.js +26 -0
  140. package/dist/utils/tenant.d.ts +14 -0
  141. package/dist/utils/tenant.js +16 -0
  142. package/dist/version.d.ts +5 -0
  143. package/dist/version.js +1 -0
  144. package/dist-cjs/agent/create-agent.d.ts +9 -1
  145. package/dist-cjs/agent/create-agent.js +40 -1
  146. package/dist-cjs/cache/memory-cache.d.ts +8 -0
  147. package/dist-cjs/cache/memory-cache.js +9 -0
  148. package/dist-cjs/cache/semantic-cache.d.ts +6 -3
  149. package/dist-cjs/cache/semantic-cache.js +5 -2
  150. package/dist-cjs/cli/args.js +3 -0
  151. package/dist-cjs/cli/db.d.ts +16 -4
  152. package/dist-cjs/cli/db.js +200 -20
  153. package/dist-cjs/cli/doctor.d.ts +17 -0
  154. package/dist-cjs/cli/doctor.js +56 -0
  155. package/dist-cjs/cli/graph.d.ts +9 -0
  156. package/dist-cjs/cli/graph.js +50 -0
  157. package/dist-cjs/core/lifecycle.js +11 -6
  158. package/dist-cjs/core/nexus.d.ts +12 -2
  159. package/dist-cjs/core/nexus.js +106 -23
  160. package/dist-cjs/doctor/index.d.ts +90 -0
  161. package/dist-cjs/doctor/index.js +276 -0
  162. package/dist-cjs/evals/judge.d.ts +11 -0
  163. package/dist-cjs/evals/judge.js +33 -0
  164. package/dist-cjs/evaluate/index.d.ts +1 -0
  165. package/dist-cjs/evaluate/index.js +3 -1
  166. package/dist-cjs/evaluate/provenance.d.ts +22 -0
  167. package/dist-cjs/evaluate/provenance.js +14 -0
  168. package/dist-cjs/evaluate/run.js +20 -4
  169. package/dist-cjs/graph/durability.d.ts +27 -0
  170. package/dist-cjs/graph/durability.js +70 -0
  171. package/dist-cjs/graph/errors.d.ts +30 -4
  172. package/dist-cjs/graph/errors.js +20 -3
  173. package/dist-cjs/graph/event-stream.d.ts +75 -0
  174. package/dist-cjs/graph/event-stream.js +295 -0
  175. package/dist-cjs/graph/functional.d.ts +80 -7
  176. package/dist-cjs/graph/functional.js +137 -34
  177. package/dist-cjs/graph/graph.d.ts +56 -8
  178. package/dist-cjs/graph/graph.js +330 -48
  179. package/dist-cjs/graph/index.d.ts +4 -2
  180. package/dist-cjs/graph/index.js +7 -1
  181. package/dist-cjs/graph/lint.d.ts +57 -0
  182. package/dist-cjs/graph/lint.js +144 -0
  183. package/dist-cjs/graph/run-control.d.ts +28 -0
  184. package/dist-cjs/graph/run-control.js +30 -0
  185. package/dist-cjs/images/evals.js +4 -2
  186. package/dist-cjs/operations/adapters.d.ts +52 -4
  187. package/dist-cjs/operations/adapters.js +193 -6
  188. package/dist-cjs/operations/runner.js +14 -15
  189. package/dist-cjs/operations/stats.js +8 -1
  190. package/dist-cjs/operations/store.d.ts +5 -1
  191. package/dist-cjs/operations/store.js +7 -0
  192. package/dist-cjs/ops/circuit-breaker.d.ts +52 -0
  193. package/dist-cjs/ops/circuit-breaker.js +4 -0
  194. package/dist-cjs/ops/circuit-store.d.ts +13 -2
  195. package/dist-cjs/ops/circuit-store.js +68 -0
  196. package/dist-cjs/ops/circuit-sync.d.ts +17 -0
  197. package/dist-cjs/ops/circuit-sync.js +87 -0
  198. package/dist-cjs/ops/health.d.ts +24 -0
  199. package/dist-cjs/ops/health.js +38 -10
  200. package/dist-cjs/ops/index.d.ts +1 -1
  201. package/dist-cjs/ops/rate-limit-adapters.d.ts +23 -0
  202. package/dist-cjs/ops/rate-limit-adapters.js +35 -1
  203. package/dist-cjs/ops/rate-limiter.d.ts +14 -2
  204. package/dist-cjs/ops/rate-limiter.js +32 -0
  205. package/dist-cjs/postgres/circuits.d.ts +21 -4
  206. package/dist-cjs/postgres/circuits.js +98 -3
  207. package/dist-cjs/postgres/client.d.ts +0 -2
  208. package/dist-cjs/postgres/client.js +0 -5
  209. package/dist-cjs/postgres/evaluate.d.ts +16 -4
  210. package/dist-cjs/postgres/evaluate.js +50 -4
  211. package/dist-cjs/postgres/index.d.ts +32 -11
  212. package/dist-cjs/postgres/index.js +69 -20
  213. package/dist-cjs/postgres/migrations.d.ts +60 -0
  214. package/dist-cjs/postgres/migrations.js +129 -0
  215. package/dist-cjs/postgres/operations.d.ts +14 -3
  216. package/dist-cjs/postgres/operations.js +78 -14
  217. package/dist-cjs/postgres/prompts.d.ts +11 -2
  218. package/dist-cjs/postgres/prompts.js +47 -2
  219. package/dist-cjs/postgres/rollups.d.ts +35 -0
  220. package/dist-cjs/postgres/rollups.js +168 -0
  221. package/dist-cjs/postgres/store.d.ts +11 -2
  222. package/dist-cjs/postgres/store.js +47 -2
  223. package/dist-cjs/postgres/traces.d.ts +13 -3
  224. package/dist-cjs/postgres/traces.js +58 -2
  225. package/dist-cjs/postgres/vectors.d.ts +11 -2
  226. package/dist-cjs/postgres/vectors.js +47 -2
  227. package/dist-cjs/router/auto-router.d.ts +34 -7
  228. package/dist-cjs/router/auto-router.js +201 -68
  229. package/dist-cjs/server/assistant.d.ts +44 -0
  230. package/dist-cjs/server/assistant.js +39 -0
  231. package/dist-cjs/server/errors.d.ts +12 -0
  232. package/dist-cjs/server/errors.js +9 -1
  233. package/dist-cjs/server/index.d.ts +1 -1
  234. package/dist-cjs/server/index.js +2 -1
  235. package/dist-cjs/server/runs.d.ts +28 -1
  236. package/dist-cjs/server/runs.js +109 -13
  237. package/dist-cjs/sqlite/index.d.ts +16 -3
  238. package/dist-cjs/sqlite/index.js +31 -10
  239. package/dist-cjs/sqlite/migrations.d.ts +26 -0
  240. package/dist-cjs/sqlite/migrations.js +71 -0
  241. package/dist-cjs/sqlite/operations.d.ts +14 -3
  242. package/dist-cjs/sqlite/operations.js +76 -15
  243. package/dist-cjs/sqlite/store.d.ts +9 -2
  244. package/dist-cjs/sqlite/store.js +47 -3
  245. package/dist-cjs/sqlite/vectors.d.ts +11 -2
  246. package/dist-cjs/sqlite/vectors.js +47 -2
  247. package/dist-cjs/store/tenant.d.ts +12 -0
  248. package/dist-cjs/store/tenant.js +33 -0
  249. package/dist-cjs/tenancy/index.d.ts +113 -0
  250. package/dist-cjs/tenancy/index.js +250 -0
  251. package/dist-cjs/testing/record.d.ts +42 -0
  252. package/dist-cjs/testing/record.js +57 -5
  253. package/dist-cjs/tracing/index.d.ts +3 -3
  254. package/dist-cjs/tracing/index.js +4 -1
  255. package/dist-cjs/tracing/instrument.js +30 -0
  256. package/dist-cjs/tracing/otlp.d.ts +91 -0
  257. package/dist-cjs/tracing/otlp.js +129 -0
  258. package/dist-cjs/tracing/rollups.d.ts +100 -0
  259. package/dist-cjs/tracing/rollups.js +147 -0
  260. package/dist-cjs/tracing/stores.d.ts +17 -0
  261. package/dist-cjs/tracing/stores.js +24 -0
  262. package/dist-cjs/tracing/tracer.d.ts +50 -4
  263. package/dist-cjs/tracing/tracer.js +87 -6
  264. package/dist-cjs/types/config.d.ts +33 -2
  265. package/dist-cjs/types/evaluate.d.ts +37 -0
  266. package/dist-cjs/types/graph.d.ts +354 -9
  267. package/dist-cjs/types/graph.js +3 -1
  268. package/dist-cjs/types/messages.d.ts +5 -0
  269. package/dist-cjs/types/operations.d.ts +22 -2
  270. package/dist-cjs/types/response.d.ts +20 -0
  271. package/dist-cjs/types/server.d.ts +18 -0
  272. package/dist-cjs/types/tracing.d.ts +11 -0
  273. package/dist-cjs/utils/schema-migrations.d.ts +97 -0
  274. package/dist-cjs/utils/schema-migrations.js +102 -0
  275. package/dist-cjs/utils/signals.d.ts +17 -0
  276. package/dist-cjs/utils/signals.js +29 -0
  277. package/dist-cjs/utils/tenant.d.ts +14 -0
  278. package/dist-cjs/utils/tenant.js +22 -0
  279. package/dist-cjs/version.d.ts +5 -0
  280. package/dist-cjs/version.js +4 -0
  281. package/llms.txt +4 -3
  282. package/package.json +110 -2
@@ -1,12 +1,20 @@
1
1
  import { type OperationRecord, type OperationStore, type OperationStoreFilter, type OperationStoreStats } from '../types/operations.js';
2
+ import type { MigrationResult, SchemaMigration } from '../utils/schema-migrations.js';
2
3
  import { type SqliteDatabaseLike, type SqliteLikeClient } from './client.js';
4
+ import type { SqliteMigrateOptions } from './migrations.js';
3
5
  /** Options for the SQLite operation store. */
4
6
  export interface SqliteOperationStoreOptions {
5
7
  /** Table name. Defaults to `nexus_operations`. */
6
8
  table?: string;
7
9
  }
8
- /** The schema, as statements. Applied by `migrate()`, or by the application's own tooling. */
10
+ /** The schema, as statements: every migration's, in order. For the application's own tooling. */
9
11
  export declare function sqliteOperationStoreMigration(options?: SqliteOperationStoreOptions): string[];
12
+ /**
13
+ * The versioned schema, which `migrate()` applies. Version 1 is the 2.0 table; version 2 indexes
14
+ * queued work by age, so a claim reads the oldest few rows instead of sorting the queue; version 3
15
+ * indexes queued work by tenant.
16
+ */
17
+ export declare function sqliteOperationStoreMigrations(options?: SqliteOperationStoreOptions): SchemaMigration[];
10
18
  /**
11
19
  * Operation records in SQLite: durable operations, and — through `OperationStoreCheckpointer` — graph
12
20
  * and workflow checkpoints, on one machine with no server to run.
@@ -20,8 +28,11 @@ export declare class SqliteOperationStore<TResult = unknown> implements Operatio
20
28
  private readonly client;
21
29
  private readonly table;
22
30
  constructor(database: SqliteLikeClient | SqliteDatabaseLike, options?: SqliteOperationStoreOptions);
23
- /** Creates the table and indexes if they do not exist. Never runs implicitly. */
24
- migrate(): Promise<void>;
31
+ /**
32
+ * Applies this store's pending migrations, recorded in `nexus_schema_migrations`, each inside
33
+ * `BEGIN IMMEDIATE`. Never runs implicitly. Safe on a file an older release created.
34
+ */
35
+ migrate(options?: SqliteMigrateOptions): Promise<MigrationResult>;
25
36
  /**
26
37
  * Inserts a new record. Throws `OperationDuplicateError` when its idempotency key is already
27
38
  * claimed. Refuses records carrying raw bytes.
@@ -5,8 +5,31 @@ import { isSqliteUniqueViolation, quoteSqliteTable, toSqliteClient, } from './cl
5
5
  const DEFAULT_TABLE = 'nexus_operations';
6
6
  const TERMINAL = TERMINAL_OPERATION_STATUSES.map((status) => `'${status}'`).join(', ');
7
7
  export function sqliteOperationStoreMigration(options = {}) {
8
+ return sqliteOperationStoreMigrations(options).flatMap((migration) => [...migration.statements]);
9
+ }
10
+ export function sqliteOperationStoreMigrations(options = {}) {
8
11
  const name = options.table ?? DEFAULT_TABLE;
9
12
  const table = quoteSqliteTable(name);
13
+ const component = `operations:${name}`;
14
+ return [
15
+ { component, version: 1, name: 'create the operations table', statements: createStatements(name, table) },
16
+ {
17
+ component,
18
+ version: 2,
19
+ name: 'index queued work by age',
20
+ statements: [`CREATE INDEX IF NOT EXISTS "${name}_queued" ON ${table} (status, created_at, id)`],
21
+ },
22
+ {
23
+ component,
24
+ version: 3,
25
+ name: 'index queued work by tenant',
26
+ statements: [
27
+ `CREATE INDEX IF NOT EXISTS "${name}_tenant_queued" ON ${table} (status, json_extract(doc, '$.tenantId'), created_at, id)`,
28
+ ],
29
+ },
30
+ ];
31
+ }
32
+ function createStatements(name, table) {
10
33
  return [
11
34
  `CREATE TABLE IF NOT EXISTS ${table} (
12
35
  id TEXT PRIMARY KEY,
@@ -32,9 +55,9 @@ export class SqliteOperationStore {
32
55
  this.client = toSqliteClient(database);
33
56
  this.table = quoteSqliteTable(options.table ?? DEFAULT_TABLE);
34
57
  }
35
- async migrate() {
36
- for (const statement of sqliteOperationStoreMigration(this.options))
37
- await this.client.exec(statement);
58
+ async migrate(options = {}) {
59
+ const { applySqliteMigrations } = await import('./migrations.js');
60
+ return applySqliteMigrations(this.client, sqliteOperationStoreMigrations(this.options), options);
38
61
  }
39
62
  async create(record) {
40
63
  assertSerializableRecord(record);
@@ -99,29 +122,21 @@ export class SqliteOperationStore {
99
122
  }
100
123
  async listQueued(limit, filter) {
101
124
  const params = [new Date().toISOString()];
102
- let kind = '';
103
- if (filter?.kindPrefix) {
104
- params.push(filter.kindPrefix, filter.kindPrefix);
105
- kind = ' AND substr(kind, 1, length(?)) = ?';
106
- }
125
+ const narrowed = filterSql(filter, params);
107
126
  params.push(limit);
108
127
  return this.many(`SELECT doc FROM ${this.table}
109
- WHERE status = 'queued' AND (lease_expires_at IS NULL OR lease_expires_at <= ?)${kind}
128
+ WHERE status = 'queued' AND (lease_expires_at IS NULL OR lease_expires_at <= ?)${narrowed}
110
129
  ORDER BY created_at, id
111
130
  LIMIT ?`, params);
112
131
  }
113
132
  async stats(now, filter) {
114
133
  const params = [now];
115
- let kind = '';
116
- if (filter?.kindPrefix) {
117
- params.push(filter.kindPrefix, filter.kindPrefix);
118
- kind = ' AND substr(kind, 1, length(?)) = ?';
119
- }
134
+ const narrowed = filterSql(filter, params);
120
135
  const rows = (await this.client.all(`SELECT status, count(*) AS count,
121
136
  min(CASE WHEN status = 'queued' THEN created_at END) AS oldest,
122
137
  sum(CASE WHEN status = 'running' AND (lease_expires_at IS NULL OR lease_expires_at <= ?) THEN 1 ELSE 0 END) AS lapsed
123
138
  FROM ${this.table}
124
- WHERE status NOT IN (${TERMINAL})${kind}
139
+ WHERE status NOT IN (${TERMINAL})${narrowed}
125
140
  GROUP BY status`, params));
126
141
  const byStatus = {};
127
142
  let oldestQueuedAt;
@@ -146,3 +161,15 @@ export class SqliteOperationStore {
146
161
  return rows.map((row) => JSON.parse(row.doc));
147
162
  }
148
163
  }
164
+ function filterSql(filter, params) {
165
+ let sql = '';
166
+ if (filter?.kindPrefix) {
167
+ params.push(filter.kindPrefix, filter.kindPrefix);
168
+ sql += ' AND substr(kind, 1, length(?)) = ?';
169
+ }
170
+ if (filter?.tenantId !== undefined) {
171
+ params.push(filter.tenantId);
172
+ sql += " AND json_extract(doc, '$.tenantId') = ?";
173
+ }
174
+ return sql;
175
+ }
@@ -1,5 +1,7 @@
1
1
  import type { Store, StoreIndexOptions, StoreItem, StoreNamespace, StorePutOptions, StoreSearchOptions } from '../types/store.js';
2
+ import type { MigrationResult, SchemaMigration } from '../utils/schema-migrations.js';
2
3
  import { type SqliteDatabaseLike, type SqliteLikeClient } from './client.js';
4
+ import type { SqliteMigrateOptions } from './migrations.js';
3
5
  /** Options for the SQLite store. */
4
6
  export interface SqliteStoreOptions {
5
7
  /** Table name. Defaults to `nexus_store`. */
@@ -11,6 +13,8 @@ export interface SqliteStoreOptions {
11
13
  }
12
14
  /** The schema, as statements. */
13
15
  export declare function sqliteStoreMigration(options?: Pick<SqliteStoreOptions, 'table'>): string[];
16
+ /** The versioned schema, which `migrate()` applies. Version 1 is the 2.0 schema. */
17
+ export declare function sqliteStoreMigrations(options?: Pick<SqliteStoreOptions, 'table'>): SchemaMigration[];
14
18
  /**
15
19
  * Long-term memory in SQLite, for one machine that needs memory to survive a restart.
16
20
  *
@@ -25,8 +29,11 @@ export declare class SqliteStore implements Store {
25
29
  private readonly table;
26
30
  private readonly now;
27
31
  constructor(database: SqliteLikeClient | SqliteDatabaseLike, options?: SqliteStoreOptions);
28
- /** Creates the table and indexes if they do not exist. Never runs implicitly. */
29
- migrate(): Promise<void>;
32
+ /**
33
+ * Applies this store's pending migrations, recorded in `nexus_schema_migrations`, each inside
34
+ * `BEGIN IMMEDIATE`. Never runs implicitly. Safe on a file an older release created.
35
+ */
36
+ migrate(options?: SqliteMigrateOptions): Promise<MigrationResult>;
30
37
  /** Stores an item, keeping its original `createdAt` when it replaces one. */
31
38
  put<V>(namespace: StoreNamespace, key: string, value: V, options?: StorePutOptions): Promise<void>;
32
39
  /** Reads an item, or `undefined` when it does not exist or has expired. */
@@ -21,6 +21,16 @@ export function sqliteStoreMigration(options = {}) {
21
21
  `CREATE INDEX IF NOT EXISTS "${name}_expiry" ON ${table} (expires_at) WHERE expires_at IS NOT NULL`,
22
22
  ];
23
23
  }
24
+ export function sqliteStoreMigrations(options = {}) {
25
+ return [
26
+ {
27
+ component: `store:${options.table ?? DEFAULT_TABLE}`,
28
+ version: 1,
29
+ name: 'create the store table',
30
+ statements: sqliteStoreMigration(options),
31
+ },
32
+ ];
33
+ }
24
34
  export class SqliteStore {
25
35
  options;
26
36
  client;
@@ -32,9 +42,9 @@ export class SqliteStore {
32
42
  this.table = quoteSqliteTable(options.table ?? DEFAULT_TABLE);
33
43
  this.now = options.now ?? (() => new Date());
34
44
  }
35
- async migrate() {
36
- for (const statement of sqliteStoreMigration(this.options))
37
- await this.client.exec(statement);
45
+ async migrate(options = {}) {
46
+ const { applySqliteMigrations } = await import('./migrations.js');
47
+ return applySqliteMigrations(this.client, sqliteStoreMigrations(this.options), options);
38
48
  }
39
49
  async put(namespace, key, value, options = {}) {
40
50
  if (!key.trim())
@@ -1,5 +1,7 @@
1
1
  import { type EmbeddingProvider, type VectorDocument, type VectorSearchOptions, type VectorSearchResult, type VectorStore } from '../hallucination/retrieval.js';
2
+ import type { MigrationResult, SchemaMigration } from '../utils/schema-migrations.js';
2
3
  import { type SqliteDatabaseLike, type SqliteLikeClient } from './client.js';
4
+ import type { SqliteMigrateOptions } from './migrations.js';
3
5
  /** Options for the SQLite vector store. */
4
6
  export interface SqliteVectorStoreOptions {
5
7
  /** Width of every vector. Must match the embedding function's output. */
@@ -20,6 +22,10 @@ export interface SqliteVectorStoreOptions {
20
22
  export declare function sqliteVectorStoreMigration(options?: {
21
23
  table?: string;
22
24
  }): string;
25
+ /** The versioned schema, which `migrate()` applies. Version 1 is the 2.0 table. */
26
+ export declare function sqliteVectorStoreMigrations(options?: {
27
+ table?: string;
28
+ }): SchemaMigration[];
23
29
  /**
24
30
  * Retrieval chunks in SQLite: one file, no server, the same contract as every other store.
25
31
  *
@@ -34,8 +40,11 @@ export declare class SqliteVectorStore implements VectorStore {
34
40
  private readonly table;
35
41
  private readonly embed;
36
42
  constructor(database: SqliteLikeClient | SqliteDatabaseLike, options: SqliteVectorStoreOptions);
37
- /** Creates the table unless it exists. Never runs implicitly. */
38
- migrate(): Promise<void>;
43
+ /**
44
+ * Applies this store's pending migrations, recorded in `nexus_schema_migrations`, each inside
45
+ * `BEGIN IMMEDIATE`. Never runs implicitly. Safe on a file an older release created.
46
+ */
47
+ migrate(options?: SqliteMigrateOptions): Promise<MigrationResult>;
39
48
  /** Adds chunks, or replaces those whose id exists, embedding those without a vector in one batch. */
40
49
  add(documents: VectorDocument[]): Promise<void>;
41
50
  /** The chunks most similar to a query, best first. */
@@ -5,6 +5,16 @@ export function sqliteVectorStoreMigration(options = {}) {
5
5
  const table = quoteSqliteTable(options.table ?? 'nexus_vectors');
6
6
  return `CREATE TABLE IF NOT EXISTS ${table} (id TEXT PRIMARY KEY, content TEXT NOT NULL, source TEXT, metadata TEXT, embedding BLOB NOT NULL)`;
7
7
  }
8
+ export function sqliteVectorStoreMigrations(options = {}) {
9
+ return [
10
+ {
11
+ component: `vectors:${options.table ?? 'nexus_vectors'}`,
12
+ version: 1,
13
+ name: 'create the vectors table',
14
+ statements: [sqliteVectorStoreMigration(options)],
15
+ },
16
+ ];
17
+ }
8
18
  export class SqliteVectorStore {
9
19
  options;
10
20
  client;
@@ -17,8 +27,9 @@ export class SqliteVectorStore {
17
27
  this.table = quoteSqliteTable(options.table ?? 'nexus_vectors');
18
28
  this.embed = options.embed ?? ((texts) => createHashEmbeddings(texts, options.dimensions));
19
29
  }
20
- async migrate() {
21
- await this.client.exec(sqliteVectorStoreMigration(this.options));
30
+ async migrate(options = {}) {
31
+ const { applySqliteMigrations } = await import('./migrations.js');
32
+ return applySqliteMigrations(this.client, sqliteVectorStoreMigrations(this.options), options);
22
33
  }
23
34
  async add(documents) {
24
35
  if (documents.length === 0)
@@ -0,0 +1,12 @@
1
+ import type { Store } from '../types/store.js';
2
+ /** The first namespace part under which every tenant's items live, so they never meet an application's own. */
3
+ export declare const TENANT_NAMESPACE = "nexus:tenant";
4
+ /**
5
+ * One tenant's view of a long-term store: every namespace it reads or writes is placed under
6
+ * `['nexus:tenant', tenantId]`, and every item it returns has that prefix taken off again. A search
7
+ * or a namespace listing cannot reach past it, so another tenant's items are not merely filtered out
8
+ * but never read. Works over any store — memory, Redis, Postgres, SQLite — with no schema change.
9
+ *
10
+ * A graph run given a `tenantId` sees its store through this view as `context.store`.
11
+ */
12
+ export declare function tenantStore(store: Store, tenantId: string): Store;
@@ -0,0 +1,29 @@
1
+ import { assertTenantId } from '../utils/tenant.js';
2
+ export const TENANT_NAMESPACE = 'nexus:tenant';
3
+ export function tenantStore(store, tenantId) {
4
+ const root = [TENANT_NAMESPACE, assertTenantId(tenantId)];
5
+ const scope = (namespace) => [...root, ...namespace];
6
+ const unscope = (item) => ({ ...item, namespace: item.namespace.slice(root.length) });
7
+ return {
8
+ put(namespace, key, value, options) {
9
+ return store.put(scope(namespace), key, value, options);
10
+ },
11
+ async get(namespace, key) {
12
+ const item = await store.get(scope(namespace), key);
13
+ return item ? unscope(item) : undefined;
14
+ },
15
+ delete(namespace, key) {
16
+ return store.delete(scope(namespace), key);
17
+ },
18
+ async search(namespacePrefix, options) {
19
+ return (await store.search(scope(namespacePrefix), options)).map(unscope);
20
+ },
21
+ async listNamespaces(options = {}) {
22
+ const namespaces = await store.listNamespaces({
23
+ prefix: scope(options.prefix ?? []),
24
+ ...(options.limit === undefined ? {} : { limit: options.limit }),
25
+ });
26
+ return namespaces.map((namespace) => namespace.slice(root.length)).filter((namespace) => namespace.length > 0);
27
+ },
28
+ };
29
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * One tenant model for every store.
3
+ *
4
+ * Each function returns one tenant's view of a shared store, implementing the same contract as the
5
+ * store itself, so a tenant's code takes a `TraceStore`, a `PromptStore`, or a `VectorStore` as it
6
+ * always did and cannot reach past its tenant. Names and keys are placed under the tenant's prefix
7
+ * (`tenant/<id>/`) and records carry `tenantId`, so the views work over the memory, Redis, Postgres,
8
+ * SQLite, and file stores alike, with no schema change. A tenant id is letters, digits, `_`, and `-`,
9
+ * so no tenant's prefix is the start of another's.
10
+ */
11
+ import type { CacheAdapter } from '../cache/adapters.js';
12
+ import type { VectorStore } from '../hallucination/retrieval.js';
13
+ import type { AssetPutOptions, AssetSignOptions, AssetStore } from '../images/asset-support.js';
14
+ import type { RollupStore } from '../tracing/rollups.js';
15
+ import type { DatasetStore, ExperimentStore } from '../types/evaluate.js';
16
+ import type { OperationStore } from '../types/operations.js';
17
+ import type { PromptStore } from '../types/prompts.js';
18
+ import type { Store } from '../types/store.js';
19
+ import type { TraceStore } from '../types/tracing.js';
20
+ export { TENANT_NAMESPACE, tenantStore } from '../store/tenant.js';
21
+ export { assertTenantId, TenantIdError } from '../utils/tenant.js';
22
+ /**
23
+ * One tenant's traces. Every run saved through it is stamped `metadata.tenantId`, the field rollups,
24
+ * the studio, and the Postgres tenant index read; `get()`, `query()`, and `tree()` return only runs so
25
+ * stamped, and feedback reaches only the tenant's own runs. `prune()` is left out, since it would
26
+ * remove every tenant's old runs.
27
+ */
28
+ export declare function tenantTraceStore(store: TraceStore, tenantId: string): TraceStore;
29
+ /**
30
+ * One tenant's operations. Records it creates carry `tenantId`, and their idempotency keys are
31
+ * placed under the tenant's prefix, so two tenants using the same key never collide. Reads, updates,
32
+ * deletes, recovery, and queue listings see only the tenant's records; `listQueued()` and `stats()`
33
+ * pass the tenant to the store as a filter, which Postgres and SQLite answer from an index.
34
+ */
35
+ export declare function tenantOperationStore<TResult = unknown>(store: OperationStore<TResult>, tenantId: string): OperationStore<TResult>;
36
+ /**
37
+ * One tenant's prompts. Every prompt name is placed under the tenant's prefix on the way in and
38
+ * taken off on the way out, so the tenant sees its own names and `listNames()` lists only them. A
39
+ * `ContextHub` given this store keeps the tenant's context bundles apart the same way.
40
+ */
41
+ export declare function tenantPromptStore(store: PromptStore, tenantId: string): PromptStore;
42
+ /** One tenant's datasets: names placed under the tenant's prefix, and `list()` showing only its own. */
43
+ export declare function tenantDatasetStore(store: DatasetStore, tenantId: string): DatasetStore;
44
+ /**
45
+ * One tenant's experiments. Ids, names, and dataset names are placed under the tenant's prefix, so
46
+ * `get()` of another tenant's experiment id finds nothing and `list()` returns only the tenant's.
47
+ */
48
+ export declare function tenantExperimentStore(store: ExperimentStore, tenantId: string): ExperimentStore;
49
+ /**
50
+ * One tenant's retrieval chunks. Each chunk's id is placed under the tenant's prefix and its metadata
51
+ * stamped `tenantId`, and every search adds `tenantId` to its filter, which every vector store applies
52
+ * natively. A search never ranks another tenant's chunks, and `delete()` reaches only the tenant's.
53
+ */
54
+ export declare function tenantVectorStore(store: VectorStore, tenantId: string): VectorStore;
55
+ /**
56
+ * One tenant's cache: every key placed under the tenant's prefix, so a tenant can never read what
57
+ * another cached. `clear()` is left out, since it would clear every tenant's entries.
58
+ */
59
+ export declare function tenantCache<T = unknown>(cache: CacheAdapter<T>, tenantId: string): CacheAdapter<T>;
60
+ /** An asset store with the tenant bound, so no call can name another. Returned by `tenantAssetStore()`. */
61
+ export interface TenantAssetStore {
62
+ /** The tenant every call acts for. */
63
+ readonly tenantId: string;
64
+ /** Stores an asset for the tenant. */
65
+ put(input: Parameters<AssetStore['put']>[0], options: Omit<AssetPutOptions, 'tenantId'>): ReturnType<AssetStore['put']>;
66
+ /** Reads one of the tenant's assets with its bytes. */
67
+ get(assetId: string): ReturnType<AssetStore['get']>;
68
+ /** Describes one of the tenant's assets. */
69
+ stat(assetId: string): ReturnType<AssetStore['stat']>;
70
+ /** Deletes one of the tenant's assets. */
71
+ delete(assetId: string): ReturnType<AssetStore['delete']>;
72
+ /** Signs a URL for one of the tenant's assets. */
73
+ sign(assetId: string, options?: AssetSignOptions): ReturnType<AssetStore['sign']>;
74
+ }
75
+ /**
76
+ * One tenant's assets. Asset stores already treat another tenant's asset as missing; this binds the
77
+ * tenant once, so code handed the view cannot pass a different one.
78
+ */
79
+ export declare function tenantAssetStore(store: AssetStore, tenantId: string): TenantAssetStore;
80
+ /** One tenant's rollups: rows it adds are counted for the tenant, and queries read only the tenant's rows. */
81
+ export declare function tenantRollupStore(store: RollupStore, tenantId: string): RollupStore;
82
+ /** The stores `tenantScope()` can scope. Each is optional. */
83
+ export interface TenantStores {
84
+ /** Long-term memory. */
85
+ store?: Store;
86
+ /** Traces. */
87
+ traces?: TraceStore;
88
+ /** Operation records. */
89
+ operations?: OperationStore<unknown>;
90
+ /** Prompt versions and labels, and the context hub's bundles. */
91
+ prompts?: PromptStore;
92
+ /** Datasets. */
93
+ datasets?: DatasetStore;
94
+ /** Experiments. */
95
+ experiments?: ExperimentStore;
96
+ /** Retrieval chunks. */
97
+ vectors?: VectorStore;
98
+ /** A cache. */
99
+ cache?: CacheAdapter<unknown>;
100
+ /** Dashboard rollups. */
101
+ rollups?: RollupStore;
102
+ }
103
+ /**
104
+ * Scopes every given store to one tenant at once, keeping each one's type, so a request handler
105
+ * builds its tenant's view in one line.
106
+ *
107
+ * ```ts
108
+ * const scoped = tenantScope(principal.tenantId, { store, traces, prompts, vectors });
109
+ * ```
110
+ */
111
+ export declare function tenantScope<T extends TenantStores>(tenantId: string, stores: T): T & {
112
+ readonly tenantId: string;
113
+ };
@@ -0,0 +1,233 @@
1
+ import { tenantPrefix } from '../utils/tenant.js';
2
+ import { tenantStore } from '../store/tenant.js';
3
+ export { TENANT_NAMESPACE, tenantStore } from '../store/tenant.js';
4
+ export { assertTenantId, TenantIdError } from '../utils/tenant.js';
5
+ function names(tenantId) {
6
+ const prefix = tenantPrefix(tenantId);
7
+ return {
8
+ prefix,
9
+ in: (name) => `${prefix}${name}`,
10
+ owns: (name) => name.startsWith(prefix),
11
+ out: (name) => name.slice(prefix.length),
12
+ };
13
+ }
14
+ function stamped(metadata, tenantId) {
15
+ return metadata?.tenantId === tenantId;
16
+ }
17
+ export function tenantTraceStore(store, tenantId) {
18
+ tenantPrefix(tenantId);
19
+ const own = (run) => (run && stamped(run.metadata, tenantId) ? run : undefined);
20
+ const prune = (tree) => ({
21
+ ...tree,
22
+ children: tree.children.filter((child) => stamped(child.metadata, tenantId)).map(prune),
23
+ });
24
+ return {
25
+ save: (run) => store.save({ ...run, metadata: { ...run.metadata, tenantId } }),
26
+ get: async (runId) => own(await store.get(runId)),
27
+ query: (query = {}) => store.query({ ...query, metadata: { ...query.metadata, tenantId } }),
28
+ async tree(traceId) {
29
+ const tree = await store.tree(traceId);
30
+ return tree && stamped(tree.metadata, tenantId) ? prune(tree) : undefined;
31
+ },
32
+ async addFeedback(runId, feedback) {
33
+ const run = own(await store.get(runId));
34
+ if (!run)
35
+ return;
36
+ if (store.addFeedback)
37
+ await store.addFeedback(runId, feedback);
38
+ else
39
+ await store.save({ ...run, feedback: [...(run.feedback ?? []), feedback] });
40
+ },
41
+ };
42
+ }
43
+ export function tenantOperationStore(store, tenantId) {
44
+ const keys = names(tenantId);
45
+ const inward = (record) => ({
46
+ ...record,
47
+ tenantId,
48
+ ...(record.idempotencyKey ? { idempotencyKey: keys.in(record.idempotencyKey) } : {}),
49
+ });
50
+ const outward = (record) => {
51
+ if (!record || record.tenantId !== tenantId)
52
+ return undefined;
53
+ return record.idempotencyKey && keys.owns(record.idempotencyKey)
54
+ ? { ...record, idempotencyKey: keys.out(record.idempotencyKey) }
55
+ : record;
56
+ };
57
+ const own = (records) => records.map(outward).filter((record) => record !== undefined);
58
+ const filtered = (filter) => ({ ...filter, tenantId });
59
+ const view = {
60
+ create: (record) => store.create(inward(record)),
61
+ read: async (id) => outward(await store.read(id)),
62
+ async update(record, expectedSequence) {
63
+ if (record.tenantId !== undefined && record.tenantId !== tenantId)
64
+ return false;
65
+ if (!outward(await store.read(record.id)))
66
+ return false;
67
+ return store.update(inward(record), expectedSequence);
68
+ },
69
+ };
70
+ if (store.delete) {
71
+ const remove = store.delete.bind(store);
72
+ view.delete = async (id) => (outward(await store.read(id)) ? remove(id) : false);
73
+ }
74
+ if (store.claimExpired) {
75
+ const claim = store.claimExpired.bind(store);
76
+ view.claimExpired = async (now, limit) => own(await claim(now, limit));
77
+ }
78
+ if (store.findByIdempotencyKey) {
79
+ const find = store.findByIdempotencyKey.bind(store);
80
+ view.findByIdempotencyKey = async (key) => outward(await find(keys.in(key)));
81
+ }
82
+ if (store.list) {
83
+ const list = store.list.bind(store);
84
+ view.list = async () => own(await list());
85
+ }
86
+ if (store.listQueued) {
87
+ const listQueued = store.listQueued.bind(store);
88
+ view.listQueued = async (limit, filter) => own(await listQueued(limit, filtered(filter)));
89
+ }
90
+ if (store.stats) {
91
+ const stats = store.stats.bind(store);
92
+ view.stats = (now, filter) => stats(now, filtered(filter));
93
+ }
94
+ return view;
95
+ }
96
+ export function tenantPromptStore(store, tenantId) {
97
+ const scoped = names(tenantId);
98
+ const named = (value) => ({ ...value, name: scoped.in(value.name) });
99
+ const unnamed = (value) => value && scoped.owns(value.name) ? { ...value, name: scoped.out(value.name) } : undefined;
100
+ const all = (values) => values.map(unnamed).filter((value) => value !== undefined);
101
+ return {
102
+ saveVersion: (version) => store.saveVersion(named(version)),
103
+ getVersion: async (name, version) => unnamed(await store.getVersion(scoped.in(name), version)),
104
+ listVersions: async (name, options) => all(await store.listVersions(scoped.in(name), options)),
105
+ getLabel: async (name, label) => unnamed(await store.getLabel(scoped.in(name), label)),
106
+ listLabels: async (name) => all(await store.listLabels(scoped.in(name))),
107
+ setLabel: (label, expected) => store.setLabel(named(label), expected),
108
+ deleteLabel: (name, label) => store.deleteLabel(scoped.in(name), label),
109
+ appendHistory: (entry) => store.appendHistory(named(entry)),
110
+ listHistory: async (name, options) => all(await store.listHistory(scoped.in(name), options)),
111
+ listNames: async () => (await store.listNames()).filter(scoped.owns).map(scoped.out).sort(),
112
+ };
113
+ }
114
+ export function tenantDatasetStore(store, tenantId) {
115
+ const scoped = names(tenantId);
116
+ return {
117
+ save: (dataset) => store.save({ ...dataset, name: scoped.in(dataset.name) }),
118
+ async get(name, version) {
119
+ const dataset = await store.get(scoped.in(name), version);
120
+ return dataset ? { ...dataset, name: name } : undefined;
121
+ },
122
+ async list() {
123
+ return (await store.list())
124
+ .filter((entry) => scoped.owns(entry.name))
125
+ .map((entry) => ({ ...entry, name: scoped.out(entry.name) }));
126
+ },
127
+ };
128
+ }
129
+ export function tenantExperimentStore(store, tenantId) {
130
+ const scoped = names(tenantId);
131
+ const inward = (experiment) => ({
132
+ ...experiment,
133
+ id: scoped.in(experiment.id),
134
+ name: scoped.in(experiment.name),
135
+ dataset: { ...experiment.dataset, name: scoped.in(experiment.dataset.name) },
136
+ });
137
+ const outward = (experiment) => experiment && scoped.owns(experiment.id)
138
+ ? {
139
+ ...experiment,
140
+ id: scoped.out(experiment.id),
141
+ name: scoped.out(experiment.name),
142
+ dataset: { ...experiment.dataset, name: scoped.out(experiment.dataset.name) },
143
+ }
144
+ : undefined;
145
+ return {
146
+ save: (experiment) => store.save(inward(experiment)),
147
+ get: async (id) => outward(await store.get(scoped.in(id))),
148
+ async list(filter = {}) {
149
+ const experiments = await store.list({
150
+ ...filter,
151
+ ...(filter.name === undefined ? {} : { name: scoped.in(filter.name) }),
152
+ ...(filter.dataset === undefined ? {} : { dataset: scoped.in(filter.dataset) }),
153
+ });
154
+ return experiments.map(outward).filter((experiment) => experiment !== undefined);
155
+ },
156
+ };
157
+ }
158
+ export function tenantVectorStore(store, tenantId) {
159
+ const scoped = names(tenantId);
160
+ const outward = (results) => results
161
+ .filter((result) => scoped.owns(result.id) && stamped(result.metadata, tenantId))
162
+ .map((result) => {
163
+ const { tenantId: _owner, ...metadata } = result.metadata ?? {};
164
+ return { ...result, id: scoped.out(result.id), metadata };
165
+ });
166
+ const filtered = (options = {}) => ({
167
+ ...options,
168
+ filter: { ...options.filter, tenantId },
169
+ });
170
+ return {
171
+ add: (documents) => store.add(documents.map((document) => ({
172
+ ...document,
173
+ id: scoped.in(document.id),
174
+ metadata: { ...document.metadata, tenantId },
175
+ }))),
176
+ search: async (query, options) => outward(await store.search(query, filtered(options))),
177
+ searchVector: async (vector, options) => outward(await store.searchVector(vector, filtered(options))),
178
+ delete: (ids) => store.delete(ids.map(scoped.in)),
179
+ };
180
+ }
181
+ export function tenantCache(cache, tenantId) {
182
+ const scoped = names(tenantId);
183
+ const view = {
184
+ get: (key) => cache.get(scoped.in(key)),
185
+ set: (key, value, ttlSeconds) => cache.set(scoped.in(key), value, ttlSeconds),
186
+ };
187
+ if (cache.delete) {
188
+ const remove = cache.delete.bind(cache);
189
+ view.delete = (key) => remove(scoped.in(key));
190
+ }
191
+ return view;
192
+ }
193
+ export function tenantAssetStore(store, tenantId) {
194
+ tenantPrefix(tenantId);
195
+ return {
196
+ tenantId,
197
+ put: (input, options) => store.put(input, { ...options, tenantId }),
198
+ get: (assetId) => store.get(assetId, tenantId),
199
+ stat: (assetId) => store.stat(assetId, tenantId),
200
+ delete: (assetId) => store.delete(assetId, tenantId),
201
+ sign: (assetId, options) => store.sign(assetId, tenantId, options),
202
+ };
203
+ }
204
+ export function tenantRollupStore(store, tenantId) {
205
+ tenantPrefix(tenantId);
206
+ return {
207
+ add: (key, totals) => store.add({ ...key, tenant: tenantId }, totals),
208
+ query: (query = {}) => store.query({ ...query, tenant: tenantId }),
209
+ };
210
+ }
211
+ export function tenantScope(tenantId, stores) {
212
+ tenantPrefix(tenantId);
213
+ const scoped = {};
214
+ if (stores.store)
215
+ scoped.store = tenantStore(stores.store, tenantId);
216
+ if (stores.traces)
217
+ scoped.traces = tenantTraceStore(stores.traces, tenantId);
218
+ if (stores.operations)
219
+ scoped.operations = tenantOperationStore(stores.operations, tenantId);
220
+ if (stores.prompts)
221
+ scoped.prompts = tenantPromptStore(stores.prompts, tenantId);
222
+ if (stores.datasets)
223
+ scoped.datasets = tenantDatasetStore(stores.datasets, tenantId);
224
+ if (stores.experiments)
225
+ scoped.experiments = tenantExperimentStore(stores.experiments, tenantId);
226
+ if (stores.vectors)
227
+ scoped.vectors = tenantVectorStore(stores.vectors, tenantId);
228
+ if (stores.cache)
229
+ scoped.cache = tenantCache(stores.cache, tenantId);
230
+ if (stores.rollups)
231
+ scoped.rollups = tenantRollupStore(stores.rollups, tenantId);
232
+ return { ...stores, ...scoped, tenantId };
233
+ }