hybridq 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,604 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+
5
+ // src/storage/memory.ts
6
+ var MemoryAdapter = class {
7
+ constructor(opts = {}) {
8
+ this.queues = /* @__PURE__ */ new Map();
9
+ this.cipher = opts.cipher ?? null;
10
+ }
11
+ bucket(queue) {
12
+ let b = this.queues.get(queue);
13
+ if (!b) this.queues.set(queue, b = []);
14
+ return b;
15
+ }
16
+ seal(job) {
17
+ if (!this.cipher) return { ...job };
18
+ const enc = this.cipher.encrypt(JSON.stringify(job.payload));
19
+ return { ...job, payload: null, _enc: enc };
20
+ }
21
+ open(stored) {
22
+ if (!this.cipher || !stored._enc) {
23
+ const { _enc: _enc2, ...job2 } = stored;
24
+ return job2;
25
+ }
26
+ const payload = JSON.parse(this.cipher.decrypt(stored._enc));
27
+ const { _enc, ...job } = stored;
28
+ return { ...job, payload };
29
+ }
30
+ async push(job) {
31
+ this.bucket(job.queue).push(this.seal(job));
32
+ }
33
+ async shiftBatch(queue, count, leaseMs) {
34
+ const now = Date.now();
35
+ const bucket = this.bucket(queue);
36
+ const claimed = [];
37
+ for (const stored of bucket) {
38
+ if (claimed.length >= count) break;
39
+ const eligible = (stored.status === "pending" || stored.status === "delayed") && stored.runAt <= now;
40
+ const lapsed = stored.status === "active" && stored.leaseUntil !== void 0 && stored.leaseUntil <= now;
41
+ if (!eligible && !lapsed) continue;
42
+ stored.status = "active";
43
+ stored.attempts += 1;
44
+ stored.leaseUntil = now + leaseMs;
45
+ claimed.push(this.open(stored));
46
+ }
47
+ return claimed;
48
+ }
49
+ async complete(queue, jobId) {
50
+ const bucket = this.bucket(queue);
51
+ const i = bucket.findIndex((j) => j.id === jobId);
52
+ if (i !== -1) bucket.splice(i, 1);
53
+ }
54
+ async fail(queue, jobId, error, retry, backoffMs) {
55
+ const job = this.bucket(queue).find((j) => j.id === jobId);
56
+ if (!job) return;
57
+ job.lastError = error;
58
+ job.leaseUntil = void 0;
59
+ if (retry && job.attempts < job.maxAttempts) {
60
+ job.status = "pending";
61
+ job.runAt = Date.now() + backoffMs;
62
+ } else {
63
+ job.status = "failed";
64
+ }
65
+ }
66
+ async release(queue, jobId) {
67
+ const job = this.bucket(queue).find((j) => j.id === jobId);
68
+ if (!job || job.status !== "active") return;
69
+ job.status = "pending";
70
+ job.leaseUntil = void 0;
71
+ job.attempts = Math.max(0, job.attempts - 1);
72
+ }
73
+ async size(queue) {
74
+ const now = Date.now();
75
+ return this.bucket(queue).filter(
76
+ (j) => (j.status === "pending" || j.status === "delayed") && j.runAt <= now
77
+ ).length;
78
+ }
79
+ };
80
+
81
+ // src/storage/redis.ts
82
+ var CLAIM_LUA = `
83
+ local pending = KEYS[1]
84
+ local delayed = KEYS[2]
85
+ local active = KEYS[3]
86
+ local jobs = KEYS[4]
87
+ local now = tonumber(ARGV[1])
88
+ local count = tonumber(ARGV[2])
89
+ local lease = tonumber(ARGV[3])
90
+
91
+ local due = redis.call('ZRANGEBYSCORE', delayed, '-inf', now)
92
+ for _, id in ipairs(due) do
93
+ redis.call('RPUSH', pending, id)
94
+ redis.call('ZREM', delayed, id)
95
+ end
96
+
97
+ local lapsed = redis.call('ZRANGEBYSCORE', active, '-inf', now)
98
+ for _, id in ipairs(lapsed) do
99
+ redis.call('RPUSH', pending, id)
100
+ redis.call('ZREM', active, id)
101
+ end
102
+
103
+ local out = {}
104
+ for i = 1, count do
105
+ local id = redis.call('LPOP', pending)
106
+ if not id then break end
107
+ local raw = redis.call('HGET', jobs, id)
108
+ if raw then
109
+ local env = cjson.decode(raw)
110
+ env.status = 'active'
111
+ env.attempts = (env.attempts or 0) + 1
112
+ env.leaseUntil = now + lease
113
+ local enc = cjson.encode(env)
114
+ redis.call('HSET', jobs, id, enc)
115
+ redis.call('ZADD', active, env.leaseUntil, id)
116
+ table.insert(out, enc)
117
+ end
118
+ end
119
+ return out
120
+ `;
121
+ var RedisAdapter = class {
122
+ constructor(redis, opts = {}) {
123
+ this.redis = redis;
124
+ this.ns = opts.namespace ?? "hq";
125
+ this.cipher = opts.cipher ?? null;
126
+ }
127
+ k(queue, suffix) {
128
+ return `${this.ns}:${queue}:${suffix}`;
129
+ }
130
+ toEnvelope(job) {
131
+ const plain = JSON.stringify(job.payload ?? null);
132
+ const enc = !!this.cipher;
133
+ return {
134
+ id: job.id,
135
+ queue: job.queue,
136
+ status: job.status,
137
+ attempts: job.attempts,
138
+ maxAttempts: job.maxAttempts,
139
+ runAt: job.runAt,
140
+ createdAt: job.createdAt,
141
+ leaseUntil: job.leaseUntil,
142
+ lastError: job.lastError,
143
+ payloadJson: enc ? this.cipher.encrypt(plain) : plain,
144
+ enc
145
+ };
146
+ }
147
+ fromEnvelope(env) {
148
+ const plain = env.enc ? this.cipher ? this.cipher.decrypt(env.payloadJson) : (() => {
149
+ throw new Error(
150
+ "hybridq: job is encrypted but no cipher is configured"
151
+ );
152
+ })() : env.payloadJson;
153
+ return {
154
+ id: env.id,
155
+ queue: env.queue,
156
+ payload: JSON.parse(plain),
157
+ status: env.status,
158
+ attempts: env.attempts,
159
+ maxAttempts: env.maxAttempts,
160
+ runAt: env.runAt,
161
+ createdAt: env.createdAt,
162
+ leaseUntil: env.leaseUntil,
163
+ lastError: env.lastError
164
+ };
165
+ }
166
+ async push(job) {
167
+ const env = this.toEnvelope(job);
168
+ const raw = JSON.stringify(env);
169
+ await this.redis.hset(this.k(job.queue, "jobs"), job.id, raw);
170
+ if (job.runAt > Date.now()) {
171
+ await this.redis.zadd(this.k(job.queue, "delayed"), job.runAt, job.id);
172
+ } else {
173
+ await this.redis.rpush(this.k(job.queue, "pending"), job.id);
174
+ }
175
+ }
176
+ async shiftBatch(queue, count, leaseMs) {
177
+ const res = await this.redis.eval(
178
+ CLAIM_LUA,
179
+ [
180
+ this.k(queue, "pending"),
181
+ this.k(queue, "delayed"),
182
+ this.k(queue, "active"),
183
+ this.k(queue, "jobs")
184
+ ],
185
+ [Date.now(), count, leaseMs]
186
+ );
187
+ if (!res || res.length === 0) return [];
188
+ return res.map((raw) => this.fromEnvelope(JSON.parse(raw)));
189
+ }
190
+ async complete(queue, jobId) {
191
+ await this.redis.zrem(this.k(queue, "active"), jobId);
192
+ await this.redis.hdel(this.k(queue, "jobs"), jobId);
193
+ }
194
+ async fail(queue, jobId, error, retry, backoffMs) {
195
+ const raw = await this.redis.hget(this.k(queue, "jobs"), jobId);
196
+ if (!raw) return;
197
+ const env = JSON.parse(raw);
198
+ env.lastError = error;
199
+ env.leaseUntil = void 0;
200
+ await this.redis.zrem(this.k(queue, "active"), jobId);
201
+ if (retry && env.attempts < env.maxAttempts) {
202
+ env.status = "pending";
203
+ env.runAt = Date.now() + backoffMs;
204
+ await this.redis.hset(this.k(queue, "jobs"), jobId, JSON.stringify(env));
205
+ if (backoffMs > 0) {
206
+ await this.redis.zadd(this.k(queue, "delayed"), env.runAt, jobId);
207
+ } else {
208
+ await this.redis.rpush(this.k(queue, "pending"), jobId);
209
+ }
210
+ } else {
211
+ env.status = "failed";
212
+ await this.redis.hset(this.k(queue, "jobs"), jobId, JSON.stringify(env));
213
+ }
214
+ }
215
+ async release(queue, jobId) {
216
+ const raw = await this.redis.hget(this.k(queue, "jobs"), jobId);
217
+ if (!raw) return;
218
+ const env = JSON.parse(raw);
219
+ if (env.status !== "active") return;
220
+ env.status = "pending";
221
+ env.leaseUntil = void 0;
222
+ env.attempts = Math.max(0, env.attempts - 1);
223
+ await this.redis.zrem(this.k(queue, "active"), jobId);
224
+ await this.redis.hset(this.k(queue, "jobs"), jobId, JSON.stringify(env));
225
+ await this.redis.rpush(this.k(queue, "pending"), jobId);
226
+ }
227
+ async size(queue) {
228
+ return this.redis.llen(this.k(queue, "pending"));
229
+ }
230
+ };
231
+ function fromUpstash(client) {
232
+ return {
233
+ eval: (script, keys, args) => client.eval(script, keys, args),
234
+ hget: (k, f) => client.hget(k, f),
235
+ hset: (k, f, v) => client.hset(k, { [f]: v }),
236
+ hdel: (k, f) => client.hdel(k, f),
237
+ rpush: (k, v) => client.rpush(k, v),
238
+ zadd: (k, score, member) => client.zadd(k, { score, member }),
239
+ zrem: (k, m) => client.zrem(k, m),
240
+ llen: (k) => client.llen(k)
241
+ };
242
+ }
243
+ function fromIORedis(client) {
244
+ return {
245
+ eval: (script, keys, args) => client.eval(script, keys.length, ...keys, ...args),
246
+ hget: (k, f) => client.hget(k, f),
247
+ hset: (k, f, v) => client.hset(k, f, v),
248
+ hdel: (k, f) => client.hdel(k, f),
249
+ rpush: (k, v) => client.rpush(k, v),
250
+ zadd: (k, score, member) => client.zadd(k, score, member),
251
+ zrem: (k, m) => client.zrem(k, m),
252
+ llen: (k) => client.llen(k)
253
+ };
254
+ }
255
+ function getSharedIORedis(factory, key = "__hybridq_ioredis__") {
256
+ const g = globalThis;
257
+ if (!g[key]) g[key] = factory();
258
+ return g[key];
259
+ }
260
+
261
+ // src/engine/processor.ts
262
+ var defaultBackoff = (attempt) => Math.min(3e4, 1e3 * 2 ** Math.max(0, attempt - 1));
263
+ function cpuSampler() {
264
+ const proc = globalThis.process;
265
+ if (!proc?.cpuUsage) return () => 0;
266
+ const startCpu = proc.cpuUsage();
267
+ const startWall = Date.now();
268
+ return () => {
269
+ const d = proc.cpuUsage(startCpu);
270
+ const wallUs = Math.max(1, (Date.now() - startWall) * 1e3);
271
+ return (d.user + d.system) / wallUs * 100;
272
+ };
273
+ }
274
+ async function drain(opts) {
275
+ const {
276
+ queue,
277
+ adapter,
278
+ handler,
279
+ budget,
280
+ lock,
281
+ batchSize = 10,
282
+ leaseMs = budget.maxExecutionTimeMs,
283
+ backoff = defaultBackoff,
284
+ onError
285
+ } = opts;
286
+ const startedAt = Date.now();
287
+ const elapsed = () => Date.now() - startedAt;
288
+ const sampleCpu = cpuSampler();
289
+ let processed = 0;
290
+ let failed = 0;
291
+ let released = 0;
292
+ let stoppedBy = "drained";
293
+ const lockKey = `${queue}:drain-lock`;
294
+ let lockToken = null;
295
+ if (lock) {
296
+ lockToken = await lock.acquire(lockKey, budget.maxExecutionTimeMs + 2e3);
297
+ if (!lockToken) {
298
+ return { processed, failed, released, stoppedBy: "lockBusy", elapsedMs: 0 };
299
+ }
300
+ }
301
+ const done = () => processed + failed;
302
+ const budgetExhausted = () => {
303
+ if (done() >= budget.maxJobsPerTrigger) return "jobCap";
304
+ if (elapsed() >= budget.maxExecutionTimeMs) return "timeBudget";
305
+ if (budget.maxCpuBudgetPct !== void 0 && sampleCpu() >= budget.maxCpuBudgetPct) {
306
+ return "cpuBudget";
307
+ }
308
+ return null;
309
+ };
310
+ try {
311
+ outer: while (true) {
312
+ const hit = budgetExhausted();
313
+ if (hit) {
314
+ stoppedBy = hit;
315
+ break;
316
+ }
317
+ const remainingByCount = budget.maxJobsPerTrigger - done();
318
+ const claimCount = Math.max(1, Math.min(batchSize, remainingByCount));
319
+ const batch = await adapter.shiftBatch(queue, claimCount, leaseMs);
320
+ if (batch.length === 0) {
321
+ stoppedBy = "drained";
322
+ break;
323
+ }
324
+ for (let i = 0; i < batch.length; i++) {
325
+ const job = batch[i];
326
+ const hit2 = budgetExhausted();
327
+ if (hit2) {
328
+ for (let j = i; j < batch.length; j++) {
329
+ await adapter.release(queue, batch[j].id);
330
+ released++;
331
+ }
332
+ stoppedBy = hit2;
333
+ break outer;
334
+ }
335
+ try {
336
+ await handler(job);
337
+ await adapter.complete(queue, job.id);
338
+ processed++;
339
+ } catch (err) {
340
+ const message = err instanceof Error ? err.message : String(err);
341
+ onError?.(err, job);
342
+ await adapter.fail(
343
+ queue,
344
+ job.id,
345
+ message,
346
+ /* retry */
347
+ true,
348
+ backoff(job.attempts)
349
+ );
350
+ failed++;
351
+ }
352
+ }
353
+ }
354
+ } finally {
355
+ if (lock && lockToken) await lock.release(lockKey, lockToken);
356
+ }
357
+ return { processed, failed, released, stoppedBy, elapsedMs: elapsed() };
358
+ }
359
+
360
+ // src/util/id.ts
361
+ function newId(prefix = "job") {
362
+ const g = globalThis;
363
+ const uuid = g.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
364
+ return `${prefix}_${uuid}`;
365
+ }
366
+
367
+ // src/engine/lock.ts
368
+ var ACQUIRE_LUA = `return redis.call('SET', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2])`;
369
+ var RELEASE_LUA = `
370
+ if redis.call('GET', KEYS[1]) == ARGV[1] then
371
+ return redis.call('DEL', KEYS[1])
372
+ end
373
+ return 0`;
374
+ var RedisLock = class {
375
+ constructor(redis) {
376
+ this.redis = redis;
377
+ }
378
+ async acquire(key, ttlMs) {
379
+ const token = newId("lock");
380
+ const res = await this.redis.eval(ACQUIRE_LUA, [key], [token, ttlMs]);
381
+ return res === "OK" || res === true ? token : null;
382
+ }
383
+ async release(key, token) {
384
+ await this.redis.eval(RELEASE_LUA, [key], [token]);
385
+ }
386
+ };
387
+ var MemoryLock = class {
388
+ constructor() {
389
+ this.held = /* @__PURE__ */ new Map();
390
+ }
391
+ async acquire(key, ttlMs) {
392
+ const now = Date.now();
393
+ const cur = this.held.get(key);
394
+ if (cur && cur.until > now) return null;
395
+ const token = newId("lock");
396
+ this.held.set(key, { token, until: now + ttlMs });
397
+ return token;
398
+ }
399
+ async release(key, token) {
400
+ const cur = this.held.get(key);
401
+ if (cur && cur.token === token) this.held.delete(key);
402
+ }
403
+ };
404
+
405
+ // src/server/queue.ts
406
+ var DEFAULT_BUDGET = {
407
+ maxJobsPerTrigger: 25,
408
+ maxExecutionTimeMs: 8e3
409
+ // safe under a 10s serverless limit
410
+ };
411
+ var Queue = class {
412
+ constructor(config) {
413
+ this.config = config;
414
+ this.budget = { ...DEFAULT_BUDGET, ...config.budget };
415
+ }
416
+ get name() {
417
+ return this.config.name;
418
+ }
419
+ /** Producer side: push a unit of work. Returns the created job. */
420
+ async enqueue(payload, opts = {}) {
421
+ const now = Date.now();
422
+ const job = {
423
+ id: opts.id ?? newId("job"),
424
+ queue: this.config.name,
425
+ payload,
426
+ status: opts.delayMs && opts.delayMs > 0 ? "delayed" : "pending",
427
+ attempts: 0,
428
+ maxAttempts: opts.maxAttempts ?? this.config.defaultMaxAttempts ?? 3,
429
+ runAt: now + (opts.delayMs ?? 0),
430
+ createdAt: now
431
+ };
432
+ await this.config.adapter.push(job);
433
+ return job;
434
+ }
435
+ /** How many jobs are currently claimable. Handy for trigger gating. */
436
+ size() {
437
+ return this.config.adapter.size(this.config.name);
438
+ }
439
+ /**
440
+ * Consumer side: drain a budgeted batch with the given handler. Safe to call
441
+ * from many concurrent requests — the lock ensures only one actually runs.
442
+ */
443
+ process(handler, budgetOverride) {
444
+ return drain({
445
+ queue: this.config.name,
446
+ adapter: this.config.adapter,
447
+ handler,
448
+ budget: { ...this.budget, ...budgetOverride },
449
+ lock: this.config.lock,
450
+ batchSize: this.config.batchSize,
451
+ backoff: this.config.backoff
452
+ });
453
+ }
454
+ };
455
+ function defineQueue(config) {
456
+ return new Queue(config);
457
+ }
458
+ var ALGO = "aes-256-gcm";
459
+ var IV_BYTES = 12;
460
+ var ENVELOPE_PREFIX = "hqv1";
461
+ function deriveKey(secret) {
462
+ return crypto.createHmac("sha256", "hybridq:payload-key").update(secret).digest();
463
+ }
464
+ function createPayloadCipher(secret) {
465
+ if (!secret) return null;
466
+ const key = deriveKey(secret);
467
+ return {
468
+ encrypt(plaintext) {
469
+ const iv = crypto.randomBytes(IV_BYTES);
470
+ const cipher = crypto.createCipheriv(ALGO, key, iv);
471
+ const ct = Buffer.concat([
472
+ cipher.update(plaintext, "utf8"),
473
+ cipher.final()
474
+ ]);
475
+ const tag = cipher.getAuthTag();
476
+ return [
477
+ ENVELOPE_PREFIX,
478
+ iv.toString("base64url"),
479
+ tag.toString("base64url"),
480
+ ct.toString("base64url")
481
+ ].join(".");
482
+ },
483
+ decrypt(envelope) {
484
+ const parts = envelope.split(".");
485
+ if (parts.length !== 4 || parts[0] !== ENVELOPE_PREFIX) {
486
+ throw new Error("hybridq: malformed encrypted payload envelope");
487
+ }
488
+ const [, ivB64, tagB64, ctB64] = parts;
489
+ const iv = Buffer.from(ivB64, "base64url");
490
+ const tag = Buffer.from(tagB64, "base64url");
491
+ const ct = Buffer.from(ctB64, "base64url");
492
+ const decipher = crypto.createDecipheriv(ALGO, key, iv);
493
+ decipher.setAuthTag(tag);
494
+ return Buffer.concat([decipher.update(ct), decipher.final()]).toString(
495
+ "utf8"
496
+ );
497
+ }
498
+ };
499
+ }
500
+ function safeEqual(a, b) {
501
+ const ab = Buffer.from(a);
502
+ const bb = Buffer.from(b);
503
+ if (ab.length !== bb.length) return false;
504
+ return crypto.timingSafeEqual(ab, bb);
505
+ }
506
+ function signTrigger(secret, path, ts = Date.now()) {
507
+ const sig = crypto.createHmac("sha256", secret).update(`${ts}.${path}`).digest("base64url");
508
+ return `t=${ts},v=${sig}`;
509
+ }
510
+ function verifyTrigger(secret, path, token, maxSkewMs = 5 * 6e4) {
511
+ const m = /^t=(\d+),v=([A-Za-z0-9_-]+)$/.exec(token.trim());
512
+ if (!m) return false;
513
+ const ts = Number(m[1]);
514
+ const sig = m[2];
515
+ if (!Number.isFinite(ts) || Math.abs(Date.now() - ts) > maxSkewMs) {
516
+ return false;
517
+ }
518
+ const expected = crypto.createHmac("sha256", secret).update(`${ts}.${path}`).digest("base64url");
519
+ return safeEqual(sig, expected);
520
+ }
521
+
522
+ // src/server/middleware.ts
523
+ var TRIGGER_SECRET_HEADER = "x-worker-trigger-secret";
524
+ var TRIGGER_TOKEN_HEADER = "x-worker-trigger-token";
525
+ function authorizeTrigger(req, auth) {
526
+ let configured = false;
527
+ if (auth.secret) {
528
+ configured = true;
529
+ const provided = req.headers.get(TRIGGER_SECRET_HEADER);
530
+ if (provided && safeEqual(provided, auth.secret)) return true;
531
+ }
532
+ if (auth.hmacSecret) {
533
+ configured = true;
534
+ const token = req.headers.get(TRIGGER_TOKEN_HEADER);
535
+ const path = auth.path ?? new URL(req.url).pathname;
536
+ if (token && verifyTrigger(auth.hmacSecret, path, token)) return true;
537
+ }
538
+ return configured ? false : false;
539
+ }
540
+ function deferDrain(run, ctx) {
541
+ const p = run().catch(() => {
542
+ });
543
+ if (ctx?.waitUntil) ctx.waitUntil(p);
544
+ }
545
+ function withTrigger(route, opts) {
546
+ return async (req) => {
547
+ const res = await route(req);
548
+ deferDrain(async () => {
549
+ if (opts.skipIfEmpty && await opts.queue.size() === 0) {
550
+ return {
551
+ processed: 0,
552
+ failed: 0,
553
+ released: 0,
554
+ stoppedBy: "drained",
555
+ elapsedMs: 0
556
+ };
557
+ }
558
+ return opts.queue.process(opts.handler, opts.budget);
559
+ }, opts.ctx);
560
+ return res;
561
+ };
562
+ }
563
+ function createTriggerEndpoint(opts) {
564
+ const background = opts.background ?? true;
565
+ return async (req) => {
566
+ if (!authorizeTrigger(req, opts.auth)) {
567
+ return new Response("Forbidden", { status: 403 });
568
+ }
569
+ if (background) {
570
+ deferDrain(() => opts.queue.process(opts.handler, opts.budget), opts.ctx);
571
+ return new Response(JSON.stringify({ accepted: true }), {
572
+ status: 202,
573
+ headers: { "content-type": "application/json" }
574
+ });
575
+ }
576
+ const report = await opts.queue.process(opts.handler, opts.budget);
577
+ return new Response(JSON.stringify(report), {
578
+ status: 200,
579
+ headers: { "content-type": "application/json" }
580
+ });
581
+ };
582
+ }
583
+
584
+ exports.MemoryAdapter = MemoryAdapter;
585
+ exports.MemoryLock = MemoryLock;
586
+ exports.Queue = Queue;
587
+ exports.RedisAdapter = RedisAdapter;
588
+ exports.RedisLock = RedisLock;
589
+ exports.TRIGGER_SECRET_HEADER = TRIGGER_SECRET_HEADER;
590
+ exports.TRIGGER_TOKEN_HEADER = TRIGGER_TOKEN_HEADER;
591
+ exports.authorizeTrigger = authorizeTrigger;
592
+ exports.createPayloadCipher = createPayloadCipher;
593
+ exports.createTriggerEndpoint = createTriggerEndpoint;
594
+ exports.defineQueue = defineQueue;
595
+ exports.drain = drain;
596
+ exports.fromIORedis = fromIORedis;
597
+ exports.fromUpstash = fromUpstash;
598
+ exports.getSharedIORedis = getSharedIORedis;
599
+ exports.newId = newId;
600
+ exports.signTrigger = signTrigger;
601
+ exports.verifyTrigger = verifyTrigger;
602
+ exports.withTrigger = withTrigger;
603
+ //# sourceMappingURL=index.cjs.map
604
+ //# sourceMappingURL=index.cjs.map