@mastra/upstash 0.3.1-alpha.3 → 0.3.1-alpha.5

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.
@@ -1,23 +1,23 @@
1
1
 
2
- > @mastra/upstash@0.3.1-alpha.3 build /home/runner/work/mastra/mastra/stores/upstash
2
+ > @mastra/upstash@0.3.1-alpha.5 build /home/runner/work/mastra/mastra/stores/upstash
3
3
  > tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting
4
4
 
5
5
  CLI Building entry: src/index.ts
6
6
  CLI Using tsconfig: tsconfig.json
7
7
  CLI tsup v8.4.0
8
8
  TSC Build start
9
- TSC ⚡️ Build success in 10479ms
9
+ TSC ⚡️ Build success in 9182ms
10
10
  DTS Build start
11
11
  CLI Target: es2022
12
- Analysis will use the bundled TypeScript version 5.8.2
12
+ Analysis will use the bundled TypeScript version 5.8.3
13
13
  Writing package typings: /home/runner/work/mastra/mastra/stores/upstash/dist/_tsup-dts-rollup.d.ts
14
- Analysis will use the bundled TypeScript version 5.8.2
14
+ Analysis will use the bundled TypeScript version 5.8.3
15
15
  Writing package typings: /home/runner/work/mastra/mastra/stores/upstash/dist/_tsup-dts-rollup.d.cts
16
- DTS ⚡️ Build success in 12075ms
16
+ DTS ⚡️ Build success in 11741ms
17
17
  CLI Cleaning output folder
18
18
  ESM Build start
19
19
  CJS Build start
20
- ESM dist/index.js 24.02 KB
21
- ESM ⚡️ Build success in 1157ms
22
- CJS dist/index.cjs 24.11 KB
23
- CJS ⚡️ Build success in 1162ms
20
+ CJS dist/index.cjs 28.65 KB
21
+ CJS ⚡️ Build success in 1190ms
22
+ ESM dist/index.js 28.51 KB
23
+ ESM ⚡️ Build success in 1193ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @mastra/upstash
2
2
 
3
+ ## 0.3.1-alpha.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 5f826d9: Moved vector store specific prompts from @mastra/rag to be exported from the store that the prompt belongs to, ie @mastra/pg
8
+ - Updated dependencies [3e7b69d]
9
+ - @mastra/core@0.9.1-alpha.5
10
+
11
+ ## 0.3.1-alpha.4
12
+
13
+ ### Patch Changes
14
+
15
+ - 479f490: [MASTRA-3131] Add getWorkflowRunByID and add resourceId as filter for getWorkflowRuns
16
+ - Updated dependencies [e4943b8]
17
+ - Updated dependencies [479f490]
18
+ - @mastra/core@0.9.1-alpha.4
19
+
3
20
  ## 0.3.1-alpha.3
4
21
 
5
22
  ### Patch Changes
@@ -14,8 +14,18 @@ import type { StorageThreadType } from '@mastra/core/memory';
14
14
  import type { TABLE_NAMES } from '@mastra/core/storage';
15
15
  import type { UpsertVectorParams } from '@mastra/core/vector';
16
16
  import type { VectorFilter } from '@mastra/core/vector/filter';
17
+ import type { WorkflowRun } from '@mastra/core/storage';
18
+ import type { WorkflowRuns } from '@mastra/core/storage';
17
19
  import type { WorkflowRunState } from '@mastra/core/workflows';
18
20
 
21
+ /**
22
+ * Vector store specific prompt that details supported operators and examples.
23
+ * This prompt helps users construct valid filters for Upstash Vector.
24
+ */
25
+ declare const UPSTASH_PROMPT = "When querying Upstash Vector, you can ONLY use the operators listed below. Any other operators will be rejected.\nImportant: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results.\nIf a user tries to give an explicit operator that is not supported, reject the filter entirely and let them know that the operator is not supported.\n\nBasic Comparison Operators:\n- $eq: Exact match (default when using field: value)\n Example: { \"category\": \"electronics\" }\n- $ne: Not equal\n Example: { \"category\": { \"$ne\": \"electronics\" } }\n- $gt: Greater than\n Example: { \"price\": { \"$gt\": 100 } }\n- $gte: Greater than or equal\n Example: { \"price\": { \"$gte\": 100 } }\n- $lt: Less than\n Example: { \"price\": { \"$lt\": 100 } }\n- $lte: Less than or equal\n Example: { \"price\": { \"$lte\": 100 } }\n\nArray Operators:\n- $in: Match any value in array\n Example: { \"category\": { \"$in\": [\"electronics\", \"books\"] } }\n- $nin: Does not match any value in array\n Example: { \"category\": { \"$nin\": [\"electronics\", \"books\"] } }\n\nLogical Operators:\n- $and: Logical AND (can be implicit or explicit)\n Implicit Example: { \"price\": { \"$gt\": 100 }, \"category\": \"electronics\" }\n Explicit Example: { \"$and\": [{ \"price\": { \"$gt\": 100 } }, { \"category\": \"electronics\" }] }\n- $or: Logical OR\n Example: { \"$or\": [{ \"price\": { \"$lt\": 50 } }, { \"category\": \"books\" }] }\n\nElement Operators:\n- $exists: Check if field exists\n Example: { \"rating\": { \"$exists\": true } }\n\nRestrictions:\n- Regex patterns are not supported\n- Only $and and $or logical operators are supported at the top level\n- Empty arrays in $in/$nin will return no results\n- Nested fields are supported using dot notation\n- Multiple conditions on the same field are supported with both implicit and explicit $and\n- At least one key-value pair is required in filter object\n- Empty objects and undefined values are treated as no filter\n- Invalid types in comparison operators will throw errors\n- All non-logical operators must be used within a field condition\n Valid: { \"field\": { \"$gt\": 100 } }\n Valid: { \"$and\": [...] }\n Invalid: { \"$gt\": 100 }\n- Logical operators must contain field conditions, not direct operators\n Valid: { \"$and\": [{ \"field\": { \"$gt\": 100 } }] }\n Invalid: { \"$and\": [{ \"$gt\": 100 }] }\n- Logical operators ($and, $or):\n - Can only be used at top level or nested within other logical operators\n - Can not be used on a field level, or be nested inside a field\n - Can not be used inside an operator\n - Valid: { \"$and\": [{ \"field\": { \"$gt\": 100 } }] }\n - Valid: { \"$or\": [{ \"$and\": [{ \"field\": { \"$gt\": 100 } }] }] }\n - Invalid: { \"field\": { \"$and\": [{ \"$gt\": 100 }] } }\n - Invalid: { \"field\": { \"$or\": [{ \"$gt\": 100 }] } }\n - Invalid: { \"field\": { \"$gt\": { \"$and\": [{...}] } } }\n\nExample Complex Query:\n{\n \"$and\": [\n { \"category\": { \"$in\": [\"electronics\", \"computers\"] } },\n { \"price\": { \"$gte\": 100, \"$lte\": 1000 } },\n { \"rating\": { \"$exists\": true, \"$gt\": 4 } },\n { \"$or\": [\n { \"stock\": { \"$gt\": 0 } },\n { \"preorder\": true }\n ]}\n ]\n}";
26
+ export { UPSTASH_PROMPT }
27
+ export { UPSTASH_PROMPT as UPSTASH_PROMPT_alias_1 }
28
+
19
29
  declare interface UpstashConfig {
20
30
  url: string;
21
31
  token: string;
@@ -107,23 +117,21 @@ declare class UpstashStore extends MastraStorage {
107
117
  workflowName: string;
108
118
  runId: string;
109
119
  }): Promise<WorkflowRunState | null>;
