@lunora/scheduler 1.0.0-alpha.86 → 1.0.0-alpha.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts
CHANGED
|
@@ -448,10 +448,27 @@ interface QueueConsumerOptions {
|
|
|
448
448
|
/**
|
|
449
449
|
* The consumer's `max_retries` as deployed in `wrangler.jsonc`, which a
|
|
450
450
|
* consumer cannot read at runtime. Used only to recognise a message's last
|
|
451
|
-
* delivery,
|
|
452
|
-
* dead-letter it becomes. Defaults to
|
|
451
|
+
* delivery, where a declined dispatch is re-enqueued through `requeue` (or
|
|
452
|
+
* logged as the drop or dead-letter it becomes without one). Defaults to
|
|
453
|
+
* Cloudflare's default of 3.
|
|
453
454
|
*/
|
|
454
455
|
maxRetries?: number;
|
|
456
|
+
/**
|
|
457
|
+
* Re-enqueue a job whose dispatch is declined on its last delivery as a
|
|
458
|
+
* copy delayed past the in-flight claim, with a fresh retry budget, instead
|
|
459
|
+
* of letting it be dead-lettered or dropped while its call is still
|
|
460
|
+
* running. Without it that loss is only logged.
|
|
461
|
+
*
|
|
462
|
+
* `queue` is the producer binding of the queue this consumer reads (the
|
|
463
|
+
* one handed to `createQueueWorkpool`). `secret` keys the MAC the copy
|
|
464
|
+
* carries — pass the admin token (`LUNORA_ADMIN_TOKEN`). The copy
|
|
465
|
+
* dispatches under the replaced message's id so the shard serves the
|
|
466
|
+
* declined call's result, and without a valid MAC no body can claim an id.
|
|
467
|
+
*/
|
|
468
|
+
requeue?: {
|
|
469
|
+
queue: QueueLike<QueueJob>;
|
|
470
|
+
secret: string;
|
|
471
|
+
};
|
|
455
472
|
}
|
|
456
473
|
/** Options for the `httpDispatcher` — the default HTTP dispatcher. */
|
|
457
474
|
interface HttpDispatcherOptions {
|
package/dist/index.d.ts
CHANGED
|
@@ -448,10 +448,27 @@ interface QueueConsumerOptions {
|
|
|
448
448
|
/**
|
|
449
449
|
* The consumer's `max_retries` as deployed in `wrangler.jsonc`, which a
|
|
450
450
|
* consumer cannot read at runtime. Used only to recognise a message's last
|
|
451
|
-
* delivery,
|
|
452
|
-
* dead-letter it becomes. Defaults to
|
|
451
|
+
* delivery, where a declined dispatch is re-enqueued through `requeue` (or
|
|
452
|
+
* logged as the drop or dead-letter it becomes without one). Defaults to
|
|
453
|
+
* Cloudflare's default of 3.
|
|
453
454
|
*/
|
|
454
455
|
maxRetries?: number;
|
|
456
|
+
/**
|
|
457
|
+
* Re-enqueue a job whose dispatch is declined on its last delivery as a
|
|
458
|
+
* copy delayed past the in-flight claim, with a fresh retry budget, instead
|
|
459
|
+
* of letting it be dead-lettered or dropped while its call is still
|
|
460
|
+
* running. Without it that loss is only logged.
|
|
461
|
+
*
|
|
462
|
+
* `queue` is the producer binding of the queue this consumer reads (the
|
|
463
|
+
* one handed to `createQueueWorkpool`). `secret` keys the MAC the copy
|
|
464
|
+
* carries — pass the admin token (`LUNORA_ADMIN_TOKEN`). The copy
|
|
465
|
+
* dispatches under the replaced message's id so the shard serves the
|
|
466
|
+
* declined call's result, and without a valid MAC no body can claim an id.
|
|
467
|
+
*/
|
|
468
|
+
requeue?: {
|
|
469
|
+
queue: QueueLike<QueueJob>;
|
|
470
|
+
secret: string;
|
|
471
|
+
};
|
|
455
472
|
}
|
|
456
473
|
/** Options for the `httpDispatcher` — the default HTTP dispatcher. */
|
|
457
474
|
interface HttpDispatcherOptions {
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{default as o}from"./packem_shared/createScheduler-BW6wVD57.mjs";import{default as a}from"./packem_shared/createWorkpool-CQs6z3Y0.mjs";import{createCronTrigger as f}from"./packem_shared/createCronTrigger-DSuDZtqj.mjs";import{CRON_SCHEDULE_KINDS as p,compileCronSchedule as c,cronJobs as d}from"./packem_shared/CRON_SCHEDULE_KINDS-xyFk800s.mjs";import{createQueueConsumer as m,createQueueWorkpool as n,httpDispatcher as x}from"./packem_shared/createQueueConsumer
|
|
1
|
+
import{default as o}from"./packem_shared/createScheduler-BW6wVD57.mjs";import{default as a}from"./packem_shared/createWorkpool-CQs6z3Y0.mjs";import{createCronTrigger as f}from"./packem_shared/createCronTrigger-DSuDZtqj.mjs";import{CRON_SCHEDULE_KINDS as p,compileCronSchedule as c,cronJobs as d}from"./packem_shared/CRON_SCHEDULE_KINDS-xyFk800s.mjs";import{createQueueConsumer as m,createQueueWorkpool as n,httpDispatcher as x}from"./packem_shared/createQueueConsumer--7v7nhxZ.mjs";import{default as i}from"./packem_shared/resolveScheduleId-DgysKSrN.mjs";import{MAX_RETRY_ATTEMPTS as E,RETRY_BASE_DELAY_MS as C,SchedulerDO as _}from"./packem_shared/MAX_RETRY_ATTEMPTS-BFBSSPrs.mjs";import{createSchedulerHost as R}from"./packem_shared/createSchedulerHost-CO6BlgVY.mjs";import{isWorkflowReference as A}from"./packem_shared/isWorkflowReference-CT3tdefh.mjs";import{assertValidCronExpression as g,isValidCronExpression as k,warnIfSecondsLeading as L}from"./packem_shared/assertValidCronExpression-DnBtukpq.mjs";import{default as W}from"./packem_shared/assertScheduleDelay-BgA4K1WB.mjs";import{default as w}from"./packem_shared/assertScheduleInstant-BzESPqyw.mjs";export{p as CRON_SCHEDULE_KINDS,E as MAX_RETRY_ATTEMPTS,C as RETRY_BASE_DELAY_MS,_ as SchedulerDO,W as assertScheduleDelay,w as assertScheduleInstant,g as assertValidCronExpression,c as compileCronSchedule,f as createCronTrigger,m as createQueueConsumer,n as createQueueWorkpool,o as createScheduler,R as createSchedulerHost,a as createWorkpool,d as cronJobs,x as httpDispatcher,k as isValidCronExpression,A as isWorkflowReference,i as resolveScheduleId,L as warnIfSecondsLeading};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{LunoraError as d,isLunoraError as k}from"@lunora/errors";import{a as B}from"./wire-codec-TIVAE0Ds.mjs";const Q=9e5,F="DISPATCH_IN_PROGRESS",H="x-lunora-dispatch-declined",W=(e,t,a)=>{if(t===void 0)return{dispose:()=>{},signal:e};const o=new AbortController,r=setTimeout(()=>{o.abort(a())},t);return{dispose:()=>{clearTimeout(r)},signal:o.signal}},G=e=>{let t="";for(let a=0;a<e.length;a+=32768)t+=String.fromCharCode(...e.subarray(a,a+32768));return btoa(t)},V=e=>{const t=atob(e),a=new Uint8Array(t.length);for(let o=0;o<t.length;o+=1)a[o]=t.codePointAt(o)??0;return a},h="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",q=e=>{let t="",a=0;const o=e.length-2;for(;a<o;a+=3){const n=e[a]<<16|e[a+1]<<8|e[a+2];t+=h.charAt(n>>18&63)+h.charAt(n>>12&63)+h.charAt(n>>6&63)+h.charAt(n&63)}const r=e.length-a;if(r===1){const n=e[a]<<16;t+=h.charAt(n>>18&63)+h.charAt(n>>12&63)}else if(r===2){const n=e[a]<<16|e[a+1]<<8;t+=h.charAt(n>>18&63)+h.charAt(n>>12&63)+h.charAt(n>>6&63)}return t};new TextDecoder;const U=new TextEncoder,N="=",X=e=>{for(let t=0;t<e.length;t+=1)if(e.charCodeAt(t)>255)return!1;return!0},z=e=>q(U.encode(JSON.stringify(e))),Y=e=>!e.startsWith(N)&&X(e)?e:`${N}${q(U.encode(e))}`,Z="$lunora.wire$",R=64,v=1024,$="__proto__",I={BigInt64Array,BigUint64Array,Float32Array,Float64Array,Int8Array,Int16Array,Int32Array,Uint8Array,Uint8ClampedArray,Uint16Array,Uint32Array},O={Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError},l=(e,t=0)=>{if(t>R)throw new RangeError(`wire-codec: value nesting exceeds the ${R}-level limit`);if(e===null||typeof e!="object")return e;if(Array.isArray(e)){if(e[0]===Z)switch(e[1]){case"-inf":return-1/0;case"arr":return e[2].map(r=>l(r,t+1));case"bigint":{const r=e[2];if(typeof r!="string"||r.length>v||!/^-?\d+$/.test(r))throw new RangeError(`wire-codec: invalid or over-long bigint (max ${v} digits)`);return BigInt(r)}case"date":{const r=l(e[2],t+1);if(typeof r!="number")throw new TypeError("wire-codec: malformed date — epoch must be a number");return new Date(r)}case"map":{const r=e[2];return new Map(r.map(n=>{if(!Array.isArray(n)||n.length!==2)throw new TypeError("wire-codec: malformed map entry — expected a [key, value] pair");return[l(n[0],t+1),l(n[1],t+1)]}))}case"set":return new Set(e[2].map(r=>l(r,t+1)));case"url":{const r=e[2];if(typeof r!="string")throw new TypeError("wire-codec: malformed url — href must be a string");return new URL(r)}case"error":{const r=e[2],n=e[3];if(typeof r!="string"||typeof n!="string")throw new TypeError("wire-codec: malformed error — name and message must be strings");const s=(Object.hasOwn(O,r)?O[r]:void 0)??Error,c=new s(n);c.name!==r&&Object.defineProperty(c,"name",{configurable:!0,value:r,writable:!0});const i=l(e[4],t+1);if(i===null||typeof i!="object"||Array.isArray(i))throw new TypeError("wire-codec: malformed error — props must be an object");for(const u of Object.keys(i))u===$?Object.defineProperty(c,u,{configurable:!0,enumerable:!0,value:i[u],writable:!0}):c[u]=i[u];return e.length>5&&Object.defineProperty(c,"cause",{configurable:!0,value:l(e[5],t+1),writable:!0}),c}case"bytes":{const r=e[2];if(typeof r!="string")throw new TypeError("wire-codec: malformed bytes — payload must be a base64 string");const n=V(r);if(G(n)!==r)throw new TypeError("wire-codec: malformed bytes — payload must be canonical padded base64");const s=e[3]??"Uint8Array";if(s==="ArrayBuffer")return n.buffer.byteLength===n.byteLength?n.buffer:n.slice().buffer;const c=Object.hasOwn(I,s)?I[s]:void 0;return c?new c(n.slice().buffer):n}case"inf":return 1/0;case"nan":return Number.NaN;case"undefined":return;default:return e.map(r=>l(r,t+1))}return e.map(r=>l(r,t+1))}const a=e,o={};for(const r of Object.keys(a)){const n=l(a[r],t+1);r===$?Object.defineProperty(o,r,{configurable:!0,enumerable:!0,value:n,writable:!0}):o[r]=n}return o},ee="/_lunora/scheduler/dispatch",te=3e4,re=e=>{let t=e.length;for(;t>0&&e[t-1]==="/";)t-=1;return e.slice(0,t)},L=Symbol("lunoraDispatchFailure"),P=Symbol("lunoraDispatchDecline"),ne=Symbol("lunoraDispatchMessageId"),x=(e,t)=>(t!==void 0&&Object.defineProperty(e,ne,{value:t}),e),ae=(e,t)=>(Object.defineProperty(e,L,{value:!0}),x(e,t)),oe=(e,t,a,o)=>{const{status:r}=t;try{const n=JSON.parse(a)?.error;if(typeof n=="object"&&n!==null&&typeof n.code=="string"){const{code:s,data:c,message:i}=n,u=ae(new d(s,typeof i=="string"?i:void 0,{data:c,status:r}),o);return t.headers.get(H)==="1"&&Object.defineProperty(u,P,{value:!0}),u}}catch{}return x(new d("INTERNAL",`${e}: function dispatch failed (${String(r)}): ${a}`,{status:r}),o)},se=e=>k(e)&&e[L]===!0,ce=e=>se(e)&&e[P]===!0,_=Q/1e3,ie=3,ue=async(e,t)=>{const a=t.maxRetries??ie,o=typeof e.attempts=="number"&&e.attempts>a;let r="";if(o&&t.requeue!==void 0)try{return await t.requeue(_),e.ack(),"requeued"}catch(n){r=` Re-enqueueing a delayed copy failed (${n instanceof Error?n.message:String(n)}).`}return e.retry({delaySeconds:_}),o&&console.error(`${t.where}: message ${e.id} was declined (${F}) on its last delivery (attempt ${String(e.attempts)} of ${String(a+1)}) — its earlier delivery is still running the call.${r} It is retried anyway, but its retries are exhausted, so it will be dead-lettered if the queue has a deadLetterQueue and dropped if not. Raise max_retries so a slow call cannot exhaust it.`),"retried"},le=(e,t,a)=>new d("INTERNAL",`${e}: function dispatch to "${t}" timed out after ${String(a)}ms`,{status:503}),de=e=>{const{label:t}=e,a=globalThis.fetch,o=e.fetchImpl??(typeof a=="function"?a.bind(globalThis):void 0);return async(r,n,s={})=>{if(typeof o!="function")throw new TypeError(`${t}: no fetch implementation available — pass fetchImpl or run on a platform with global fetch`);const c=()=>n??{},i=e.env.LUNORA_ORIGIN_URL;if(typeof i!="string"||i.length===0)throw new d("INTERNAL",`${t}: \`LUNORA_ORIGIN_URL\` must be set on the Worker env so a handler can call back into Lunora functions`);const u=e.env.LUNORA_ADMIN_TOKEN;if(typeof u!="string"||u.length===0)throw new d("INTERNAL",`${t}: \`LUNORA_ADMIN_TOKEN\` must be set on the Worker env to authenticate function dispatch`);const w=`${re(i)}${ee}`,m={authorization:`Bearer ${u}`,"content-type":"application/json"};e.traceparent!==void 0&&e.traceparent.length>0&&(m.traceparent=e.traceparent),e.identity?.userId!==void 0&&(m["x-lunora-userid"]=Y(e.identity.userId)),e.identity?.claims!==void 0&&(m["x-lunora-identity"]=z(e.identity.claims));const b=s.timeoutMs??te,E=W(void 0,b,()=>new DOMException(`dispatch timed out after ${String(b)}ms`,"TimeoutError")),A=y=>{throw y instanceof Error&&y.name==="TimeoutError"?le(t,r.__lunoraRef,b):y};let f;try{try{f=await o(w,{body:JSON.stringify({args:c(),functionPath:r.__lunoraRef,id:s.dedupId,shardKey:s.shardKey}),headers:m,method:"POST",signal:E.signal})}catch(g){return A(g)}if(!f.ok){let g;try{g=await f.text()}catch(J){return A(J)}throw oe(t,f,g,s.messageId)}let y;try{y=await f.text()}catch(g){return A(g)}if(y.length===0)return;let p;try{p=JSON.parse(y)}catch{throw new d("INTERNAL",`${t}: function dispatch returned a non-JSON body (${String(f.status)}): ${y}`,{status:f.status})}if(p===null||typeof p!="object"||Array.isArray(p)||!("result"in p))throw new d("INTERNAL",`${t}: function dispatch returned a JSON body that is not a { result } envelope (${String(f.status)}): ${y}`,{status:f.status});return l(p.result)}finally{E.dispose()}}},fe="lunora.requeue.v1",M=new TextEncoder,j=async(e,t)=>crypto.subtle.importKey("raw",M.encode(e),{hash:"SHA-256",name:"HMAC"},!1,[t]),D=(e,t,a)=>M.encode(`${fe}
|
|
2
|
+
${e}
|
|
3
|
+
${t}
|
|
4
|
+
${a}`),ye=e=>[...new Uint8Array(e)].map(t=>t.toString(16).padStart(2,"0")).join(""),he=/^[\da-f]{64}$/u,pe=async(e,t,a,o)=>ye(await crypto.subtle.sign("HMAC",await j(e,"sign"),D(t,a,o))),ge=async(e,t,a,o,r)=>{if(typeof r!="string"||!he.test(r))return!1;const n=Uint8Array.from({length:r.length/2},(s,c)=>Number.parseInt(r.slice(c*2,c*2+2),16));return crypto.subtle.verify("HMAC",await j(e,"verify"),n,D(t,a,o))},S=100,me=3e5,T=e=>e===void 0?void 0:B(e),Ne=e=>{if(!e.queue)throw new d("INTERNAL","@lunora/scheduler: `queue` (a Cloudflare Queue binding) is required");return{enqueue:async(o,r,n={})=>{const s={args:T(r),functionPath:o.__lunoraRef,shardKey:n.shardKey},c=n.delaySeconds===void 0?void 0:{delaySeconds:n.delaySeconds};await e.queue.send(s,c)},enqueueBatch:async(o,r)=>{if(o.length>S)throw new d("VALIDATION_ERROR",`@lunora/scheduler: enqueueBatch exceeds ${String(S)} (got ${String(o.length)}) — split across calls`);const n=o.map(s=>({body:{args:T(s.args),functionPath:s.ref.__lunoraRef,shardKey:s.shardKey}}));await e.queue.sendBatch(n,r)}}},we=e=>typeof e=="object"&&e!==null&&typeof e.functionPath=="string",K="scheduler",C=e=>JSON.stringify({args:e.args,functionPath:e.functionPath,shardKey:e.shardKey}),be=async(e,t,a)=>{const{requeuedFrom:o,requeueMac:r,...n}=e;return typeof o=="string"&&a!==void 0&&await ge(a,K,o,C(n),r).catch(()=>!1)?{id:o,job:n,requeued:!0}:{id:t,job:n,requeued:!1}},Re=e=>async t=>{await Promise.all(t.messages.map(async a=>{let o;try{if(!we(a.body))throw new d("INTERNAL","@lunora/scheduler: queue message body is not a QueueJob (missing functionPath)");o=await be(a.body,a.id,e.requeue?.secret),await e.dispatch(o.job,o.id),a.ack()}catch(r){if(ce(r)&&o!==void 0){const{requeue:n}=e,{id:s,job:c,requeued:i}=o;await ue(a,{maxRetries:e.maxRetries,requeue:n===void 0||i?void 0:async u=>{const w=await pe(n.secret,K,s,C(c));await n.queue.send({...c,requeuedFrom:s,requeueMac:w},{delaySeconds:u})},where:`@lunora/scheduler: queue "${t.queue}"`});return}a.retry()}}))},ve=e=>{const t=de({env:{LUNORA_ADMIN_TOKEN:e.adminToken,LUNORA_ORIGIN_URL:e.originUrl},fetchImpl:e.fetchImpl,label:"@lunora/scheduler"}),a=e.timeoutMs??me;return async(o,r)=>{await t({__lunoraRef:o.functionPath},o.args,{dedupId:r,messageId:r,shardKey:o.shardKey,timeoutMs:a})}};export{Re as createQueueConsumer,Ne as createQueueWorkpool,ve as httpDispatcher};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/scheduler",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.87",
|
|
4
4
|
"description": "Scheduling for Lunora: runAfter / runAt and Cron Triggers via SchedulerDO",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@lunora/errors": "1.0.0-alpha.41",
|
|
50
|
-
"@lunora/platform": "1.0.0-alpha.
|
|
50
|
+
"@lunora/platform": "1.0.0-alpha.39",
|
|
51
51
|
"cron-parser": "5.10.0"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as d,isLunoraError as j}from"@lunora/errors";import{a as M}from"./wire-codec-TIVAE0Ds.mjs";const q=9e5,B="DISPATCH_IN_PROGRESS",K="x-lunora-dispatch-declined",k=(e,t,n)=>{if(t===void 0)return{dispose:()=>{},signal:e};const a=new AbortController,r=setTimeout(()=>{a.abort(n())},t);return{dispose:()=>{clearTimeout(r)},signal:a.signal}},J=e=>{let t="";for(let n=0;n<e.length;n+=32768)t+=String.fromCharCode(...e.subarray(n,n+32768));return btoa(t)},C=e=>{const t=atob(e),n=new Uint8Array(t.length);for(let a=0;a<t.length;a+=1)n[a]=t.codePointAt(a)??0;return n},y="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",$=e=>{let t="",n=0;const a=e.length-2;for(;n<a;n+=3){const o=e[n]<<16|e[n+1]<<8|e[n+2];t+=y.charAt(o>>18&63)+y.charAt(o>>12&63)+y.charAt(o>>6&63)+y.charAt(o&63)}const r=e.length-n;if(r===1){const o=e[n]<<16;t+=y.charAt(o>>18&63)+y.charAt(o>>12&63)}else if(r===2){const o=e[n]<<16|e[n+1]<<8;t+=y.charAt(o>>18&63)+y.charAt(o>>12&63)+y.charAt(o>>6&63)}return t};new TextDecoder;const S=new TextEncoder,E="=",Q=e=>{for(let t=0;t<e.length;t+=1)if(e.charCodeAt(t)>255)return!1;return!0},W=e=>$(S.encode(JSON.stringify(e))),F=e=>!e.startsWith(E)&&Q(e)?e:`${E}${$(S.encode(e))}`,G="$lunora.wire$",N=64,R=1024,I="__proto__",O={BigInt64Array,BigUint64Array,Float32Array,Float64Array,Int8Array,Int16Array,Int32Array,Uint8Array,Uint8ClampedArray,Uint16Array,Uint32Array},_={Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError},l=(e,t=0)=>{if(t>N)throw new RangeError(`wire-codec: value nesting exceeds the ${N}-level limit`);if(e===null||typeof e!="object")return e;if(Array.isArray(e)){if(e[0]===G)switch(e[1]){case"-inf":return-1/0;case"arr":return e[2].map(r=>l(r,t+1));case"bigint":{const r=e[2];if(typeof r!="string"||r.length>R||!/^-?\d+$/.test(r))throw new RangeError(`wire-codec: invalid or over-long bigint (max ${R} digits)`);return BigInt(r)}case"date":{const r=l(e[2],t+1);if(typeof r!="number")throw new TypeError("wire-codec: malformed date — epoch must be a number");return new Date(r)}case"map":{const r=e[2];return new Map(r.map(o=>{if(!Array.isArray(o)||o.length!==2)throw new TypeError("wire-codec: malformed map entry — expected a [key, value] pair");return[l(o[0],t+1),l(o[1],t+1)]}))}case"set":return new Set(e[2].map(r=>l(r,t+1)));case"url":{const r=e[2];if(typeof r!="string")throw new TypeError("wire-codec: malformed url — href must be a string");return new URL(r)}case"error":{const r=e[2],o=e[3];if(typeof r!="string"||typeof o!="string")throw new TypeError("wire-codec: malformed error — name and message must be strings");const s=(Object.hasOwn(_,r)?_[r]:void 0)??Error,c=new s(o);c.name!==r&&Object.defineProperty(c,"name",{configurable:!0,value:r,writable:!0});const i=l(e[4],t+1);if(i===null||typeof i!="object"||Array.isArray(i))throw new TypeError("wire-codec: malformed error — props must be an object");for(const u of Object.keys(i))u===I?Object.defineProperty(c,u,{configurable:!0,enumerable:!0,value:i[u],writable:!0}):c[u]=i[u];return e.length>5&&Object.defineProperty(c,"cause",{configurable:!0,value:l(e[5],t+1),writable:!0}),c}case"bytes":{const r=e[2];if(typeof r!="string")throw new TypeError("wire-codec: malformed bytes — payload must be a base64 string");const o=C(r);if(J(o)!==r)throw new TypeError("wire-codec: malformed bytes — payload must be canonical padded base64");const s=e[3]??"Uint8Array";if(s==="ArrayBuffer")return o.buffer.byteLength===o.byteLength?o.buffer:o.slice().buffer;const c=Object.hasOwn(O,s)?O[s]:void 0;return c?new c(o.slice().buffer):o}case"inf":return 1/0;case"nan":return Number.NaN;case"undefined":return;default:return e.map(r=>l(r,t+1))}return e.map(r=>l(r,t+1))}const n=e,a={};for(const r of Object.keys(n)){const o=l(n[r],t+1);r===I?Object.defineProperty(a,r,{configurable:!0,enumerable:!0,value:o,writable:!0}):a[r]=o}return a},H="/_lunora/scheduler/dispatch",z=3e4,V=e=>{let t=e.length;for(;t>0&&e[t-1]==="/";)t-=1;return e.slice(0,t)},L=Symbol("lunoraDispatchFailure"),U=Symbol("lunoraDispatchDecline"),X=Symbol("lunoraDispatchMessageId"),x=(e,t)=>(t!==void 0&&Object.defineProperty(e,X,{value:t}),e),Y=(e,t)=>(Object.defineProperty(e,L,{value:!0}),x(e,t)),Z=(e,t,n,a)=>{const{status:r}=t;try{const o=JSON.parse(n)?.error;if(typeof o=="object"&&o!==null&&typeof o.code=="string"){const{code:s,data:c,message:i}=o,u=Y(new d(s,typeof i=="string"?i:void 0,{data:c,status:r}),a);return t.headers.get(K)==="1"&&Object.defineProperty(u,U,{value:!0}),u}}catch{}return x(new d("INTERNAL",`${e}: function dispatch failed (${String(r)}): ${n}`,{status:r}),a)},ee=e=>j(e)&&e[L]===!0,te=e=>ee(e)&&e[U]===!0,re=q/1e3,ne=3,oe=(e,t)=>{const n=t.maxRetries??ne;e.retry({delaySeconds:re}),typeof e.attempts=="number"&&e.attempts>n&&console.error(`${t.where}: message ${e.id} was declined (${B}) on its last delivery (attempt ${String(e.attempts)} of ${String(n+1)}) — its earlier delivery is still running the call. It is retried anyway, but its retries are exhausted, so it will be dead-lettered if the queue has a deadLetterQueue and dropped if not. Raise max_retries so a slow call cannot exhaust it.`)},ae=(e,t,n)=>new d("INTERNAL",`${e}: function dispatch to "${t}" timed out after ${String(n)}ms`,{status:503}),se=e=>{const{label:t}=e,n=globalThis.fetch,a=e.fetchImpl??(typeof n=="function"?n.bind(globalThis):void 0);return async(r,o,s={})=>{if(typeof a!="function")throw new TypeError(`${t}: no fetch implementation available — pass fetchImpl or run on a platform with global fetch`);const c=()=>o??{},i=e.env.LUNORA_ORIGIN_URL;if(typeof i!="string"||i.length===0)throw new d("INTERNAL",`${t}: \`LUNORA_ORIGIN_URL\` must be set on the Worker env so a handler can call back into Lunora functions`);const u=e.env.LUNORA_ADMIN_TOKEN;if(typeof u!="string"||u.length===0)throw new d("INTERNAL",`${t}: \`LUNORA_ADMIN_TOKEN\` must be set on the Worker env to authenticate function dispatch`);const P=`${V(i)}${H}`,w={authorization:`Bearer ${u}`,"content-type":"application/json"};e.traceparent!==void 0&&e.traceparent.length>0&&(w.traceparent=e.traceparent),e.identity?.userId!==void 0&&(w["x-lunora-userid"]=F(e.identity.userId)),e.identity?.claims!==void 0&&(w["x-lunora-identity"]=W(e.identity.claims));const g=s.timeoutMs??z,A=k(void 0,g,()=>new DOMException(`dispatch timed out after ${String(g)}ms`,"TimeoutError")),b=h=>{throw h instanceof Error&&h.name==="TimeoutError"?ae(t,r.__lunoraRef,g):h};let f;try{try{f=await a(P,{body:JSON.stringify({args:c(),functionPath:r.__lunoraRef,id:s.dedupId,shardKey:s.shardKey}),headers:w,method:"POST",signal:A.signal})}catch(m){return b(m)}if(!f.ok){let m;try{m=await f.text()}catch(D){return b(D)}throw Z(t,f,m,s.messageId)}let h;try{h=await f.text()}catch(m){return b(m)}if(h.length===0)return;let p;try{p=JSON.parse(h)}catch{throw new d("INTERNAL",`${t}: function dispatch returned a non-JSON body (${String(f.status)}): ${h}`,{status:f.status})}if(p===null||typeof p!="object"||Array.isArray(p)||!("result"in p))throw new d("INTERNAL",`${t}: function dispatch returned a JSON body that is not a { result } envelope (${String(f.status)}): ${h}`,{status:f.status});return l(p.result)}finally{A.dispose()}}},T=100,ce=3e5,v=e=>e===void 0?void 0:M(e),de=e=>{if(!e.queue)throw new d("INTERNAL","@lunora/scheduler: `queue` (a Cloudflare Queue binding) is required");return{enqueue:async(a,r,o={})=>{const s={args:v(r),functionPath:a.__lunoraRef,shardKey:o.shardKey},c=o.delaySeconds===void 0?void 0:{delaySeconds:o.delaySeconds};await e.queue.send(s,c)},enqueueBatch:async(a,r)=>{if(a.length>T)throw new d("VALIDATION_ERROR",`@lunora/scheduler: enqueueBatch exceeds ${String(T)} (got ${String(a.length)}) — split across calls`);const o=a.map(s=>({body:{args:v(s.args),functionPath:s.ref.__lunoraRef,shardKey:s.shardKey}}));await e.queue.sendBatch(o,r)}}},ie=e=>typeof e=="object"&&e!==null&&typeof e.functionPath=="string",fe=e=>async t=>{await Promise.all(t.messages.map(async n=>{try{if(!ie(n.body))throw new d("INTERNAL","@lunora/scheduler: queue message body is not a QueueJob (missing functionPath)");await e.dispatch(n.body,n.id),n.ack()}catch(a){if(te(a)){oe(n,{maxRetries:e.maxRetries,where:`@lunora/scheduler: queue "${t.queue}"`});return}n.retry()}}))},he=e=>{const t=se({env:{LUNORA_ADMIN_TOKEN:e.adminToken,LUNORA_ORIGIN_URL:e.originUrl},fetchImpl:e.fetchImpl,label:"@lunora/scheduler"}),n=e.timeoutMs??ce;return async(a,r)=>{await t({__lunoraRef:a.functionPath},a.args,{dedupId:r,messageId:r,shardKey:a.shardKey,timeoutMs:n})}};export{fe as createQueueConsumer,de as createQueueWorkpool,he as httpDispatcher};
|