@mastra/spanner 1.5.0-alpha.0 → 1.5.0-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 CHANGED
@@ -1,5 +1,49 @@
1
1
  # @mastra/spanner
2
2
 
3
+ ## 1.5.0-alpha.1
4
+
5
+ ### Minor Changes
6
+
7
+ - Stored workflow definitions now persist across restarts on every major database backend. ([#20471](https://github.com/mastra-ai/mastra/pull/20471))
8
+
9
+ Implement the `workflowDefinitions` storage domain for libsql, pg, mysql, mssql, mongodb, and spanner. Previously the stored-workflow persistence path (`POST /stored/workflows`, `Mastra.addStoredWorkflow`) only worked against `@mastra/core`'s in-memory store. Persistent adapters returned `undefined` from `storage.getStore('workflowDefinitions')` and threw when the HTTP handler tried to read/write a workflow.
10
+
11
+ ```ts
12
+ const workflowDefinitions = await storage.getStore('workflowDefinitions');
13
+ if (!workflowDefinitions) {
14
+ throw new Error('This storage adapter does not support the workflowDefinitions domain');
15
+ }
16
+
17
+ await workflowDefinitions.upsert({
18
+ id: 'greeting-workflow',
19
+ inputSchema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] },
20
+ outputSchema: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] },
21
+ graph: [{ type: 'agent', id: 'greet', agentId: 'greeter-agent' }],
22
+ });
23
+
24
+ const { definitions, total } = await workflowDefinitions.list({ status: 'active' });
25
+ const definition = await workflowDefinitions.get('greeting-workflow');
26
+ await workflowDefinitions.delete('greeting-workflow');
27
+ ```
28
+
29
+ Each adapter now ships a `WorkflowDefinitions*` domain that:
30
+
31
+ - Creates the shared `mastra_workflow_definitions` table (or Mongo collection) from `WORKFLOW_DEFINITIONS_SCHEMA` during `init()`, plus a default index on `status`.
32
+ - Implements `upsert` / `get` / `list` / `delete` matching `WorkflowDefinitionsStorage` semantics (`list` supports `status` and `authorId` filters and orders by `updatedAt` desc). Partial upserts preserve unspecified fields, including `authorId` updates and `createdAt` / `updatedAt` semantics.
33
+ - Handles concurrent first-writes race-safely: if two callers upsert the same new id simultaneously, the losing insert detects the duplicate key, re-reads the row, and applies the partial-update path instead of failing.
34
+ - Round-trips the JSON columns (`inputSchema`, `outputSchema`, `stateSchema`, `requestContextSchema`, `metadata`, `graph`) through each adapter's JSON handling, so declarative workflow graphs rehydrate identically no matter which backend they were stored in. Malformed persisted JSON surfaces as an actionable error naming the row and column instead of hydrating raw strings.
35
+
36
+ Exported class names by adapter: `WorkflowDefinitionsLibSQL`, `WorkflowDefinitionsPG`, `WorkflowDefinitionsMySQL`, `WorkflowDefinitionsMSSQL`, `MongoDBWorkflowDefinitionsStore`, `WorkflowDefinitionsSpanner`. The composite stores (`LibSQLStore`, `PostgresStore`, `MySQLStore`, `MSSQLStore`, `MongoDBStore`, `SpannerStore`) auto-wire the new domain, so callers do not need to construct it manually — `storage.getStore('workflowDefinitions')` now returns a live handle.
37
+
38
+ The pg adapter reads `createdAt` / `updatedAt` from the auto-added `createdAtZ` / `updatedAtZ` `timestamptz` companion columns to avoid the naive-timestamp / local-TZ drift that a plain `TIMESTAMP` read exhibits under node-pg.
39
+
40
+ `@mastra/clickhouse` and `@mastra/cloudflare` register the new `mastra_workflow_definitions` table in their table/type maps so shared table constants stay exhaustive (no workflow-definitions domain implementation yet).
41
+
42
+ ### Patch Changes
43
+
44
+ - Updated dependencies [[`4844167`](https://github.com/mastra-ai/mastra/commit/4844167cff2d5ec5004e94edd34970833040fa3f), [`5faf93f`](https://github.com/mastra-ai/mastra/commit/5faf93f03e19daea394b9e2a923f2e4f833407f2), [`80ad891`](https://github.com/mastra-ai/mastra/commit/80ad891f8cd10379aa5b5af7510c763783b2ab56), [`a1cb98d`](https://github.com/mastra-ai/mastra/commit/a1cb98d11990b560b98482292a1f34aa1a2d9092), [`598ad82`](https://github.com/mastra-ai/mastra/commit/598ad82d41c41389a686338a1d0e50b7400e1938), [`1fd6aad`](https://github.com/mastra-ai/mastra/commit/1fd6aad1ea4a9d32f65efa832307c35e981a4c0a)]:
45
+ - @mastra/core@1.56.0-alpha.4
46
+
3
47
  ## 1.5.0-alpha.0
4
48
 
5
49
  ### Minor Changes
@@ -3,7 +3,7 @@ name: mastra-spanner
3
3
  description: Documentation for @mastra/spanner. Use when working with @mastra/spanner APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/spanner"
6
- version: "1.5.0-alpha.0"
6
+ version: "1.5.0-alpha.1"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.5.0-alpha.0",
2
+ "version": "1.5.0-alpha.1",
3
3
  "package": "@mastra/spanner",
4
4
  "exports": {},
5
5
  "modules": {}
package/dist/index.cjs CHANGED
@@ -11501,6 +11501,169 @@ var SkillsSpanner = class SkillsSpanner extends _mastra_core_storage.SkillsStora
11501
11501
  }
11502
11502
  };
11503
11503
  //#endregion
11504
+ //#region src/storage/domains/workflow-definitions/index.ts
11505
+ function rowToDefinition(row) {
11506
+ const parsed = transformFromSpannerRow({
11507
+ tableName: _mastra_core_storage.TABLE_WORKFLOW_DEFINITIONS,
11508
+ row
11509
+ });
11510
+ if (parsed.inputSchema == null || parsed.outputSchema == null || parsed.graph == null) throw new Error(`Workflow definition row "${String(parsed.id)}" is missing required JSON columns.`);
11511
+ const def = {
11512
+ id: String(parsed.id),
11513
+ inputSchema: parsed.inputSchema,
11514
+ outputSchema: parsed.outputSchema,
11515
+ graph: parsed.graph,
11516
+ status: parsed.status,
11517
+ source: parsed.source,
11518
+ createdAt: parsed.createdAt instanceof Date ? parsed.createdAt : new Date(parsed.createdAt),
11519
+ updatedAt: parsed.updatedAt instanceof Date ? parsed.updatedAt : new Date(parsed.updatedAt)
11520
+ };
11521
+ if (parsed.description != null) def.description = parsed.description;
11522
+ if (parsed.metadata != null) def.metadata = parsed.metadata;
11523
+ if (parsed.stateSchema != null) def.stateSchema = parsed.stateSchema;
11524
+ if (parsed.requestContextSchema != null) def.requestContextSchema = parsed.requestContextSchema;
11525
+ if (parsed.authorId != null) def.authorId = parsed.authorId;
11526
+ return def;
11527
+ }
11528
+ var WorkflowDefinitionsSpanner = class WorkflowDefinitionsSpanner extends _mastra_core_storage.WorkflowDefinitionsStorage {
11529
+ database;
11530
+ db;
11531
+ skipDefaultIndexes;
11532
+ indexes;
11533
+ static MANAGED_TABLES = [_mastra_core_storage.TABLE_WORKFLOW_DEFINITIONS];
11534
+ constructor(config) {
11535
+ super();
11536
+ const { database, indexes, skipDefaultIndexes, initMode } = resolveSpannerConfig(config);
11537
+ this.database = database;
11538
+ this.db = new SpannerDB({
11539
+ database,
11540
+ skipDefaultIndexes,
11541
+ initMode
11542
+ });
11543
+ this.skipDefaultIndexes = skipDefaultIndexes;
11544
+ this.indexes = indexes?.filter((idx) => WorkflowDefinitionsSpanner.MANAGED_TABLES.includes(idx.table));
11545
+ }
11546
+ async init() {
11547
+ await this.db.createTable({
11548
+ tableName: _mastra_core_storage.TABLE_WORKFLOW_DEFINITIONS,
11549
+ schema: _mastra_core_storage.WORKFLOW_DEFINITIONS_SCHEMA
11550
+ });
11551
+ await this.createDefaultIndexes();
11552
+ await this.createCustomIndexes();
11553
+ }
11554
+ getDefaultIndexDefinitions() {
11555
+ return [{
11556
+ name: "mastra_workflow_definitions_status_idx",
11557
+ table: _mastra_core_storage.TABLE_WORKFLOW_DEFINITIONS,
11558
+ columns: ["status"]
11559
+ }];
11560
+ }
11561
+ async createDefaultIndexes() {
11562
+ if (this.skipDefaultIndexes) return;
11563
+ await this.db.createIndexes(this.getDefaultIndexDefinitions());
11564
+ }
11565
+ async createCustomIndexes() {
11566
+ if (!this.indexes || this.indexes.length === 0) return;
11567
+ await this.db.createIndexes(this.indexes);
11568
+ }
11569
+ async dangerouslyClearAll() {
11570
+ await this.db.clearTable({ tableName: _mastra_core_storage.TABLE_WORKFLOW_DEFINITIONS });
11571
+ }
11572
+ async upsert(input) {
11573
+ const now = /* @__PURE__ */ new Date();
11574
+ if (!await this.get(input.id)) {
11575
+ if (!("inputSchema" in input) || input.inputSchema === void 0) throw new Error(`Cannot create workflow definition "${input.id}": inputSchema is required.`);
11576
+ if (!("outputSchema" in input) || input.outputSchema === void 0) throw new Error(`Cannot create workflow definition "${input.id}": outputSchema is required.`);
11577
+ if (!("graph" in input) || input.graph === void 0) throw new Error(`Cannot create workflow definition "${input.id}": graph is required.`);
11578
+ const record = {
11579
+ id: input.id,
11580
+ description: input.description ?? null,
11581
+ metadata: input.metadata ?? null,
11582
+ inputSchema: input.inputSchema,
11583
+ outputSchema: input.outputSchema,
11584
+ stateSchema: input.stateSchema ?? null,
11585
+ requestContextSchema: input.requestContextSchema ?? null,
11586
+ graph: input.graph,
11587
+ status: "active",
11588
+ source: "storage",
11589
+ authorId: "authorId" in input ? input.authorId ?? null : null,
11590
+ createdAt: now,
11591
+ updatedAt: now
11592
+ };
11593
+ try {
11594
+ await this.db.insert({
11595
+ tableName: _mastra_core_storage.TABLE_WORKFLOW_DEFINITIONS,
11596
+ record
11597
+ });
11598
+ } catch (error) {
11599
+ if (!await this.get(input.id)) throw error;
11600
+ return this.applyUpdate(input, now);
11601
+ }
11602
+ const created = await this.get(input.id);
11603
+ if (!created) throw new Error(`Failed to persist workflow definition "${input.id}".`);
11604
+ return created;
11605
+ }
11606
+ return this.applyUpdate(input, now);
11607
+ }
11608
+ async applyUpdate(input, now) {
11609
+ const data = { updatedAt: now };
11610
+ if ("description" in input && input.description !== void 0) data.description = input.description;
11611
+ if ("metadata" in input && input.metadata !== void 0) data.metadata = input.metadata;
11612
+ if ("inputSchema" in input && input.inputSchema !== void 0) data.inputSchema = input.inputSchema;
11613
+ if ("outputSchema" in input && input.outputSchema !== void 0) data.outputSchema = input.outputSchema;
11614
+ if ("stateSchema" in input && input.stateSchema !== void 0) data.stateSchema = input.stateSchema;
11615
+ if ("requestContextSchema" in input && input.requestContextSchema !== void 0) data.requestContextSchema = input.requestContextSchema;
11616
+ if ("graph" in input && input.graph !== void 0) data.graph = input.graph;
11617
+ if ("status" in input && input.status !== void 0) data.status = input.status;
11618
+ if ("authorId" in input && input.authorId !== void 0) data.authorId = input.authorId;
11619
+ await this.db.update({
11620
+ tableName: _mastra_core_storage.TABLE_WORKFLOW_DEFINITIONS,
11621
+ keys: { id: input.id },
11622
+ data
11623
+ });
11624
+ const updated = await this.get(input.id);
11625
+ if (!updated) throw new Error(`Failed to update workflow definition "${input.id}".`);
11626
+ return updated;
11627
+ }
11628
+ async get(id) {
11629
+ const row = await this.db.load({
11630
+ tableName: _mastra_core_storage.TABLE_WORKFLOW_DEFINITIONS,
11631
+ keys: { id }
11632
+ });
11633
+ return row ? rowToDefinition(row) : null;
11634
+ }
11635
+ async list(args) {
11636
+ const params = {};
11637
+ const conditions = [];
11638
+ if (args?.status) {
11639
+ params.status = args.status;
11640
+ conditions.push(`${quoteIdent("status", "column name")} = @status`);
11641
+ }
11642
+ if (args?.authorId !== void 0) {
11643
+ params.authorId = args.authorId;
11644
+ conditions.push(`${quoteIdent("authorId", "column name")} = @authorId`);
11645
+ }
11646
+ const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
11647
+ const sql = `SELECT * FROM ${quoteIdent(_mastra_core_storage.TABLE_WORKFLOW_DEFINITIONS, "table name")} ${where} ORDER BY ${quoteIdent("updatedAt", "column name")} DESC`;
11648
+ const [rows] = await this.database.run({
11649
+ sql,
11650
+ params,
11651
+ json: true
11652
+ });
11653
+ const definitions = rows.map(rowToDefinition);
11654
+ return {
11655
+ definitions,
11656
+ total: definitions.length
11657
+ };
11658
+ }
11659
+ async delete(id) {
11660
+ await this.db.runDml({
11661
+ sql: `DELETE FROM ${quoteIdent(_mastra_core_storage.TABLE_WORKFLOW_DEFINITIONS, "table name")} WHERE id = @id`,
11662
+ params: { id }
11663
+ });
11664
+ }
11665
+ };
11666
+ //#endregion
11504
11667
  //#region src/storage/domains/workflows/index.ts
11505
11668
  /**
11506
11669
  * Spanner-backed storage for workflow run snapshots, including persistence,
@@ -12669,7 +12832,8 @@ const SPANNER_DOMAIN_KEYS = [
12669
12832
  "datasets",
12670
12833
  "experiments",
12671
12834
  "favorites",
12672
- "workspaces"
12835
+ "workspaces",
12836
+ "workflowDefinitions"
12673
12837
  ];
12674
12838
  const isPreConfiguredDatabase = (config) => "database" in config && !!config.database;
12675
12839
  /**
@@ -12757,7 +12921,8 @@ var SpannerStore = class extends _mastra_core_storage.MastraCompositeStore {
12757
12921
  ...wants("datasets") && { datasets: new DatasetsSpanner(domainConfig) },
12758
12922
  ...wants("experiments") && { experiments: new ExperimentsSpanner(domainConfig) },
12759
12923
  ...wants("favorites") && { favorites: new FavoritesSpanner(domainConfig) },
12760
- ...wants("workspaces") && { workspaces: new WorkspacesSpanner(domainConfig) }
12924
+ ...wants("workspaces") && { workspaces: new WorkspacesSpanner(domainConfig) },
12925
+ ...wants("workflowDefinitions") && { workflowDefinitions: new WorkflowDefinitionsSpanner(domainConfig) }
12761
12926
  };
12762
12927
  } catch (e) {
12763
12928
  throw new _mastra_core_error.MastraError({
@@ -12813,7 +12978,8 @@ var SpannerStore = class extends _mastra_core_storage.MastraCompositeStore {
12813
12978
  "datasets",
12814
12979
  "experiments",
12815
12980
  "workspaces",
12816
- "favorites"
12981
+ "favorites",
12982
+ "workflowDefinitions"
12817
12983
  ]) {
12818
12984
  const store = this.stores?.[key];
12819
12985
  if (store) await store.init();
@@ -12863,6 +13029,7 @@ exports.ScorerDefinitionsSpanner = ScorerDefinitionsSpanner;
12863
13029
  exports.ScoresSpanner = ScoresSpanner;
12864
13030
  exports.SkillsSpanner = SkillsSpanner;
12865
13031
  exports.SpannerStore = SpannerStore;
13032
+ exports.WorkflowDefinitionsSpanner = WorkflowDefinitionsSpanner;
12866
13033
  exports.WorkflowsSpanner = WorkflowsSpanner;
12867
13034
  exports.WorkspacesSpanner = WorkspacesSpanner;
12868
13035