110
- getWorkflowRuns({ namespace, workflowName, fromDate, toDate, limit, offset, }?: {
120
+ private parseWorkflowRun;
121
+ getWorkflowRuns({ namespace, workflowName, fromDate, toDate, limit, offset, resourceId, }?: {
111
122
  namespace: string;
112
123
  workflowName?: string;
113
124
  fromDate?: Date;
114
125
  toDate?: Date;
115
126
  limit?: number;
116
127
  offset?: number;
117
- }): Promise<{
118
- runs: Array<{
119
- workflowName: string;
120
- runId: string;
121
- snapshot: WorkflowRunState | string;
122
- createdAt: Date;
123
- updatedAt: Date;
124
- }>;
125
- total: number;
126
- }>;
128
+ resourceId?: string;
129
+ }): Promise<WorkflowRuns>;
130
+ getWorkflowRunById({ namespace, runId, workflowName, }: {
131
+ namespace: string;
132
+ runId: string;
133
+ workflowName?: string;
134
+ }): Promise<WorkflowRun | null>;
127
135
  close(): Promise<void>;
128
136
  }
129
137
  export { UpstashStore }
@@ -14,8 +14,18 @@ import type { StorageThreadType } from '@mastra/core/memory';
14
14
  import type { TABLE_NAMES } from '@mastra/core/storage';
15
15
  import type { UpsertVectorParams } from '@mastra/core/vector';
16
16
  import type { VectorFilter } from '@mastra/core/vector/filter';
17
+ import type { WorkflowRun } from '@mastra/core/storage';
18
+ import type { WorkflowRuns } from '@mastra/core/storage';
17
19
  import type { WorkflowRunState } from '@mastra/core/workflows';
18
20
 
21
+ /**
22
+ * Vector store specific prompt that details supported operators and examples.
23
+ * This prompt helps users construct valid filters for Upstash Vector.
24
+ */
25
+ declare const UPSTASH_PROMPT = "When querying Upstash Vector, you can ONLY use the operators listed below. Any other operators will be rejected.\nImportant: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results.\nIf a user tries to give an explicit operator that is not supported, reject the filter entirely and let them know that the operator is not supported.\n\nBasic Comparison Operators:\n- $eq: Exact match (default when using field: value)\n Example: { \"category\": \"electronics\" }\n- $ne: Not equal\n Example: { \"category\": { \"$ne\": \"electronics\" } }\n- $gt: Greater than\n Example: { \"price\": { \"$gt\": 100 } }\n- $gte: Greater than or equal\n Example: { \"price\": { \"$gte\": 100 } }\n- $lt: Less than\n Example: { \"price\": { \"$lt\": 100 } }\n- $lte: Less than or equal\n Example: { \"price\": { \"$lte\": 100 } }\n\nArray Operators:\n- $in: Match any value in array\n Example: { \"category\": { \"$in\": [\"electronics\", \"books\"] } }\n- $nin: Does not match any value in array\n Example: { \"category\": { \"$nin\": [\"electronics\", \"books\"] } }\n\nLogical Operators:\n- $and: Logical AND (can be implicit or explicit)\n Implicit Example: { \"price\": { \"$gt\": 100 }, \"category\": \"electronics\" }\n Explicit Example: { \"$and\": [{ \"price\": { \"$gt\": 100 } }, { \"category\": \"electronics\" }] }\n- $or: Logical OR\n Example: { \"$or\": [{ \"price\": { \"$lt\": 50 } }, { \"category\": \"books\" }] }\n\nElement Operators:\n- $exists: Check if field exists\n Example: { \"rating\": { \"$exists\": true } }\n\nRestrictions:\n- Regex patterns are not supported\n- Only $and and $or logical operators are supported at the top level\n- Empty arrays in $in/$nin will return no results\n- Nested fields are supported using dot notation\n- Multiple conditions on the same field are supported with both implicit and explicit $and\n- At least one key-value pair is required in filter object\n- Empty objects and undefined values are treated as no filter\n- Invalid types in comparison operators will throw errors\n- All non-logical operators must be used within a field condition\n Valid: { \"field\": { \"$gt\": 100 } }\n Valid: { \"$and\": [...] }\n Invalid: { \"$gt\": 100 }\n- Logical operators must contain field conditions, not direct operators\n Valid: { \"$and\": [{ \"field\": { \"$gt\": 100 } }] }\n Invalid: { \"$and\": [{ \"$gt\": 100 }] }\n- Logical operators ($and, $or):\n - Can only be used at top level or nested within other logical operators\n - Can not be used on a field level, or be nested inside a field\n - Can not be used inside an operator\n - Valid: { \"$and\": [{ \"field\": { \"$gt\": 100 } }] }\n - Valid: { \"$or\": [{ \"$and\": [{ \"field\": { \"$gt\": 100 } }] }] }\n - Invalid: { \"field\": { \"$and\": [{ \"$gt\": 100 }] } }\n - Invalid: { \"field\": { \"$or\": [{ \"$gt\": 100 }] } }\n - Invalid: { \"field\": { \"$gt\": { \"$and\": [{...}] } } }\n\nExample Complex Query:\n{\n \"$and\": [\n { \"category\": { \"$in\": [\"electronics\", \"computers\"] } },\n { \"price\": { \"$gte\": 100, \"$lte\": 1000 } },\n { \"rating\": { \"$exists\": true, \"$gt\": 4 } },\n { \"$or\": [\n { \"stock\": { \"$gt\": 0 } },\n { \"preorder\": true }\n ]}\n ]\n}";
26
+ export { UPSTASH_PROMPT }
27
+ export { UPSTASH_PROMPT as UPSTASH_PROMPT_alias_1 }
28
+
19
29
  declare interface UpstashConfig {
20
30
  url: string;
21
31
  token: string;
@@ -107,23 +117,21 @@ declare class UpstashStore extends MastraStorage {
107
117
  workflowName: string;
108
118
  runId: string;
109
119
  }): Promise<WorkflowRunState | null>;
110
- getWorkflowRuns({ namespace, workflowName, fromDate, toDate, limit, offset, }?: {
120
+ private parseWorkflowRun;
121
+ getWorkflowRuns({ namespace, workflowName, fromDate, toDate, limit, offset, resourceId, }?: {
111
122
  namespace: string;
112
123
  workflowName?: string;
113
124
  fromDate?: Date;
114
125
  toDate?: Date;
115
126
  limit?: number;
116
127
  offset?: number;
117
- }): Promise<{
118
- runs: Array<{
119
- workflowName: string;
120
- runId: string;
121
- snapshot: WorkflowRunState | string;
122
- createdAt: Date;
123
- updatedAt: Date;
124
- }>;
125
- total: number;
126
- }>;
128
+ resourceId?: string;
129
+ }): Promise<WorkflowRuns>;
130
+ getWorkflowRunById({ namespace, runId, workflowName, }: {
131
+ namespace: string;
132
+ runId: string;
133
+ workflowName?: string;
134
+ }): Promise<WorkflowRun | null>;
127
135
  close(): Promise<void>;
128
136
  }
129
137
  export { UpstashStore }
