@mastra/libsql 1.15.0-alpha.0 → 1.15.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,150 @@
1
1
  # @mastra/libsql
2
2
 
3
+ ## 1.15.0-alpha.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Raise `@mastra/core` peer floor to `>=1.49.0-0` on all storage adapters so the tenancy-related named exports the adapters now consume are guaranteed to exist at install time. ([#18861](https://github.com/mastra-ai/mastra/pull/18861))
8
+
9
+ ## 1.15.0-alpha.1
10
+
11
+ ### Patch Changes
12
+
13
+ - Added optional tenancy arguments to `getDataset`, `updateDataset`, and `deleteDataset`. ([#18750](https://github.com/mastra-ai/mastra/pull/18750))
14
+
15
+ You can now pass `organizationId` and `projectId` to scope dataset reads, updates, and deletes to a specific tenant. Reads and updates against a dataset in a different tenant throw `DATASET_NOT_FOUND` (surfaced as a 404 over HTTP). Deletes silently no-op on a tenancy mismatch — matching the existing "delete non-existent id is a no-op" semantics so cross-tenant existence is never leaked via error timing or status.
16
+
17
+ **Example**
18
+
19
+ ```ts
20
+ // Before
21
+ await client.getDataset('abc123');
22
+ await client.deleteDataset('abc123');
23
+ await client.updateDataset({ id: 'abc123', name: 'renamed' });
24
+
25
+ // After — scope to a tenant
26
+ await client.getDataset('abc123', { organizationId: 'org_a', projectId: 'proj_1' });
27
+ await client.deleteDataset('abc123', { organizationId: 'org_a' });
28
+ await client.updateDataset({ id: 'abc123', name: 'renamed', organizationId: 'org_a' });
29
+ ```
30
+
31
+ - Pushed remaining dataset read filters and pagination down to storage. ([#18710](https://github.com/mastra-ai/mastra/pull/18710))
32
+
33
+ `DatasetsManager.list({ filters })` now accepts `targetType`, `targetIds` (overlap/union semantics), and `name` (substring, case-insensitive) in addition to the existing tenancy and candidate filters. Filtering is pushed down to the storage layer so callers no longer have to post-filter results.
34
+
35
+ Storage adapters must also be upgraded to the versions listed below to honor the new filters. If a caller is on this version of `@mastra/core` but on an older storage adapter, the new `targetType`/`targetIds`/`name` filter keys are silently ignored by the adapter — no runtime error, but the filter has no effect and every dataset in the tenancy is returned.
36
+
37
+ `Dataset.listItems({ version, search, page, perPage })` now applies `search` and pagination at the storage layer when `version` is provided alongside any of those. Previously they were silently dropped whenever `version` was set. The return shape is unchanged: passing only `version` still returns a bare `DatasetItem[]` snapshot; passing `search`, `page`, or `perPage` (with or without `version`) returns the paginated `{ items, pagination }` shape. The bare-array branch is marked `@deprecated`; prefer passing `page` / `perPage` to always receive the paginated shape.
38
+
39
+ - Fixed a double-encoding bug where `createDataset` stored `targetIds` and `scorerIds` as JSON-encoded strings instead of arrays. This caused the new `listDatasets({ filters: { targetIds } })` overlap query to never match. ([#18710](https://github.com/mastra-ai/mastra/pull/18710))
40
+
41
+ Existing rows written before this fix are still double-encoded and will not be matched by the new `targetIds` filter. They self-heal on the next `updateDataset` call. Deployments with a long tail of pre-existing datasets should run a one-time backfill (re-encoding `targetIds` and `scorerIds` on affected rows) rather than rely on incidental writes; this can be tracked as a follow-up if needed.
42
+
43
+ - Tenancy-scope experiments `getById` and `delete*` on `ExperimentsStorage`. ([#18770](https://github.com/mastra-ai/mastra/pull/18770))
44
+
45
+ `ExperimentsStorage.getExperimentById`, `getExperimentResultById`, `deleteExperiment`, and `deleteExperimentResults` used to key on the primary id alone, so any caller who knew the id could read or delete the row regardless of tenant. All four now accept an optional `filters: { organizationId?, projectId? }` argument that is enforced on every adapter (inmemory, libsql, pg, mysql, mongodb, spanner):
46
+ - On tenancy mismatch, `get*` returns `null` at the storage layer.
47
+ - On tenancy mismatch, `delete*` is a silent no-op.
48
+ - The tenancy predicate is folded into the destructive DML itself (scoped `WHERE` on the DELETE, an atomic gate + delete inside a transaction, or a scoped subquery for the results cascade). A concurrent tenant swap of the same id between a pre-check and the DELETE cannot let a scoped delete hit another tenant's row.
49
+
50
+ Both behaviors match how a missing id already responds, so existence does not leak through error timing or messages.
51
+
52
+ The same atomic-DML pattern is also applied to `DatasetsStorage.deleteDataset` across all 5 store adapters, closing a TOCTOU window between the pre-check and the parent DELETE that was introduced when tenancy filters were originally added.
53
+
54
+ `Dataset.getExperiment` and the shared experiment-ownership gate on `Dataset` now forward the dataset's tenancy scope to storage, so experiment reads and downstream mutations (list results, update result, delete experiment) reached through a dataset handle are automatically scoped to the owning tenant.
55
+
56
+ Legacy calls that omit `filters` are unchanged, so this is fully backwards-compatible.
57
+
58
+ ```ts
59
+ // Before: any caller who knew the id could read/delete across tenants.
60
+ await store.experiments.getExperimentById({ id: experimentId });
61
+ await store.experiments.deleteExperiment({ id: experimentId });
62
+
63
+ // After: pass the caller's scope; wrong tenant gets null / silent no-op.
64
+ await store.experiments.getExperimentById({
65
+ id: experimentId,
66
+ filters: { organizationId, projectId },
67
+ });
68
+ await store.experiments.deleteExperiment({
69
+ id: experimentId,
70
+ filters: { organizationId, projectId },
71
+ });
72
+ ```
73
+
74
+ - Fixed a cross-tenant data-access issue on datasets by scoping `DatasetsManager.get` and `DatasetsManager.delete` to tenancy filters. ([#18750](https://github.com/mastra-ai/mastra/pull/18750))
75
+
76
+ Previously `get({ id })` and `delete({ id })` looked up a dataset by its primary key alone. Any caller who knew a dataset id could read or delete it regardless of which `organizationId` / `projectId` it belonged to. This is now closed at the storage layer via a scoped SQL predicate (option (a) — no fetch-then-assert).
77
+
78
+ **What changed**
79
+ - `DatasetsManager.get` and `DatasetsManager.delete` accept optional `organizationId` and `projectId`.
80
+ - The tenancy is stashed on the returned `Dataset` handle and forwarded to every downstream storage call (`getDetails`, `update`, `addItem`, item batch ops, `startExperimentAsync`).
81
+ - The abstract storage contract (`getDatasetById`, `deleteDataset`) gained an optional `filters?: DatasetTenancyFilters` arg.
82
+ - Item-mutation inputs (`AddDatasetItemInput`, `UpdateDatasetItemInput`, `BatchInsertItemsInput`, `BatchDeleteItemsInput`) and `UpdateDatasetInput` accept optional `filters` for the internal existence check.
83
+
84
+ **Behavior**
85
+ - Omitting tenancy preserves the existing behavior (no predicate added) — fully backwards compatible.
86
+ - On tenancy mismatch, `get` throws NOT_FOUND (returns null at the storage layer) and `delete` is a silent no-op — matching how a missing id already behaves, so existence does not leak through error timing or messages.
87
+
88
+ **Example**
89
+
90
+ ```ts
91
+ // Before
92
+ const ds = await mastra.datasets.get({ id });
93
+ await mastra.datasets.delete({ id });
94
+
95
+ // After — scope to a tenant
96
+ const ds = await mastra.datasets.get({ id, organizationId, projectId });
97
+ await mastra.datasets.delete({ id, organizationId, projectId });
98
+ ```
99
+
100
+ - Add optional `batchId`, `datasetId`, and `datasetItemId` fields to persisted scores so saved baseline scores can be grouped as one scoring pass and joined back to the dataset items they came from. ([#18331](https://github.com/mastra-ai/mastra/pull/18331))
101
+ - `scoreTrace()` accepts top-level `batchId`, `datasetId`, and `datasetItemId` when persisting a score for a stored trace.
102
+ - `ScoreRowData` and score save payloads now include nullable `batchId`, `datasetId`, and `datasetItemId`.
103
+ - Built-in stores with explicit score schema or attribute mappings now persist these provenance fields on saved scores.
104
+ - D1, DSQL, MSSQL, and Upstash score stores now apply additive provenance migrations or deterministic score ordering for persisted score reads.
105
+
106
+ ```ts
107
+ await scoreTrace({
108
+ storage,
109
+ scorer,
110
+ target: { traceId },
111
+ batchId: 'baseline-batch-1',
112
+ datasetId,
113
+ datasetItemId,
114
+ });
115
+ ```
116
+
117
+ - Added optional `organizationId` and `projectId` fields to scores for multi-tenant isolation. Scores can now be saved with tenancy metadata and the `listScoresBy*` methods accept a `filters` option to scope results by organization and project. ([#18331](https://github.com/mastra-ai/mastra/pull/18331))
118
+
119
+ ```ts
120
+ await storage.saveScore({ ...score, organizationId: 'org-a', projectId: 'proj-1' });
121
+
122
+ const result = await storage.listScoresByScorerId({
123
+ scorerId,
124
+ filters: { organizationId: 'org-a', projectId: 'proj-1' },
125
+ });
126
+ ```
127
+
128
+ `projectId` identifies the project scope, separate from `resourceId` which continues to mean the agent memory resource.
129
+
130
+ - Scoped `getDatasetById` and `deleteDataset` to tenancy filters when the caller passes `organizationId` / `projectId`. ([#18750](https://github.com/mastra-ai/mastra/pull/18750))
131
+
132
+ The adapters now push the tenancy predicate into the SQL/query when the new optional `filters` argument is present. Legacy calls that omit tenancy are unchanged. On mismatch, `getDatasetById` returns `null` and `deleteDataset` is a silent no-op — the cascade delete (dataset items and versions) is gated by a scoped parent pre-check, so cross-tenant data is never touched.
133
+
134
+ - Added optional `organizationId` and `projectId` query parameters to the dataset routes. ([#18750](https://github.com/mastra-ai/mastra/pull/18750))
135
+
136
+ `GET /datasets/:datasetId`, `PATCH /datasets/:datasetId`, and `DELETE /datasets/:datasetId` now accept optional tenancy query parameters. When provided, they are forwarded to `mastra.datasets.get` / `.delete` and the operation returns 404 if the dataset does not belong to the requested tenant. Requests that omit the query parameters keep their existing behavior.
137
+
138
+ **Example**
139
+
140
+ ```
141
+ GET /datasets/abc123?organizationId=org_a&projectId=proj_1
142
+ DELETE /datasets/abc123?organizationId=org_a
143
+ ```
144
+
145
+ - Updated dependencies [[`9250acd`](https://github.com/mastra-ai/mastra/commit/9250acd1357f0f1f33d0dcca16f9655084c58eca), [`215f9b0`](https://github.com/mastra-ai/mastra/commit/215f9b0f3f3f6fc165edad360582dd4d3d7ea748), [`c64c2a8`](https://github.com/mastra-ai/mastra/commit/c64c2a8503a50252f9ca6b8e8c54cadee31b92a2), [`06e2680`](https://github.com/mastra-ai/mastra/commit/06e26806b51d2cbd858afdc66daa2b86ff3ba64a), [`1240f05`](https://github.com/mastra-ai/mastra/commit/1240f051c8e5371f1c014448bf37b1a1b9a05e47), [`215f9b0`](https://github.com/mastra-ai/mastra/commit/215f9b0f3f3f6fc165edad360582dd4d3d7ea748), [`24c10d3`](https://github.com/mastra-ai/mastra/commit/24c10d333e6649ac06075903aeeee13a933db3b3), [`24c10d3`](https://github.com/mastra-ai/mastra/commit/24c10d333e6649ac06075903aeeee13a933db3b3), [`24c10d3`](https://github.com/mastra-ai/mastra/commit/24c10d333e6649ac06075903aeeee13a933db3b3), [`24c10d3`](https://github.com/mastra-ai/mastra/commit/24c10d333e6649ac06075903aeeee13a933db3b3), [`215f9b0`](https://github.com/mastra-ai/mastra/commit/215f9b0f3f3f6fc165edad360582dd4d3d7ea748), [`215f9b0`](https://github.com/mastra-ai/mastra/commit/215f9b0f3f3f6fc165edad360582dd4d3d7ea748)]:
146
+ - @mastra/core@1.49.0-alpha.5
147
+
3
148
  ## 1.15.0-alpha.0
4
149
 
5
150
  ### Minor Changes
@@ -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.15.0-alpha.0"
6
+ version: "1.15.0-alpha.2"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.15.0-alpha.0",
2
+ "version": "1.15.0-alpha.2",
3
3
  "package": "@mastra/libsql",
4
4
  "exports": {},
5
5
  "modules": {}
package/dist/index.cjs CHANGED
@@ -3826,6 +3826,30 @@ var ChannelsLibSQL = class extends storage.ChannelsStorage {
3826
3826
  };
3827
3827
  }
3828
3828
  };
3829
+
3830
+ // src/storage/domains/utils.ts
3831
+ function tenancyWhere(filters) {
3832
+ const conditions = [];
3833
+ const params = [];
3834
+ if (filters?.organizationId !== void 0) {
3835
+ conditions.push("organizationId = ?");
3836
+ params.push(filters.organizationId);
3837
+ }
3838
+ if (filters?.projectId !== void 0) {
3839
+ conditions.push("projectId = ?");
3840
+ params.push(filters.projectId);
3841
+ }
3842
+ return { conditions, params };
3843
+ }
3844
+ function buildScopedWhere(idColumn, idValue, filters) {
3845
+ const { conditions, params } = tenancyWhere(filters);
3846
+ return {
3847
+ sql: [`${idColumn} = ?`, ...conditions].join(" AND "),
3848
+ args: [idValue, ...params]
3849
+ };
3850
+ }
3851
+
3852
+ // src/storage/domains/datasets/index.ts
3829
3853
  function jsonbArg(value) {
3830
3854
  return value === void 0 || value === null ? null : JSON.stringify(value);
3831
3855
  }
@@ -3906,6 +3930,18 @@ var DatasetsLibSQL = class extends storage.DatasetsStorage {
3906
3930
  await this.#db.deleteData({ tableName: storage.TABLE_DATASET_ITEMS });
3907
3931
  await this.#db.deleteData({ tableName: storage.TABLE_DATASETS });
3908
3932
  }
3933
+ async experimentTablesExist() {
3934
+ try {
3935
+ const result = await this.#client.execute({
3936
+ sql: `SELECT COUNT(*) AS c FROM sqlite_master WHERE type = 'table' AND name IN (?, ?)`,
3937
+ args: [storage.TABLE_EXPERIMENTS, storage.TABLE_EXPERIMENT_RESULTS]
3938
+ });
3939
+ const row = result.rows?.[0];
3940
+ return Number(row?.c ?? 0) === 2;
3941
+ } catch {
3942
+ return false;
3943
+ }
3944
+ }
3909
3945
  // --- Row transformers ---
3910
3946
  transformDatasetRow(row) {
3911
3947
  return {
@@ -3992,8 +4028,8 @@ var DatasetsLibSQL = class extends storage.DatasetsStorage {
3992
4028
  groundTruthSchema: input.groundTruthSchema ?? null,
3993
4029
  requestContextSchema: input.requestContextSchema ?? null,
3994
4030
  targetType: input.targetType ?? null,
3995
- targetIds: input.targetIds ? JSON.stringify(input.targetIds) : null,
3996
- scorerIds: input.scorerIds ? JSON.stringify(input.scorerIds) : null,
4031
+ targetIds: input.targetIds ?? null,
4032
+ scorerIds: input.scorerIds ?? null,
3997
4033
  version: 0,
3998
4034
  organizationId: input.organizationId ?? null,
3999
4035
  projectId: input.projectId ?? null,
@@ -4033,11 +4069,16 @@ var DatasetsLibSQL = class extends storage.DatasetsStorage {
4033
4069
  );
4034
4070
  }
4035
4071
  }
4036
- async getDatasetById({ id }) {
4072
+ async getDatasetById({
4073
+ id,
4074
+ filters
4075
+ }) {
4037
4076
  try {
4077
+ const { conditions, params } = tenancyWhere(filters);
4078
+ const whereSql = ["id = ?", ...conditions].join(" AND ");
4038
4079
  const result = await this.#client.execute({
4039
- sql: `SELECT ${buildSelectColumns(storage.TABLE_DATASETS)} FROM ${storage.TABLE_DATASETS} WHERE id = ?`,
4040
- args: [id]
4080
+ sql: `SELECT ${buildSelectColumns(storage.TABLE_DATASETS)} FROM ${storage.TABLE_DATASETS} WHERE ${whereSql}`,
4081
+ args: [id, ...params]
4041
4082
  });
4042
4083
  return result.rows?.[0] ? this.transformDatasetRow(result.rows[0]) : null;
4043
4084
  } catch (error$1) {
@@ -4053,7 +4094,7 @@ var DatasetsLibSQL = class extends storage.DatasetsStorage {
4053
4094
  }
4054
4095
  async _doUpdateDataset(args) {
4055
4096
  try {
4056
- const existing = await this.getDatasetById({ id: args.id });
4097
+ const existing = await this.getDatasetById({ id: args.id, filters: args.filters });
4057
4098
  if (!existing) {
4058
4099
  throw new error.MastraError({
4059
4100
  id: storage.createStorageErrorId("LIBSQL", "UPDATE_DATASET", "NOT_FOUND"),
@@ -4136,30 +4177,31 @@ var DatasetsLibSQL = class extends storage.DatasetsStorage {
4136
4177
  );
4137
4178
  }
4138
4179
  }
4139
- async deleteDataset({ id }) {
4180
+ async deleteDataset({ id, filters }) {
4140
4181
  try {
4141
- try {
4142
- await this.#client.execute({
4182
+ const { conditions, params } = tenancyWhere(filters);
4183
+ const scopedWhere = ["id = ?", ...conditions].join(" AND ");
4184
+ const exists = await this.#client.execute({
4185
+ sql: `SELECT id FROM ${storage.TABLE_DATASETS} WHERE ${scopedWhere}`,
4186
+ args: [id, ...params]
4187
+ });
4188
+ if (!exists.rows?.[0]) return;
4189
+ const experimentTablesExist = await this.experimentTablesExist();
4190
+ const statements = [];
4191
+ if (experimentTablesExist) {
4192
+ statements.push({
4143
4193
  sql: `DELETE FROM ${storage.TABLE_EXPERIMENT_RESULTS} WHERE experimentId IN (SELECT id FROM ${storage.TABLE_EXPERIMENTS} WHERE datasetId = ?)`,
4144
4194
  args: [id]
4145
4195
  });
4146
- } catch {
4147
- }
4148
- try {
4149
- await this.#client.execute({
4196
+ statements.push({
4150
4197
  sql: `UPDATE ${storage.TABLE_EXPERIMENTS} SET datasetId = NULL, datasetVersion = NULL WHERE datasetId = ?`,
4151
4198
  args: [id]
4152
4199
  });
4153
- } catch {
4154
4200
  }
4155
- await this.#client.batch(
4156
- [
4157
- { sql: `DELETE FROM ${storage.TABLE_DATASET_VERSIONS} WHERE datasetId = ?`, args: [id] },
4158
- { sql: `DELETE FROM ${storage.TABLE_DATASET_ITEMS} WHERE datasetId = ?`, args: [id] },
4159
- { sql: `DELETE FROM ${storage.TABLE_DATASETS} WHERE id = ?`, args: [id] }
4160
- ],
4161
- "write"
4162
- );
4201
+ statements.push({ sql: `DELETE FROM ${storage.TABLE_DATASET_VERSIONS} WHERE datasetId = ?`, args: [id] });
4202
+ statements.push({ sql: `DELETE FROM ${storage.TABLE_DATASET_ITEMS} WHERE datasetId = ?`, args: [id] });
4203
+ statements.push({ sql: `DELETE FROM ${storage.TABLE_DATASETS} WHERE ${scopedWhere}`, args: [id, ...params] });
4204
+ await this.#client.batch(statements, "write");
4163
4205
  } catch (error$1) {
4164
4206
  throw new error.MastraError(
4165
4207
  {
@@ -4192,6 +4234,21 @@ var DatasetsLibSQL = class extends storage.DatasetsStorage {
4192
4234
  filterConditions.push("candidateId = ?");
4193
4235
  filterParams.push(args.filters.candidateId);
4194
4236
  }
4237
+ if (args.filters?.targetType !== void 0) {
4238
+ filterConditions.push("targetType = ?");
4239
+ filterParams.push(args.filters.targetType);
4240
+ }
4241
+ if (args.filters?.targetIds !== void 0 && args.filters.targetIds.length > 0) {
4242
+ const placeholders = args.filters.targetIds.map(() => "?").join(",");
4243
+ filterConditions.push(
4244
+ `EXISTS (SELECT 1 FROM json_each(${storage.TABLE_DATASETS}.targetIds) WHERE value IN (${placeholders}))`
4245
+ );
4246
+ for (const id of args.filters.targetIds) filterParams.push(id);
4247
+ }
4248
+ if (args.filters?.name !== void 0 && args.filters.name.length > 0) {
4249
+ filterConditions.push("LOWER(name) LIKE ?");
4250
+ filterParams.push(`%${args.filters.name.toLowerCase()}%`);
4251
+ }
4195
4252
  const whereClause = filterConditions.length > 0 ? `WHERE ${filterConditions.join(" AND ")}` : "";
4196
4253
  const countResult = await this.#client.execute({
4197
4254
  sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_DATASETS} ${whereClause}`,
@@ -5157,9 +5214,10 @@ var ExperimentsLibSQL = class extends storage.ExperimentsStorage {
5157
5214
  }
5158
5215
  async getExperimentById(args) {
5159
5216
  try {
5217
+ const scoped = buildScopedWhere("id", args.id, args.filters);
5160
5218
  const result = await this.#client.execute({
5161
- sql: `SELECT ${buildSelectColumns(storage.TABLE_EXPERIMENTS)} FROM ${storage.TABLE_EXPERIMENTS} WHERE id = ?`,
5162
- args: [args.id]
5219
+ sql: `SELECT ${buildSelectColumns(storage.TABLE_EXPERIMENTS)} FROM ${storage.TABLE_EXPERIMENTS} WHERE ${scoped.sql}`,
5220
+ args: scoped.args
5163
5221
  });
5164
5222
  return result.rows?.[0] ? this.transformExperimentRow(result.rows[0]) : null;
5165
5223
  } catch (error$1) {
@@ -5251,14 +5309,17 @@ var ExperimentsLibSQL = class extends storage.ExperimentsStorage {
5251
5309
  }
5252
5310
  async deleteExperiment(args) {
5253
5311
  try {
5254
- await this.#client.execute({
5255
- sql: `DELETE FROM ${storage.TABLE_EXPERIMENT_RESULTS} WHERE experimentId = ?`,
5256
- args: [args.id]
5257
- });
5258
- await this.#client.execute({
5259
- sql: `DELETE FROM ${storage.TABLE_EXPERIMENTS} WHERE id = ?`,
5260
- args: [args.id]
5261
- });
5312
+ const parentScoped = buildScopedWhere("id", args.id, args.filters);
5313
+ const { conditions, params } = tenancyWhere(args.filters);
5314
+ const cascadeWhere = conditions.length ? `experimentId IN (SELECT id FROM ${storage.TABLE_EXPERIMENTS} WHERE ${["id = ?", ...conditions].join(" AND ")})` : `experimentId = ?`;
5315
+ const cascadeArgs = conditions.length ? [args.id, ...params] : [args.id];
5316
+ await this.#client.batch(
5317
+ [
5318
+ { sql: `DELETE FROM ${storage.TABLE_EXPERIMENT_RESULTS} WHERE ${cascadeWhere}`, args: cascadeArgs },
5319
+ { sql: `DELETE FROM ${storage.TABLE_EXPERIMENTS} WHERE ${parentScoped.sql}`, args: parentScoped.args }
5320
+ ],
5321
+ "write"
5322
+ );
5262
5323
  } catch (error$1) {
5263
5324
  throw new error.MastraError(
5264
5325
  {
@@ -5396,9 +5457,10 @@ var ExperimentsLibSQL = class extends storage.ExperimentsStorage {
5396
5457
  }
5397
5458
  async getExperimentResultById(args) {
5398
5459
  try {
5460
+ const scoped = buildScopedWhere("id", args.id, args.filters);
5399
5461
  const result = await this.#client.execute({
5400
- sql: `SELECT ${buildSelectColumns(storage.TABLE_EXPERIMENT_RESULTS)} FROM ${storage.TABLE_EXPERIMENT_RESULTS} WHERE id = ?`,
5401
- args: [args.id]
5462
+ sql: `SELECT ${buildSelectColumns(storage.TABLE_EXPERIMENT_RESULTS)} FROM ${storage.TABLE_EXPERIMENT_RESULTS} WHERE ${scoped.sql}`,
5463
+ args: scoped.args
5402
5464
  });
5403
5465
  return result.rows?.[0] ? this.transformExperimentResultRow(result.rows[0]) : null;
5404
5466
  } catch (error$1) {
@@ -5478,6 +5540,14 @@ var ExperimentsLibSQL = class extends storage.ExperimentsStorage {
5478
5540
  }
5479
5541
  async deleteExperimentResults(args) {
5480
5542
  try {
5543
+ const { conditions, params } = tenancyWhere(args.filters);
5544
+ if (conditions.length) {
5545
+ await this.#client.execute({
5546
+ sql: `DELETE FROM ${storage.TABLE_EXPERIMENT_RESULTS} WHERE experimentId IN (SELECT id FROM ${storage.TABLE_EXPERIMENTS} WHERE ${["id = ?", ...conditions].join(" AND ")})`,
5547
+ args: [args.experimentId, ...params]
5548
+ });
5549
+ return;
5550
+ }
5481
5551
  await this.#client.execute({
5482
5552
  sql: `DELETE FROM ${storage.TABLE_EXPERIMENT_RESULTS} WHERE experimentId = ?`,
5483
5553
  args: [args.experimentId]
@@ -11044,6 +11114,16 @@ var ScorerDefinitionsLibSQL = class extends storage.ScorerDefinitionsStorage {
11044
11114
  };
11045
11115
  }
11046
11116
  };
11117
+ function appendTenancyConditions(conditions, args, filters) {
11118
+ if (filters?.organizationId !== void 0) {
11119
+ conditions.push(`organizationId = ?`);
11120
+ args.push(filters.organizationId);
11121
+ }
11122
+ if (filters?.projectId !== void 0) {
11123
+ conditions.push(`projectId = ?`);
11124
+ args.push(filters.projectId);
11125
+ }
11126
+ }
11047
11127
  var ScoresLibSQL = class _ScoresLibSQL extends storage.ScoresStorage {
11048
11128
  /**
11049
11129
  * Scorer results accumulate as evals run. Single table, anchored on `createdAt`.
@@ -11064,7 +11144,7 @@ var ScoresLibSQL = class _ScoresLibSQL extends storage.ScoresStorage {
11064
11144
  await this.#db.alterTable({
11065
11145
  tableName: storage.TABLE_SCORERS,
11066
11146
  schema: storage.SCORERS_SCHEMA,
11067
- ifNotExists: ["spanId", "requestContext"]
11147
+ ifNotExists: ["spanId", "requestContext", "organizationId", "projectId", "batchId", "datasetId", "datasetItemId"]
11068
11148
  });
11069
11149
  }
11070
11150
  async dangerouslyClearAll() {
@@ -11081,13 +11161,18 @@ var ScoresLibSQL = class _ScoresLibSQL extends storage.ScoresStorage {
11081
11161
  }
11082
11162
  async listScoresByRunId({
11083
11163
  runId,
11084
- pagination
11164
+ pagination,
11165
+ filters
11085
11166
  }) {
11086
11167
  try {
11087
11168
  const { page, perPage: perPageInput } = pagination;
11169
+ const conditions = [`runId = ?`];
11170
+ const queryParams = [runId];
11171
+ appendTenancyConditions(conditions, queryParams, filters);
11172
+ const whereClause = `WHERE ${conditions.join(" AND ")}`;
11088
11173
  const countResult = await this.#client.execute({
11089
- sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_SCORERS} WHERE runId = ?`,
11090
- args: [runId]
11174
+ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_SCORERS} ${whereClause}`,
11175
+ args: queryParams
11091
11176
  });
11092
11177
  const total = Number(countResult.rows?.[0]?.count ?? 0);
11093
11178
  if (total === 0) {
@@ -11106,8 +11191,8 @@ var ScoresLibSQL = class _ScoresLibSQL extends storage.ScoresStorage {
11106
11191
  const limitValue = perPageInput === false ? total : perPage;
11107
11192
  const end = perPageInput === false ? total : start + perPage;
11108
11193
  const result = await this.#client.execute({
11109
- sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORERS)} FROM ${storage.TABLE_SCORERS} WHERE runId = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
11110
- args: [runId, limitValue, start]
11194
+ sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORERS)} FROM ${storage.TABLE_SCORERS} ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
11195
+ args: [...queryParams, limitValue, start]
11111
11196
  });
11112
11197
  const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
11113
11198
  return {
@@ -11135,7 +11220,8 @@ var ScoresLibSQL = class _ScoresLibSQL extends storage.ScoresStorage {
11135
11220
  entityId,
11136
11221
  entityType,
11137
11222
  source,
11138
- pagination
11223
+ pagination,
11224
+ filters
11139
11225
  }) {
11140
11226
  try {
11141
11227
  const { page, perPage: perPageInput } = pagination;
@@ -11157,6 +11243,7 @@ var ScoresLibSQL = class _ScoresLibSQL extends storage.ScoresStorage {
11157
11243
  conditions.push(`source = ?`);
11158
11244
  queryParams.push(source);
11159
11245
  }
11246
+ appendTenancyConditions(conditions, queryParams, filters);
11160
11247
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
11161
11248
  const countResult = await this.#client.execute({
11162
11249
  sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_SCORERS} ${whereClause}`,
@@ -11264,13 +11351,18 @@ var ScoresLibSQL = class _ScoresLibSQL extends storage.ScoresStorage {
11264
11351
  async listScoresByEntityId({
11265
11352
  entityId,
11266
11353
  entityType,
11267
- pagination
11354
+ pagination,
11355
+ filters
11268
11356
  }) {
11269
11357
  try {
11270
11358
  const { page, perPage: perPageInput } = pagination;
11359
+ const conditions = [`entityId = ?`, `entityType = ?`];
11360
+ const queryParams = [entityId, entityType];
11361
+ appendTenancyConditions(conditions, queryParams, filters);
11362
+ const whereClause = `WHERE ${conditions.join(" AND ")}`;
11271
11363
  const countResult = await this.#client.execute({
11272
- sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_SCORERS} WHERE entityId = ? AND entityType = ?`,
11273
- args: [entityId, entityType]
11364
+ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_SCORERS} ${whereClause}`,
11365
+ args: queryParams
11274
11366
  });
11275
11367
  const total = Number(countResult.rows?.[0]?.count ?? 0);
11276
11368
  if (total === 0) {
@@ -11289,8 +11381,8 @@ var ScoresLibSQL = class _ScoresLibSQL extends storage.ScoresStorage {
11289
11381
  const limitValue = perPageInput === false ? total : perPage;
11290
11382
  const end = perPageInput === false ? total : start + perPage;
11291
11383
  const result = await this.#client.execute({
11292
- sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORERS)} FROM ${storage.TABLE_SCORERS} WHERE entityId = ? AND entityType = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
11293
- args: [entityId, entityType, limitValue, start]
11384
+ sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORERS)} FROM ${storage.TABLE_SCORERS} ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
11385
+ args: [...queryParams, limitValue, start]
11294
11386
  });
11295
11387
  const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
11296
11388
  return {
@@ -11316,22 +11408,27 @@ var ScoresLibSQL = class _ScoresLibSQL extends storage.ScoresStorage {
11316
11408
  async listScoresBySpan({
11317
11409
  traceId,
11318
11410
  spanId,
11319
- pagination
11411
+ pagination,
11412
+ filters
11320
11413
  }) {
11321
11414
  try {
11322
11415
  const { page, perPage: perPageInput } = pagination;
11323
11416
  const perPage = storage.normalizePerPage(perPageInput, 100);
11324
11417
  const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
11418
+ const conditions = [`traceId = ?`, `spanId = ?`];
11419
+ const queryParams = [traceId, spanId];
11420
+ appendTenancyConditions(conditions, queryParams, filters);
11421
+ const whereClause = `WHERE ${conditions.join(" AND ")}`;
11325
11422
  const countSQLResult = await this.#client.execute({
11326
- sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_SCORERS} WHERE traceId = ? AND spanId = ?`,
11327
- args: [traceId, spanId]
11423
+ sql: `SELECT COUNT(*) as count FROM ${storage.TABLE_SCORERS} ${whereClause}`,
11424
+ args: queryParams
11328
11425
  });
11329
11426
  const total = Number(countSQLResult.rows?.[0]?.count ?? 0);
11330
11427
  const limitValue = perPageInput === false ? total : perPage;
11331
11428
  const end = perPageInput === false ? total : start + perPage;
11332
11429
  const result = await this.#client.execute({
11333
- sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORERS)} FROM ${storage.TABLE_SCORERS} WHERE traceId = ? AND spanId = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
11334
- args: [traceId, spanId, limitValue, start]
11430
+ sql: `SELECT ${buildSelectColumns(storage.TABLE_SCORERS)} FROM ${storage.TABLE_SCORERS} ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
11431
+ args: [...queryParams, limitValue, start]
11335
11432
  });
11336
11433
  const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
11337
11434
  return {