@mastra/libsql 1.16.0 → 1.17.0-alpha.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,79 @@
1
1
  # @mastra/libsql
2
2
 
3
+ ## 1.17.0-alpha.3
4
+
5
+ ### Minor Changes
6
+
7
+ - Added configurable local journal modes while preserving WAL as the default. ([#19901](https://github.com/mastra-ai/mastra/pull/19901))
8
+
9
+ ```ts
10
+ const storage = new LibSQLStore({
11
+ id: 'local-storage',
12
+ url: 'file:./mastra.db',
13
+ journalMode: 'delete',
14
+ });
15
+ ```
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies [[`c7d30cd`](https://github.com/mastra-ai/mastra/commit/c7d30cd86009c407df91105591f03cd6e3d2854d), [`ef03fbc`](https://github.com/mastra-ai/mastra/commit/ef03fbcc556bcbc04c9b3d06fab88771ecaa043c), [`a7bbe77`](https://github.com/mastra-ai/mastra/commit/a7bbe773577f60bc4761b534ef7ec6b476332dad), [`a7bbe77`](https://github.com/mastra-ai/mastra/commit/a7bbe773577f60bc4761b534ef7ec6b476332dad), [`4e68363`](https://github.com/mastra-ai/mastra/commit/4e683634f94ebd062d26a3bb6093a8dfc7263d37), [`9251370`](https://github.com/mastra-ai/mastra/commit/9251370ad413af464aa22d7566338bec5613e8de)]:
20
+ - @mastra/core@1.52.0-alpha.11
21
+
22
+ ## 1.17.0-alpha.2
23
+
24
+ ### Patch Changes
25
+
26
+ - Replaced GitHub-specific Mastra Code session state with Factory project and linked-repository identities. This lets SDK consumers represent sessions independently of a source-control provider and select a repository explicitly when sandbox execution is required. ([#19849](https://github.com/mastra-ai/mastra/pull/19849))
27
+
28
+ Updated Mastra Code onboarding to be Factory-first: create a Factory by name, then link repositories from your connected source-control installations in a separate step. A Factory is valid with zero linked repositories, and the Board, Metrics, and Audit pages stay available for any server-backed Factory. Factory pages keep project-scoped data separate from repository-scoped intake and provide a repository selector when a Factory has multiple linked repositories. Creating a Factory from a local folder remains available as a secondary option.
29
+
30
+ **Before**
31
+
32
+ ```ts
33
+ const state = { githubProjectId: 'project-1', sandboxId, sandboxWorkdir };
34
+ ```
35
+
36
+ **After**
37
+
38
+ ```ts
39
+ const state = {
40
+ factoryProjectId: 'factory-project-1',
41
+ projectRepositoryId: 'project-repository-1',
42
+ sandboxId,
43
+ sandboxWorkdir,
44
+ };
45
+ ```
46
+
47
+ - Updated dependencies [[`41a5392`](https://github.com/mastra-ai/mastra/commit/41a5392d9f6c5e18d6b227f0fc0ddf49c50774e9), [`675fbff`](https://github.com/mastra-ai/mastra/commit/675fbff84d3274391b33e852f76083c38a5514e5), [`da009e1`](https://github.com/mastra-ai/mastra/commit/da009e1aacd89ed94b8d1b2af09c9d4fe7c4db49), [`35c2181`](https://github.com/mastra-ai/mastra/commit/35c2181e6a50e47c90ba36260db7c9723d54696f), [`b4b7ea8`](https://github.com/mastra-ai/mastra/commit/b4b7ea8733f033fc441ea47ed03f6afb17ec2248), [`675fbff`](https://github.com/mastra-ai/mastra/commit/675fbff84d3274391b33e852f76083c38a5514e5), [`c328769`](https://github.com/mastra-ai/mastra/commit/c3287698ff8ef98dba86d415faa566fa3e5f4d56), [`232fcbc`](https://github.com/mastra-ai/mastra/commit/232fcbc14fce625dd672ba043329c0b732c62be2), [`3491666`](https://github.com/mastra-ai/mastra/commit/34916663c4fdd43b48c21f4ab2d5fb6dcccc94f9)]:
48
+ - @mastra/core@1.52.0-alpha.10
49
+
50
+ ## 1.17.0-alpha.1
51
+
52
+ ### Minor Changes
53
+
54
+ - Added LibSQLFactoryStorage for persisting Mastra agent state and lifecycle-managed application domains through one LibSQL connection. ([#19681](https://github.com/mastra-ai/mastra/pull/19681))
55
+
56
+ ```ts
57
+ import { LibSQLFactoryStorage } from '@mastra/libsql';
58
+
59
+ const storage = new LibSQLFactoryStorage({ url: 'file:mastra.db' });
60
+ await storage.init();
61
+ ```
62
+
63
+ ### Patch Changes
64
+
65
+ - Updated dependencies [[`3b77e77`](https://github.com/mastra-ai/mastra/commit/3b77e7704936522e4769d29de1b5ea6901f302bd), [`6b1bf3b`](https://github.com/mastra-ai/mastra/commit/6b1bf3b9494bd51aa8f654c68c9355d6046fa2a1), [`72e437c`](https://github.com/mastra-ai/mastra/commit/72e437c515942c80b9def5b026e0bdee61b469d9)]:
66
+ - @mastra/core@1.52.0-alpha.8
67
+
68
+ ## 1.16.1-alpha.0
69
+
70
+ ### Patch Changes
71
+
72
+ - Fixed an uncaught `RangeError: Maximum call stack size exceeded` when writing deeply-nested values (such as a long chained `Error.cause`) to LibSQL storage. `safeStringify` now caps its recursion depth and substitutes a `"[Max depth exceeded]"` sentinel instead of overflowing the stack. ([#19610](https://github.com/mastra-ai/mastra/pull/19610))
73
+
74
+ - Updated dependencies [[`ec857fc`](https://github.com/mastra-ai/mastra/commit/ec857fc79c264b53b38e16478c789b7177f2ad59), [`e1f2fae`](https://github.com/mastra-ai/mastra/commit/e1f2faebaf048c3d4c2e2c01d293767c195d5794), [`63aa799`](https://github.com/mastra-ai/mastra/commit/63aa799c6b44eacc7806cda6846b7c5bbee06b37), [`73db8db`](https://github.com/mastra-ai/mastra/commit/73db8db90d69ab6153c7942749f624db0d96952d), [`73db8db`](https://github.com/mastra-ai/mastra/commit/73db8db90d69ab6153c7942749f624db0d96952d), [`76b7181`](https://github.com/mastra-ai/mastra/commit/76b71810366e6d90b9d3973149d1c7ba3659ffb9), [`0c0e8d7`](https://github.com/mastra-ai/mastra/commit/0c0e8d7becd4d1445c656b78d5d845f606c1ff9d), [`9f7c67a`](https://github.com/mastra-ai/mastra/commit/9f7c67abeeb52c41c51a9b5edee60b62afe7cd8d), [`3b65e68`](https://github.com/mastra-ai/mastra/commit/3b65e68d7f1c771c7a70eea42d83fefdd28cad88), [`e3868e2`](https://github.com/mastra-ai/mastra/commit/e3868e22babfffd0133771669ca724501c2dd58e)]:
75
+ - @mastra/core@1.52.0-alpha.5
76
+
3
77
  ## 1.16.0
4
78
 
5
79
  ### Minor Changes
@@ -917,9 +991,7 @@
917
991
  **Example**
918
992
 
919
993
  ```ts
920
- const storage = new LibSQLStore({
921
- /* config */
922
- });
994
+ const storage = new LibSQLStore({/* config */});
923
995
  const favorites = await storage.getStore('favorites');
924
996
 
925
997
  await favorites?.favorite({
@@ -947,9 +1019,7 @@
947
1019
  **Example**
948
1020
 
949
1021
  ```ts
950
- const storage = new LibSQLStore({
951
- /* config */
952
- });
1022
+ const storage = new LibSQLStore({/* config */});
953
1023
  const favorites = await storage.getStore('favorites');
954
1024
 
955
1025
  await favorites?.favorite({
@@ -3,7 +3,7 @@ name: mastra-libsql
3
3
  description: Documentation for @mastra/libsql. Use when working with @mastra/libsql APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/libsql"
6
- version: "1.16.0"
6
+ version: "1.17.0-alpha.3"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.16.0",
2
+ "version": "1.17.0-alpha.3",
3
3
  "package": "@mastra/libsql",
4
4
  "exports": {},
5
5
  "modules": {}
@@ -29,6 +29,7 @@ import { LibSQLStore } from '@mastra/libsql'
29
29
 
30
30
  new Mastra({
31
31
  storage: new LibSQLStore({
32
+ id: 'mastra-storage',
32
33
  url: process.env.DATABASE_URL!,
33
34
  authToken: process.env.DATABASE_AUTH_TOKEN,
34
35
  }),
@@ -67,7 +67,7 @@ import { createBuilderAgent } from '@mastra/editor/ee'
67
67
  import { LibSQLStore } from '@mastra/libsql'
68
68
 
69
69
  export const mastra = new Mastra({
70
- storage: new LibSQLStore({ url: 'file:./mastra.db' }),
70
+ storage: new LibSQLStore({ id: 'mastra-storage', url: 'file:./mastra.db' }),
71
71
  agents: { builderAgent: createBuilderAgent() },
72
72
  editor: new MastraEditor({
73
73
  builder: {
@@ -58,7 +58,7 @@ import { Memory } from '@mastra/memory'
58
58
  import { LibSQLStore } from '@mastra/libsql'
59
59
 
60
60
  const memory = new Memory({
61
- storage: new LibSQLStore({ url: 'file:./collab.db' }),
61
+ storage: new LibSQLStore({ id: 'collab-storage', url: 'file:./collab.db' }),
62
62
  options: {
63
63
  lastMessages: 20,
64
64
  },
@@ -128,7 +128,7 @@ import { Memory } from '@mastra/memory'
128
128
  import { LibSQLStore } from '@mastra/libsql'
129
129
 
130
130
  const memory = new Memory({
131
- storage: new LibSQLStore({ url: 'file:./collab.db' }),
131
+ storage: new LibSQLStore({ id: 'collab-storage', url: 'file:./collab.db' }),
132
132
  options: {
133
133
  lastMessages: 20,
134
134
  },
@@ -148,7 +148,7 @@ import { Memory } from '@mastra/memory'
148
148
  import { LibSQLStore } from '@mastra/libsql'
149
149
 
150
150
  const memory = new Memory({
151
- storage: new LibSQLStore({ url: 'file:./collab.db' }),
151
+ storage: new LibSQLStore({ id: 'collab-storage', url: 'file:./collab.db' }),
152
152
  options: {
153
153
  lastMessages: 20,
154
154
  observationalMemory: true,
@@ -171,7 +171,7 @@ import { Memory } from '@mastra/memory'
171
171
  import { LibSQLStore } from '@mastra/libsql'
172
172
 
173
173
  const memory = new Memory({
174
- storage: new LibSQLStore({ url: 'file:./collab.db' }),
174
+ storage: new LibSQLStore({ id: 'collab-storage', url: 'file:./collab.db' }),
175
175
  options: {
176
176
  lastMessages: 20,
177
177
  workingMemory: {
@@ -81,6 +81,8 @@ Use memory when your agent needs to maintain multi-turn conversations that refer
81
81
 
82
82
  3. Add the storage provider to your main Mastra instance to enable memory across all configured agents.
83
83
 
84
+ **For AI agents:** Important: Add `id` to `LibSQLStore`.
85
+
84
86
  ```typescript
85
87
  import { Mastra } from '@mastra/core'
86
88
  import { LibSQLStore } from '@mastra/libsql'
@@ -151,6 +153,8 @@ To list all threads for a resource, or retrieve a specific thread, [use the memo
151
153
 
152
154
  For long-running conversations, raw message history grows until it fills the context window, degrading agent performance. [Observational Memory](https://mastra.ai/docs/memory/observational-memory) solves this by running background agents that compress old messages into dense observations, keeping the context window small while preserving long-term memory.
153
155
 
156
+ **For AI agents:** Using Observational Memory requires a storage provider! You either need to set it on the Mastra instance at `src/mastra/index.ts` or pass it to the Agent constructor.
157
+
154
158
  ```typescript
155
159
  import { Agent } from '@mastra/core/agent'
156
160
  import { Memory } from '@mastra/memory'
@@ -403,7 +403,7 @@ By default, working memory reaches the model as part of the system message. You
403
403
 
404
404
  ```typescript
405
405
  const memory = new Memory({
406
- storage: new LibSQLStore({ url: 'file:./mastra.db' }),
406
+ storage: new LibSQLStore({ id: 'mastra-storage', url: 'file:./mastra.db' }),
407
407
  options: {
408
408
  workingMemory: {
409
409
  enabled: true,
@@ -53,7 +53,7 @@ Visit the [Configuration reference](https://mastra.ai/reference/configuration) f
53
53
 
54
54
  **agents** (`Record<string, Agent>`): Agent instances to register, keyed by name (Default: `{}`)
55
55
 
56
- **tools** (`Record<string, ToolApi>`): Custom tools to register. Structured as a key-value pair, with keys being the tool name and values being the tool function. (Default: `{}`)
56
+ **tools** (`Record<string, ToolApi>`): Tool instances to register. Keys are registration keys used by \`getTool()\`, and values are tool instances. Use \`getToolById()\` for intrinsic ID lookup and \`listTools()\` to read the registry. (Default: `{}`)
57
57
 
58
58
  **storage** (`MastraCompositeStore`): Storage engine instance for persisting data
59
59
 
@@ -228,7 +228,7 @@ const storage = new MongoDBStore({
228
228
  })
229
229
  ```
230
230
 
231
- > **Tip:** TTL indexes delete documents shortly after they expire (background thread runs every \~60 seconds), but the exact timing is not guaranteed. For precise, immediate cleanup, use `prune()` instead.
231
+ > **Tip:** TTL indexes delete documents shortly after they expire (background thread runs every \~60 seconds), but the exact timing isn't guaranteed. For precise, immediate cleanup, use `prune()` instead.
232
232
 
233
233
  ## Reclaiming disk
234
234
 
package/dist/index.cjs CHANGED
@@ -1198,26 +1198,28 @@ var LibSQLVector = class extends vector.MastraVector {
1198
1198
  });
1199
1199
  }
1200
1200
  };
1201
+ var MAX_DEPTH = 100;
1201
1202
  var safeStringify = (value) => {
1202
1203
  const ancestors = /* @__PURE__ */ new Set();
1203
- const sanitize = (val) => {
1204
+ const sanitize = (val, depth = 0) => {
1204
1205
  if (val === null || val === void 0) return val;
1205
1206
  if (typeof val === "function") return void 0;
1206
1207
  if (typeof val === "symbol") return void 0;
1207
1208
  if (typeof val === "bigint") return val.toString();
1208
1209
  if (typeof val !== "object") return val;
1210
+ if (depth > MAX_DEPTH) return "[Max depth exceeded]";
1209
1211
  if (ancestors.has(val)) return void 0;
1210
1212
  if (typeof val.toJSON === "function") {
1211
- return sanitize(val.toJSON());
1213
+ return sanitize(val.toJSON(), depth + 1);
1212
1214
  }
1213
1215
  ancestors.add(val);
1214
1216
  try {
1215
1217
  if (Array.isArray(val)) {
1216
- return val.map((item) => sanitize(item));
1218
+ return val.map((item) => sanitize(item, depth + 1));
1217
1219
  }
1218
1220
  const result = {};
1219
1221
  for (const key of Object.keys(val)) {
1220
- const sanitized = sanitize(val[key]);
1222
+ const sanitized = sanitize(val[key], depth + 1);
1221
1223
  if (sanitized !== void 0) {
1222
1224
  result[key] = sanitized;
1223
1225
  }
@@ -13347,6 +13349,402 @@ var WorkspacesLibSQL = class extends storage.WorkspacesStorage {
13347
13349
  };
13348
13350
  }
13349
13351
  };
13352
+ var IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
13353
+ var COLUMN_DDL = {
13354
+ text: "TEXT",
13355
+ bigint: "INTEGER",
13356
+ integer: "INTEGER",
13357
+ boolean: "INTEGER",
13358
+ json: "TEXT",
13359
+ timestamp: "TEXT",
13360
+ "uuid-pk": "TEXT"
13361
+ };
13362
+ function assertIdentifier(kind, name) {
13363
+ if (!IDENTIFIER_RE.test(name)) {
13364
+ throw new Error(`LibSQLFactoryStorage: invalid ${kind} identifier '${name}'`);
13365
+ }
13366
+ }
13367
+ function isUniqueViolation(error) {
13368
+ const message = error instanceof Error ? error.message : String(error);
13369
+ return message.includes("UNIQUE constraint failed") || message.includes("SQLITE_CONSTRAINT");
13370
+ }
13371
+ function primaryKeyOf(schema) {
13372
+ const pks = Object.entries(schema.columns).filter(([, spec]) => spec.type === "uuid-pk" || spec.primaryKey);
13373
+ if (pks.length !== 1) {
13374
+ throw new Error(
13375
+ `LibSQLFactoryStorage: collection '${schema.name}' must declare exactly one primary key (found ${pks.length})`
13376
+ );
13377
+ }
13378
+ return pks[0][0];
13379
+ }
13380
+ function assertUpsertConflict(schema, conflictKeys, row) {
13381
+ const keys = new Set(conflictKeys);
13382
+ if (keys.size !== conflictKeys.length || conflictKeys.some((key) => row[key] === void 0)) {
13383
+ throw new Error(
13384
+ `LibSQLFactoryStorage: upsert conflict keys for '${schema.name}' must be unique and present in the row`
13385
+ );
13386
+ }
13387
+ const primaryKey = primaryKeyOf(schema);
13388
+ const candidates = [{ columns: [primaryKey] }, ...schema.uniqueIndexes ?? []];
13389
+ const matches = candidates.some((candidate) => {
13390
+ if (candidate.columns.length !== keys.size || candidate.columns.some((column) => !keys.has(column))) return false;
13391
+ if ("whereNotNull" in candidate && candidate.whereNotNull && row[candidate.whereNotNull] == null) return false;
13392
+ if ("whereNull" in candidate && candidate.whereNull && row[candidate.whereNull] != null) return false;
13393
+ return true;
13394
+ });
13395
+ if (!matches) {
13396
+ throw new Error(
13397
+ `LibSQLFactoryStorage: upsert conflict keys [${conflictKeys.join(", ")}] do not match an applicable primary key or unique index on '${schema.name}'`
13398
+ );
13399
+ }
13400
+ }
13401
+ function serializeDefault(value) {
13402
+ if (typeof value === "string") return `'${value.replace(/'/g, "''")}'`;
13403
+ if (typeof value === "boolean") return value ? "1" : "0";
13404
+ return String(value);
13405
+ }
13406
+ var Mutex = class {
13407
+ #tail = Promise.resolve();
13408
+ run(fn) {
13409
+ const result = this.#tail.then(fn, fn);
13410
+ this.#tail = result.catch(() => {
13411
+ });
13412
+ return result;
13413
+ }
13414
+ };
13415
+ var LibSQLFactoryStorageOps = class {
13416
+ #client;
13417
+ #schemas;
13418
+ #writeMutex = new Mutex();
13419
+ constructor(client, schemas) {
13420
+ this.#client = client;
13421
+ this.#schemas = schemas;
13422
+ }
13423
+ #schema(collection) {
13424
+ const schema = this.#schemas.get(collection);
13425
+ if (!schema) {
13426
+ throw new Error(
13427
+ `LibSQLFactoryStorage: unknown collection '${collection}' \u2014 register it via ensureCollections() first`
13428
+ );
13429
+ }
13430
+ return schema;
13431
+ }
13432
+ #column(schema, column) {
13433
+ const spec = schema.columns[column];
13434
+ if (!spec) {
13435
+ throw new Error(`LibSQLFactoryStorage: unknown column '${column}' on collection '${schema.name}'`);
13436
+ }
13437
+ return spec;
13438
+ }
13439
+ #serialize(spec, value) {
13440
+ if (value === null || value === void 0) return null;
13441
+ switch (spec.type) {
13442
+ case "timestamp":
13443
+ return value instanceof Date ? value.toISOString() : String(value);
13444
+ case "boolean":
13445
+ return value ? 1 : 0;
13446
+ case "json":
13447
+ return JSON.stringify(value);
13448
+ case "bigint":
13449
+ case "integer":
13450
+ return Number(value);
13451
+ default:
13452
+ return String(value);
13453
+ }
13454
+ }
13455
+ #deserializeRow(schema, raw) {
13456
+ const row = {};
13457
+ for (const [name, spec] of Object.entries(schema.columns)) {
13458
+ const value = raw[name];
13459
+ if (value === null || value === void 0) {
13460
+ row[name] = null;
13461
+ continue;
13462
+ }
13463
+ switch (spec.type) {
13464
+ case "timestamp":
13465
+ row[name] = new Date(String(value));
13466
+ break;
13467
+ case "boolean":
13468
+ row[name] = Number(value) === 1;
13469
+ break;
13470
+ case "json":
13471
+ row[name] = JSON.parse(String(value));
13472
+ break;
13473
+ case "bigint":
13474
+ case "integer":
13475
+ row[name] = Number(value);
13476
+ break;
13477
+ default:
13478
+ row[name] = String(value);
13479
+ }
13480
+ }
13481
+ return row;
13482
+ }
13483
+ /** Builds `WHERE` SQL (without the keyword). `{}` yields a match-all clause. */
13484
+ #buildWhere(schema, where) {
13485
+ const clauses = [];
13486
+ const args = [];
13487
+ for (const [column, condition] of Object.entries(where)) {
13488
+ const spec = this.#column(schema, column);
13489
+ if (condition !== null && typeof condition === "object" && !(condition instanceof Date) && "in" in condition) {
13490
+ if (condition.in.length === 0) {
13491
+ clauses.push("1 = 0");
13492
+ continue;
13493
+ }
13494
+ const nonNull = condition.in.filter((value) => value !== null);
13495
+ const includesNull = nonNull.length !== condition.in.length;
13496
+ const inClause = nonNull.length > 0 ? `"${column}" IN (${nonNull.map(() => "?").join(", ")})` : void 0;
13497
+ clauses.push(
13498
+ inClause && includesNull ? `(${inClause} OR "${column}" IS NULL)` : inClause ?? `"${column}" IS NULL`
13499
+ );
13500
+ args.push(...nonNull.map((value) => this.#serialize(spec, value)));
13501
+ } else if (condition === null) {
13502
+ clauses.push(`"${column}" IS NULL`);
13503
+ } else {
13504
+ clauses.push(`"${column}" = ?`);
13505
+ args.push(this.#serialize(spec, condition));
13506
+ }
13507
+ }
13508
+ return { sql: clauses.length > 0 ? clauses.join(" AND ") : "1 = 1", args };
13509
+ }
13510
+ /** Keyset condition: rows strictly after `cursor.values` in the `orderBy` order. */
13511
+ #buildCursor(schema, orderBy, values) {
13512
+ if (values.length !== orderBy.length) {
13513
+ throw new Error(
13514
+ `LibSQLFactoryStorage: cursor has ${values.length} values but orderBy has ${orderBy.length} columns`
13515
+ );
13516
+ }
13517
+ const branches = [];
13518
+ const args = [];
13519
+ for (let i = 0; i < orderBy.length; i++) {
13520
+ const parts = [];
13521
+ for (let j = 0; j < i; j++) {
13522
+ const [column2] = orderBy[j];
13523
+ parts.push(`"${column2}" = ?`);
13524
+ args.push(this.#serialize(this.#column(schema, column2), values[j]));
13525
+ }
13526
+ const [column, dir] = orderBy[i];
13527
+ parts.push(`"${column}" ${dir === "desc" ? "<" : ">"} ?`);
13528
+ args.push(this.#serialize(this.#column(schema, column), values[i]));
13529
+ branches.push(`(${parts.join(" AND ")})`);
13530
+ }
13531
+ return { sql: `(${branches.join(" OR ")})`, args };
13532
+ }
13533
+ async #select(collection, where, opts) {
13534
+ const schema = this.#schema(collection);
13535
+ const filter = this.#buildWhere(schema, where);
13536
+ let sql = `SELECT * FROM "${schema.name}" WHERE ${filter.sql}`;
13537
+ const args = [...filter.args];
13538
+ if (opts?.cursor) {
13539
+ if (!opts.orderBy || opts.orderBy.length === 0) {
13540
+ throw new Error("LibSQLFactoryStorage: cursor pagination requires orderBy");
13541
+ }
13542
+ const cursor = this.#buildCursor(schema, opts.orderBy, opts.cursor.values);
13543
+ sql += ` AND ${cursor.sql}`;
13544
+ args.push(...cursor.args);
13545
+ }
13546
+ if (opts?.orderBy && opts.orderBy.length > 0) {
13547
+ const order = opts.orderBy.map(([column, dir]) => {
13548
+ this.#column(schema, column);
13549
+ return `"${column}" ${dir === "desc" ? "DESC" : "ASC"}`;
13550
+ }).join(", ");
13551
+ sql += ` ORDER BY ${order}`;
13552
+ }
13553
+ if (opts?.limit !== void 0) {
13554
+ sql += ` LIMIT ?`;
13555
+ args.push(opts.limit);
13556
+ }
13557
+ const result = await this.#client.execute({ sql, args });
13558
+ return result.rows.map((raw) => this.#deserializeRow(schema, raw));
13559
+ }
13560
+ async findOne(collection, where) {
13561
+ const rows = await this.#select(collection, where, { limit: 1 });
13562
+ return rows[0] ?? null;
13563
+ }
13564
+ async findMany(collection, where, opts) {
13565
+ return this.#select(collection, where, opts);
13566
+ }
13567
+ async insertOne(collection, row) {
13568
+ const schema = this.#schema(collection);
13569
+ const pk = primaryKeyOf(schema);
13570
+ const values = { ...row };
13571
+ if (schema.columns[pk].type === "uuid-pk" && values[pk] === void 0) {
13572
+ values[pk] = crypto$1.randomUUID();
13573
+ }
13574
+ const columns = Object.keys(values).filter((column) => values[column] !== void 0);
13575
+ for (const column of columns) this.#column(schema, column);
13576
+ const sql = `INSERT INTO "${schema.name}" (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`;
13577
+ const args = columns.map((column) => this.#serialize(this.#column(schema, column), values[column]));
13578
+ try {
13579
+ await this.#client.execute({ sql, args });
13580
+ } catch (error) {
13581
+ if (isUniqueViolation(error)) throw new storage.UniqueViolationError(collection, { cause: error });
13582
+ throw error;
13583
+ }
13584
+ const inserted = await this.findOne(collection, { [pk]: values[pk] });
13585
+ if (!inserted) throw new Error(`LibSQLFactoryStorage: failed to read back inserted row from '${collection}'`);
13586
+ return inserted;
13587
+ }
13588
+ async upsertOne(collection, conflictKeys, row) {
13589
+ const schema = this.#schema(collection);
13590
+ const pk = primaryKeyOf(schema);
13591
+ assertUpsertConflict(schema, conflictKeys, row);
13592
+ const keyWhere = Object.fromEntries(conflictKeys.map((key) => [key, row[key]]));
13593
+ let lastError;
13594
+ for (let attempt = 0; attempt < 2; attempt++) {
13595
+ const existing = await this.findOne(collection, keyWhere);
13596
+ if (existing) {
13597
+ const set = Object.fromEntries(
13598
+ Object.entries(row).filter(
13599
+ ([column, value]) => value !== void 0 && column !== pk && !conflictKeys.includes(column)
13600
+ )
13601
+ );
13602
+ if (Object.keys(set).length > 0) {
13603
+ await this.updateMany(collection, { [pk]: existing[pk] }, set);
13604
+ }
13605
+ const updated = await this.findOne(collection, { [pk]: existing[pk] });
13606
+ if (!updated) continue;
13607
+ return updated;
13608
+ }
13609
+ try {
13610
+ return await this.insertOne(collection, row);
13611
+ } catch (error) {
13612
+ if (!(error instanceof storage.UniqueViolationError)) throw error;
13613
+ lastError = error;
13614
+ }
13615
+ }
13616
+ throw lastError ?? new Error(`LibSQLFactoryStorage: upsert into '${collection}' did not converge`);
13617
+ }
13618
+ async updateMany(collection, where, set) {
13619
+ const schema = this.#schema(collection);
13620
+ const columns = Object.keys(set).filter((column) => set[column] !== void 0);
13621
+ if (columns.length === 0) return 0;
13622
+ const filter = this.#buildWhere(schema, where);
13623
+ const sql = `UPDATE "${schema.name}" SET ${columns.map((c) => `"${c}" = ?`).join(", ")} WHERE ${filter.sql}`;
13624
+ const args = [...columns.map((column) => this.#serialize(this.#column(schema, column), set[column])), ...filter.args];
13625
+ const result = await this.#client.execute({ sql, args });
13626
+ return result.rowsAffected;
13627
+ }
13628
+ async deleteMany(collection, where) {
13629
+ const schema = this.#schema(collection);
13630
+ const filter = this.#buildWhere(schema, where);
13631
+ const result = await this.#client.execute({
13632
+ sql: `DELETE FROM "${schema.name}" WHERE ${filter.sql}`,
13633
+ args: filter.args
13634
+ });
13635
+ return result.rowsAffected;
13636
+ }
13637
+ async updateAtomic(collection, where, fn) {
13638
+ const schema = this.#schema(collection);
13639
+ const pk = primaryKeyOf(schema);
13640
+ return this.#writeMutex.run(async () => {
13641
+ const row = await this.findOne(collection, where);
13642
+ if (!row) return null;
13643
+ const patch = await fn(row);
13644
+ if (patch === null) return row;
13645
+ const pkWhere = { [pk]: row[pk] };
13646
+ await this.updateMany(collection, pkWhere, patch);
13647
+ return this.findOne(collection, pkWhere);
13648
+ });
13649
+ }
13650
+ };
13651
+ var LibSQLFactoryStorage = class extends storage.FactoryStorage {
13652
+ ops;
13653
+ #client;
13654
+ #config;
13655
+ #schemas = /* @__PURE__ */ new Map();
13656
+ #mastraStorage;
13657
+ constructor(config) {
13658
+ super();
13659
+ this.#config = config;
13660
+ const isLocalDb = config.url.startsWith("file:") || config.url.includes(":memory:");
13661
+ this.#client = client.createClient({
13662
+ url: config.url,
13663
+ ...config.authToken ? { authToken: config.authToken } : {},
13664
+ ...isLocalDb ? { timeout: DEFAULT_CONNECTION_TIMEOUT_MS } : {}
13665
+ });
13666
+ this.ops = new LibSQLFactoryStorageOps(this.#client, this.#schemas);
13667
+ }
13668
+ getMastraStorage() {
13669
+ this.#mastraStorage ??= new LibSQLStore({
13670
+ id: this.#config.id ?? "libsql-factory",
13671
+ client: this.#client,
13672
+ ...this.#config.retention ? { retention: this.#config.retention } : {}
13673
+ });
13674
+ return this.#mastraStorage;
13675
+ }
13676
+ async initStorage() {
13677
+ await this.#client.execute("SELECT 1");
13678
+ }
13679
+ async withTransaction(fn) {
13680
+ if (this.#config.url.includes(":memory:")) return fn(this.ops);
13681
+ const transaction = await this.#client.transaction("write");
13682
+ try {
13683
+ const result = await fn(new LibSQLFactoryStorageOps(transaction, this.#schemas));
13684
+ await transaction.commit();
13685
+ return result;
13686
+ } catch (error) {
13687
+ await transaction.rollback();
13688
+ throw error;
13689
+ } finally {
13690
+ transaction.close();
13691
+ }
13692
+ }
13693
+ async ensureCollections(schemas) {
13694
+ for (const schema of schemas) {
13695
+ await this.#ensureCollection(schema);
13696
+ this.#schemas.set(schema.name, schema);
13697
+ }
13698
+ }
13699
+ async close() {
13700
+ this.#client.close();
13701
+ }
13702
+ authDatabase() {
13703
+ return { dialect: "libsql", client: this.#client };
13704
+ }
13705
+ #columnDdl(name, spec) {
13706
+ assertIdentifier("column", name);
13707
+ let ddl = `"${name}" ${COLUMN_DDL[spec.type]}`;
13708
+ if (spec.type === "uuid-pk" || spec.primaryKey) ddl += " PRIMARY KEY";
13709
+ if (!spec.nullable && spec.type !== "uuid-pk" && !spec.primaryKey) ddl += " NOT NULL";
13710
+ if (spec.default !== void 0) ddl += ` DEFAULT ${serializeDefault(spec.default)}`;
13711
+ return ddl;
13712
+ }
13713
+ async #ensureCollection(schema) {
13714
+ assertIdentifier("collection", schema.name);
13715
+ primaryKeyOf(schema);
13716
+ const columns = Object.entries(schema.columns).map(([name, spec]) => this.#columnDdl(name, spec));
13717
+ await this.#client.execute(`CREATE TABLE IF NOT EXISTS "${schema.name}" (${columns.join(", ")})`);
13718
+ const info = await this.#client.execute(`PRAGMA table_info("${schema.name}")`);
13719
+ const existing = new Set(info.rows.map((row) => String(row.name)));
13720
+ for (const [name, spec] of Object.entries(schema.columns)) {
13721
+ if (existing.has(name)) continue;
13722
+ await this.#client.execute(`ALTER TABLE "${schema.name}" ADD COLUMN ${this.#columnDdl(name, spec)}`);
13723
+ }
13724
+ for (const index of schema.uniqueIndexes ?? []) {
13725
+ assertIdentifier("index", index.name);
13726
+ index.columns.forEach((column) => assertIdentifier("column", column));
13727
+ let where = "";
13728
+ if (index.whereNotNull) {
13729
+ assertIdentifier("column", index.whereNotNull);
13730
+ where = ` WHERE "${index.whereNotNull}" IS NOT NULL`;
13731
+ } else if (index.whereNull) {
13732
+ assertIdentifier("column", index.whereNull);
13733
+ where = ` WHERE "${index.whereNull}" IS NULL`;
13734
+ }
13735
+ await this.#client.execute(
13736
+ `CREATE UNIQUE INDEX IF NOT EXISTS "${index.name}" ON "${schema.name}" (${index.columns.map((c) => `"${c}"`).join(", ")})${where}`
13737
+ );
13738
+ }
13739
+ for (const index of schema.indexes ?? []) {
13740
+ assertIdentifier("index", index.name);
13741
+ index.columns.forEach((column) => assertIdentifier("column", column));
13742
+ await this.#client.execute(
13743
+ `CREATE INDEX IF NOT EXISTS "${index.name}" ON "${schema.name}" (${index.columns.map((c) => `"${c}"`).join(", ")})`
13744
+ );
13745
+ }
13746
+ }
13747
+ };
13350
13748
 
13351
13749
  // src/storage/index.ts
13352
13750
  var DEFAULT_LOCAL_CACHE_SIZE = -16e3;
@@ -13640,6 +14038,7 @@ exports.ExperimentsLibSQL = ExperimentsLibSQL;
13640
14038
  exports.FavoritesLibSQL = FavoritesLibSQL;
13641
14039
  exports.HarnessLibSQL = HarnessLibSQL;
13642
14040
  exports.LIBSQL_PROMPT = LIBSQL_PROMPT;
14041
+ exports.LibSQLFactoryStorage = LibSQLFactoryStorage;
13643
14042
  exports.LibSQLStore = LibSQLStore;
13644
14043
  exports.LibSQLVector = LibSQLVector;
13645
14044
  exports.MCPClientsLibSQL = MCPClientsLibSQL;