package/dist/index.cjs CHANGED
@@ -207,7 +207,8 @@ var UpstashStore = class extends storage.MastraStorage {
207
207
  key = this.getKey(tableName, {
208
208
  namespace: record.namespace || "workflows",
209
209
  workflow_name: record.workflow_name,
210
- run_id: record.run_id
210
+ run_id: record.run_id,
211
+ ...record.resourceId ? { resourceId: record.resourceId } : {}
211
212
  });
212
213
  } else if (tableName === storage.TABLE_EVALS) {
213
214
  key = this.getKey(tableName, { id: record.run_id });
@@ -372,48 +373,90 @@ var UpstashStore = class extends storage.MastraStorage {
372
373
  if (!data) return null;
373
374
  return data.snapshot;
374
375
  }
376
+ parseWorkflowRun(row) {
377
+ let parsedSnapshot = row.snapshot;
378
+ if (typeof parsedSnapshot === "string") {
379
+ try {
380
+ parsedSnapshot = JSON.parse(row.snapshot);
381
+ } catch (e) {
382
+ console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
383
+ }
384
+ }
385
+ return {
386
+ workflowName: row.workflow_name,
387
+ runId: row.run_id,
388
+ snapshot: parsedSnapshot,
389
+ createdAt: this.ensureDate(row.createdAt),
390
+ updatedAt: this.ensureDate(row.updatedAt),
391
+ resourceId: row.resourceId
392
+ };
393
+ }
375
394
  async getWorkflowRuns({
376
395
  namespace,
377
396
  workflowName,
378
397
  fromDate,
379
398
  toDate,
380
399
  limit,
381
- offset
400
+ offset,
401
+ resourceId
382
402
  } = { namespace: "workflows" }) {
383
- const pattern = workflowName ? this.getKey(storage.TABLE_WORKFLOW_SNAPSHOT, { namespace, workflow_name: workflowName }) + ":*" : this.getKey(storage.TABLE_WORKFLOW_SNAPSHOT, { namespace }) + ":*";
384
- const keys = await this.redis.keys(pattern);
385
- const workflows = await Promise.all(
386
- keys.map(async (key) => {
387
- const data = await this.redis.get(key);
388
- return data;
389
- })
390
- );
391
- let runs = workflows.filter((w) => w !== null).map((w) => {
392
- let parsedSnapshot = w.snapshot;
393
- if (typeof parsedSnapshot === "string") {
394
- try {
395
- parsedSnapshot = JSON.parse(w.snapshot);
396
- } catch {
397
- console.warn(`Failed to parse snapshot for workflow ${w.workflow_name}:`);
398
- }
403
+ try {
404
+ let pattern = this.getKey(storage.TABLE_WORKFLOW_SNAPSHOT, { namespace }) + ":*";
405
+ if (workflowName && resourceId) {
406
+ pattern = this.getKey(storage.TABLE_WORKFLOW_SNAPSHOT, {
407
+ namespace,
408
+ workflow_name: workflowName,
409
+ run_id: "*",
410
+ resourceId
411
+ });
412
+ } else if (workflowName) {
413
+ pattern = this.getKey(storage.TABLE_WORKFLOW_SNAPSHOT, { namespace, workflow_name: workflowName }) + ":*";
414
+ } else if (resourceId) {
415
+ pattern = this.getKey(storage.TABLE_WORKFLOW_SNAPSHOT, { namespace, workflow_name: "*", run_id: "*", resourceId });
416
+ }
417
+ const keys = await this.redis.keys(pattern);
418
+ const workflows = await Promise.all(
419
+ keys.map(async (key) => {
420
+ const data = await this.redis.get(key);
421
+ return data;
422
+ })
423
+ );
424
+ let runs = workflows.filter((w) => w !== null).map((w) => this.parseWorkflowRun(w)).filter((w) => {
425
+ if (fromDate && w.createdAt < fromDate) return false;
426
+ if (toDate && w.createdAt > toDate) return false;
427
+ return true;
428
+ }).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
429
+ const total = runs.length;
430
+ if (limit !== void 0 && offset !== void 0) {
431
+ runs = runs.slice(offset, offset + limit);
399
432
  }
400
- return {
401
- workflowName: w.workflow_name,
402
- runId: w.run_id,
403
- snapshot: parsedSnapshot,
404
- createdAt: this.ensureDate(w.createdAt),
405
- updatedAt: this.ensureDate(w.updatedAt)
406
- };
407
- }).filter((w) => {
408
- if (fromDate && w.createdAt < fromDate) return false;
409
- if (toDate && w.createdAt > toDate) return false;
410
- return true;
411
- }).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
412
- const total = runs.length;
413
- if (limit !== void 0 && offset !== void 0) {
414
- runs = runs.slice(offset, offset + limit);
415
- }
416
- return { runs, total };
433
+ return { runs, total };
434
+ } catch (error) {
435
+ console.error("Error getting workflow runs:", error);
436
+ throw error;
437
+ }
438
+ }
439
+ async getWorkflowRunById({
440
+ namespace = "workflows",
441
+ runId,
442
+ workflowName
443
+ }) {
444
+ try {
445
+ const key = this.getKey(storage.TABLE_WORKFLOW_SNAPSHOT, { namespace, workflow_name: workflowName, run_id: runId }) + "*";
446
+ const keys = await this.redis.keys(key);
447
+ const workflows = await Promise.all(
448
+ keys.map(async (key2) => {
449
+ const data2 = await this.redis.get(key2);
450
+ return data2;
451
+ })
452
+ );
453
+ const data = workflows.find((w) => w?.run_id === runId && w?.workflow_name === workflowName);
454
+ if (!data) return null;
455
+ return this.parseWorkflowRun(data);
456
+ } catch (error) {
457
+ console.error("Error getting workflow run by ID:", error);
458
+ throw error;
459
+ }
417
460
  }
418
461
  async close() {
419
462
  }
@@ -713,5 +756,81 @@ var UpstashVector = class extends vector.MastraVector {
713
756
  }
714
757
  };
715
758
 
759
+ // src/vector/prompt.ts
760
+ var UPSTASH_PROMPT = `When querying Upstash Vector, you can ONLY use the operators listed below. Any other operators will be rejected.
761
+ Important: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results.
762
+ If a user tries to give an explicit operator that is not supported, reject the filter entirely and let them know that the operator is not supported.
763
+
764
+ Basic Comparison Operators:
765
+ - $eq: Exact match (default when using field: value)
766
+ Example: { "category": "electronics" }
767
+ - $ne: Not equal
768
+ Example: { "category": { "$ne": "electronics" } }
769
+ - $gt: Greater than
770
+ Example: { "price": { "$gt": 100 } }
771
+ - $gte: Greater than or equal
772
+ Example: { "price": { "$gte": 100 } }
773
+ - $lt: Less than
774
+ Example: { "price": { "$lt": 100 } }
775
+ - $lte: Less than or equal
776
+ Example: { "price": { "$lte": 100 } }
777
+
778
+ Array Operators:
779
+ - $in: Match any value in array
780
+ Example: { "category": { "$in": ["electronics", "books"] } }
781
+ - $nin: Does not match any value in array
782
+ Example: { "category": { "$nin": ["electronics", "books"] } }
783
+
784
+ Logical Operators:
785
+ - $and: Logical AND (can be implicit or explicit)
786
+ Implicit Example: { "price": { "$gt": 100 }, "category": "electronics" }
787
+ Explicit Example: { "$and": [{ "price": { "$gt": 100 } }, { "category": "electronics" }] }
788
+ - $or: Logical OR
789
+ Example: { "$or": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
790
+
791
+ Element Operators:
792
+ - $exists: Check if field exists
793
+ Example: { "rating": { "$exists": true } }
794
+
795
+ Restrictions:
796
+ - Regex patterns are not supported
797
+ - Only $and and $or logical operators are supported at the top level
798
+ - Empty arrays in $in/$nin will return no results
799
+ - Nested fields are supported using dot notation
800
+ - Multiple conditions on the same field are supported with both implicit and explicit $and
801
+ - At least one key-value pair is required in filter object
802
+ - Empty objects and undefined values are treated as no filter
803
+ - Invalid types in comparison operators will throw errors
804
+ - All non-logical operators must be used within a field condition
805
+ Valid: { "field": { "$gt": 100 } }
806
+ Valid: { "$and": [...] }
807
+ Invalid: { "$gt": 100 }
808
+ - Logical operators must contain field conditions, not direct operators
809
+ Valid: { "$and": [{ "field": { "$gt": 100 } }] }
810
+ Invalid: { "$and": [{ "$gt": 100 }] }
811
+ - Logical operators ($and, $or):
812
+ - Can only be used at top level or nested within other logical operators
813
+ - Can not be used on a field level, or be nested inside a field
814
+ - Can not be used inside an operator
815
+ - Valid: { "$and": [{ "field": { "$gt": 100 } }] }
816
+ - Valid: { "$or": [{ "$and": [{ "field": { "$gt": 100 } }] }] }
817
+ - Invalid: { "field": { "$and": [{ "$gt": 100 }] } }
818
+ - Invalid: { "field": { "$or": [{ "$gt": 100 }] } }
819
+ - Invalid: { "field": { "$gt": { "$and": [{...}] } } }
820
+
821
+ Example Complex Query:
822
+ {
823
+ "$and": [
824
+ { "category": { "$in": ["electronics", "computers"] } },
825
+ { "price": { "$gte": 100, "$lte": 1000 } },
826
+ { "rating": { "$exists": true, "$gt": 4 } },
827
+ { "$or": [
828
+ { "stock": { "$gt": 0 } },
829
+ { "preorder": true }
830
+ ]}
831
+ ]
832
+ }`;
833
+
834
+ exports.UPSTASH_PROMPT = UPSTASH_PROMPT;
716
835
  exports.UpstashStore = UpstashStore;
717
836
  exports.UpstashVector = UpstashVector;
package/dist/index.d.cts CHANGED
@@ -1,3 +1,4 @@
1
+ export { UPSTASH_PROMPT } from './_tsup-dts-rollup.cjs';
1
2
  export { UpstashConfig } from './_tsup-dts-rollup.cjs';
2
3
  export { UpstashStore } from './_tsup-dts-rollup.cjs';
3
4
  export { UpstashVector } from './_tsup-dts-rollup.cjs';
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { UPSTASH_PROMPT } from './_tsup-dts-rollup.js';
1
2
  export { UpstashConfig } from './_tsup-dts-rollup.js';
2
3
  export { UpstashStore } from './_tsup-dts-rollup.js';
3
4
  export { UpstashVector } from './_tsup-dts-rollup.js';
package/dist/index.js CHANGED
@@ -205,7 +205,8 @@ var UpstashStore = class extends MastraStorage {
205
205
  key = this.getKey(tableName, {
206
206
  namespace: record.namespace || "workflows",
207
207
  workflow_name: record.workflow_name,
208
- run_id: record.run_id
208
+ run_id: record.run_id,
209
+ ...record.resourceId ? { resourceId: record.resourceId } : {}
209
210
  });
210
211
  } else if (tableName === TABLE_EVALS) {
211
212
  key = this.getKey(tableName, { id: record.run_id });
@@ -370,48 +371,90 @@ var UpstashStore = class extends MastraStorage {
370
371
  if (!data) return null;
371
372
  return data.snapshot;
372
373
  }
374
+ parseWorkflowRun(row) {
375
+ let parsedSnapshot = row.snapshot;
376
+ if (typeof parsedSnapshot === "string") {
377
+ try {
378
+ parsedSnapshot = JSON.parse(row.snapshot);
379
+ } catch (e) {
380
+ console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
381
+ }
382
+ }
383
+ return {
384
+ workflowName: row.workflow_name,
385
+ runId: row.run_id,
386
+ snapshot: parsedSnapshot,
387
+ createdAt: this.ensureDate(row.createdAt),
388
+ updatedAt: this.ensureDate(row.updatedAt),
389
+ resourceId: row.resourceId
390
+ };
391
+ }
373
392
  async getWorkflowRuns({
374
393
  namespace,
375
394
  workflowName,
376
395
  fromDate,
377
396
  toDate,
378
397
  limit,
379
- offset
398
+ offset,
399
+ resourceId
380
400
  } = { namespace: "workflows" }) {
381
- const pattern = workflowName ? this.getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace, workflow_name: workflowName }) + ":*" : this.getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace }) + ":*";
382
- const keys = await this.redis.keys(pattern);
383
- const workflows = await Promise.all(
384
- keys.map(async (key) => {
385
- const data = await this.redis.get(key);
386
- return data;
387
- })
388
- );
389
- let runs = workflows.filter((w) => w !== null).map((w) => {
390
- let parsedSnapshot = w.snapshot;
391
- if (typeof parsedSnapshot === "string") {
392
- try {
393
- parsedSnapshot = JSON.parse(w.snapshot);
394
- } catch {
395
- console.warn(`Failed to parse snapshot for workflow ${w.workflow_name}:`);
396
- }
401
+ try {
402
+ let pattern = this.getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace }) + ":*";
403
+ if (workflowName && resourceId) {
404
+ pattern = this.getKey(TABLE_WORKFLOW_SNAPSHOT, {
405
+ namespace,
406
+ workflow_name: workflowName,
407
+ run_id: "*",
408
+ resourceId
409
+ });
410
+ } else if (workflowName) {
411
+ pattern = this.getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace, workflow_name: workflowName }) + ":*";
412
+ } else if (resourceId) {
413
+ pattern = this.getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace, workflow_name: "*", run_id: "*", resourceId });
414
+ }
415
+ const keys = await this.redis.keys(pattern);
416
+ const workflows = await Promise.all(
417
+ keys.map(async (key) => {
418
+ const data = await this.redis.get(key);
419
+ return data;
420
+ })
421
+ );
422
+ let runs = workflows.filter((w) => w !== null).map((w) => this.parseWorkflowRun(w)).filter((w) => {
423
+ if (fromDate && w.createdAt < fromDate) return false;
424
+ if (toDate && w.createdAt > toDate) return false;
425
+ return true;
426
+ }).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
427
+ const total = runs.length;
428
+ if (limit !== void 0 && offset !== void 0) {
429
+ runs = runs.slice(offset, offset + limit);
397
430
  }
