@mastra/spanner 1.2.4-alpha.1 → 1.3.0

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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Spanner } from '@google-cloud/spanner';
2
2
  import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
3
- import { AgentsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, AGENTS_SCHEMA, AGENT_VERSIONS_SCHEMA, createStorageErrorId, normalizePerPage, calculatePagination, TABLE_FAVORITES, BackgroundTasksStorage, TABLE_BACKGROUND_TASKS, TABLE_SCHEMAS, BlobStore, TABLE_SKILL_BLOBS, SKILL_BLOBS_SCHEMA, ChannelsStorage, TABLE_CHANNEL_INSTALLATIONS, TABLE_CHANNEL_CONFIG, DatasetsStorage, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENT_RESULTS, TABLE_EXPERIMENTS, ExperimentsStorage, FavoritesStorage, TABLE_SKILLS, MCPClientsStorage, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, MCP_CLIENTS_SCHEMA, MCP_CLIENT_VERSIONS_SCHEMA, MCPServersStorage, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, MCP_SERVERS_SCHEMA, MCP_SERVER_VERSIONS_SCHEMA, MemoryStorage, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES, ObservabilityStorage, TABLE_SPANS, listTracesArgsSchema, TraceStatus, toTraceSpans, PromptBlocksStorage, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, PROMPT_BLOCKS_SCHEMA, PROMPT_BLOCK_VERSIONS_SCHEMA, SchedulesStorage, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, ScorerDefinitionsStorage, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, SCORER_DEFINITIONS_SCHEMA, SCORER_DEFINITION_VERSIONS_SCHEMA, ScoresStorage, TABLE_SCORERS, SkillsStorage, TABLE_SKILL_VERSIONS, SKILLS_SCHEMA, SKILL_VERSIONS_SCHEMA, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, WorkspacesStorage, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, WORKSPACES_SCHEMA, WORKSPACE_VERSIONS_SCHEMA, MastraCompositeStore, getDefaultValue, TABLE_CONFIGS, listMetricsArgsSchema, normalizeScheduleTarget, transformScoreRow as transformScoreRow$1, METRIC_DISTINCT_COLUMNS } from '@mastra/core/storage';
3
+ import { AgentsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, AGENTS_SCHEMA, AGENT_VERSIONS_SCHEMA, createStorageErrorId, normalizePerPage, calculatePagination, TABLE_FAVORITES, BackgroundTasksStorage, TABLE_BACKGROUND_TASKS, TABLE_SCHEMAS, BlobStore, TABLE_SKILL_BLOBS, SKILL_BLOBS_SCHEMA, ChannelsStorage, TABLE_CHANNEL_INSTALLATIONS, TABLE_CHANNEL_CONFIG, DatasetsStorage, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENT_RESULTS, TABLE_EXPERIMENTS, hasErrorCode, ExperimentsStorage, FavoritesStorage, TABLE_SKILLS, MCPClientsStorage, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, MCP_CLIENTS_SCHEMA, MCP_CLIENT_VERSIONS_SCHEMA, MCPServersStorage, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, MCP_SERVERS_SCHEMA, MCP_SERVER_VERSIONS_SCHEMA, MemoryStorage, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES, ObservabilityStorage, TABLE_SPANS, listTracesArgsSchema, TraceStatus, toTraceSpans, PromptBlocksStorage, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, PROMPT_BLOCKS_SCHEMA, PROMPT_BLOCK_VERSIONS_SCHEMA, SchedulesStorage, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, ScorerDefinitionsStorage, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, SCORER_DEFINITIONS_SCHEMA, SCORER_DEFINITION_VERSIONS_SCHEMA, ScoresStorage, TABLE_SCORERS, SkillsStorage, TABLE_SKILL_VERSIONS, SKILLS_SCHEMA, SKILL_VERSIONS_SCHEMA, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, WorkspacesStorage, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, WORKSPACES_SCHEMA, WORKSPACE_VERSIONS_SCHEMA, MastraCompositeStore, getDefaultValue, TABLE_CONFIGS, listMetricsArgsSchema, normalizeScheduleTarget, transformScoreRow as transformScoreRow$1, METRIC_DISTINCT_COLUMNS } from '@mastra/core/storage';
4
4
  import { MastraBase } from '@mastra/core/base';
5
5
  import { parseSqlIdentifier } from '@mastra/core/utils';
6
6
  import { randomUUID } from 'crypto';
