@stacksjs/queue 0.74.58 → 0.74.60

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.
Files changed (2) hide show
  1. package/dist/job.js +1 -1
  2. package/package.json +11 -11
package/dist/job.js CHANGED
@@ -1 +1 @@
1
- var {require}=import.meta;import{appPath,frameworkPath}from"@stacksjs/path";import{env as envVars}from"@stacksjs/env";import{enqueueAfterCommit,isInTransaction}from"@stacksjs/database/runtime";import{moveToDeadLetter}from"./dead-letter";import{assertEnvelopeSerializable,createEnvelope,serializeEnvelope}from"./envelope";import{claimDispatchKey,releaseDispatchKey}from"./idempotency";import{isQuarantined}from"./poison";import{runNamedAction}from"./action-runner";let testingModule,databaseModule,traceModule;function loadTraceModule(){return traceModule??=import("@stacksjs/router").catch((error)=>{traceModule=void 0;throw error})}function getQueueDriver(){return envVars.QUEUE_DRIVER||"sync"}let _warnedAfterCommitNoTx=!1;function warnAfterCommitOutsideTransaction(){if(_warnedAfterCommitNoTx)return;_warnedAfterCommitNoTx=!0;console.warn("[queue] .afterCommit() was called outside of `db.transaction(...)`. Dispatching immediately. Wrap the call in a transaction or drop .afterCommit() to silence this message.")}class JobBuilder{name;payload;options={};txMode="auto";constructor(name,payload){this.name=name;this.payload=payload}onQueue(queue){this.options.queue=queue;return this}delay(seconds){this.options.delay=seconds;return this}tries(count){this.options.tries=count;return this}timeout(seconds){this.options.timeout=seconds;return this}backoff(delays){this.options.backoff=delays;return this}withContext(context){this.options.context=context;return this}withIdempotencyKey(key){this.options.idempotencyKey=key;return this}afterCommit(){this.txMode="after";return this}withoutCommit(){this.txMode="immediate";return this}async dispatch(){const{isFaked,getFakeQueue}=await(testingModule??=import("./testing").catch((error)=>{testingModule=void 0;throw error}));if(isFaked()){getFakeQueue()?.dispatch(this.name,this.payload,this.options);return}if(this.txMode!=="immediate"){if(isInTransaction()){const buffered=this.runDispatchPipeline.bind(this);if(enqueueAfterCommit(async()=>{await buffered()}))return}else if(this.txMode==="after")warnAfterCommitOutsideTransaction()}await this.runDispatchPipeline()}async runDispatchPipeline(){let claimedKey=!1;if(this.options.idempotencyKey){const claim=await claimDispatchKey(this.options.idempotencyKey,this.name,this.options.queue);if(claim==="duplicate")return;claimedKey=claim==="claimed"}try{if(await isQuarantined(this.name,this.payload)){const envelope=createEnvelope(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});if(await moveToDeadLetter({queue:this.options.queue||"default",payload:serializeEnvelope(envelope),exception:`quarantined: ${this.name}`},"poison-detected"))return}const driver=getQueueDriver();if(driver==="database")await this.dispatchToDatabase();else if(driver==="redis")await this.dispatchToRedis();else if(driver==="sync")await runJob(this.name,{payload:this.payload,context:this.options.context,traceId:await currentTraceId()});else if(driver==="sqs"||driver==="memory"||driver==="beanstalkd")throw Error(`[queue] Driver "${driver}" is not implemented yet. Set QUEUE_DRIVER to one of: redis, database, sync.`);else throw Error(`[queue] Unknown QUEUE_DRIVER "${driver}". Allowed values: redis, database, sync.`)}catch(err){if(claimedKey&&this.options.idempotencyKey)await releaseDispatchKey(this.options.idempotencyKey);throw err}}async dispatchIf(condition){if(condition)await this.dispatch()}async dispatchUnless(condition){if(!condition)await this.dispatch()}async dispatchToDatabase(){const now=Math.floor(Date.now()/1000),availableAt=this.options.delay?now+this.options.delay:now,envelope=createEnvelope(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff},await currentTraceId()),payloadJson=serializeEnvelope(envelope),{db}=await(databaseModule??=import("@stacksjs/database/runtime").catch((error)=>{databaseModule=void 0;throw error}));await db.insertInto("jobs").values({queue:this.options.queue||"default",payload:payloadJson,attempts:0,reserved_at:null,available_at:availableAt,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis(){const envelope=createEnvelope(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});assertEnvelopeSerializable(envelope);const{RedisQueue}=await import("./drivers/redis"),{queue:queueConfig}=await import("@stacksjs/config"),redisConfig=queueConfig?.connections?.redis;if(!redisConfig)throw Error("Redis queue connection is not configured. Check config/queue.ts");await new RedisQueue(this.options.queue||"default",redisConfig).add(envelope,{delay:this.options.delay,maxTries:this.options.tries,timeout:this.options.timeout,backoff:this.options.backoff})}async dispatchNow(){await runJob(this.name,{payload:this.payload,context:this.options.context})}}export function job(name,payload){return new JobBuilder(name,payload)}export const Jobs={make(name,payload){return new JobBuilder(name,payload)},async dispatch(name,payload){await new JobBuilder(name,payload).dispatch()},async dispatchIf(condition,name,payload){if(condition)await new JobBuilder(name,payload).dispatch()},async dispatchUnless(condition,name,payload){if(!condition)await new JobBuilder(name,payload).dispatch()},async dispatchNow(name,payload){await new JobBuilder(name,payload).dispatchNow()},dispatchAfter(seconds,name,payload){return new JobBuilder(name,payload).delay(seconds)},async dispatchOnce(key,name,payload){await new JobBuilder(name,payload).withIdempotencyKey(key).dispatch()},async dispatchAfterCommit(name,payload){await new JobBuilder(name,payload).afterCommit().dispatch()}};export function jobBatch(jobs){const{PendingBatch}=require("./batch");return new PendingBatch(jobs)}export async function resolveJobFile(name){const candidates=[appPath(`Jobs/${name}.ts`),frameworkPath(`defaults/app/Jobs/${name}.ts`)];for(const candidate of candidates)if(await Bun.file(candidate).exists())return candidate;let packageCandidate;try{const pkgUrl=import.meta.resolve("@stacksjs/defaults/package.json");packageCandidate=`${new URL(".",pkgUrl).pathname}app/Jobs/${name}.ts`}catch{}if(packageCandidate&&await Bun.file(packageCandidate).exists())return packageCandidate;return null}export async function runJob(name,options={}){const{withTraceId}=await loadTraceModule(),traceId=options.traceId??`job:${name}:${Math.random().toString(36).slice(2,10)}`;await withTraceId(traceId,async()=>{const jobPath=await resolveJobFile(name);if(!jobPath)throw Error(`Job ${name} not found. Looked in app/Jobs/${name}.ts and the framework defaults (storage/framework/defaults/app/Jobs, @stacksjs/defaults).`);const jobConfig=(await import(jobPath)).default;if(!jobConfig)throw Error(`Job ${name} does not export a default`);if(typeof jobConfig.handle==="function")await jobConfig.handle(options.payload);else if(typeof jobConfig.action==="string")await runNamedAction(jobConfig.action);else if(typeof jobConfig.action==="function")await jobConfig.action();else if(typeof jobConfig==="function")await jobConfig(options.payload,options.context);else throw Error(`Job ${name} does not have a valid handler`)})}async function currentTraceId(){try{const{getTraceId}=await loadTraceModule();return getTraceId()}catch{return}}
1
+ var {require}=import.meta;import{appPath,frameworkPath}from"@stacksjs/path";import{env as envVars}from"@stacksjs/env";import{enqueueAfterCommit,isInTransaction}from"@stacksjs/database/runtime";import{moveToDeadLetter}from"./dead-letter";import{assertEnvelopeSerializable,createEnvelope,serializeEnvelope}from"./envelope";import{claimDispatchKey,releaseDispatchKey}from"./idempotency";import{isQuarantined}from"./poison";import{runNamedAction}from"./action-runner";let testingModule,databaseModule,traceModule;function loadTraceModule(){return traceModule??=import("@stacksjs/router").catch((error)=>{traceModule=void 0;throw error})}function getQueueDriver(){return envVars.QUEUE_DRIVER||"sync"}let _warnedAfterCommitNoTx=!1;function warnAfterCommitOutsideTransaction(){if(_warnedAfterCommitNoTx)return;_warnedAfterCommitNoTx=!0;console.warn("[queue] .afterCommit() was called outside of `db.transaction(...)`. Dispatching immediately. Wrap the call in a transaction or drop .afterCommit() to silence this message.")}class JobBuilder{name;payload;options={};txMode="auto";constructor(name,payload){this.name=name;this.payload=payload}onQueue(queue){this.options.queue=queue;return this}delay(seconds){this.options.delay=seconds;return this}tries(count){this.options.tries=count;return this}timeout(seconds){this.options.timeout=seconds;return this}backoff(delays){this.options.backoff=delays;return this}withContext(context){this.options.context=context;return this}withIdempotencyKey(key){this.options.idempotencyKey=key;return this}afterCommit(){this.txMode="after";return this}withoutCommit(){this.txMode="immediate";return this}async dispatch(){const{isFaked,getFakeQueue}=await(testingModule??=import("./testing").catch((error)=>{testingModule=void 0;throw error}));if(isFaked()){getFakeQueue()?.dispatch(this.name,this.payload,this.options);return}if(this.txMode!=="immediate"){if(isInTransaction()){const buffered=this.runDispatchPipeline.bind(this);if(enqueueAfterCommit(async()=>{await buffered()}))return}else if(this.txMode==="after")warnAfterCommitOutsideTransaction()}await this.runDispatchPipeline()}async runDispatchPipeline(){let claimedKey=!1;if(this.options.idempotencyKey){const claim=await claimDispatchKey(this.options.idempotencyKey,this.name,this.options.queue);if(claim==="duplicate")return;claimedKey=claim==="claimed"}try{if(await isQuarantined(this.name,this.payload)){const envelope=createEnvelope(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});if(await moveToDeadLetter({queue:this.options.queue||"default",payload:serializeEnvelope(envelope),exception:`quarantined: ${this.name}`},"poison-detected"))return}const driver=getQueueDriver();if(driver==="database")await this.dispatchToDatabase();else if(driver==="redis")await this.dispatchToRedis();else if(driver==="sync")await runJob(this.name,{payload:this.payload,context:this.options.context,traceId:await currentTraceId()});else if(driver==="sqs"||driver==="memory"||driver==="beanstalkd")throw Error(`[queue] Driver "${driver}" is not implemented yet. Set QUEUE_DRIVER to one of: redis, database, sync.`);else throw Error(`[queue] Unknown QUEUE_DRIVER "${driver}". Allowed values: redis, database, sync.`)}catch(err){if(claimedKey&&this.options.idempotencyKey)await releaseDispatchKey(this.options.idempotencyKey);throw err}}async dispatchIf(condition){if(condition)await this.dispatch()}async dispatchUnless(condition){if(!condition)await this.dispatch()}async dispatchToDatabase(){const now=Math.floor(Date.now()/1000),availableAt=this.options.delay?now+this.options.delay:now,envelope=createEnvelope(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff},await currentTraceId()),payloadJson=serializeEnvelope(envelope),{db}=await(databaseModule??=import("@stacksjs/database/runtime").catch((error)=>{databaseModule=void 0;throw error}));await db.insertInto("jobs").values({queue:this.options.queue||"default",payload:payloadJson,attempts:0,reserved_at:null,available_at:availableAt,created_at:new Date().toISOString().slice(0,19).replace("T"," ")}).execute()}async dispatchToRedis(){const envelope=createEnvelope(this.name,this.payload,{queue:this.options.queue,timeout:this.options.timeout,tries:this.options.tries,backoff:this.options.backoff});assertEnvelopeSerializable(envelope);const{RedisQueue}=await import("./drivers/redis"),{queue:queueConfig}=await import("@stacksjs/config"),redisConfig=queueConfig?.connections?.redis;if(!redisConfig)throw Error("Redis queue connection is not configured. Check config/queue.ts");await new RedisQueue(this.options.queue||"default",redisConfig).add(envelope,{delay:this.options.delay,maxTries:this.options.tries,timeout:this.options.timeout,backoff:this.options.backoff})}async dispatchNow(){await runJob(this.name,{payload:this.payload,context:this.options.context})}}export function job(name,payload){return new JobBuilder(name,payload)}export const Jobs={make(name,payload){return new JobBuilder(name,payload)},async dispatch(name,payload){await new JobBuilder(name,payload).dispatch()},async dispatchIf(condition,name,payload){if(condition)await new JobBuilder(name,payload).dispatch()},async dispatchUnless(condition,name,payload){if(!condition)await new JobBuilder(name,payload).dispatch()},async dispatchNow(name,payload){await new JobBuilder(name,payload).dispatchNow()},dispatchAfter(seconds,name,payload){return new JobBuilder(name,payload).delay(seconds)},async dispatchOnce(key,name,payload){await new JobBuilder(name,payload).withIdempotencyKey(key).dispatch()},async dispatchAfterCommit(name,payload){await new JobBuilder(name,payload).afterCommit().dispatch()}};export function jobBatch(jobs){const{PendingBatch}=require("./batch");return new PendingBatch(jobs)}export async function resolveJobFile(name){const files=name.endsWith("Job")?[`${name}.ts`]:[`${name}.ts`,`${name}Job.ts`],candidates=[...files.map((file)=>appPath(`Jobs/${file}`)),...files.map((file)=>frameworkPath(`defaults/app/Jobs/${file}`))];for(const candidate of candidates)if(await Bun.file(candidate).exists())return candidate;let packageRoot;try{const pkgUrl=import.meta.resolve("@stacksjs/defaults/package.json");packageRoot=new URL(".",pkgUrl).pathname}catch{}if(packageRoot)for(const file of files){const packageCandidate=`${packageRoot}app/Jobs/${file}`;if(await Bun.file(packageCandidate).exists())return packageCandidate}return null}export async function runJob(name,options={}){const{withTraceId}=await loadTraceModule(),traceId=options.traceId??`job:${name}:${Math.random().toString(36).slice(2,10)}`;await withTraceId(traceId,async()=>{const jobPath=await resolveJobFile(name);if(!jobPath)throw Error(`Job ${name} not found. Looked in app/Jobs/${name}.ts${name.endsWith("Job")?"":` (and ${name}Job.ts)`} and the framework defaults (storage/framework/defaults/app/Jobs, @stacksjs/defaults).`);const jobConfig=(await import(jobPath)).default;if(!jobConfig)throw Error(`Job ${name} does not export a default`);if(typeof jobConfig.handle==="function")await jobConfig.handle(options.payload);else if(typeof jobConfig.action==="string")await runNamedAction(jobConfig.action);else if(typeof jobConfig.action==="function")await jobConfig.action();else if(typeof jobConfig==="function")await jobConfig(options.payload,options.context);else throw Error(`Job ${name} does not have a valid handler`)})}async function currentTraceId(){try{const{getTraceId}=await loadTraceModule();return getTraceId()}catch{return}}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/queue",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.74.58",
5
+ "version": "0.74.60",
6
6
  "description": "The Stacks Queue system powered by bun-queue.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -63,22 +63,22 @@