398
- return {
399
- workflowName: w.workflow_name,
400
- runId: w.run_id,
401
- snapshot: parsedSnapshot,
402
- createdAt: this.ensureDate(w.createdAt),
403
- updatedAt: this.ensureDate(w.updatedAt)
404
- };
405
- }).filter((w) => {
406
- if (fromDate && w.createdAt < fromDate) return false;
407
- if (toDate && w.createdAt > toDate) return false;
408
- return true;
409
- }).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
410
- const total = runs.length;
411
- if (limit !== void 0 && offset !== void 0) {
412
- runs = runs.slice(offset, offset + limit);
413
- }
414
- return { runs, total };
431
+ return { runs, total };
432
+ } catch (error) {
433
+ console.error("Error getting workflow runs:", error);
434
+ throw error;
435
+ }
436
+ }
437
+ async getWorkflowRunById({
438
+ namespace = "workflows",
439
+ runId,
440
+ workflowName
441
+ }) {
442
+ try {
443
+ const key = this.getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace, workflow_name: workflowName, run_id: runId }) + "*";
444
+ const keys = await this.redis.keys(key);
445
+ const workflows = await Promise.all(
446
+ keys.map(async (key2) => {
447
+ const data2 = await this.redis.get(key2);
448
+ return data2;
449
+ })
450
+ );
451
+ const data = workflows.find((w) => w?.run_id === runId && w?.workflow_name === workflowName);
452
+ if (!data) return null;
453
+ return this.parseWorkflowRun(data);
454
+ } catch (error) {
455
+ console.error("Error getting workflow run by ID:", error);
456
+ throw error;
457
+ }
415
458
  }
416
459
  async close() {
417
460
  }
@@ -711,4 +754,79 @@ var UpstashVector = class extends MastraVector {
711
754
  }
712
755
  };
713
756
 