@@ -2993,6 +2993,7 @@ function rowToItem(row) {
2993
2993
  id: String(t.id),
2994
2994
  datasetId: String(t.datasetId),
2995
2995
  datasetVersion: Number(t.datasetVersion),
2996
+ externalId: t.externalId ?? null,
2996
2997
  organizationId: t.organizationId ?? null,
2997
2998
  projectId: t.projectId ?? null,
2998
2999
  input: t.input,
@@ -3057,7 +3058,7 @@ var DatasetsSpanner = class _DatasetsSpanner extends DatasetsStorage {
3057
3058
  await this.db.alterTable({
3058
3059
  tableName: TABLE_DATASET_ITEMS,
3059
3060
  schema: TABLE_SCHEMAS[TABLE_DATASET_ITEMS],
3060
- ifNotExists: ["organizationId", "projectId"]
3061
+ ifNotExists: ["organizationId", "projectId", "externalId"]
3061
3062
  });
3062
3063
  await this.createDefaultIndexes();
3063
3064
  await this.createCustomIndexes();
@@ -3076,6 +3077,11 @@ var DatasetsSpanner = class _DatasetsSpanner extends DatasetsStorage {
3076
3077
  table: TABLE_DATASET_ITEMS,
3077
3078
  columns: ["datasetId", "datasetVersion"]
3078
3079
  },
3080
+ {
3081
+ name: "mastra_dataset_items_dataset_externalid_version_idx",
3082
+ table: TABLE_DATASET_ITEMS,
3083
+ columns: ["datasetId", "externalId", "datasetVersion"]
3084
+ },
3079
3085
  {
3080
3086
  // Unique invariant: one snapshot row per (datasetId, version). The DESC
3081
3087
  // ordering also serves listDatasetVersions' newest-first scan.
@@ -3119,7 +3125,8 @@ var DatasetsSpanner = class _DatasetsSpanner extends DatasetsStorage {
3119
3125
  async createDataset(input) {
3120
3126
  try {
3121
3127
  const now = /* @__PURE__ */ new Date();
3122
- const id = randomUUID();
3128
+ const id = input.id ?? randomUUID();
3129
+ if (input.id !== void 0) this.validateCallerDefinedDatasetId(input.id);
3123
3130
  const record = {
3124
3131
  id,
3125
3132
  name: input.name,
@@ -3165,6 +3172,11 @@ var DatasetsSpanner = class _DatasetsSpanner extends DatasetsStorage {
3165
3172
  });
3166
3173
  return record;
3167
3174
  } catch (error) {
3175
+ if (input.id !== void 0 && hasErrorCode(error, /* @__PURE__ */ new Set([6, "ALREADY_EXISTS"]))) {
3176
+ const existing = await this.getDatasetById({ id: input.id });
3177
+ if (existing) return this.resolveExistingDataset(existing, { ...input, id: input.id });
3178
+ }
3179
+ if (error instanceof MastraError) throw error;
3168
3180
  throw new MastraError(
3169
3181
  {
3170
3182
  id: createStorageErrorId("SPANNER", "CREATE_DATASET", "FAILED"),
@@ -3587,6 +3599,7 @@ var DatasetsSpanner = class _DatasetsSpanner extends DatasetsStorage {
3587
3599
  id: args.id,
3588
3600
  datasetId: args.datasetId,
3589
3601
  datasetVersion: newVersion,
3602
+ externalId: existing.externalId ?? null,
3590
3603
  organizationId,
3591
3604
  projectId,
3592
3605
  validTo: null,
@@ -3609,6 +3622,7 @@ var DatasetsSpanner = class _DatasetsSpanner extends DatasetsStorage {
3609
3622
  id: args.id,
3610
3623
  datasetId: args.datasetId,
3611
3624
  datasetVersion: newVersion,
3625
+ externalId: existing.externalId ?? null,
3612
3626
  organizationId,
3613
3627
  projectId,
3614
3628
  input: merged.input,
@@ -3668,6 +3682,7 @@ var DatasetsSpanner = class _DatasetsSpanner extends DatasetsStorage {
3668
3682
  id: args.id,
3669
3683
  datasetId: args.datasetId,
3670
3684
  datasetVersion: newVersion,
3685
+ externalId: existing.externalId ?? null,
3671
3686
  organizationId,
3672
3687
  projectId,
3673
3688
  validTo: null,
@@ -3937,55 +3952,75 @@ var DatasetsSpanner = class _DatasetsSpanner extends DatasetsStorage {
3937
3952
  async _doBatchInsertItems(input) {
3938
3953
  try {
3939
3954
  if (input.items.length === 0) return [];
3940
- const now = /* @__PURE__ */ new Date();
3941
- const prepared = input.items.map((item) => ({ id: randomUUID(), item }));
3942
3955
  let result = [];
3943
3956
  await this.db.runWithAbortRetry(
3944
3957
  () => this.database.runTransactionAsync(async (tx) => {
3945
3958
  try {
3946
- const { version: newVersion, organizationId, projectId } = await this.bumpVersion(tx, input.datasetId, now);
3947
- for (const { id, item } of prepared) {
3948
- await this.db.insert({
3949
- tableName: TABLE_DATASET_ITEMS,
3950
- record: {
3951
- id,
3959
+ const [datasetRows] = await tx.run({
3960
+ sql: `SELECT * FROM ${quoteIdent(TABLE_DATASETS, "table name")} WHERE ${quoteIdent("id", "column name")} = @id LIMIT 1`,
3961
+ params: { id: input.datasetId },
3962
+ json: true
3963
+ });
3964
+ const dataset = datasetRows[0];
3965
+ if (!dataset) {
3966
+ throw new MastraError({
3967
+ id: createStorageErrorId("SPANNER", "BATCH_INSERT_ITEMS", "DATASET_NOT_FOUND"),
3968
+ domain: ErrorDomain.STORAGE,
3969
+ category: ErrorCategory.USER,
3970
+ details: { datasetId: input.datasetId }
3971
+ });
3972
+ }
3973
+ const externalIds = [...new Set(input.items.flatMap((item) => item.externalId ? [item.externalId] : []))];
3974
+ let historyRows = [];
3975
+ if (externalIds.length > 0) {
3976
+ const [rows] = await tx.run({
3977
+ sql: `SELECT * FROM ${quoteIdent(TABLE_DATASET_ITEMS, "table name")} WHERE ${quoteIdent("datasetId", "column name")} = @datasetId AND ${quoteIdent("externalId", "column name")} IN UNNEST(@externalIds) ORDER BY ${quoteIdent("datasetVersion", "column name")}`,
3978
+ params: { datasetId: input.datasetId, externalIds },
3979
+ types: { externalIds: { type: "array", child: "string" } },
3980
+ json: true
3981
+ });
3982
+ historyRows = rows.map(rowToItemRow);
3983
+ }
3984
+ const plan = this.planDatasetItemBatch(input.items, historyRows, randomUUID);
3985
+ const resolved = new Map(
3986
+ [...plan.existingCurrentItems].map(([id, row]) => [id, this.datasetItemFromRow(row)])
3987
+ );
3988
+ if (plan.inserts.length > 0) {
3989
+ const now = /* @__PURE__ */ new Date();
3990
+ const {
3991
+ version: newVersion,
3992
+ organizationId,
3993
+ projectId
3994
+ } = await this.bumpVersion(tx, input.datasetId, now);
3995
+ for (const insert of plan.inserts) {
3996
+ const item = {
3997
+ id: insert.id,
3952
3998
  datasetId: input.datasetId,
3953
3999
  datasetVersion: newVersion,
4000
+ externalId: insert.item.externalId ?? null,
3954
4001
  organizationId,
3955
4002
  projectId,
3956
- validTo: null,
3957
- isDeleted: false,
3958
- input: item.input,
3959
- groundTruth: item.groundTruth ?? null,
3960
- expectedTrajectory: item.expectedTrajectory ?? null,
3961
- toolMocks: item.toolMocks ?? null,
3962
- requestContext: item.requestContext ?? null,
3963
- metadata: item.metadata ?? null,
3964
- source: item.source ?? null,
4003
+ input: insert.item.input,
4004
+ groundTruth: insert.item.groundTruth,
4005
+ expectedTrajectory: insert.item.expectedTrajectory,
4006
+ toolMocks: insert.item.toolMocks,
4007
+ requestContext: insert.item.requestContext,
4008
+ metadata: insert.item.metadata,
4009
+ source: insert.item.source,
3965
4010
  createdAt: now,
3966
4011
  updatedAt: now
3967
- },
3968
- transaction: tx
3969
- });
4012
+ };
4013
+ await this.db.insert({
4014
+ tableName: TABLE_DATASET_ITEMS,
4015
+ record: { ...item, validTo: null, isDeleted: false },
4016
+ transaction: tx
4017
+ });
4018
+ resolved.set(item.id, item);
4019
+ }
4020
+ await this.insertVersionRow(tx, input.datasetId, newVersion, now);
3970
4021
  }
3971
- await this.insertVersionRow(tx, input.datasetId, newVersion, now);
3972
4022
  await tx.commit();
3973
- result = prepared.map(({ id, item }) => ({
3974
- id,
3975
- datasetId: input.datasetId,
3976
- datasetVersion: newVersion,
3977
- organizationId,
3978
- projectId,
3979
- input: item.input,
3980
- groundTruth: item.groundTruth,
3981
- expectedTrajectory: item.expectedTrajectory,
3982
- toolMocks: item.toolMocks,
3983
- requestContext: item.requestContext,
3984
- metadata: item.metadata,
3985
- source: item.source,
3986
- createdAt: now,
3987
- updatedAt: now
3988
- }));
4023
+ result = plan.resolvedIds.map((id) => resolved.get(id));
3989
4024
  } catch (err) {
3990
4025
  await tx.rollback().catch((rollbackErr) => {
3991
4026
  throw new AggregateError([err, rollbackErr], "Transaction and rollback both failed");
@@ -4029,6 +4064,7 @@ var DatasetsSpanner = class _DatasetsSpanner extends DatasetsStorage {
4029
4064
  id: existing.id,
4030
4065
  datasetId: input.datasetId,
4031
4066
  datasetVersion: newVersion,
4067
+ externalId: existing.externalId ?? null,
4032
4068
  organizationId,
4033
4069
  projectId,
4034
4070
  validTo: null,