@mastra/redis 1.4.6 → 1.4.7

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
@@ -36,8 +36,7 @@ function processRecord(tableName, record) {
36
36
  else if (tableName === TABLE_WORKFLOW_SNAPSHOT) key = getKey(tableName, {
37
37
  namespace: record.namespace || "workflows",
38
38
  workflow_name: record.workflow_name,
39
- run_id: record.run_id,
40
- ...record.resourceId ? { resourceId: record.resourceId } : {}
39
+ run_id: record.run_id
41
40
  });
42
41
  else key = getKey(tableName, { id: record.id });
43
42
  const processedRecord = {
@@ -1171,6 +1170,22 @@ function parseWorkflowRun(row) {
1171
1170
  resourceId: row.resourceId
1172
1171
  };
1173
1172
  }
1173
+ function escapeGlob(value) {
1174
+ return value.replace(/[*?[\]\\]/g, "\\$&");
1175
+ }
1176
+ function snapshotKey(namespace, workflowName, runId) {
1177
+ return getKey(TABLE_WORKFLOW_SNAPSHOT, {
1178
+ namespace,
1179
+ workflow_name: workflowName,
1180
+ run_id: runId
1181
+ });
1182
+ }
1183
+ /**
1184
+ * Earlier versions appended `:resourceId:<id>` to snapshot keys. Matches those keys for a run.
1185
+ */
1186
+ function legacySnapshotKeyPattern(namespace, workflowName, runId) {
1187
+ return `${escapeGlob(snapshotKey(namespace, workflowName, runId))}:resourceId:*`;
1188
+ }
1174
1189
  var WorkflowsRedis = class extends WorkflowsStorage {
1175
1190
  client;
1176
1191
  db;
@@ -1179,6 +1194,18 @@ var WorkflowsRedis = class extends WorkflowsStorage {
1179
1194
  this.client = config.client;
1180
1195
  this.db = new RedisDB({ client: config.client });
1181
1196
  }
1197
+ /**
1198
+ * Reads a run's snapshot record by its canonical key, falling back to legacy
1199
+ * `:resourceId:`-suffixed keys only when the canonical key is missing.
1200
+ */
1201
+ async getSnapshotRecord(namespace, workflowName, runId) {
1202
+ const data = await this.client.get(snapshotKey(namespace, workflowName, runId));
1203
+ if (data) return JSON.parse(data);
1204
+ const legacyKeys = await this.db.scanKeys(legacySnapshotKeyPattern(namespace, workflowName, runId));
1205
+ if (legacyKeys.length === 0) return null;
1206
+ const legacyData = await this.client.get(legacyKeys[0]);
1207
+ return legacyData ? JSON.parse(legacyData) : null;
1208
+ }
1182
1209
  supportsConcurrentUpdates() {
1183
1210
  return false;
1184
1211
  }
@@ -1187,14 +1214,7 @@ var WorkflowsRedis = class extends WorkflowsStorage {
1187
1214
  }
1188
1215
  async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext }) {
1189
1216
  try {
1190
- const existingRecord = await this.db.get({
1191
- tableName: TABLE_WORKFLOW_SNAPSHOT,
1192
- keys: {
1193
- namespace: "workflows",
1194
- workflow_name: workflowName,
1195
- run_id: runId
1196
- }
1197
- });
1217
+ const existingRecord = await this.getSnapshotRecord("workflows", workflowName, runId);
1198
1218
  let snapshot = existingRecord?.snapshot;
1199
1219
  if (!snapshot) snapshot = {
1200
1220
  context: {},
@@ -1219,6 +1239,7 @@ var WorkflowsRedis = class extends WorkflowsStorage {
1219
1239
  namespace: "workflows",
1220
1240
  workflowName,
1221
1241
  runId,
1242
+ resourceId: existingRecord?.resourceId,
1222
1243
  snapshot,
1223
1244
  createdAt: existingRecord?.createdAt ? ensureDate(existingRecord.createdAt) : void 0
1224
1245
  });
@@ -1239,14 +1260,7 @@ var WorkflowsRedis = class extends WorkflowsStorage {
1239
1260
  }
1240
1261
  async updateWorkflowState({ workflowName, runId, opts }) {
1241
1262
  try {
1242
- const existingRecord = await this.db.get({
1243
- tableName: TABLE_WORKFLOW_SNAPSHOT,
1244
- keys: {
1245
- namespace: "workflows",
1246
- workflow_name: workflowName,
1247
- run_id: runId
1248
- }
1249
- });
1263
+ const existingRecord = await this.getSnapshotRecord("workflows", workflowName, runId);
1250
1264
  const existingSnapshot = existingRecord?.snapshot;
1251
1265
  if (!existingSnapshot || !existingSnapshot.context) return;
1252
1266
  const { expectedStatus, ...state } = opts;
@@ -1259,6 +1273,7 @@ var WorkflowsRedis = class extends WorkflowsStorage {
1259
1273
  namespace: "workflows",
1260
1274
  workflowName,
1261
1275
  runId,
1276
+ resourceId: existingRecord.resourceId,
1262
1277
  snapshot: updatedSnapshot,
1263
1278
  createdAt: existingRecord?.createdAt ? ensureDate(existingRecord.createdAt) : void 0
1264
1279
  });
@@ -1280,16 +1295,13 @@ var WorkflowsRedis = class extends WorkflowsStorage {
1280
1295
  const { namespace = "workflows", workflowName, runId, resourceId, snapshot, createdAt, updatedAt } = params;
1281
1296
  try {
1282
1297
  let finalCreatedAt = createdAt;
1283
- if (!finalCreatedAt) {
1284
- const existing = await this.db.get({
1285
- tableName: TABLE_WORKFLOW_SNAPSHOT,
1286
- keys: {
1287
- namespace,
1288
- workflow_name: workflowName,
1289
- run_id: runId
1290
- }
1291
- });
1292
- finalCreatedAt = existing?.createdAt ? ensureDate(existing.createdAt) : /* @__PURE__ */ new Date();
1298
+ let finalResourceId = resourceId;
1299
+ if (!finalCreatedAt || !finalResourceId) {
1300
+ const canonicalKey = snapshotKey(namespace, workflowName, runId);
1301
+ const existingData = await this.client.get(canonicalKey) ?? (resourceId ? await this.client.get(`${canonicalKey}:resourceId:${resourceId}`) : null);
1302
+ const existing = existingData ? JSON.parse(existingData) : null;
1303
+ finalCreatedAt ??= existing?.createdAt ? ensureDate(existing.createdAt) : /* @__PURE__ */ new Date();
1304
+ finalResourceId ??= existing?.resourceId;
1293
1305
  }
1294
1306
  await this.db.insert({
1295
1307
  tableName: TABLE_WORKFLOW_SNAPSHOT,
@@ -1297,7 +1309,7 @@ var WorkflowsRedis = class extends WorkflowsStorage {
1297
1309
  namespace,
1298
1310
  workflow_name: workflowName,
1299
1311
  run_id: runId,
1300
- resourceId,
1312
+ resourceId: finalResourceId,
1301
1313
  snapshot,
1302
1314
  createdAt: finalCreatedAt,
1303
1315
  updatedAt: updatedAt ?? /* @__PURE__ */ new Date()
@@ -1318,15 +1330,8 @@ var WorkflowsRedis = class extends WorkflowsStorage {
1318
1330
  }
1319
1331
  async loadWorkflowSnapshot(params) {
1320
1332
  const { namespace = "workflows", workflowName, runId } = params;
1321
- const key = getKey(TABLE_WORKFLOW_SNAPSHOT, {
1322
- namespace,
1323
- workflow_name: workflowName,
1324
- run_id: runId
1325
- });
1326
1333
  try {
1327
- const data = await this.client.get(key);
1328
- if (!data) return null;
1329
- return JSON.parse(data).snapshot;
1334
+ return (await this.getSnapshotRecord(namespace, workflowName, runId))?.snapshot ?? null;
1330
1335
  } catch (error) {
1331
1336
  throw new MastraError({
1332
1337
  id: createStorageErrorId("REDIS", "LOAD_WORKFLOW_SNAPSHOT", "FAILED"),
@@ -1342,12 +1347,9 @@ var WorkflowsRedis = class extends WorkflowsStorage {
1342
1347
  }
1343
1348
  async getWorkflowRunById({ runId, workflowName }) {
1344
1349
  try {
1345
- const key = getKey(TABLE_WORKFLOW_SNAPSHOT, {
1346
- namespace: "workflows",
1347
- workflow_name: workflowName,
1348
- run_id: runId
1349
- }) + "*";
1350
- const keys = await this.db.scanKeys(key);
1350
+ const workflowNamePattern = workflowName ? escapeGlob(workflowName) : "*";
1351
+ const canonicalPattern = `${escapeGlob(getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace: "workflows" }))}:workflow_name:${workflowNamePattern}:run_id:${escapeGlob(runId)}`;
1352
+ const keys = [...await this.db.scanKeys(canonicalPattern), ...await this.db.scanKeys(`${canonicalPattern}:resourceId:*`)];
1351
1353
  if (keys.length === 0) return null;
1352
1354
  const data = (await this.client.mGet(keys)).filter((data) => data !== null).map((data) => JSON.parse(data)).find((workflow) => {
1353
1355
  if (!workflow) return false;
@@ -1371,13 +1373,9 @@ var WorkflowsRedis = class extends WorkflowsStorage {
1371
1373
  }
1372
1374
  }
1373
1375
  async deleteWorkflowRunById({ runId, workflowName }) {
1374
- const key = getKey(TABLE_WORKFLOW_SNAPSHOT, {
1375
- namespace: "workflows",
1376
- workflow_name: workflowName,
1377
- run_id: runId
1378
- });
1379
1376
  try {
1380
- await this.client.del(key);
1377
+ const legacyKeys = await this.db.scanKeys(legacySnapshotKeyPattern("workflows", workflowName, runId));
1378
+ await this.client.del([snapshotKey("workflows", workflowName, runId), ...legacyKeys]);
1381
1379
  } catch (error) {
1382
1380
  throw new MastraError({
1383
1381
  id: createStorageErrorId("REDIS", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
@@ -1401,29 +1399,30 @@ var WorkflowsRedis = class extends WorkflowsStorage {
1401
1399
  }, /* @__PURE__ */ new Error("page must be >= 0"));
1402
1400
  const normalizedFrom = fromDate ? ensureDate(fromDate) : void 0;
1403
1401
  const normalizedTo = toDate ? ensureDate(toDate) : void 0;
1404
- let pattern = getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace: "workflows" }) + ":*";
1405
- if (workflowName && resourceId) pattern = getKey(TABLE_WORKFLOW_SNAPSHOT, {
1406
- namespace: "workflows",
1407
- workflow_name: workflowName,
1408
- run_id: "*",
1409
- resourceId
1410
- });
1411
- else if (workflowName) pattern = getKey(TABLE_WORKFLOW_SNAPSHOT, {
1402
+ const pattern = workflowName ? `${escapeGlob(getKey(TABLE_WORKFLOW_SNAPSHOT, {
1412
1403
  namespace: "workflows",
1413
1404
  workflow_name: workflowName
1414
- }) + ":*";
1415
- else if (resourceId) pattern = getKey(TABLE_WORKFLOW_SNAPSHOT, {
1416
- namespace: "workflows",
1417
- workflow_name: "*",
1418
- run_id: "*",
1419
- resourceId
1420
- });
1405
+ }))}:*` : `${getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace: "workflows" })}:*`;
1421
1406
  const keys = await this.db.scanKeys(pattern);
1422
1407
  if (keys.length === 0) return {
1423
1408
  runs: [],
1424
1409
  total: 0
1425
1410
  };
1426
- let runs = (await this.client.mGet(keys)).filter((data) => data !== null).map((data) => JSON.parse(data)).filter((record) => record !== null && record !== void 0 && typeof record === "object" && "workflow_name" in record).filter((record) => !workflowName || record.workflow_name === workflowName).map((w) => parseWorkflowRun(w)).filter((w) => {
1411
+ const results = await this.client.mGet(keys);
1412
+ const recordsByRun = /* @__PURE__ */ new Map();
1413
+ results.forEach((data, index) => {
1414
+ if (data === null) return;
1415
+ const record = JSON.parse(data);
1416
+ if (record === null || typeof record !== "object" || !("workflow_name" in record)) return;
1417
+ const legacy = keys[index] !== snapshotKey(record.namespace ?? "workflows", record.workflow_name, record.run_id);
1418
+ const runKey = `${record.workflow_name}\u0000${record.run_id}`;
1419
+ const current = recordsByRun.get(runKey);
1420
+ if (!current || current.legacy && !legacy) recordsByRun.set(runKey, {
1421
+ record,
1422
+ legacy
1423
+ });
1424
+ });
1425
+ let runs = [...recordsByRun.values()].map(({ record }) => record).filter((record) => !workflowName || record.workflow_name === workflowName).filter((record) => !resourceId || record.resourceId === resourceId).map((w) => parseWorkflowRun(w)).filter((w) => {
1427
1426
  if (normalizedFrom && w.createdAt < normalizedFrom) return false;
1428
1427
  if (normalizedTo && w.createdAt > normalizedTo) return false;
1429
1428
  if (status) {