714
- export { UpstashStore, UpstashVector };
757
+ // src/vector/prompt.ts
758
+ var UPSTASH_PROMPT = `When querying Upstash Vector, you can ONLY use the operators listed below. Any other operators will be rejected.
759
+ Important: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results.
760
+ If a user tries to give an explicit operator that is not supported, reject the filter entirely and let them know that the operator is not supported.
761
+
762
+ Basic Comparison Operators:
763
+ - $eq: Exact match (default when using field: value)
764
+ Example: { "category": "electronics" }
765
+ - $ne: Not equal
766
+ Example: { "category": { "$ne": "electronics" } }
767
+ - $gt: Greater than
768
+ Example: { "price": { "$gt": 100 } }
769
+ - $gte: Greater than or equal
770
+ Example: { "price": { "$gte": 100 } }
771
+ - $lt: Less than
772
+ Example: { "price": { "$lt": 100 } }
773
+ - $lte: Less than or equal
774
+ Example: { "price": { "$lte": 100 } }
775
+
776
+ Array Operators:
777
+ - $in: Match any value in array
778
+ Example: { "category": { "$in": ["electronics", "books"] } }
779
+ - $nin: Does not match any value in array
780
+ Example: { "category": { "$nin": ["electronics", "books"] } }
781
+
782
+ Logical Operators:
783
+ - $and: Logical AND (can be implicit or explicit)
784
+ Implicit Example: { "price": { "$gt": 100 }, "category": "electronics" }
785
+ Explicit Example: { "$and": [{ "price": { "$gt": 100 } }, { "category": "electronics" }] }
786
+ - $or: Logical OR
787
+ Example: { "$or": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
788
+
789
+ Element Operators:
790
+ - $exists: Check if field exists
791
+ Example: { "rating": { "$exists": true } }
792
+
793
+ Restrictions:
794
+ - Regex patterns are not supported
795
+ - Only $and and $or logical operators are supported at the top level
796
+ - Empty arrays in $in/$nin will return no results
797
+ - Nested fields are supported using dot notation
798
+ - Multiple conditions on the same field are supported with both implicit and explicit $and
799
+ - At least one key-value pair is required in filter object
800
+ - Empty objects and undefined values are treated as no filter
801
+ - Invalid types in comparison operators will throw errors
802
+ - All non-logical operators must be used within a field condition
803
+ Valid: { "field": { "$gt": 100 } }
804
+ Valid: { "$and": [...] }
805
+ Invalid: { "$gt": 100 }
806
+ - Logical operators must contain field conditions, not direct operators
807
+ Valid: { "$and": [{ "field": { "$gt": 100 } }] }
808
+ Invalid: { "$and": [{ "$gt": 100 }] }
809
+ - Logical operators ($and, $or):
810
+ - Can only be used at top level or nested within other logical operators
811
+ - Can not be used on a field level, or be nested inside a field
812
+ - Can not be used inside an operator
813
+ - Valid: { "$and": [{ "field": { "$gt": 100 } }] }
814
+ - Valid: { "$or": [{ "$and": [{ "field": { "$gt": 100 } }] }] }
815
+ - Invalid: { "field": { "$and": [{ "$gt": 100 }] } }
816
+ - Invalid: { "field": { "$or": [{ "$gt": 100 }] } }
817
+ - Invalid: { "field": { "$gt": { "$and": [{...}] } } }
818
+
819
+ Example Complex Query:
820
+ {
821
+ "$and": [
822
+ { "category": { "$in": ["electronics", "computers"] } },
823
+ { "price": { "$gte": 100, "$lte": 1000 } },
824
+ { "rating": { "$exists": true, "$gt": 4 } },
825
+ { "$or": [
826
+ { "stock": { "$gt": 0 } },
827
+ { "preorder": true }
828
+ ]}
829
+ ]
830
+ }`;
831
+
832
+ export { UPSTASH_PROMPT, UpstashStore, UpstashVector };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/upstash",
3
- "version": "0.3.1-alpha.3",
3
+ "version": "0.3.1-alpha.5",
4
4
  "description": "Upstash provider for Mastra - includes both vector and db storage capabilities",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,16 +22,16 @@
22
22
  "dependencies": {
23
23
  "@upstash/redis": "^1.34.5",
24
24
  "@upstash/vector": "^1.2.1",
25
- "@mastra/core": "^0.9.1-alpha.3"
25
+ "@mastra/core": "^0.9.1-alpha.5"
26
26
  },
27
27
  "devDependencies": {
28
- "@microsoft/api-extractor": "^7.52.1",
28
+ "@microsoft/api-extractor": "^7.52.5",
29
29
  "@types/node": "^20.17.27",
30
30
  "dotenv": "^16.4.7",
31
31
  "eslint": "^9.23.0",
32
32
  "tsup": "^8.4.0",
33
33
  "typescript": "^5.8.2",
34
- "vitest": "^3.0.9",
34
+ "vitest": "^3.1.2",
35
35
  "@internal/lint": "0.0.2"
36
36
  },
37
37
  "scripts": {
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './storage';
2
2
  export * from './vector';
3
+ export { UPSTASH_PROMPT } from './vector/prompt';
@@ -8,7 +8,14 @@ import {
8
8
  TABLE_EVALS,
9
9
  TABLE_TRACES,
10
10
  } from '@mastra/core/storage';
11
- import type { TABLE_NAMES, StorageColumn, StorageGetMessagesArg, EvalRow } from '@mastra/core/storage';
11
+ import type {
12
+ TABLE_NAMES,
13
+ StorageColumn,
14
+ StorageGetMessagesArg,
15
+ EvalRow,
16
+ WorkflowRuns,
17
+ WorkflowRun,
18
+ } from '@mastra/core/storage';
12
19
  import type { WorkflowRunState } from '@mastra/core/workflows';
13
20
  import { Redis } from '@upstash/redis';
14
21
 
@@ -298,6 +305,7 @@ export class UpstashStore extends MastraStorage {
298
305
  namespace: record.namespace || 'workflows',
299
306
  workflow_name: record.workflow_name,
300
307
  run_id: record.run_id,
308
+ ...(record.resourceId ? { resourceId: record.resourceId } : {}),
301
309
  });
302
310
  } else if (tableName === TABLE_EVALS) {
303
311
  key = this.getKey(tableName, { id: record.run_id });
@@ -531,6 +539,27 @@ export class UpstashStore extends MastraStorage {
531
539
  return data.snapshot;
532
540
  }
533
541
 
542
+ private parseWorkflowRun(row: any): WorkflowRun {
543
+ let parsedSnapshot: WorkflowRunState | string = row.snapshot as string;
544
+ if (typeof parsedSnapshot === 'string') {
545
+ try {
546
+ parsedSnapshot = JSON.parse(row.snapshot as string) as WorkflowRunState;
547
+ } catch (e) {
548
+ // If parsing fails, return the raw snapshot string
549
+ console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
550
+ }
551
+ }
552
+
553
+ return {
554
+ workflowName: row.workflow_name,
555
+ runId: row.run_id,
556
+ snapshot: parsedSnapshot,
557
+ createdAt: this.ensureDate(row.createdAt)!,
558
+ updatedAt: this.ensureDate(row.updatedAt)!,
559
+ resourceId: row.resourceId,
560
+ };
561
+ }
562
+
534
563
  async getWorkflowRuns(
535
564
  {
536
565
  namespace,
@@ -539,6 +568,7 @@ export class UpstashStore extends MastraStorage {
539
568
  toDate,
540
569
  limit,
541
570
  offset,
571
+ resourceId,
542
572
  }: {
543
573
  namespace: string;
544
574
  workflowName?: string;
@@ -546,74 +576,98 @@ export class UpstashStore extends MastraStorage {
546
576
  toDate?: Date;
547
577
  limit?: number;
548
578
  offset?: number;
579
+ resourceId?: string;
549
580
  } = { namespace: 'workflows' },
550
- ): Promise<{
551
- runs: Array<{
552
- workflowName: string;
553
- runId: string;
554
- snapshot: WorkflowRunState | string;
555
- createdAt: Date;
556
- updatedAt: Date;
557
- }>;
558
- total: number;
559
- }> {
560
- // Get all workflow keys
561
- const pattern = workflowName
562
- ? this.getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace, workflow_name: workflowName }) + ':*'
563
- : this.getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace }) + ':*';
581
+ ): Promise<WorkflowRuns> {
582
+ try {
583
+ // Get all workflow keys
584
+ let pattern = this.getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace }) + ':*';
585
+ if (workflowName && resourceId) {
586
+ pattern = this.getKey(TABLE_WORKFLOW_SNAPSHOT, {
587
+ namespace,
588
+ workflow_name: workflowName,
589
+ run_id: '*',
590
+ resourceId,
591
+ });
592
+ } else if (workflowName) {
593
+ pattern = this.getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace, workflow_name: workflowName }) + ':*';
594
+ } else if (resourceId) {
595
+ pattern = this.getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace, workflow_name: '*', run_id: '*', resourceId });
596
+ }
597
+ const keys = await this.redis.keys(pattern);
564
598
 
565
- const keys = await this.redis.keys(pattern);
599
+ // Get all workflow data
600
+ const workflows = await Promise.all(
601
+ keys.map(async key => {
602
+ const data = await this.redis.get<{
603
+ workflow_name: string;
604
+ run_id: string;
605
+ snapshot: WorkflowRunState | string;
606
+ createdAt: string | Date;
607
+ updatedAt: string | Date;
608
+ resourceId: string;
609
+ }>(key);
610
+ return data;
611
+ }),
612
+ );
566
613
 
