@mastra/upstash 1.5.2 → 1.5.3
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/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/reference-vectors-upstash.md +10 -4
- package/dist/index.cjs +376 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +377 -30
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +15 -5
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/run-fencing.d.ts +44 -0
- package/dist/storage/domains/run-fencing.d.ts.map +1 -0
- package/dist/storage/domains/workflows/index.d.ts +15 -4
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/package.json +7 -7
package/dist/docs/SKILL.md
CHANGED
|
@@ -8,6 +8,8 @@ The UpstashVector class provides vector search using [Upstash Vector](https://up
|
|
|
8
8
|
|
|
9
9
|
## Constructor options
|
|
10
10
|
|
|
11
|
+
**id** (`string`): Unique identifier for this vector store instance
|
|
12
|
+
|
|
11
13
|
**url** (`string`): Upstash Vector database URL
|
|
12
14
|
|
|
13
15
|
**token** (`string`): Upstash Vector API token
|
|
@@ -194,24 +196,28 @@ Query results are returned in this format:
|
|
|
194
196
|
interface QueryResult {
|
|
195
197
|
id: string
|
|
196
198
|
score: number
|
|
197
|
-
metadata
|
|
199
|
+
metadata?: Record<string, any>
|
|
198
200
|
vector?: number[] // Only included if includeVector is true
|
|
199
201
|
}
|
|
200
202
|
```
|
|
201
203
|
|
|
202
204
|
## Error handling
|
|
203
205
|
|
|
204
|
-
The store throws
|
|
206
|
+
The store throws `MastraError` instances from `@mastra/core/error`:
|
|
205
207
|
|
|
206
208
|
```typescript
|
|
209
|
+
import { MastraError } from '@mastra/core/error'
|
|
210
|
+
|
|
207
211
|
try {
|
|
208
212
|
await store.query({
|
|
209
213
|
indexName: 'index_name',
|
|
210
214
|
queryVector: queryVector,
|
|
211
215
|
})
|
|
212
216
|
} catch (error) {
|
|
213
|
-
if (error instanceof
|
|
214
|
-
console.log(error.
|
|
217
|
+
if (error instanceof MastraError) {
|
|
218
|
+
console.log(error.id) // 'MASTRA_VECTOR_UPSTASH_QUERY_FAILED'
|
|
219
|
+
console.log(error.domain) // 'STORAGE'
|
|
220
|
+
console.log(error.category) // 'USER' | 'THIRD_PARTY'
|
|
215
221
|
console.log(error.details) // Additional error context
|
|
216
222
|
}
|
|
217
223
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -351,6 +351,239 @@ var BackgroundTasksUpstash = class extends _mastra_core_storage.BackgroundTasksS
|
|
|
351
351
|
}
|
|
352
352
|
};
|
|
353
353
|
//#endregion
|
|
354
|
+
//#region src/storage/domains/run-fencing.ts
|
|
355
|
+
function runClaimKey(claims, runId) {
|
|
356
|
+
return `${claims}:run_id:${runId}`;
|
|
357
|
+
}
|
|
358
|
+
const RUN_FENCE_CONFLICT = "MASTRA_RUN_FENCE_CONFLICT";
|
|
359
|
+
const RUN_CLAIM_RETENTION_MS = 10080 * 60 * 1e3;
|
|
360
|
+
const FENCE_GUARD = `
|
|
361
|
+
local fenceClaim = redis.call('HMGET', KEYS[#KEYS], 'generation', 'ownerId')
|
|
362
|
+
if tonumber(fenceClaim[1]) ~= tonumber(ARGV[#ARGV - 1]) or fenceClaim[2] ~= ARGV[#ARGV] then
|
|
363
|
+
return '${RUN_FENCE_CONFLICT}'
|
|
364
|
+
end
|
|
365
|
+
`;
|
|
366
|
+
const OWNER = `
|
|
367
|
+
local function owner(flag)
|
|
368
|
+
local r = redis.call('HMGET', KEYS[1], 'generation', 'ownerId', 'leaseExpiresAt')
|
|
369
|
+
return cjson.encode({ flag, r[1], r[2], r[3] })
|
|
370
|
+
end
|
|
371
|
+
`;
|
|
372
|
+
const CLAIM = `${OWNER}
|
|
373
|
+
local generation = tonumber(redis.call('HGET', KEYS[1], 'generation'))
|
|
374
|
+
local expected = ARGV[3]
|
|
375
|
+
if not generation then
|
|
376
|
+
if expected ~= '' and expected ~= '0' then return owner(0) end
|
|
377
|
+
-- The first generation comes from the clock, so a claim after this hash is
|
|
378
|
+
-- removed still outranks a surviving fence.
|
|
379
|
+
redis.call('HSET', KEYS[1], 'generation', string.format('%d', math.max(1, tonumber(ARGV[4]))), 'ownerId', ARGV[1], 'leaseExpiresAt', ARGV[5], 'updatedAt', ARGV[4])
|
|
380
|
+
redis.call('PEXPIRE', KEYS[1], ARGV[6])
|
|
381
|
+
return owner(1)
|
|
382
|
+
end
|
|
383
|
+
if expected ~= '' and generation ~= tonumber(expected) then return owner(0) end
|
|
384
|
+
if ARGV[2] ~= '1' then
|
|
385
|
+
local expires = tonumber(redis.call('HGET', KEYS[1], 'leaseExpiresAt'))
|
|
386
|
+
if expires and expires > tonumber(ARGV[4]) then return owner(0) end
|
|
387
|
+
end
|
|
388
|
+
redis.call('HSET', KEYS[1], 'generation', string.format('%d', generation + 1), 'ownerId', ARGV[1], 'leaseExpiresAt', ARGV[5], 'updatedAt', ARGV[4])
|
|
389
|
+
redis.call('PEXPIRE', KEYS[1], ARGV[6])
|
|
390
|
+
return owner(1)
|
|
391
|
+
`;
|
|
392
|
+
const RENEW = `${OWNER}
|
|
393
|
+
local r = redis.call('HMGET', KEYS[1], 'generation', 'ownerId', 'leaseExpiresAt')
|
|
394
|
+
if tonumber(r[1]) ~= tonumber(ARGV[1]) or r[2] ~= ARGV[2] or not r[3] then return owner(0) end
|
|
395
|
+
redis.call('HSET', KEYS[1], 'leaseExpiresAt', ARGV[3], 'updatedAt', ARGV[4])
|
|
396
|
+
redis.call('PEXPIRE', KEYS[1], ARGV[5])
|
|
397
|
+
return owner(1)
|
|
398
|
+
`;
|
|
399
|
+
const RELEASE = `
|
|
400
|
+
local r = redis.call('HMGET', KEYS[1], 'generation', 'ownerId')
|
|
401
|
+
if tonumber(r[1]) ~= tonumber(ARGV[1]) or r[2] ~= ARGV[2] then return 0 end
|
|
402
|
+
redis.call('HDEL', KEYS[1], 'leaseExpiresAt')
|
|
403
|
+
redis.call('HSET', KEYS[1], 'updatedAt', ARGV[3])
|
|
404
|
+
redis.call('PEXPIRE', KEYS[1], ARGV[4])
|
|
405
|
+
return 1
|
|
406
|
+
`;
|
|
407
|
+
const READ = `${OWNER}
|
|
408
|
+
return owner(1)
|
|
409
|
+
`;
|
|
410
|
+
const RAISE = `
|
|
411
|
+
local r = redis.call('HMGET', KEYS[1], 'generation', 'ownerId')
|
|
412
|
+
local current = tonumber(r[1])
|
|
413
|
+
if not current or current < tonumber(ARGV[1]) then
|
|
414
|
+
redis.call('HSET', KEYS[1], 'generation', ARGV[1], 'ownerId', ARGV[2])
|
|
415
|
+
elseif current ~= tonumber(ARGV[1]) or r[2] ~= ARGV[2] then
|
|
416
|
+
return 0
|
|
417
|
+
end
|
|
418
|
+
-- Every write un-retires the fence.
|
|
419
|
+
redis.call('HDEL', KEYS[1], 'retiredAt')
|
|
420
|
+
redis.call('PERSIST', KEYS[1])
|
|
421
|
+
return 1
|
|
422
|
+
`;
|
|
423
|
+
const RETIRE = `
|
|
424
|
+
local r = redis.call('HMGET', KEYS[1], 'generation', 'ownerId')
|
|
425
|
+
if tonumber(r[1]) ~= tonumber(ARGV[1]) or r[2] ~= ARGV[2] then return 0 end
|
|
426
|
+
redis.call('HSET', KEYS[1], 'retiredAt', ARGV[3])
|
|
427
|
+
redis.call('PEXPIRE', KEYS[1], ARGV[4])
|
|
428
|
+
return 1
|
|
429
|
+
`;
|
|
430
|
+
const FENCED_WRITE = `
|
|
431
|
+
local i = 1
|
|
432
|
+
while i <= #ARGV - 2 do
|
|
433
|
+
local n = tonumber(ARGV[i])
|
|
434
|
+
redis.call(unpack(ARGV, i + 1, i + n))
|
|
435
|
+
i = i + n + 1
|
|
436
|
+
end
|
|
437
|
+
return 1
|
|
438
|
+
`;
|
|
439
|
+
/**
|
|
440
|
+
* Runs `script`, first checking the run's claim against `check`'s fence in
|
|
441
|
+
* the same script when `check` is set. A stale fence throws
|
|
442
|
+
* `RunFenceConflictError` before the script does anything.
|
|
443
|
+
*/
|
|
444
|
+
async function evalFenced(client, script, keys, args, check) {
|
|
445
|
+
if (!check) return client.eval(script, keys, args);
|
|
446
|
+
const { claims, fence, operation } = check;
|
|
447
|
+
const result = await client.eval(FENCE_GUARD + script, [...keys, runClaimKey(claims, fence.runId)], [
|
|
448
|
+
...args,
|
|
449
|
+
String(fence.generation),
|
|
450
|
+
fence.ownerId
|
|
451
|
+
]);
|
|
452
|
+
if (result === RUN_FENCE_CONFLICT) throw new _mastra_core_storage.RunFenceConflictError(fence, operation);
|
|
453
|
+
return result;
|
|
454
|
+
}
|
|
455
|
+
function toRunOwnershipRecord(runId, reply, now) {
|
|
456
|
+
const [, generation, ownerId, leaseExpiresAt] = reply;
|
|
457
|
+
if (!generation) return null;
|
|
458
|
+
const expiresAt = leaseExpiresAt ? Number(leaseExpiresAt) : null;
|
|
459
|
+
return {
|
|
460
|
+
runId,
|
|
461
|
+
generation: Number(generation),
|
|
462
|
+
ownerId: String(ownerId),
|
|
463
|
+
leaseExpiresAt: expiresAt === null ? null : new Date(expiresAt),
|
|
464
|
+
live: expiresAt !== null && expiresAt > now
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
async function evalOwner(client, script, runId, args, now) {
|
|
468
|
+
const raw = await client.eval(script, [runClaimKey(_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS, runId)], args);
|
|
469
|
+
const reply = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
470
|
+
return {
|
|
471
|
+
applied: Number(reply[0]) === 1,
|
|
472
|
+
record: toRunOwnershipRecord(runId, reply, now)
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
async function claimRunOwnership(client, { runId, ownerId, leaseMs, force, expectedGeneration }) {
|
|
476
|
+
const now = Date.now();
|
|
477
|
+
const { applied, record } = await evalOwner(client, CLAIM, runId, [
|
|
478
|
+
ownerId,
|
|
479
|
+
force ? "1" : "0",
|
|
480
|
+
expectedGeneration === void 0 ? "" : String(expectedGeneration),
|
|
481
|
+
String(now),
|
|
482
|
+
String(now + leaseMs),
|
|
483
|
+
String(RUN_CLAIM_RETENTION_MS)
|
|
484
|
+
], now);
|
|
485
|
+
return applied ? {
|
|
486
|
+
acquired: true,
|
|
487
|
+
record
|
|
488
|
+
} : {
|
|
489
|
+
acquired: false,
|
|
490
|
+
record
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
async function renewRunOwnership(client, { leaseMs, ...fence }) {
|
|
494
|
+
const now = Date.now();
|
|
495
|
+
const { applied, record } = await evalOwner(client, RENEW, fence.runId, [
|
|
496
|
+
String(fence.generation),
|
|
497
|
+
fence.ownerId,
|
|
498
|
+
String(now + leaseMs),
|
|
499
|
+
String(now),
|
|
500
|
+
String(RUN_CLAIM_RETENTION_MS)
|
|
501
|
+
], now);
|
|
502
|
+
return applied ? {
|
|
503
|
+
renewed: true,
|
|
504
|
+
record
|
|
505
|
+
} : {
|
|
506
|
+
renewed: false,
|
|
507
|
+
record
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
async function releaseRunOwnership(client, fence) {
|
|
511
|
+
const released = await client.eval(RELEASE, [runClaimKey(_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS, fence.runId)], [
|
|
512
|
+
String(fence.generation),
|
|
513
|
+
fence.ownerId,
|
|
514
|
+
String(Date.now()),
|
|
515
|
+
String(RUN_CLAIM_RETENTION_MS)
|
|
516
|
+
]);
|
|
517
|
+
return Number(released) === 1;
|
|
518
|
+
}
|
|
519
|
+
async function getRunOwnership(client, runId) {
|
|
520
|
+
return (await evalOwner(client, READ, runId, [], Date.now())).record;
|
|
521
|
+
}
|
|
522
|
+
async function raiseRunFence(client, fence) {
|
|
523
|
+
const raised = await client.eval(RAISE, [runClaimKey(_mastra_core_storage.TABLE_MEMORY_RUN_FENCES, fence.runId)], [String(fence.generation), fence.ownerId]);
|
|
524
|
+
return Number(raised) === 1;
|
|
525
|
+
}
|
|
526
|
+
async function retireRunFence(client, fence) {
|
|
527
|
+
const retired = await client.eval(RETIRE, [runClaimKey(_mastra_core_storage.TABLE_MEMORY_RUN_FENCES, fence.runId)], [
|
|
528
|
+
String(fence.generation),
|
|
529
|
+
fence.ownerId,
|
|
530
|
+
String(Date.now()),
|
|
531
|
+
String(RUN_CLAIM_RETENTION_MS)
|
|
532
|
+
]);
|
|
533
|
+
return Number(retired) === 1;
|
|
534
|
+
}
|
|
535
|
+
function serialize(value) {
|
|
536
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Collects writes and applies them in one script that first checks the run's
|
|
540
|
+
* claim against the fence. A fence that is no longer current makes `exec()`
|
|
541
|
+
* throw `RunFenceConflictError` without writing anything. Scripts run
|
|
542
|
+
* atomically, so a takeover lands either before the batch (failing its check)
|
|
543
|
+
* or after it.
|
|
544
|
+
*/
|
|
545
|
+
var FencedWriteBatch = class {
|
|
546
|
+
client;
|
|
547
|
+
check;
|
|
548
|
+
#keys = /* @__PURE__ */ new Set();
|
|
549
|
+
#args = [];
|
|
550
|
+
constructor(client, check) {
|
|
551
|
+
this.client = client;
|
|
552
|
+
this.check = check;
|
|
553
|
+
}
|
|
554
|
+
#add(key, ...command) {
|
|
555
|
+
this.#keys.add(key);
|
|
556
|
+
this.#args.push(String(command.length), ...command);
|
|
557
|
+
return this;
|
|
558
|
+
}
|
|
559
|
+
set(key, value) {
|
|
560
|
+
return this.#add(key, "SET", key, serialize(value));
|
|
561
|
+
}
|
|
562
|
+
del(key) {
|
|
563
|
+
return this.#add(key, "DEL", key);
|
|
564
|
+
}
|
|
565
|
+
zadd(key, { score, member }) {
|
|
566
|
+
return this.#add(key, "ZADD", key, String(score), member);
|
|
567
|
+
}
|
|
568
|
+
zrem(key, member) {
|
|
569
|
+
return this.#add(key, "ZREM", key, member);
|
|
570
|
+
}
|
|
571
|
+
async exec() {
|
|
572
|
+
await evalFenced(this.client, FENCED_WRITE, [...this.#keys], this.#args, this.check);
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
/** A pipeline, or a fenced batch when `check` is set. */
|
|
576
|
+
function writeBatch(client, check) {
|
|
577
|
+
return check ? new FencedWriteBatch(client, check) : client.pipeline();
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Rejects a fenced write that turns out to have nothing to write when its
|
|
581
|
+
* fence is no longer current, as the write itself would have been rejected.
|
|
582
|
+
*/
|
|
583
|
+
async function assertRunFence(client, check) {
|
|
584
|
+
if (check) await new FencedWriteBatch(client, check).exec();
|
|
585
|
+
}
|
|
586
|
+
//#endregion
|
|
354
587
|
//#region src/storage/domains/memory/index.ts
|
|
355
588
|
function getThreadMessagesKey(threadId) {
|
|
356
589
|
return `thread:${threadId}:messages`;
|
|
@@ -374,10 +607,46 @@ var StoreMemoryUpstash = class extends _mastra_core_storage.MemoryStorage {
|
|
|
374
607
|
this.client = client;
|
|
375
608
|
this.#db = new UpstashDB({ client });
|
|
376
609
|
}
|
|
610
|
+
supportsRunFencing() {
|
|
611
|
+
return true;
|
|
612
|
+
}
|
|
613
|
+
async raiseRunFence(fence) {
|
|
614
|
+
try {
|
|
615
|
+
return await raiseRunFence(this.client, fence);
|
|
616
|
+
} catch (error) {
|
|
617
|
+
throw new _mastra_core_error.MastraError({
|
|
618
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("UPSTASH", "RAISE_RUN_FENCE", "FAILED"),
|
|
619
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
620
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
621
|
+
details: { runId: fence.runId }
|
|
622
|
+
}, error);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
async retireRunFence(fence) {
|
|
626
|
+
try {
|
|
627
|
+
return await retireRunFence(this.client, fence);
|
|
628
|
+
} catch (error) {
|
|
629
|
+
throw new _mastra_core_error.MastraError({
|
|
630
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("UPSTASH", "RETIRE_RUN_FENCE", "FAILED"),
|
|
631
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
632
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
633
|
+
details: { runId: fence.runId }
|
|
634
|
+
}, error);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
#runFenceCheck(fence, operation) {
|
|
638
|
+
const resolved = (0, _mastra_core_storage.resolveRunFence)(this, fence);
|
|
639
|
+
return resolved && {
|
|
640
|
+
claims: _mastra_core_storage.TABLE_MEMORY_RUN_FENCES,
|
|
641
|
+
fence: resolved,
|
|
642
|
+
operation
|
|
643
|
+
};
|
|
644
|
+
}
|
|
377
645
|
async dangerouslyClearAll() {
|
|
378
646
|
await this.#db.deleteData({ tableName: _mastra_core_storage.TABLE_THREADS });
|
|
379
647
|
await this.#db.deleteData({ tableName: _mastra_core_storage.TABLE_MESSAGES });
|
|
380
648
|
await this.#db.deleteData({ tableName: _mastra_core_storage.TABLE_RESOURCES });
|
|
649
|
+
await this.#db.scanAndDelete(`${runClaimKey(_mastra_core_storage.TABLE_MEMORY_RUN_FENCES, "")}*`);
|
|
381
650
|
}
|
|
382
651
|
async getThreadById({ threadId, resourceId }) {
|
|
383
652
|
try {
|
|
@@ -486,14 +755,16 @@ var StoreMemoryUpstash = class extends _mastra_core_storage.MemoryStorage {
|
|
|
486
755
|
throw mastraError;
|
|
487
756
|
}
|
|
488
757
|
}
|
|
489
|
-
async saveThread({ thread }) {
|
|
758
|
+
async saveThread({ thread, fence }) {
|
|
759
|
+
const check = this.#runFenceCheck(fence, "saveThread");
|
|
490
760
|
try {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
761
|
+
const { key, processedRecord } = processRecord(_mastra_core_storage.TABLE_THREADS, thread);
|
|
762
|
+
const batch = writeBatch(this.client, check);
|
|
763
|
+
batch.set(key, processedRecord);
|
|
764
|
+
await batch.exec();
|
|
495
765
|
return thread;
|
|
496
766
|
} catch (error) {
|
|
767
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
497
768
|
const mastraError = new _mastra_core_error.MastraError({
|
|
498
769
|
id: (0, _mastra_core_storage.createStorageErrorId)("UPSTASH", "SAVE_THREAD", "FAILED"),
|
|
499
770
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -505,7 +776,7 @@ var StoreMemoryUpstash = class extends _mastra_core_storage.MemoryStorage {
|
|
|
505
776
|
throw mastraError;
|
|
506
777
|
}
|
|
507
778
|
}
|
|
508
|
-
async updateThread({ id, title, metadata }) {
|
|
779
|
+
async updateThread({ id, title, metadata, fence }) {
|
|
509
780
|
const thread = await this.getThreadById({ threadId: id });
|
|
510
781
|
if (!thread) throw new _mastra_core_error.MastraError({
|
|
511
782
|
id: (0, _mastra_core_storage.createStorageErrorId)("UPSTASH", "UPDATE_THREAD", "FAILED"),
|
|
@@ -525,9 +796,13 @@ var StoreMemoryUpstash = class extends _mastra_core_storage.MemoryStorage {
|
|
|
525
796
|
updatedAt: now
|
|
526
797
|
};
|
|
527
798
|
try {
|
|
528
|
-
await this.saveThread({
|
|
799
|
+
await this.saveThread({
|
|
800
|
+
thread: updatedThread,
|
|
801
|
+
fence
|
|
802
|
+
});
|
|
529
803
|
return updatedThread;
|
|
530
804
|
} catch (error) {
|
|
805
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
531
806
|
throw new _mastra_core_error.MastraError({
|
|
532
807
|
id: (0, _mastra_core_storage.createStorageErrorId)("UPSTASH", "UPDATE_THREAD", "FAILED"),
|
|
533
808
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -562,6 +837,7 @@ var StoreMemoryUpstash = class extends _mastra_core_storage.MemoryStorage {
|
|
|
562
837
|
}
|
|
563
838
|
async saveMessages(args) {
|
|
564
839
|
const { messages } = args;
|
|
840
|
+
const check = this.#runFenceCheck(args.fence, "saveMessages");
|
|
565
841
|
if (messages.length === 0) return { messages: [] };
|
|
566
842
|
try {
|
|
567
843
|
for (const message of messages) {
|
|
@@ -616,7 +892,7 @@ var StoreMemoryUpstash = class extends _mastra_core_storage.MemoryStorage {
|
|
|
616
892
|
}, /* @__PURE__ */ new Error(`Thread ${targetThreadId} not found`));
|
|
617
893
|
for (let i = 0; i < messagesWithIndex.length; i += batchSize) {
|
|
618
894
|
const batch = messagesWithIndex.slice(i, i + batchSize);
|
|
619
|
-
const pipeline = this.client
|
|
895
|
+
const pipeline = writeBatch(this.client, check);
|
|
620
896
|
const batchTouchedThreadIds = /* @__PURE__ */ new Set();
|
|
621
897
|
const batchExistingThreadIds = existingThreadIds.slice(i, i + batch.length);
|
|
622
898
|
for (const [batchIndex, message] of batch.entries()) {
|
|
@@ -931,6 +1207,9 @@ var StoreMemoryUpstash = class extends _mastra_core_storage.MemoryStorage {
|
|
|
931
1207
|
}
|
|
932
1208
|
}
|
|
933
1209
|
async saveResource({ resource }) {
|
|
1210
|
+
return this.#saveResource(resource, void 0);
|
|
1211
|
+
}
|
|
1212
|
+
async #saveResource(resource, check) {
|
|
934
1213
|
try {
|
|
935
1214
|
const key = `${_mastra_core_storage.TABLE_RESOURCES}:${resource.id}`;
|
|
936
1215
|
const serializedResource = {
|
|
@@ -939,14 +1218,17 @@ var StoreMemoryUpstash = class extends _mastra_core_storage.MemoryStorage {
|
|
|
939
1218
|
createdAt: resource.createdAt.toISOString(),
|
|
940
1219
|
updatedAt: resource.updatedAt.toISOString()
|
|
941
1220
|
};
|
|
942
|
-
|
|
1221
|
+
const batch = writeBatch(this.client, check);
|
|
1222
|
+
batch.set(key, serializedResource);
|
|
1223
|
+
await batch.exec();
|
|
943
1224
|
return resource;
|
|
944
1225
|
} catch (error) {
|
|
945
|
-
this.logger.error("Error saving resource:", error);
|
|
1226
|
+
if (!(0, _mastra_core_storage.isRunFenceConflictError)(error)) this.logger.error("Error saving resource:", error);
|
|
946
1227
|
throw error;
|
|
947
1228
|
}
|
|
948
1229
|
}
|
|
949
|
-
async updateResource({ resourceId, workingMemory, metadata }) {
|
|
1230
|
+
async updateResource({ resourceId, workingMemory, metadata, fence }) {
|
|
1231
|
+
const check = this.#runFenceCheck(fence, "updateResource");
|
|
950
1232
|
try {
|
|
951
1233
|
const existingResource = await this.getResourceById({ resourceId });
|
|
952
1234
|
if (!existingResource) {
|
|
@@ -957,7 +1239,7 @@ var StoreMemoryUpstash = class extends _mastra_core_storage.MemoryStorage {
|
|
|
957
1239
|
createdAt: /* @__PURE__ */ new Date(),
|
|
958
1240
|
updatedAt: /* @__PURE__ */ new Date()
|
|
959
1241
|
};
|
|
960
|
-
return this
|
|
1242
|
+
return await this.#saveResource(newResource, check);
|
|
961
1243
|
}
|
|
962
1244
|
const updatedResource = {
|
|
963
1245
|
...existingResource,
|
|
@@ -968,16 +1250,17 @@ var StoreMemoryUpstash = class extends _mastra_core_storage.MemoryStorage {
|
|
|
968
1250
|
},
|
|
969
1251
|
updatedAt: /* @__PURE__ */ new Date()
|
|
970
1252
|
};
|
|
971
|
-
await this
|
|
1253
|
+
await this.#saveResource(updatedResource, check);
|
|
972
1254
|
return updatedResource;
|
|
973
1255
|
} catch (error) {
|
|
974
|
-
this.logger.error("Error updating resource:", error);
|
|
1256
|
+
if (!(0, _mastra_core_storage.isRunFenceConflictError)(error)) this.logger.error("Error updating resource:", error);
|
|
975
1257
|
throw error;
|
|
976
1258
|
}
|
|
977
1259
|
}
|
|
978
1260
|
async updateMessages(args) {
|
|
979
1261
|
const { messages } = args;
|
|
980
1262
|
if (messages.length === 0) return [];
|
|
1263
|
+
const check = this.#runFenceCheck(args.fence, "updateMessages");
|
|
981
1264
|
try {
|
|
982
1265
|
const messageIds = messages.map((m) => m.id);
|
|
983
1266
|
const updatesById = new Map(messages.map((message) => [message.id, message]));
|
|
@@ -1030,7 +1313,10 @@ var StoreMemoryUpstash = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1030
1313
|
for (const [messageId, threadId] of Object.entries(backfillIndexValues)) backfillPipeline.set(getMessageIndexKey(messageId), threadId);
|
|
1031
1314
|
await backfillPipeline.exec();
|
|
1032
1315
|
}
|
|
1033
|
-
if (existingMessages.length === 0)
|
|
1316
|
+
if (existingMessages.length === 0) {
|
|
1317
|
+
await assertRunFence(this.client, check);
|
|
1318
|
+
return [];
|
|
1319
|
+
}
|
|
1034
1320
|
const threadIdsToUpdate = /* @__PURE__ */ new Set();
|
|
1035
1321
|
const destinationThreadIds = /* @__PURE__ */ new Set();
|
|
1036
1322
|
for (const existingMessage of existingMessages) {
|
|
@@ -1062,7 +1348,7 @@ var StoreMemoryUpstash = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1062
1348
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
1063
1349
|
category: _mastra_core_error.ErrorCategory.USER
|
|
1064
1350
|
}, /* @__PURE__ */ new Error(`Thread ${destinationThreadId} not found`));
|
|
1065
|
-
const pipeline = this.client
|
|
1351
|
+
const pipeline = writeBatch(this.client, check);
|
|
1066
1352
|
for (const existingMessage of existingMessages) {
|
|
1067
1353
|
const updatePayload = updatesById.get(existingMessage.id);
|
|
1068
1354
|
if (!updatePayload) continue;
|
|
@@ -1132,8 +1418,9 @@ var StoreMemoryUpstash = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1132
1418
|
}, error);
|
|
1133
1419
|
}
|
|
1134
1420
|
}
|
|
1135
|
-
async deleteMessages(messageIds) {
|
|
1421
|
+
async deleteMessages(messageIds, options) {
|
|
1136
1422
|
if (!messageIds || messageIds.length === 0) return;
|
|
1423
|
+
const check = this.#runFenceCheck(options?.fence, "deleteMessages");
|
|
1137
1424
|
try {
|
|
1138
1425
|
const threadIds = /* @__PURE__ */ new Set();
|
|
1139
1426
|
const messageKeys = [];
|
|
@@ -1169,8 +1456,11 @@ var StoreMemoryUpstash = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1169
1456
|
}
|
|
1170
1457
|
}
|
|
1171
1458
|
}
|
|
1172
|
-
if (messageKeys.length === 0)
|
|
1173
|
-
|
|
1459
|
+
if (messageKeys.length === 0) {
|
|
1460
|
+
await assertRunFence(this.client, check);
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
const pipeline = writeBatch(this.client, check);
|
|
1174
1464
|
for (const key of messageKeys) pipeline.del(key);
|
|
1175
1465
|
for (const messageId of foundMessageIds) pipeline.del(getMessageIndexKey(messageId));
|
|
1176
1466
|
if (threadIds.size > 0) for (const threadId of threadIds) {
|
|
@@ -1186,6 +1476,7 @@ var StoreMemoryUpstash = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1186
1476
|
}
|
|
1187
1477
|
await pipeline.exec();
|
|
1188
1478
|
} catch (error) {
|
|
1479
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
1189
1480
|
throw new _mastra_core_error.MastraError({
|
|
1190
1481
|
id: (0, _mastra_core_storage.createStorageErrorId)("UPSTASH", "DELETE_MESSAGES", "FAILED"),
|
|
1191
1482
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -1613,8 +1904,56 @@ var WorkflowsUpstash = class extends _mastra_core_storage.WorkflowsStorage {
|
|
|
1613
1904
|
resourceId: row.resourceId
|
|
1614
1905
|
};
|
|
1615
1906
|
}
|
|
1907
|
+
supportsRunFencing() {
|
|
1908
|
+
return true;
|
|
1909
|
+
}
|
|
1616
1910
|
async dangerouslyClearAll() {
|
|
1617
1911
|
await this.#db.deleteData({ tableName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT });
|
|
1912
|
+
await this.#db.scanAndDelete(`${runClaimKey(_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS, "")}*`);
|
|
1913
|
+
}
|
|
1914
|
+
#runFenceCheck(fence, runId, operation) {
|
|
1915
|
+
const resolved = (0, _mastra_core_storage.resolveRunFence)(this, fence, runId);
|
|
1916
|
+
return resolved && {
|
|
1917
|
+
claims: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS,
|
|
1918
|
+
fence: resolved,
|
|
1919
|
+
operation
|
|
1920
|
+
};
|
|
1921
|
+
}
|
|
1922
|
+
#ownershipError(operation, runId, error) {
|
|
1923
|
+
return new _mastra_core_error.MastraError({
|
|
1924
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("UPSTASH", operation, "FAILED"),
|
|
1925
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
1926
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
1927
|
+
details: { runId }
|
|
1928
|
+
}, error);
|
|
1929
|
+
}
|
|
1930
|
+
async claimRunOwnership(args) {
|
|
1931
|
+
try {
|
|
1932
|
+
return await claimRunOwnership(this.client, args);
|
|
1933
|
+
} catch (error) {
|
|
1934
|
+
throw this.#ownershipError("CLAIM_RUN_OWNERSHIP", args.runId, error);
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
async renewRunOwnership(args) {
|
|
1938
|
+
try {
|
|
1939
|
+
return await renewRunOwnership(this.client, args);
|
|
1940
|
+
} catch (error) {
|
|
1941
|
+
throw this.#ownershipError("RENEW_RUN_OWNERSHIP", args.runId, error);
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
async releaseRunOwnership(fence) {
|
|
1945
|
+
try {
|
|
1946
|
+
return await releaseRunOwnership(this.client, fence);
|
|
1947
|
+
} catch (error) {
|
|
1948
|
+
throw this.#ownershipError("RELEASE_RUN_OWNERSHIP", fence.runId, error);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
async getRunOwnership({ runId }) {
|
|
1952
|
+
try {
|
|
1953
|
+
return await getRunOwnership(this.client, runId);
|
|
1954
|
+
} catch (error) {
|
|
1955
|
+
throw this.#ownershipError("GET_RUN_OWNERSHIP", runId, error);
|
|
1956
|
+
}
|
|
1618
1957
|
}
|
|
1619
1958
|
async getWorkflowRunRecord({ namespace, runId, resourceId, workflowName }) {
|
|
1620
1959
|
if (workflowName) {
|
|
@@ -1642,7 +1981,8 @@ var WorkflowsUpstash = class extends _mastra_core_storage.WorkflowsStorage {
|
|
|
1642
1981
|
return this.client.get(key);
|
|
1643
1982
|
}))).find((w) => w?.run_id === runId && (!workflowName || w.workflow_name === workflowName) && (!resourceId || w.resourceId === resourceId)) ?? null;
|
|
1644
1983
|
}
|
|
1645
|
-
async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state }) {
|
|
1984
|
+
async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state, fence }) {
|
|
1985
|
+
const check = this.#runFenceCheck(fence, runId, "updateWorkflowResults");
|
|
1646
1986
|
try {
|
|
1647
1987
|
const key = getKey(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, {
|
|
1648
1988
|
namespace: "workflows",
|
|
@@ -1650,7 +1990,7 @@ var WorkflowsUpstash = class extends _mastra_core_storage.WorkflowsStorage {
|
|
|
1650
1990
|
run_id: runId
|
|
1651
1991
|
});
|
|
1652
1992
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1653
|
-
const resultJson = await this.client
|
|
1993
|
+
const resultJson = await evalFenced(this.client, `
|
|
1654
1994
|
local key = KEYS[1]
|
|
1655
1995
|
local stepId = ARGV[1]
|
|
1656
1996
|
local resultJson = ARGV[2]
|
|
@@ -1740,7 +2080,7 @@ var WorkflowsUpstash = class extends _mastra_core_storage.WorkflowsStorage {
|
|
|
1740
2080
|
runId,
|
|
1741
2081
|
String(Date.now()),
|
|
1742
2082
|
state === void 0 ? "" : JSON.stringify(state)
|
|
1743
|
-
]);
|
|
2083
|
+
], check);
|
|
1744
2084
|
let data;
|
|
1745
2085
|
if (typeof resultJson === "string") data = JSON.parse(resultJson);
|
|
1746
2086
|
else data = resultJson;
|
|
@@ -1759,7 +2099,8 @@ var WorkflowsUpstash = class extends _mastra_core_storage.WorkflowsStorage {
|
|
|
1759
2099
|
}, error);
|
|
1760
2100
|
}
|
|
1761
2101
|
}
|
|
1762
|
-
async updateWorkflowState({ workflowName, runId, opts }) {
|
|
2102
|
+
async updateWorkflowState({ workflowName, runId, opts, fence }) {
|
|
2103
|
+
const check = this.#runFenceCheck(fence, runId, "updateWorkflowState");
|
|
1763
2104
|
try {
|
|
1764
2105
|
const key = getKey(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, {
|
|
1765
2106
|
namespace: "workflows",
|
|
@@ -1824,11 +2165,11 @@ var WorkflowsUpstash = class extends _mastra_core_storage.WorkflowsStorage {
|
|
|
1824
2165
|
`;
|
|
1825
2166
|
const { expectedStatus, ...state } = opts;
|
|
1826
2167
|
const expectedStatusJson = expectedStatus === void 0 ? "" : JSON.stringify(Array.isArray(expectedStatus) ? expectedStatus : [expectedStatus]);
|
|
1827
|
-
const resultJson = await this.client
|
|
2168
|
+
const resultJson = await evalFenced(this.client, luaScript, [key], [
|
|
1828
2169
|
JSON.stringify(state),
|
|
1829
2170
|
now,
|
|
1830
2171
|
expectedStatusJson
|
|
1831
|
-
]);
|
|
2172
|
+
], check);
|
|
1832
2173
|
if (!resultJson) return;
|
|
1833
2174
|
let data;
|
|
1834
2175
|
if (typeof resultJson === "string") data = JSON.parse(resultJson);
|
|
@@ -1849,6 +2190,7 @@ var WorkflowsUpstash = class extends _mastra_core_storage.WorkflowsStorage {
|
|
|
1849
2190
|
}
|
|
1850
2191
|
async persistWorkflowSnapshot(params) {
|
|
1851
2192
|
const { namespace = "workflows", workflowName, runId, resourceId, snapshot, createdAt, updatedAt } = params;
|
|
2193
|
+
const check = this.#runFenceCheck(params.fence, runId, "persistWorkflowSnapshot");
|
|
1852
2194
|
try {
|
|
1853
2195
|
const now = /* @__PURE__ */ new Date();
|
|
1854
2196
|
const key = getKey(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, {
|
|
@@ -1866,7 +2208,7 @@ var WorkflowsUpstash = class extends _mastra_core_storage.WorkflowsStorage {
|
|
|
1866
2208
|
createdAt: (createdAt ?? now).toISOString(),
|
|
1867
2209
|
updatedAt: (updatedAt ?? now).toISOString()
|
|
1868
2210
|
};
|
|
1869
|
-
await this.client
|
|
2211
|
+
await evalFenced(this.client, `
|
|
1870
2212
|
local existing = redis.call("GET", KEYS[1])
|
|
1871
2213
|
local next = cjson.decode(ARGV[1])
|
|
1872
2214
|
if existing then
|
|
@@ -1877,8 +2219,9 @@ var WorkflowsUpstash = class extends _mastra_core_storage.WorkflowsStorage {
|
|
|
1877
2219
|
end
|
|
1878
2220
|
redis.call("SET", KEYS[1], cjson.encode(next))
|
|
1879
2221
|
return 1
|
|
1880
|
-
`, [key], [JSON.stringify(record)]);
|
|
2222
|
+
`, [key], [JSON.stringify(record)], check);
|
|
1881
2223
|
} catch (error) {
|
|
2224
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
1882
2225
|
throw new _mastra_core_error.MastraError({
|
|
1883
2226
|
id: (0, _mastra_core_storage.createStorageErrorId)("UPSTASH", "PERSIST_WORKFLOW_SNAPSHOT", "FAILED"),
|
|
1884
2227
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -1936,7 +2279,8 @@ var WorkflowsUpstash = class extends _mastra_core_storage.WorkflowsStorage {
|
|
|
1936
2279
|
}, error);
|
|
1937
2280
|
}
|
|
1938
2281
|
}
|
|
1939
|
-
async deleteWorkflowRunById({ runId, workflowName }) {
|
|
2282
|
+
async deleteWorkflowRunById({ runId, workflowName, fence }) {
|
|
2283
|
+
const check = this.#runFenceCheck(fence, runId, "deleteWorkflowRunById");
|
|
1940
2284
|
try {
|
|
1941
2285
|
const record = await this.getWorkflowRunRecord({
|
|
1942
2286
|
namespace: "workflows",
|
|
@@ -1949,8 +2293,11 @@ var WorkflowsUpstash = class extends _mastra_core_storage.WorkflowsStorage {
|
|
|
1949
2293
|
run_id: runId,
|
|
1950
2294
|
...record?.resourceId ? { resourceId: record.resourceId } : {}
|
|
1951
2295
|
});
|
|
1952
|
-
|
|
2296
|
+
const batch = writeBatch(this.client, check);
|
|
2297
|
+
batch.del(key);
|
|
2298
|
+
await batch.exec();
|
|
1953
2299
|
} catch (error) {
|
|
2300
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
1954
2301
|
throw new _mastra_core_error.MastraError({
|
|
1955
2302
|
id: (0, _mastra_core_storage.createStorageErrorId)("UPSTASH", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
|
|
1956
2303
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|