@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 +76 -6
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-agent-builder-deploying.md +1 -0
- package/dist/docs/references/docs-agent-builder-overview.md +1 -1
- package/dist/docs/references/docs-memory-multi-user-threads.md +4 -4
- package/dist/docs/references/docs-memory-overview.md +4 -0
- package/dist/docs/references/docs-memory-working-memory.md +1 -1
- package/dist/docs/references/reference-core-mastra-class.md +1 -1
- package/dist/docs/references/reference-storage-retention.md +1 -1
- package/dist/index.cjs +403 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +404 -6
- package/dist/index.js.map +1 -1
- package/dist/storage/db/utils.d.ts.map +1 -1
- package/dist/storage/factory-storage.d.ts +31 -0
- package/dist/storage/factory-storage.d.ts.map +1 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/vector/sql-builder.d.ts.map +1 -1
- package/package.json +5 -5
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({
|
package/dist/docs/SKILL.md
CHANGED
|
@@ -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>`):
|
|
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
|
|
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;
|