567
- // Get all workflow data
568
- const workflows = await Promise.all(
569
- keys.map(async key => {
570
- const data = await this.redis.get<{
571
- workflow_name: string;
572
- run_id: string;
573
- snapshot: WorkflowRunState | string;
574
- createdAt: string | Date;
575
- updatedAt: string | Date;
576
- }>(key);
577
- return data;
578
- }),
579
- );
614
+ // Filter and transform results
615
+ let runs = workflows
616
+ .filter(w => w !== null)
617
+ .map(w => this.parseWorkflowRun(w!))
618
+ .filter(w => {
619
+ if (fromDate && w.createdAt < fromDate) return false;
620
+ if (toDate && w.createdAt > toDate) return false;
621
+ return true;
622
+ })
623
+ .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
624
+
625
+ const total = runs.length;
626
+
627
+ // Apply pagination if requested
628
+ if (limit !== undefined && offset !== undefined) {
629
+ runs = runs.slice(offset, offset + limit);
630
+ }
580
631
 
581
- // Filter and transform results
582
- let runs = workflows
583
- .filter(w => w !== null)
584
- .map(w => {
585
- let parsedSnapshot: WorkflowRunState | string = w!.snapshot as string;
586
- if (typeof parsedSnapshot === 'string') {
587
- try {
588
- parsedSnapshot = JSON.parse(w!.snapshot as string) as WorkflowRunState;
589
- } catch {
590
- // If parsing fails, return the raw snapshot string
591
- console.warn(`Failed to parse snapshot for workflow ${w!.workflow_name}:`);
592
- }
593
- }
594
- return {
595
- workflowName: w!.workflow_name,
596
- runId: w!.run_id,
597
- snapshot: parsedSnapshot,
598
- createdAt: this.ensureDate(w!.createdAt)!,
599
- updatedAt: this.ensureDate(w!.updatedAt)!,
600
- };
601
- })
602
- .filter(w => {
603
- if (fromDate && w.createdAt < fromDate) return false;
604
- if (toDate && w.createdAt > toDate) return false;
605
- return true;
606
- })
607
- .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
608
-
609
- const total = runs.length;
610
-
611
- // Apply pagination if requested
612
- if (limit !== undefined && offset !== undefined) {
613
- runs = runs.slice(offset, offset + limit);
632
+ return { runs, total };
633
+ } catch (error) {
634
+ console.error('Error getting workflow runs:', error);
635
+ throw error;
614
636
  }
637
+ }
615
638
 
616
- return { runs, total };
639
+ async getWorkflowRunById({
640
+ namespace = 'workflows',
641
+ runId,
642
+ workflowName,
643
+ }: {
644
+ namespace: string;
645
+ runId: string;
646
+ workflowName?: string;
647
+ }): Promise<WorkflowRun | null> {
648
+ try {
649
+ const key = this.getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace, workflow_name: workflowName, run_id: runId }) + '*';
650
+ const keys = await this.redis.keys(key);
651
+ const workflows = await Promise.all(
652
+ keys.map(async key => {
653
+ const data = await this.redis.get<{
654
+ workflow_name: string;
655
+ run_id: string;
656
+ snapshot: WorkflowRunState | string;
657
+ createdAt: string | Date;
658
+ updatedAt: string | Date;
659
+ resourceId: string;
660
+ }>(key);
661
+ return data;
662
+ }),
663
+ );
664
+ const data = workflows.find(w => w?.run_id === runId && w?.workflow_name === workflowName) as WorkflowRun | null;
665
+ if (!data) return null;
666
+ return this.parseWorkflowRun(data);
667
+ } catch (error) {
668
+ console.error('Error getting workflow run by ID:', error);
669
+ throw error;
670
+ }
617
671
  }
618
672
 
619
673
  async close(): Promise<void> {
@@ -25,27 +25,26 @@ const createSampleThread = (date?: Date) => ({
25
25
  metadata: { key: 'value' },
26
26
  });
27
27
 
28
- const createSampleMessage = (threadId: string, content: string = 'Hello') =>
29
- ({
30
- id: `msg-${randomUUID()}`,
31
- role: 'user',
32
- type: 'text',
33
- threadId,
34
- content: [{ type: 'text', text: content }],
35
- createdAt: new Date(),
36
- }) as any;
28
+ const createSampleMessage = (threadId: string, content: string = 'Hello'): MessageType => ({
29
+ id: `msg-${randomUUID()}`,
30
+ role: 'user',
31
+ type: 'text',
32
+ threadId,
33
+ content: [{ type: 'text', text: content }],
34
+ createdAt: new Date(),
35
+ resourceId: `resource-${randomUUID()}`,
36
+ });
37
37
 
38
38
  const createSampleWorkflowSnapshot = (status: string, createdAt?: Date) => {
39
39
  const runId = `run-${randomUUID()}`;
40
40
  const stepId = `step-${randomUUID()}`;
41
41
  const timestamp = createdAt || new Date();
42
- const snapshot = {
43
- result: { success: true },
42
+ const snapshot: WorkflowRunState = {
44
43
  value: {},
45
44
  context: {
46
45
  steps: {
47
46
  [stepId]: {
48
- status,
47
+ status: status as WorkflowRunState['context']['steps'][string]['status'],
49
48
  payload: {},
50
49
  error: undefined,
51
50
  },
@@ -54,9 +53,10 @@ const createSampleWorkflowSnapshot = (status: string, createdAt?: Date) => {
54
53
  attempts: {},
55
54
  },
56
55
  activePaths: [],
56
+ suspendedPaths: {},
57
57
  runId,
58
58
  timestamp: timestamp.getTime(),
59
- } as WorkflowRunState;
59
+ };
60
60
  return { snapshot, runId, stepId };
61
61
  };
62
62
 
@@ -94,6 +94,13 @@ const createSampleEval = (agentName: string, isTest = false) => {
94
94
  };
95
95
  };
96
96
 
97
+ const checkWorkflowSnapshot = (snapshot: WorkflowRunState | string, stepId: string, status: string) => {
98
+ if (typeof snapshot === 'string') {
99
+ throw new Error('Expected WorkflowRunState, got string');
100
+ }
101
+ expect(snapshot.context?.steps[stepId]?.status).toBe(status);
102
+ };
103
+
97
104
  describe('UpstashStore', () => {
98
105
  let store: UpstashStore;
99
106
  const testTableName = 'test_table';
@@ -303,17 +310,17 @@ describe('UpstashStore', () => {
303
310
  });
304
311
 
305
312
  it('should save and retrieve messages in order', async () => {
306
- const messages = [
313
+ const messages: MessageType[] = [
307
314
  createSampleMessage(threadId, 'First'),
308
315
  createSampleMessage(threadId, 'Second'),
309
316
  createSampleMessage(threadId, 'Third'),
310
317
  ];
311
318
 
312
- await store.saveMessages({ messages: messages as MessageType[] });
319
+ await store.saveMessages({ messages: messages });
313
320
 
314
- const retrievedMessages = await store.getMessages({ threadId });
321
+ const retrievedMessages = await store.getMessages<MessageType[]>({ threadId });
315
322
  expect(retrievedMessages).toHaveLength(3);
316
- expect(retrievedMessages.map(m => m.content[0].text)).toEqual(['First', 'Second', 'Third']);
323
+ expect(retrievedMessages.map((m: any) => m.content[0].text)).toEqual(['First', 'Second', 'Third']);
317
324
  });
318
325
 
319
326
  it('should handle empty message array', async () => {
@@ -335,11 +342,11 @@ describe('UpstashStore', () => {
335
342
  ],
336
343
  createdAt: new Date(),
337
344
  },
338
- ];
345
+ ] as MessageType[];
339
346
 
340
- await store.saveMessages({ messages: messages as MessageType[] });
347
+ await store.saveMessages({ messages });
341
348
 
342
- const retrievedMessages = await store.getMessages({ threadId });
349
+ const retrievedMessages = await store.getMessages<MessageType>({ threadId });
343
350
  expect(retrievedMessages[0].content).toEqual(messages[0].content);
344
351
  });
345
352
  });
@@ -451,13 +458,15 @@ describe('UpstashStore', () => {
451
458
  stepResults: {
452
459
  step1: { status: 'success', payload: { result: 'done' } },
453
460
  },
461
+ steps: {},
454
462
  attempts: {},
455
463
  triggerData: {},
456
464
  },
457
465
  runId: testRunId,
458
466
  activePaths: [],
467
+ suspendedPaths: {},
459
468
  timestamp: Date.now(),
460
- } as unknown as WorkflowRunState;
469
+ };
461
470
 