63
63
  "prepublishOnly": "bun run build"
64
64
  },
65
65
  "dependencies": {
66
- "@stacksjs/action-runner": "0.74.58",
66
+ "@stacksjs/action-runner": "0.74.60",
67
67
  "@stacksjs/bun-queue": "^0.1.8",
68
- "@stacksjs/cache": "0.74.58",
69
- "@stacksjs/config": "0.74.58",
70
- "@stacksjs/database": "0.74.58",
71
- "@stacksjs/env": "0.74.58",
72
- "@stacksjs/error-handling": "0.74.58",
73
- "@stacksjs/logging": "0.74.58",
74
- "@stacksjs/path": "0.74.58"
68
+ "@stacksjs/cache": "0.74.60",
69
+ "@stacksjs/config": "0.74.60",
70
+ "@stacksjs/database": "0.74.60",
71
+ "@stacksjs/env": "0.74.60",
72
+ "@stacksjs/error-handling": "0.74.60",
73
+ "@stacksjs/logging": "0.74.60",
74
+ "@stacksjs/path": "0.74.60"
75
75
  },
76
76
  "devDependencies": {
77
77
  "better-dx": "^0.2.24"
78
78
  },
79
79
  "peerDependencies": {
80
- "@stacksjs/email": "0.74.58",
81
- "@stacksjs/router": "0.74.58"
80
+ "@stacksjs/email": "0.74.60",
81
+ "@stacksjs/router": "0.74.60"
82
82
  },
83
83
  "peerDependenciesMeta": {
84
84
  "@stacksjs/email": {