@mastra/spanner 1.2.1 → 1.2.2-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/CHANGELOG.md +128 -0
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/reference-storage-spanner.md +2 -0
- package/dist/index.cjs +229 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +229 -53
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/datasets/index.d.ts +5 -5
- package/dist/storage/domains/datasets/index.d.ts.map +1 -1
- package/dist/storage/domains/experiments/index.d.ts +5 -1
- package/dist/storage/domains/experiments/index.d.ts.map +1 -1
- package/dist/storage/domains/scores/index.d.ts +9 -5
- package/dist/storage/domains/scores/index.d.ts.map +1 -1
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,133 @@
|
|
|
1
1
|
# @mastra/spanner
|
|
2
2
|
|
|
3
|
+
## 1.2.2-alpha.1
|
|
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.2.2-alpha.0
|
|
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
|
+
- Tenancy-scope experiments `getById` and `delete*` on `ExperimentsStorage`. ([#18770](https://github.com/mastra-ai/mastra/pull/18770))
|
|
40
|
+
|
|
41
|
+
`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):
|
|
42
|
+
- On tenancy mismatch, `get*` returns `null` at the storage layer.
|
|
43
|
+
- On tenancy mismatch, `delete*` is a silent no-op.
|
|
44
|
+
- 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.
|
|
45
|
+
|
|
46
|
+
Both behaviors match how a missing id already responds, so existence does not leak through error timing or messages.
|
|
47
|
+
|
|
48
|
+
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.
|
|
49
|
+
|
|
50
|
+
`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.
|
|
51
|
+
|
|
52
|
+
Legacy calls that omit `filters` are unchanged, so this is fully backwards-compatible.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// Before: any caller who knew the id could read/delete across tenants.
|
|
56
|
+
await store.experiments.getExperimentById({ id: experimentId });
|
|
57
|
+
await store.experiments.deleteExperiment({ id: experimentId });
|
|
58
|
+
|
|
59
|
+
// After: pass the caller's scope; wrong tenant gets null / silent no-op.
|
|
60
|
+
await store.experiments.getExperimentById({
|
|
61
|
+
id: experimentId,
|
|
62
|
+
filters: { organizationId, projectId },
|
|
63
|
+
});
|
|
64
|
+
await store.experiments.deleteExperiment({
|
|
65
|
+
id: experimentId,
|
|
66
|
+
filters: { organizationId, projectId },
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- 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))
|
|
71
|
+
|
|
72
|
+
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).
|
|
73
|
+
|
|
74
|
+
**What changed**
|
|
75
|
+
- `DatasetsManager.get` and `DatasetsManager.delete` accept optional `organizationId` and `projectId`.
|
|
76
|
+
- The tenancy is stashed on the returned `Dataset` handle and forwarded to every downstream storage call (`getDetails`, `update`, `addItem`, item batch ops, `startExperimentAsync`).
|
|
77
|
+
- The abstract storage contract (`getDatasetById`, `deleteDataset`) gained an optional `filters?: DatasetTenancyFilters` arg.
|
|
78
|
+
- Item-mutation inputs (`AddDatasetItemInput`, `UpdateDatasetItemInput`, `BatchInsertItemsInput`, `BatchDeleteItemsInput`) and `UpdateDatasetInput` accept optional `filters` for the internal existence check.
|
|
79
|
+
|
|
80
|
+
**Behavior**
|
|
81
|
+
- Omitting tenancy preserves the existing behavior (no predicate added) — fully backwards compatible.
|
|
82
|
+
- 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.
|
|
83
|
+
|
|
84
|
+
**Example**
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
// Before
|
|
88
|
+
const ds = await mastra.datasets.get({ id });
|
|
89
|
+
await mastra.datasets.delete({ id });
|
|
90
|
+
|
|
91
|
+
// After — scope to a tenant
|
|
92
|
+
const ds = await mastra.datasets.get({ id, organizationId, projectId });
|
|
93
|
+
await mastra.datasets.delete({ id, organizationId, projectId });
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- 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))
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
await storage.saveScore({ ...score, organizationId: 'org-a', projectId: 'proj-1' });
|
|
100
|
+
|
|
101
|
+
const result = await storage.listScoresByScorerId({
|
|
102
|
+
scorerId,
|
|
103
|
+
filters: { organizationId: 'org-a', projectId: 'proj-1' },
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`projectId` identifies the project scope, separate from `resourceId` which continues to mean the agent memory resource.
|
|
108
|
+
|
|
109
|
+
- Widened `SpannerStore` dataset initialization to backfill `targetType`, `targetIds`, and `scorerIds` on pre-existing `mastra_datasets` tables. The `createDataset` / `updateDataset` write paths and the new `listDatasets` `targetType` / `targetIds` filters reference these columns; deployments that upgraded in place before these columns were declared would otherwise hit column-not-found errors on both writes and the new filter path. ([#18710](https://github.com/mastra-ai/mastra/pull/18710))
|
|
110
|
+
|
|
111
|
+
Fresh databases were already unaffected because `createTable` reads the full `DATASETS_SCHEMA`.
|
|
112
|
+
|
|
113
|
+
- Scoped `getDatasetById` and `deleteDataset` to tenancy filters when the caller passes `organizationId` / `projectId`. ([#18750](https://github.com/mastra-ai/mastra/pull/18750))
|
|
114
|
+
|
|
115
|
+
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.
|
|
116
|
+
|
|
117
|
+
- Added optional `organizationId` and `projectId` query parameters to the dataset routes. ([#18750](https://github.com/mastra-ai/mastra/pull/18750))
|
|
118
|
+
|
|
119
|
+
`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.
|
|
120
|
+
|
|
121
|
+
**Example**
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
GET /datasets/abc123?organizationId=org_a&projectId=proj_1
|
|
125
|
+
DELETE /datasets/abc123?organizationId=org_a
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- 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)]:
|
|
129
|
+
- @mastra/core@1.49.0-alpha.5
|
|
130
|
+
|
|
3
131
|
## 1.2.1
|
|
4
132
|
|
|
5
133
|
### Patch Changes
|
package/dist/docs/SKILL.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
> Discover all available pages from the documentation index: https://mastra.ai/llms.txt
|
|
2
|
+
|
|
1
3
|
# Google Cloud Spanner storage
|
|
2
4
|
|
|
3
5
|
The Google Cloud Spanner storage implementation provides a horizontally scalable, strongly consistent storage backend for Mastra. It targets the GoogleSQL dialect of Cloud Spanner.
|
package/dist/index.cjs
CHANGED
|
@@ -3034,7 +3034,15 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
|
|
|
3034
3034
|
await this.db.alterTable({
|
|
3035
3035
|
tableName: storage.TABLE_DATASETS,
|
|
3036
3036
|
schema: storage.TABLE_SCHEMAS[storage.TABLE_DATASETS],
|
|
3037
|
-
ifNotExists: [
|
|
3037
|
+
ifNotExists: [
|
|
3038
|
+
"organizationId",
|
|
3039
|
+
"projectId",
|
|
3040
|
+
"candidateKey",
|
|
3041
|
+
"candidateId",
|
|
3042
|
+
"targetType",
|
|
3043
|
+
"targetIds",
|
|
3044
|
+
"scorerIds"
|
|
3045
|
+
]
|
|
3038
3046
|
});
|
|
3039
3047
|
await this.db.alterTable({
|
|
3040
3048
|
tableName: storage.TABLE_DATASET_ITEMS,
|
|
@@ -3081,6 +3089,20 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
|
|
|
3081
3089
|
await this.db.clearTable({ tableName: storage.TABLE_DATASET_ITEMS });
|
|
3082
3090
|
await this.db.clearTable({ tableName: storage.TABLE_DATASETS });
|
|
3083
3091
|
}
|
|
3092
|
+
async experimentTablesExist() {
|
|
3093
|
+
try {
|
|
3094
|
+
const [rows] = await this.database.run({
|
|
3095
|
+
sql: `SELECT COUNT(*) AS c FROM INFORMATION_SCHEMA.TABLES
|
|
3096
|
+
WHERE TABLE_SCHEMA = "" AND TABLE_NAME IN (@a, @b)`,
|
|
3097
|
+
params: { a: storage.TABLE_EXPERIMENTS, b: storage.TABLE_EXPERIMENT_RESULTS },
|
|
3098
|
+
json: true
|
|
3099
|
+
});
|
|
3100
|
+
const row = rows?.[0];
|
|
3101
|
+
return Number(row?.c ?? 0) === 2;
|
|
3102
|
+
} catch {
|
|
3103
|
+
return false;
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3084
3106
|
// ==========================================================================
|
|
3085
3107
|
// Dataset CRUD
|
|
3086
3108
|
// ==========================================================================
|
|
@@ -3145,7 +3167,27 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
|
|
|
3145
3167
|
}
|
|
3146
3168
|
async getDatasetById(args) {
|
|
3147
3169
|
try {
|
|
3148
|
-
const
|
|
3170
|
+
const hasTenancy = args.filters?.organizationId !== void 0 || args.filters?.projectId !== void 0;
|
|
3171
|
+
if (!hasTenancy) {
|
|
3172
|
+
const row2 = await this.db.load({ tableName: storage.TABLE_DATASETS, keys: { id: args.id } });
|
|
3173
|
+
return row2 ? rowToDataset(row2) : null;
|
|
3174
|
+
}
|
|
3175
|
+
const conditions = [`${quoteIdent("id", "column name")} = @id`];
|
|
3176
|
+
const params = { id: args.id };
|
|
3177
|
+
if (args.filters?.organizationId !== void 0) {
|
|
3178
|
+
conditions.push(`${quoteIdent("organizationId", "column name")} = @organizationId`);
|
|
3179
|
+
params.organizationId = args.filters.organizationId;
|
|
3180
|
+
}
|
|
3181
|
+
if (args.filters?.projectId !== void 0) {
|
|
3182
|
+
conditions.push(`${quoteIdent("projectId", "column name")} = @projectId`);
|
|
3183
|
+
params.projectId = args.filters.projectId;
|
|
3184
|
+
}
|
|
3185
|
+
const [rows] = await this.database.run({
|
|
3186
|
+
sql: `SELECT * FROM ${quoteIdent(storage.TABLE_DATASETS, "table name")} WHERE ${conditions.join(" AND ")} LIMIT 1`,
|
|
3187
|
+
params,
|
|
3188
|
+
json: true
|
|
3189
|
+
});
|
|
3190
|
+
const row = rows[0];
|
|
3149
3191
|
return row ? rowToDataset(row) : null;
|
|
3150
3192
|
} catch (error$1) {
|
|
3151
3193
|
throw new error.MastraError(
|
|
@@ -3174,7 +3216,7 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
|
|
|
3174
3216
|
if (args.scorerIds !== void 0) data.scorerIds = args.scorerIds;
|
|
3175
3217
|
data.updatedAt = /* @__PURE__ */ new Date();
|
|
3176
3218
|
await this.db.update({ tableName: storage.TABLE_DATASETS, keys: { id: args.id }, data });
|
|
3177
|
-
const updated = await this.getDatasetById({ id: args.id });
|
|
3219
|
+
const updated = await this.getDatasetById({ id: args.id, filters: args.filters });
|
|
3178
3220
|
if (!updated) {
|
|
3179
3221
|
throw new error.MastraError({
|
|
3180
3222
|
id: storage.createStorageErrorId("SPANNER", "UPDATE_DATASET", "NOT_FOUND"),
|
|
@@ -3200,26 +3242,46 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
|
|
|
3200
3242
|
}
|
|
3201
3243
|
async deleteDataset(args) {
|
|
3202
3244
|
try {
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
});
|
|
3211
|
-
|
|
3212
|
-
sql: `UPDATE ${quoteIdent(storage.TABLE_EXPERIMENTS, "table name")}
|
|
3213
|
-
SET ${quoteIdent("datasetId", "column name")} = NULL,
|
|
3214
|
-
${quoteIdent("datasetVersion", "column name")} = NULL
|
|
3215
|
-
WHERE ${quoteIdent("datasetId", "column name")} = @id`,
|
|
3216
|
-
params: { id: args.id }
|
|
3217
|
-
});
|
|
3218
|
-
} catch {
|
|
3245
|
+
const tenancyConditions = [];
|
|
3246
|
+
const tenancyParams = {};
|
|
3247
|
+
if (args.filters?.organizationId !== void 0) {
|
|
3248
|
+
tenancyConditions.push(`${quoteIdent("organizationId", "column name")} = @organizationId`);
|
|
3249
|
+
tenancyParams.organizationId = args.filters.organizationId;
|
|
3250
|
+
}
|
|
3251
|
+
if (args.filters?.projectId !== void 0) {
|
|
3252
|
+
tenancyConditions.push(`${quoteIdent("projectId", "column name")} = @projectId`);
|
|
3253
|
+
tenancyParams.projectId = args.filters.projectId;
|
|
3219
3254
|
}
|
|
3255
|
+
const scopedWhere = [`${quoteIdent("id", "column name")} = @id`, ...tenancyConditions].join(" AND ");
|
|
3256
|
+
const experimentTablesExist = await this.experimentTablesExist();
|
|
3220
3257
|
await this.db.runWithAbortRetry(
|
|
3221
3258
|
() => this.database.runTransactionAsync(async (tx) => {
|
|
3222
3259
|
try {
|
|
3260
|
+
const [rows] = await tx.run({
|
|
3261
|
+
sql: `SELECT ${quoteIdent("id", "column name")} FROM ${quoteIdent(storage.TABLE_DATASETS, "table name")} WHERE ${scopedWhere}`,
|
|
3262
|
+
params: { id: args.id, ...tenancyParams },
|
|
3263
|
+
json: true
|
|
3264
|
+
});
|
|
3265
|
+
if (!rows || rows.length === 0) {
|
|
3266
|
+
await tx.commit();
|
|
3267
|
+
return;
|
|
3268
|
+
}
|
|
3269
|
+
if (experimentTablesExist) {
|
|
3270
|
+
await tx.runUpdate({
|
|
3271
|
+
sql: `DELETE FROM ${quoteIdent(storage.TABLE_EXPERIMENT_RESULTS, "table name")}
|
|
3272
|
+
WHERE ${quoteIdent("experimentId", "column name")} IN (
|
|
3273
|
+
SELECT ${quoteIdent("id", "column name")} FROM ${quoteIdent(storage.TABLE_EXPERIMENTS, "table name")}
|
|
3274
|
+
WHERE ${quoteIdent("datasetId", "column name")} = @id)`,
|
|
3275
|
+
params: { id: args.id }
|
|
3276
|
+
});
|
|
3277
|
+
await tx.runUpdate({
|
|
3278
|
+
sql: `UPDATE ${quoteIdent(storage.TABLE_EXPERIMENTS, "table name")}
|
|
3279
|
+
SET ${quoteIdent("datasetId", "column name")} = NULL,
|
|
3280
|
+
${quoteIdent("datasetVersion", "column name")} = NULL
|
|
3281
|
+
WHERE ${quoteIdent("datasetId", "column name")} = @id`,
|
|
3282
|
+
params: { id: args.id }
|
|
3283
|
+
});
|
|
3284
|
+
}
|
|
3223
3285
|
for (const table of [storage.TABLE_DATASET_VERSIONS, storage.TABLE_DATASET_ITEMS]) {
|
|
3224
3286
|
await tx.runUpdate({
|
|
3225
3287
|
sql: `DELETE FROM ${quoteIdent(table, "table name")} WHERE ${quoteIdent("datasetId", "column name")} = @id`,
|
|
@@ -3227,8 +3289,8 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
|
|
|
3227
3289
|
});
|
|
3228
3290
|
}
|
|
3229
3291
|
await tx.runUpdate({
|
|
3230
|
-
sql: `DELETE FROM ${quoteIdent(storage.TABLE_DATASETS, "table name")} WHERE ${
|
|
3231
|
-
params: { id: args.id }
|
|
3292
|
+
sql: `DELETE FROM ${quoteIdent(storage.TABLE_DATASETS, "table name")} WHERE ${scopedWhere}`,
|
|
3293
|
+
params: { id: args.id, ...tenancyParams }
|
|
3232
3294
|
});
|
|
3233
3295
|
await tx.commit();
|
|
3234
3296
|
} catch (err) {
|
|
@@ -3274,10 +3336,26 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
|
|
|
3274
3336
|
filterConditions.push(`${quoteIdent("candidateId", "column name")} = @candidateId`);
|
|
3275
3337
|
filterParams.candidateId = args.filters.candidateId;
|
|
3276
3338
|
}
|
|
3339
|
+
if (args.filters?.targetType !== void 0) {
|
|
3340
|
+
filterConditions.push(`${quoteIdent("targetType", "column name")} = @targetType`);
|
|
3341
|
+
filterParams.targetType = args.filters.targetType;
|
|
3342
|
+
}
|
|
3343
|
+
if (args.filters?.targetIds !== void 0 && args.filters.targetIds.length > 0) {
|
|
3344
|
+
filterConditions.push(
|
|
3345
|
+
`EXISTS (SELECT 1 FROM UNNEST(JSON_QUERY_ARRAY(${quoteIdent("targetIds", "column name")})) AS t WHERE JSON_VALUE(t) IN UNNEST(@targetIds))`
|
|
3346
|
+
);
|
|
3347
|
+
filterParams.targetIds = args.filters.targetIds;
|
|
3348
|
+
}
|
|
3349
|
+
if (args.filters?.name !== void 0 && args.filters.name.length > 0) {
|
|
3350
|
+
filterConditions.push(`LOWER(${quoteIdent("name", "column name")}) LIKE LOWER(@nameSubstring)`);
|
|
3351
|
+
filterParams.nameSubstring = `%${args.filters.name}%`;
|
|
3352
|
+
}
|
|
3277
3353
|
const whereClause = filterConditions.length > 0 ? `WHERE ${filterConditions.join(" AND ")}` : "";
|
|
3354
|
+
const filterTypes = args.filters?.targetIds !== void 0 && args.filters.targetIds.length > 0 ? { targetIds: { type: "array", child: { type: "string" } } } : {};
|
|
3278
3355
|
const [countRows] = await this.database.run({
|
|
3279
3356
|
sql: `SELECT COUNT(*) AS count FROM ${tableName} ${whereClause}`,
|
|
3280
3357
|
params: filterParams,
|
|
3358
|
+
types: filterTypes,
|
|
3281
3359
|
json: true
|
|
3282
3360
|
});
|
|
3283
3361
|
const total = Number(countRows[0]?.count ?? 0);
|
|
@@ -3290,6 +3368,7 @@ var DatasetsSpanner = class _DatasetsSpanner extends storage.DatasetsStorage {
|
|
|
3290
3368
|
ORDER BY ${quoteIdent("createdAt", "column name")} DESC, ${quoteIdent("id", "column name")} ASC
|
|
3291
3369
|
LIMIT @limit OFFSET @offset`,
|
|
3292
3370
|
params: { ...filterParams, limit, offset },
|
|
3371
|
+
types: filterTypes,
|
|
3293
3372
|
json: true
|
|
3294
3373
|
});
|
|
3295
3374
|
const datasets = rows.map(rowToDataset);
|
|
@@ -4214,7 +4293,27 @@ var ExperimentsSpanner = class _ExperimentsSpanner extends storage.ExperimentsSt
|
|
|
4214
4293
|
}
|
|
4215
4294
|
async getExperimentById(args) {
|
|
4216
4295
|
try {
|
|
4217
|
-
const
|
|
4296
|
+
const hasTenancy = args.filters?.organizationId !== void 0 || args.filters?.projectId !== void 0;
|
|
4297
|
+
if (!hasTenancy) {
|
|
4298
|
+
const row2 = await this.db.load({ tableName: storage.TABLE_EXPERIMENTS, keys: { id: args.id } });
|
|
4299
|
+
return row2 ? rowToExperiment(row2) : null;
|
|
4300
|
+
}
|
|
4301
|
+
const conditions = [`${quoteIdent("id", "column name")} = @id`];
|
|
4302
|
+
const params = { id: args.id };
|
|
4303
|
+
if (args.filters?.organizationId !== void 0) {
|
|
4304
|
+
conditions.push(`${quoteIdent("organizationId", "column name")} = @organizationId`);
|
|
4305
|
+
params.organizationId = args.filters.organizationId;
|
|
4306
|
+
}
|
|
4307
|
+
if (args.filters?.projectId !== void 0) {
|
|
4308
|
+
conditions.push(`${quoteIdent("projectId", "column name")} = @projectId`);
|
|
4309
|
+
params.projectId = args.filters.projectId;
|
|
4310
|
+
}
|
|
4311
|
+
const [rows] = await this.database.run({
|
|
4312
|
+
sql: `SELECT * FROM ${quoteIdent(storage.TABLE_EXPERIMENTS, "table name")} WHERE ${conditions.join(" AND ")} LIMIT 1`,
|
|
4313
|
+
params,
|
|
4314
|
+
json: true
|
|
4315
|
+
});
|
|
4316
|
+
const row = rows[0];
|
|
4218
4317
|
return row ? rowToExperiment(row) : null;
|
|
4219
4318
|
} catch (error$1) {
|
|
4220
4319
|
throw new error.MastraError(
|
|
@@ -4308,18 +4407,43 @@ var ExperimentsSpanner = class _ExperimentsSpanner extends storage.ExperimentsSt
|
|
|
4308
4407
|
}
|
|
4309
4408
|
async deleteExperiment(args) {
|
|
4310
4409
|
try {
|
|
4410
|
+
const tenancyConditions = [];
|
|
4411
|
+
const tenancyParams = {};
|
|
4412
|
+
if (args.filters?.organizationId !== void 0) {
|
|
4413
|
+
tenancyConditions.push(`${quoteIdent("organizationId", "column name")} = @organizationId`);
|
|
4414
|
+
tenancyParams.organizationId = args.filters.organizationId;
|
|
4415
|
+
}
|
|
4416
|
+
if (args.filters?.projectId !== void 0) {
|
|
4417
|
+
tenancyConditions.push(`${quoteIdent("projectId", "column name")} = @projectId`);
|
|
4418
|
+
tenancyParams.projectId = args.filters.projectId;
|
|
4419
|
+
}
|
|
4420
|
+
const gateWhere = [`${quoteIdent("id", "column name")} = @id`, ...tenancyConditions].join(" AND ");
|
|
4311
4421
|
await this.db.runWithAbortRetry(
|
|
4312
4422
|
() => this.database.runTransactionAsync(async (tx) => {
|
|
4313
4423
|
try {
|
|
4424
|
+
const [gateRows] = await tx.run({
|
|
4425
|
+
sql: `SELECT ${quoteIdent("id", "column name")} FROM ${quoteIdent(storage.TABLE_EXPERIMENTS, "table name")}
|
|
4426
|
+
WHERE ${gateWhere} LIMIT 1`,
|
|
4427
|
+
params: { id: args.id, ...tenancyParams },
|
|
4428
|
+
json: true
|
|
4429
|
+
});
|
|
4430
|
+
if (!Array.isArray(gateRows) || gateRows.length === 0) {
|
|
4431
|
+
await tx.commit();
|
|
4432
|
+
return;
|
|
4433
|
+
}
|
|
4434
|
+
const cascadeWhere = [
|
|
4435
|
+
`${quoteIdent("experimentId", "column name")} = @id`,
|
|
4436
|
+
// Result rows carry the same organizationId/projectId as their parent,
|
|
4437
|
+
// so applying tenancy here makes the cascade itself tenant-scoped.
|
|
4438
|
+
...tenancyConditions
|
|
4439
|
+
].join(" AND ");
|
|
4314
4440
|
await tx.runUpdate({
|
|
4315
|
-
sql: `DELETE FROM ${quoteIdent(storage.TABLE_EXPERIMENT_RESULTS, "table name")}
|
|
4316
|
-
|
|
4317
|
-
params: { id: args.id }
|
|
4441
|
+
sql: `DELETE FROM ${quoteIdent(storage.TABLE_EXPERIMENT_RESULTS, "table name")} WHERE ${cascadeWhere}`,
|
|
4442
|
+
params: { id: args.id, ...tenancyParams }
|
|
4318
4443
|
});
|
|
4319
4444
|
await tx.runUpdate({
|
|
4320
|
-
sql: `DELETE FROM ${quoteIdent(storage.TABLE_EXPERIMENTS, "table name")}
|
|
4321
|
-
|
|
4322
|
-
params: { id: args.id }
|
|
4445
|
+
sql: `DELETE FROM ${quoteIdent(storage.TABLE_EXPERIMENTS, "table name")} WHERE ${gateWhere}`,
|
|
4446
|
+
params: { id: args.id, ...tenancyParams }
|
|
4323
4447
|
});
|
|
4324
4448
|
await tx.commit();
|
|
4325
4449
|
} catch (err) {
|
|
@@ -4475,10 +4599,30 @@ var ExperimentsSpanner = class _ExperimentsSpanner extends storage.ExperimentsSt
|
|
|
4475
4599
|
}
|
|
4476
4600
|
async getExperimentResultById(args) {
|
|
4477
4601
|
try {
|
|
4478
|
-
const
|
|
4479
|
-
|
|
4480
|
-
|
|
4602
|
+
const hasTenancy = args.filters?.organizationId !== void 0 || args.filters?.projectId !== void 0;
|
|
4603
|
+
if (!hasTenancy) {
|
|
4604
|
+
const row2 = await this.db.load({
|
|
4605
|
+
tableName: storage.TABLE_EXPERIMENT_RESULTS,
|
|
4606
|
+
keys: { id: args.id }
|
|
4607
|
+
});
|
|
4608
|
+
return row2 ? rowToExperimentResult(row2) : null;
|
|
4609
|
+
}
|
|
4610
|
+
const conditions = [`${quoteIdent("id", "column name")} = @id`];
|
|
4611
|
+
const params = { id: args.id };
|
|
4612
|
+
if (args.filters?.organizationId !== void 0) {
|
|
4613
|
+
conditions.push(`${quoteIdent("organizationId", "column name")} = @organizationId`);
|
|
4614
|
+
params.organizationId = args.filters.organizationId;
|
|
4615
|
+
}
|
|
4616
|
+
if (args.filters?.projectId !== void 0) {
|
|
4617
|
+
conditions.push(`${quoteIdent("projectId", "column name")} = @projectId`);
|
|
4618
|
+
params.projectId = args.filters.projectId;
|
|
4619
|
+
}
|
|
4620
|
+
const [rows] = await this.database.run({
|
|
4621
|
+
sql: `SELECT * FROM ${quoteIdent(storage.TABLE_EXPERIMENT_RESULTS, "table name")} WHERE ${conditions.join(" AND ")} LIMIT 1`,
|
|
4622
|
+
params,
|
|
4623
|
+
json: true
|
|
4481
4624
|
});
|
|
4625
|
+
const row = rows[0];
|
|
4482
4626
|
return row ? rowToExperimentResult(row) : null;
|
|
4483
4627
|
} catch (error$1) {
|
|
4484
4628
|
throw new error.MastraError(
|
|
@@ -4561,6 +4705,23 @@ var ExperimentsSpanner = class _ExperimentsSpanner extends storage.ExperimentsSt
|
|
|
4561
4705
|
}
|
|
4562
4706
|
async deleteExperimentResults(args) {
|
|
4563
4707
|
try {
|
|
4708
|
+
if (args.filters?.organizationId !== void 0 || args.filters?.projectId !== void 0) {
|
|
4709
|
+
const conditions = [`${quoteIdent("experimentId", "column name")} = @experimentId`];
|
|
4710
|
+
const params = { experimentId: args.experimentId };
|
|
4711
|
+
if (args.filters?.organizationId !== void 0) {
|
|
4712
|
+
conditions.push(`${quoteIdent("organizationId", "column name")} = @organizationId`);
|
|
4713
|
+
params.organizationId = args.filters.organizationId;
|
|
4714
|
+
}
|
|
4715
|
+
if (args.filters?.projectId !== void 0) {
|
|
4716
|
+
conditions.push(`${quoteIdent("projectId", "column name")} = @projectId`);
|
|
4717
|
+
params.projectId = args.filters.projectId;
|
|
4718
|
+
}
|
|
4719
|
+
await this.db.runDml({
|
|
4720
|
+
sql: `DELETE FROM ${quoteIdent(storage.TABLE_EXPERIMENT_RESULTS, "table name")} WHERE ${conditions.join(" AND ")}`,
|
|
4721
|
+
params
|
|
4722
|
+
});
|
|
4723
|
+
return;
|
|
4724
|
+
}
|
|
4564
4725
|
await this.db.runDml({
|
|
4565
4726
|
sql: `DELETE FROM ${quoteIdent(storage.TABLE_EXPERIMENT_RESULTS, "table name")}
|
|
4566
4727
|
WHERE ${quoteIdent("experimentId", "column name")} = @experimentId`,
|
|
@@ -10550,6 +10711,16 @@ function transformScoreRow(row) {
|
|
|
10550
10711
|
const normalized = transformFromSpannerRow({ tableName: storage.TABLE_SCORERS, row });
|
|
10551
10712
|
return storage.transformScoreRow(normalized, { convertTimestamps: true });
|
|
10552
10713
|
}
|
|
10714
|
+
function applyTenancyFilters(conditions, params, filters) {
|
|
10715
|
+
if (filters?.organizationId !== void 0) {
|
|
10716
|
+
conditions.push(`${quoteIdent("organizationId", "column name")} = @organizationId`);
|
|
10717
|
+
params.organizationId = filters.organizationId;
|
|
10718
|
+
}
|
|
10719
|
+
if (filters?.projectId !== void 0) {
|
|
10720
|
+
conditions.push(`${quoteIdent("projectId", "column name")} = @projectId`);
|
|
10721
|
+
params.projectId = filters.projectId;
|
|
10722
|
+
}
|
|
10723
|
+
}
|
|
10553
10724
|
var ScoresSpanner = class _ScoresSpanner extends storage.ScoresStorage {
|
|
10554
10725
|
database;
|
|
10555
10726
|
db;
|
|
@@ -10728,7 +10899,8 @@ var ScoresSpanner = class _ScoresSpanner extends storage.ScoresStorage {
|
|
|
10728
10899
|
pagination,
|
|
10729
10900
|
entityId,
|
|
10730
10901
|
entityType,
|
|
10731
|
-
source
|
|
10902
|
+
source,
|
|
10903
|
+
filters
|
|
10732
10904
|
}) {
|
|
10733
10905
|
try {
|
|
10734
10906
|
const conditions = [`${quoteIdent("scorerId", "column name")} = @scorerId`];
|
|
@@ -10745,6 +10917,7 @@ var ScoresSpanner = class _ScoresSpanner extends storage.ScoresStorage {
|
|
|
10745
10917
|
conditions.push(`${quoteIdent("source", "column name")} = @source`);
|
|
10746
10918
|
params.source = source;
|
|
10747
10919
|
}
|
|
10920
|
+
applyTenancyFilters(conditions, params, filters);
|
|
10748
10921
|
return await this.listScoresByConditions(conditions, params, pagination);
|
|
10749
10922
|
} catch (error$1) {
|
|
10750
10923
|
throw new error.MastraError(
|
|
@@ -10760,14 +10933,14 @@ var ScoresSpanner = class _ScoresSpanner extends storage.ScoresStorage {
|
|
|
10760
10933
|
}
|
|
10761
10934
|
async listScoresByRunId({
|
|
10762
10935
|
runId,
|
|
10763
|
-
pagination
|
|
10936
|
+
pagination,
|
|
10937
|
+
filters
|
|
10764
10938
|
}) {
|
|
10765
10939
|
try {
|
|
10766
|
-
|
|
10767
|
-
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
);
|
|
10940
|
+
const conditions = [`${quoteIdent("runId", "column name")} = @runId`];
|
|
10941
|
+
const params = { runId };
|
|
10942
|
+
applyTenancyFilters(conditions, params, filters);
|
|
10943
|
+
return await this.listScoresByConditions(conditions, params, pagination);
|
|
10771
10944
|
} catch (error$1) {
|
|
10772
10945
|
throw new error.MastraError(
|
|
10773
10946
|
{
|
|
@@ -10783,17 +10956,17 @@ var ScoresSpanner = class _ScoresSpanner extends storage.ScoresStorage {
|
|
|
10783
10956
|
async listScoresByEntityId({
|
|
10784
10957
|
entityId,
|
|
10785
10958
|
entityType,
|
|
10786
|
-
pagination
|
|
10959
|
+
pagination,
|
|
10960
|
+
filters
|
|
10787
10961
|
}) {
|
|
10788
10962
|
try {
|
|
10789
|
-
|
|
10790
|
-
|
|
10791
|
-
|
|
10792
|
-
|
|
10793
|
-
|
|
10794
|
-
|
|
10795
|
-
|
|
10796
|
-
);
|
|
10963
|
+
const conditions = [
|
|
10964
|
+
`${quoteIdent("entityId", "column name")} = @entityId`,
|
|
10965
|
+
`${quoteIdent("entityType", "column name")} = @entityType`
|
|
10966
|
+
];
|
|
10967
|
+
const params = { entityId, entityType };
|
|
10968
|
+
applyTenancyFilters(conditions, params, filters);
|
|
10969
|
+
return await this.listScoresByConditions(conditions, params, pagination);
|
|
10797
10970
|
} catch (error$1) {
|
|
10798
10971
|
throw new error.MastraError(
|
|
10799
10972
|
{
|
|
@@ -10809,14 +10982,17 @@ var ScoresSpanner = class _ScoresSpanner extends storage.ScoresStorage {
|
|
|
10809
10982
|
async listScoresBySpan({
|
|
10810
10983
|
traceId,
|
|
10811
10984
|
spanId,
|
|
10812
|
-
pagination
|
|
10985
|
+
pagination,
|
|
10986
|
+
filters
|
|
10813
10987
|
}) {
|
|
10814
10988
|
try {
|
|
10815
|
-
|
|
10816
|
-
|
|
10817
|
-
{
|
|
10818
|
-
|
|
10819
|
-
|
|
10989
|
+
const conditions = [
|
|
10990
|
+
`${quoteIdent("traceId", "column name")} = @traceId`,
|
|
10991
|
+
`${quoteIdent("spanId", "column name")} = @spanId`
|
|
10992
|
+
];
|
|
10993
|
+
const params = { traceId, spanId };
|
|
10994
|
+
applyTenancyFilters(conditions, params, filters);
|
|
10995
|
+
return await this.listScoresByConditions(conditions, params, pagination);
|
|
10820
10996
|
} catch (error$1) {
|
|
10821
10997
|
throw new error.MastraError(
|
|
10822
10998
|
{
|