462
471
  await store.persistWorkflowSnapshot({
463
472
  namespace: testNamespace,
@@ -556,8 +565,8 @@ describe('UpstashStore', () => {
556
565
  const workflowName1 = 'default_test_1';
557
566
  const workflowName2 = 'default_test_2';
558
567
 
559
- const { snapshot: workflow1, runId: runId1, stepId: stepId1 } = createSampleWorkflowSnapshot('completed');
560
- const { snapshot: workflow2, runId: runId2, stepId: stepId2 } = createSampleWorkflowSnapshot('running');
568
+ const { snapshot: workflow1, runId: runId1, stepId: stepId1 } = createSampleWorkflowSnapshot('success');
569
+ const { snapshot: workflow2, runId: runId2, stepId: stepId2 } = createSampleWorkflowSnapshot('waiting');
561
570
 
562
571
  await store.persistWorkflowSnapshot({
563
572
  namespace: testNamespace,
@@ -578,17 +587,17 @@ describe('UpstashStore', () => {
578
587
  expect(total).toBe(2);
579
588
  expect(runs[0]!.workflowName).toBe(workflowName2); // Most recent first
580
589
  expect(runs[1]!.workflowName).toBe(workflowName1);
581
- const firstSnapshot = runs[0]!.snapshot as WorkflowRunState;
582
- const secondSnapshot = runs[1]!.snapshot as WorkflowRunState;
583
- expect(firstSnapshot.context?.steps[stepId2]?.status).toBe('running');
584
- expect(secondSnapshot.context?.steps[stepId1]?.status).toBe('completed');
590
+ const firstSnapshot = runs[0]!.snapshot;
591
+ const secondSnapshot = runs[1]!.snapshot;
592
+ checkWorkflowSnapshot(firstSnapshot, stepId2, 'waiting');
593
+ checkWorkflowSnapshot(secondSnapshot, stepId1, 'success');
585
594
  });
586
595
 
587
596
  it('filters by workflow name', async () => {
588
597
  const workflowName1 = 'filter_test_1';
589
598
  const workflowName2 = 'filter_test_2';
590
599
 
591
- const { snapshot: workflow1, runId: runId1, stepId: stepId1 } = createSampleWorkflowSnapshot('completed');
600
+ const { snapshot: workflow1, runId: runId1, stepId: stepId1 } = createSampleWorkflowSnapshot('success');
592
601
  const { snapshot: workflow2, runId: runId2 } = createSampleWorkflowSnapshot('failed');
593
602
 
594
603
  await store.persistWorkflowSnapshot({
@@ -609,8 +618,11 @@ describe('UpstashStore', () => {
609
618
  expect(runs).toHaveLength(1);
610
619
  expect(total).toBe(1);
611
620
  expect(runs[0]!.workflowName).toBe(workflowName1);
612
- const snapshot = runs[0]!.snapshot as WorkflowRunState;
613
- expect(snapshot.context?.steps[stepId1]?.status).toBe('completed');
621
+ const snapshot = runs[0]!.snapshot;
622
+ if (typeof snapshot === 'string') {
623
+ throw new Error('Expected WorkflowRunState, got string');
624
+ }
625
+ expect(snapshot.context?.steps[stepId1]?.status).toBe('success');
614
626
  });
615
627
 
616
628
  it('filters by date range', async () => {
@@ -621,9 +633,9 @@ describe('UpstashStore', () => {
621
633
  const workflowName2 = 'date_test_2';
622
634
  const workflowName3 = 'date_test_3';
623
635
 
624
- const { snapshot: workflow1, runId: runId1 } = createSampleWorkflowSnapshot('completed');
625
- const { snapshot: workflow2, runId: runId2, stepId: stepId2 } = createSampleWorkflowSnapshot('running');
626
- const { snapshot: workflow3, runId: runId3, stepId: stepId3 } = createSampleWorkflowSnapshot('waiting');
636
+ const { snapshot: workflow1, runId: runId1 } = createSampleWorkflowSnapshot('success');
637
+ const { snapshot: workflow2, runId: runId2, stepId: stepId2 } = createSampleWorkflowSnapshot('waiting');
638
+ const { snapshot: workflow3, runId: runId3, stepId: stepId3 } = createSampleWorkflowSnapshot('skipped');
627
639
 
628
640
  await store.insert({
629
641
  tableName: TABLE_WORKFLOW_SNAPSHOT,
@@ -668,10 +680,10 @@ describe('UpstashStore', () => {
668
680
  expect(runs).toHaveLength(2);
669
681
  expect(runs[0]!.workflowName).toBe(workflowName3);
670
682
  expect(runs[1]!.workflowName).toBe(workflowName2);
671
- const firstSnapshot = runs[0]!.snapshot as WorkflowRunState;
672
- const secondSnapshot = runs[1]!.snapshot as WorkflowRunState;
673
- expect(firstSnapshot.context?.steps[stepId3]?.status).toBe('waiting');
674
- expect(secondSnapshot.context?.steps[stepId2]?.status).toBe('running');
683
+ const firstSnapshot = runs[0]!.snapshot;
684
+ const secondSnapshot = runs[1]!.snapshot;
685
+ checkWorkflowSnapshot(firstSnapshot, stepId3, 'skipped');
686
+ checkWorkflowSnapshot(secondSnapshot, stepId2, 'waiting');
675
687
  });
676
688
 
677
689
  it('handles pagination', async () => {
@@ -679,9 +691,9 @@ describe('UpstashStore', () => {
679
691
  const workflowName2 = 'page_test_2';
680
692
  const workflowName3 = 'page_test_3';
681
693
 
682
- const { snapshot: workflow1, runId: runId1, stepId: stepId1 } = createSampleWorkflowSnapshot('completed');
683
- const { snapshot: workflow2, runId: runId2, stepId: stepId2 } = createSampleWorkflowSnapshot('running');
684
- const { snapshot: workflow3, runId: runId3, stepId: stepId3 } = createSampleWorkflowSnapshot('waiting');
694
+ const { snapshot: workflow1, runId: runId1, stepId: stepId1 } = createSampleWorkflowSnapshot('success');
695
+ const { snapshot: workflow2, runId: runId2, stepId: stepId2 } = createSampleWorkflowSnapshot('waiting');
696
+ const { snapshot: workflow3, runId: runId3, stepId: stepId3 } = createSampleWorkflowSnapshot('skipped');
685
697
 
686
698
  await store.persistWorkflowSnapshot({
687
699
  namespace: testNamespace,
@@ -714,10 +726,10 @@ describe('UpstashStore', () => {
714
726
  expect(page1.total).toBe(3); // Total count of all records
715
727
  expect(page1.runs[0]!.workflowName).toBe(workflowName3);
716
728
  expect(page1.runs[1]!.workflowName).toBe(workflowName2);
717
- const firstSnapshot = page1.runs[0]!.snapshot as WorkflowRunState;
718
- const secondSnapshot = page1.runs[1]!.snapshot as WorkflowRunState;
719
- expect(firstSnapshot.context?.steps[stepId3]?.status).toBe('waiting');
720
- expect(secondSnapshot.context?.steps[stepId2]?.status).toBe('running');
729
+ const firstSnapshot = page1.runs[0]!.snapshot;
730
+ const secondSnapshot = page1.runs[1]!.snapshot;
731
+ checkWorkflowSnapshot(firstSnapshot, stepId3, 'skipped');
732
+ checkWorkflowSnapshot(secondSnapshot, stepId2, 'waiting');
721
733
 
722
734
  // Get second page
723
735
  const page2 = await store.getWorkflowRuns({
@@ -728,8 +740,118 @@ describe('UpstashStore', () => {
728
740
  expect(page2.runs).toHaveLength(1);
729
741
  expect(page2.total).toBe(3);
730
742
  expect(page2.runs[0]!.workflowName).toBe(workflowName1);
731
- const snapshot = page2.runs[0]!.snapshot as WorkflowRunState;
732
- expect(snapshot.context?.steps[stepId1]?.status).toBe('completed');
743
+ const snapshot = page2.runs[0]!.snapshot;
744
+ checkWorkflowSnapshot(snapshot, stepId1, 'success');
745
+ });
746
+ });
747
+ describe('getWorkflowRunById', () => {
748
+ const testNamespace = 'test-workflows-id';
749
+ const workflowName = 'workflow-id-test';
750
+ let runId: string;
751
+ let stepId: string;
752
+
753
+ beforeAll(async () => {
754
+ // Insert a workflow run for positive test
755
+ const sample = createSampleWorkflowSnapshot('success');
756
+ runId = sample.runId;
757
+ stepId = sample.stepId;
758
+ await store.insert({
759
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
760
+ record: {
761
+ namespace: testNamespace,
762
+ workflow_name: workflowName,
763
+ run_id: runId,
764
+ resourceId: 'resource-abc',
765
+ snapshot: sample.snapshot,
766
+ createdAt: new Date(),
767
+ updatedAt: new Date(),
768
+ },
769
+ });
770
+ });
771
+
772
+ it('should retrieve a workflow run by ID', async () => {
773
+ const found = await store.getWorkflowRunById({
774
+ namespace: testNamespace,
775
+ runId,
776
+ workflowName,
777
+ });
778
+ expect(found).not.toBeNull();
779
+ expect(found?.runId).toBe(runId);
780
+ const snapshot = found?.snapshot;
781
+ checkWorkflowSnapshot(snapshot!, stepId, 'success');
782
+ });
783
+
784
+ it('should return null for non-existent workflow run ID', async () => {
785
+ const notFound = await store.getWorkflowRunById({
786
+ namespace: testNamespace,
787
+ runId: 'non-existent-id',
788
+ workflowName,
789
+ });
790
+ expect(notFound).toBeNull();
791
+ });
792
+ });
793
+ describe('getWorkflowRuns with resourceId', () => {
794
+ const testNamespace = 'test-workflows-id';
795
+ const workflowName = 'workflow-id-test';
796
+ let resourceId: string;
797
+ let runIds: string[] = [];
798
+
799
+ beforeAll(async () => {
800
+ // Insert multiple workflow runs for the same resourceId
801
+ resourceId = 'resource-shared';
802
+ for (const status of ['success', 'waiting']) {
803
+ const sample = createSampleWorkflowSnapshot(status);
804
+ runIds.push(sample.runId);
805
+ await store.insert({
806
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
807
+ record: {
808
+ namespace: testNamespace,
809
+ workflow_name: workflowName,
810
+ run_id: sample.runId,
811
+ resourceId,
812
+ snapshot: sample.snapshot,
813
+ createdAt: new Date(),
814
+ updatedAt: new Date(),
815
+ },
816
+ });
817
+ }
818
+ // Insert a run with a different resourceId
819
+ const other = createSampleWorkflowSnapshot('waiting');
820
+ await store.insert({
821
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
822
+ record: {
823
+ namespace: testNamespace,
824
+ workflow_name: workflowName,
825
+ run_id: other.runId,
826
+ resourceId: 'resource-other',
827
+ snapshot: other.snapshot,
828
+ createdAt: new Date(),
829
+ updatedAt: new Date(),
830
+ },
831
+ });
832
+ });
833
+
834
+ it('should retrieve all workflow runs by resourceId', async () => {
835
+ const { runs } = await store.getWorkflowRuns({
836
+ namespace: testNamespace,
837
+ resourceId,
838
+ workflowName,
839
+ });
840
+ expect(Array.isArray(runs)).toBe(true);
841
+ expect(runs.length).toBeGreaterThanOrEqual(2);
842
+ for (const run of runs) {
843
+ expect(run.resourceId).toBe(resourceId);
844
+ }
845
+ });
846
+
847
+ it('should return an empty array if no workflow runs match resourceId', async () => {
848
+ const { runs } = await store.getWorkflowRuns({
849
+ namespace: testNamespace,
850
+ resourceId: 'non-existent-resource',
851
+ workflowName,
852
+ });
853
+ expect(Array.isArray(runs)).toBe(true);
854
+ expect(runs.length).toBe(0);
733
855
  });
734
856
  });
735
857
  });
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Vector store specific prompt that details supported operators and examples.
3
+ * This prompt helps users construct valid filters for Upstash Vector.
4
+ */
5
+ export const UPSTASH_PROMPT = `When querying Upstash Vector, you can ONLY use the operators listed below. Any other operators will be rejected.
6
+ Important: Don't explain how to construct the filter - use the specified operators and fields to search the content and return relevant results.
7
+ If a user tries to give an explicit operator that is not supported, reject the filter entirely and let them know that the operator is not supported.
8
+
9
+ Basic Comparison Operators:
10
+ - $eq: Exact match (default when using field: value)
11
+ Example: { "category": "electronics" }
12
+ - $ne: Not equal
13
+ Example: { "category": { "$ne": "electronics" } }
14
+ - $gt: Greater than
15
+ Example: { "price": { "$gt": 100 } }
16
+ - $gte: Greater than or equal
17
+ Example: { "price": { "$gte": 100 } }
18
+ - $lt: Less than
19
+ Example: { "price": { "$lt": 100 } }
20
+ - $lte: Less than or equal
21
+ Example: { "price": { "$lte": 100 } }
22
+
23
+ Array Operators:
24
+ - $in: Match any value in array
25
+ Example: { "category": { "$in": ["electronics", "books"] } }
26
+ - $nin: Does not match any value in array
27
+ Example: { "category": { "$nin": ["electronics", "books"] } }
28
+
29
+ Logical Operators:
30
+ - $and: Logical AND (can be implicit or explicit)
31
+ Implicit Example: { "price": { "$gt": 100 }, "category": "electronics" }
32
+ Explicit Example: { "$and": [{ "price": { "$gt": 100 } }, { "category": "electronics" }] }
33
+ - $or: Logical OR
34
+ Example: { "$or": [{ "price": { "$lt": 50 } }, { "category": "books" }] }
35
+
36
+ Element Operators:
37
+ - $exists: Check if field exists
38
+ Example: { "rating": { "$exists": true } }
39
+
40
+ Restrictions:
41
+ - Regex patterns are not supported
42
+ - Only $and and $or logical operators are supported at the top level
43
+ - Empty arrays in $in/$nin will return no results
44
+ - Nested fields are supported using dot notation
45
+ - Multiple conditions on the same field are supported with both implicit and explicit $and
46
+ - At least one key-value pair is required in filter object
47
+ - Empty objects and undefined values are treated as no filter
48
+ - Invalid types in comparison operators will throw errors
49
+ - All non-logical operators must be used within a field condition
50
+ Valid: { "field": { "$gt": 100 } }
51
+ Valid: { "$and": [...] }
52
+ Invalid: { "$gt": 100 }
53
+ - Logical operators must contain field conditions, not direct operators
54
+ Valid: { "$and": [{ "field": { "$gt": 100 } }] }
55
+ Invalid: { "$and": [{ "$gt": 100 }] }
56
+ - Logical operators ($and, $or):
57
+ - Can only be used at top level or nested within other logical operators
58
+ - Can not be used on a field level, or be nested inside a field
59
+ - Can not be used inside an operator
60
+ - Valid: { "$and": [{ "field": { "$gt": 100 } }] }
61
+ - Valid: { "$or": [{ "$and": [{ "field": { "$gt": 100 } }] }] }
62
+ - Invalid: { "field": { "$and": [{ "$gt": 100 }] } }
63
+ - Invalid: { "field": { "$or": [{ "$gt": 100 }] } }
64
+ - Invalid: { "field": { "$gt": { "$and": [{...}] } } }
65
+
66
+ Example Complex Query:
67
+ {
68
+ "$and": [
69
+ { "category": { "$in": ["electronics", "computers"] } },
70
+ { "price": { "$gte": 100, "$lte": 1000 } },
71
+ { "rating": { "$exists": true, "$gt": 4 } },
72
+ { "$or": [
73
+ { "stock": { "$gt": 0 } },
74
+ { "preorder": true }
75
+ ]}
76
+ ]
77
+ }`;