@mastra/libsql 1.22.4 → 1.22.5-alpha.1
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/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-memory-message-history.md +1 -1
- package/dist/docs/references/docs-memory-multi-user-threads.md +1 -1
- package/dist/docs/references/docs-memory-overview.md +4 -4
- package/dist/index.cjs +103 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +103 -13
- package/dist/index.js.map +1 -1
- package/dist/shared/single-connection-client.d.ts +29 -0
- package/dist/shared/single-connection-client.d.ts.map +1 -0
- package/dist/storage/db/write-lock.d.ts.map +1 -1
- package/dist/storage/domains/experiments/index.d.ts.map +1 -1
- package/dist/storage/factory-storage.d.ts.map +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/vector/index.d.ts.map +1 -1
- package/package.json +6 -5
package/dist/index.js
CHANGED
|
@@ -11,6 +11,84 @@ import { randomUUID } from "crypto";
|
|
|
11
11
|
import { MessageList } from "@mastra/core/agent";
|
|
12
12
|
import { saveScorePayloadSchema } from "@mastra/core/evals";
|
|
13
13
|
import { skillSnapshotFieldValuesEqual } from "@mastra/core/storage/domains/skills";
|
|
14
|
+
//#region src/shared/single-connection-client.ts
|
|
15
|
+
/**
|
|
16
|
+
* Whether `@libsql/client` backs this database with exactly one connection.
|
|
17
|
+
*
|
|
18
|
+
* An in-memory database exists only on the connection that opened it, and each
|
|
19
|
+
* embedded-replica connection carries its own sync state, so `@libsql/client`
|
|
20
|
+
* (>= 0.18.0) gives both a pool of one. Any `execute`/`batch` issued while an
|
|
21
|
+
* interactive `transaction()` holds that connection is rejected immediately
|
|
22
|
+
* with `TRANSACTION_ACTIVE` instead of waiting for the transaction to settle.
|
|
23
|
+
*/
|
|
24
|
+
function isSingleConnectionDatabase({ url, syncUrl }) {
|
|
25
|
+
return url.includes(":memory:") || Boolean(syncUrl);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Wraps a single-connection client so client calls queue behind open
|
|
29
|
+
* transactions rather than failing with `TRANSACTION_ACTIVE`.
|
|
30
|
+
*
|
|
31
|
+
* `transaction()` takes the gate and releases it when the transaction commits,
|
|
32
|
+
* rolls back, or closes. `execute`, `batch`, `executeMultiple`, and `migrate`
|
|
33
|
+
* wait for the gate to be free before running but do not hold it — the driver
|
|
34
|
+
* executes them synchronously on the connection, so they cannot interleave
|
|
35
|
+
* with each other. Every other member passes through untouched.
|
|
36
|
+
*
|
|
37
|
+
* Callers must not issue client calls from inside their own open transaction
|
|
38
|
+
* (use `tx.execute`); such a call would wait for the transaction it is part of.
|
|
39
|
+
*/
|
|
40
|
+
function gateSingleConnectionClient(client) {
|
|
41
|
+
let gate = Promise.resolve();
|
|
42
|
+
const waitForGate = (run) => gate.then(run, run);
|
|
43
|
+
const transaction = async (mode) => {
|
|
44
|
+
let release;
|
|
45
|
+
const held = new Promise((resolve) => {
|
|
46
|
+
release = resolve;
|
|
47
|
+
});
|
|
48
|
+
const previous = gate;
|
|
49
|
+
gate = previous.then(() => held, () => held);
|
|
50
|
+
await previous.then(() => void 0, () => void 0);
|
|
51
|
+
let tx;
|
|
52
|
+
try {
|
|
53
|
+
tx = await client.transaction(mode);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
release();
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
return new Proxy(tx, { get(target, prop) {
|
|
59
|
+
if (prop === "commit" || prop === "rollback") return async () => {
|
|
60
|
+
try {
|
|
61
|
+
await target[prop].call(target);
|
|
62
|
+
} finally {
|
|
63
|
+
release();
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
if (prop === "close") return () => {
|
|
67
|
+
try {
|
|
68
|
+
target.close();
|
|
69
|
+
} finally {
|
|
70
|
+
release();
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const value = Reflect.get(target, prop);
|
|
74
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
75
|
+
} });
|
|
76
|
+
};
|
|
77
|
+
return new Proxy(client, { get(target, prop) {
|
|
78
|
+
switch (prop) {
|
|
79
|
+
case "transaction": return transaction;
|
|
80
|
+
case "execute":
|
|
81
|
+
case "batch":
|
|
82
|
+
case "executeMultiple":
|
|
83
|
+
case "migrate": return (...args) => waitForGate(() => target[prop].apply(target, args));
|
|
84
|
+
default: {
|
|
85
|
+
const value = Reflect.get(target, prop);
|
|
86
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} });
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
14
92
|
//#region src/vector/filter.ts
|
|
15
93
|
/**
|
|
16
94
|
* Translates MongoDB-style filters to LibSQL compatible filters.
|
|
@@ -426,13 +504,17 @@ var LibSQLVector = class extends MastraVector {
|
|
|
426
504
|
this.isMemoryDb = url.includes(":memory:");
|
|
427
505
|
const isLocalDb = (url.startsWith("file:") || this.isMemoryDb) && !syncUrl;
|
|
428
506
|
const cwd = process.cwd();
|
|
429
|
-
|
|
507
|
+
const client = createClient({
|
|
430
508
|
url,
|
|
431
509
|
syncUrl,
|
|
432
510
|
authToken,
|
|
433
511
|
syncInterval,
|
|
434
512
|
...isLocalDb ? { timeout: 5e3 } : {}
|
|
435
513
|
});
|
|
514
|
+
this.turso = isSingleConnectionDatabase({
|
|
515
|
+
url,
|
|
516
|
+
syncUrl
|
|
517
|
+
}) ? gateSingleConnectionClient(client) : client;
|
|
436
518
|
this.maxRetries = maxRetries;
|
|
437
519
|
this.initialBackoffMs = initialBackoffMs;
|
|
438
520
|
if (!Number.isInteger(vectorTopKOverFetchMultiplier) || vectorTopKOverFetchMultiplier < 1) throw new Error("vectorTopKOverFetchMultiplier must be a positive integer");
|
|
@@ -1279,24 +1361,26 @@ function transformFromSqlRow({ tableName, sqlRow }) {
|
|
|
1279
1361
|
/**
|
|
1280
1362
|
* Per-client write serialization.
|
|
1281
1363
|
*
|
|
1282
|
-
* `@libsql/client`
|
|
1283
|
-
*
|
|
1284
|
-
* `
|
|
1285
|
-
*
|
|
1286
|
-
*
|
|
1287
|
-
*
|
|
1288
|
-
* of as its own statement. Two concurrent interactive transactions collide the
|
|
1289
|
-
* same way ("cannot start a transaction within a transaction").
|
|
1364
|
+
* `@libsql/client` >= 0.18.0 pools connections for local `file:` databases, but
|
|
1365
|
+
* SQLite still admits one writer at a time: an interactive
|
|
1366
|
+
* `client.transaction('write')` holds `BEGIN` open across every
|
|
1367
|
+
* `await tx.execute(...)`, and any other write on the same database in that
|
|
1368
|
+
* window contends on the file lock and can fail with `SQLITE_BUSY` once
|
|
1369
|
+
* `busy_timeout` expires.
|
|
1290
1370
|
*
|
|
1291
1371
|
* This is dormant under the default engine but the evented engine runs many
|
|
1292
1372
|
* concurrent workflow snapshot writes per agent run, so a write issued by an
|
|
1293
|
-
* unrelated domain (e.g. creating a dataset experiment) can
|
|
1373
|
+
* unrelated domain (e.g. creating a dataset experiment) can fail spuriously.
|
|
1294
1374
|
*
|
|
1295
1375
|
* Serializing every write on a given client closes that window: writes — both
|
|
1296
1376
|
* autocommit statements and full interactive transactions — run one at a time,
|
|
1297
1377
|
* so none can interleave with an open transaction. Reads are intentionally not
|
|
1298
1378
|
* gated; WAL readers never observe a partial write and must not queue behind a
|
|
1299
1379
|
* long-running writer.
|
|
1380
|
+
*
|
|
1381
|
+
* `:memory:` databases and embedded replicas get a single pooled connection
|
|
1382
|
+
* instead; see `shared/single-connection-client.ts`, which gates *all* calls
|
|
1383
|
+
* (reads included) behind open transactions for those clients.
|
|
1300
1384
|
*/
|
|
1301
1385
|
const clientWriteChains = /* @__PURE__ */ new WeakMap();
|
|
1302
1386
|
/**
|
|
@@ -4697,6 +4781,7 @@ var DatasetsLibSQL = class extends DatasetsStorage {
|
|
|
4697
4781
|
//#endregion
|
|
4698
4782
|
//#region src/storage/domains/experiments/index.ts
|
|
4699
4783
|
const DEFAULT_PRUNE_BATCH_SIZE = 1e3;
|
|
4784
|
+
const TAGS_IS_JSON = `CASE typeof(tags) WHEN 'blob' THEN 1 WHEN 'text' THEN json_valid(tags) ELSE 0 END`;
|
|
4700
4785
|
var ExperimentsLibSQL = class extends ExperimentsStorage {
|
|
4701
4786
|
/**
|
|
4702
4787
|
* An experiment is pruned as a whole unit: when `experiments.completedAt` is
|
|
@@ -5432,6 +5517,10 @@ var ExperimentsLibSQL = class extends ExperimentsStorage {
|
|
|
5432
5517
|
conditions.push("status = ?");
|
|
5433
5518
|
queryParams.push(args.status);
|
|
5434
5519
|
}
|
|
5520
|
+
for (const tag of args.tags ?? []) {
|
|
5521
|
+
conditions.push(`CASE WHEN ${TAGS_IS_JSON} THEN EXISTS (SELECT 1 FROM json_each(tags) WHERE value = ?) ELSE 0 END`);
|
|
5522
|
+
queryParams.push(tag);
|
|
5523
|
+
}
|
|
5435
5524
|
if (args.filters) {
|
|
5436
5525
|
const { organizationId, projectId } = args.filters;
|
|
5437
5526
|
if (organizationId !== void 0) {
|
|
@@ -14221,11 +14310,12 @@ var LibSQLFactoryStorage = class extends FactoryStorage {
|
|
|
14221
14310
|
super();
|
|
14222
14311
|
this.#config = config;
|
|
14223
14312
|
const isLocalDb = config.url.startsWith("file:") || config.url.includes(":memory:");
|
|
14224
|
-
|
|
14313
|
+
const client = createClient({
|
|
14225
14314
|
url: config.url,
|
|
14226
14315
|
...config.authToken ? { authToken: config.authToken } : {},
|
|
14227
14316
|
...isLocalDb ? { timeout: DEFAULT_CONNECTION_TIMEOUT_MS } : {}
|
|
14228
14317
|
});
|
|
14318
|
+
this.#client = isSingleConnectionDatabase(config) ? gateSingleConnectionClient(client) : client;
|
|
14229
14319
|
this.ops = new LibSQLFactoryStorageOps(this.#client, this.#schemas, (fn) => withClientWriteLock(this.#client, fn));
|
|
14230
14320
|
}
|
|
14231
14321
|
getMastraStorage() {
|
|
@@ -14383,15 +14473,15 @@ var LibSQLStore = class extends MastraCompositeStore {
|
|
|
14383
14473
|
mmapSize: config.localPragmas?.mmapSize ?? DEFAULT_LOCAL_MMAP_SIZE
|
|
14384
14474
|
};
|
|
14385
14475
|
if ("url" in config) {
|
|
14386
|
-
if (config.url.includes(":memory:")) this.shouldCacheInit = false;
|
|
14387
14476
|
this.isLocalDb = (config.url.startsWith("file:") || config.url.includes(":memory:")) && !config.syncUrl;
|
|
14388
|
-
|
|
14477
|
+
const client = createClient({
|
|
14389
14478
|
url: config.url,
|
|
14390
14479
|
...config.authToken ? { authToken: config.authToken } : {},
|
|
14391
14480
|
...config.syncUrl ? { syncUrl: config.syncUrl } : {},
|
|
14392
14481
|
...config.syncInterval !== void 0 ? { syncInterval: config.syncInterval } : {},
|
|
14393
14482
|
...this.isLocalDb ? { timeout: this.connectionTimeoutMs } : {}
|
|
14394
14483
|
});
|
|
14484
|
+
this.client = isSingleConnectionDatabase(config) ? gateSingleConnectionClient(client) : client;
|
|
14395
14485
|
this.pragmasReady = this.isLocalDb ? this.applyLocalPragmas() : Promise.resolve();
|
|
14396
14486
|
} else {
|
|
14397
14487
|
this.client = config.client;
|