@lunora/runtime 1.0.0-alpha.2 → 1.0.0-alpha.200
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/LICENSE.md +126 -0
- package/README.md +59 -0
- package/__assets__/package-og.svg +1 -1
- package/dist/index.d.mts +4488 -1218
- package/dist/index.d.ts +4488 -1218
- package/dist/index.mjs +1 -14
- package/dist/packem_shared/BACKUP_KEY_PREFIX-BOtSu-_3.mjs +1 -0
- package/dist/packem_shared/DEFAULT_LOG_COLUMNS-gYMhXLzO.mjs +1 -0
- package/dist/packem_shared/DEFAULT_REGISTRY_CACHE_TTL_MS-CdACWUQW.mjs +1 -0
- package/dist/packem_shared/HEALTH_PATH-CAX2UeFy.mjs +1 -0
- package/dist/packem_shared/LOG_ARCHIVE_NOT_CONFIGURED-CDpi4yFD.mjs +1 -0
- package/dist/packem_shared/LOG_ARCHIVE_PATH-Hmjpz_Ko.mjs +1 -0
- package/dist/packem_shared/LunoraError-DksAgIpa.mjs +1 -0
- package/dist/packem_shared/NOOP_EXECUTION_CONTEXT-YmXqH-jH.mjs +1 -0
- package/dist/packem_shared/STORAGE_UPLOAD_MAX_BODY_BYTES-BIiV5kqV.mjs +1 -0
- package/dist/packem_shared/analyticsEngineSink-CDfEAQHU.mjs +4 -0
- package/dist/packem_shared/applyJurisdiction-D4vryLhj.mjs +1 -0
- package/dist/packem_shared/applyRestCache-CEcBz3qq.mjs +1 -0
- package/dist/packem_shared/argsFromQuery-8InwnYmO.mjs +1 -0
- package/dist/packem_shared/base64-Bl1_r2k1.mjs +1 -0
- package/dist/packem_shared/composeIdentityResolvers-BHZ4jH8o.mjs +1 -0
- package/dist/packem_shared/composeWorker-DjghpuuH.mjs +6 -0
- package/dist/packem_shared/createCrossShardRelationCapabilities-CWQpukAv.mjs +1 -0
- package/dist/packem_shared/createKvCursorStore-4nVJ0-ad.mjs +1 -0
- package/dist/packem_shared/createQueryCoordinator-CXdh_RxT.mjs +1 -0
- package/dist/packem_shared/createShardClient-C2YyONnL.mjs +1 -0
- package/dist/packem_shared/declareAppJurisdiction-DEba2p42.mjs +1 -0
- package/dist/packem_shared/decorateResponse-CRrFNW1v.mjs +1 -0
- package/dist/packem_shared/emitLogEvent-C4keuLS-.mjs +1 -0
- package/dist/packem_shared/evict-oldest-BNXsKx4s.mjs +1 -0
- package/dist/packem_shared/export-tap-GSRLYAb6.mjs +3 -0
- package/dist/packem_shared/identity-header-pR617HTs.mjs +1 -0
- package/dist/packem_shared/memoizeIdentity-DnOj3QRi.mjs +1 -0
- package/dist/packem_shared/method-guard-BG_vJNTl.mjs +1 -0
- package/dist/packem_shared/observability-DgurIZIR.mjs +1 -0
- package/dist/packem_shared/origin-paywall-B6eOO67m.mjs +1 -0
- package/dist/packem_shared/otlp-resource-BwFJnvwB.mjs +1 -0
- package/dist/packem_shared/parseStackFrames-CZddjveh.mjs +2 -0
- package/dist/packem_shared/pipeline-log-reader-C-nuWG_e.mjs +1 -0
- package/dist/packem_shared/portable-json-CTm_YALS.mjs +1 -0
- package/dist/packem_shared/rest-cache-CmHN871W.mjs +1 -0
- package/dist/packem_shared/rest-routes-DQkyqlym.mjs +1 -0
- package/dist/packem_shared/toAirbyteMessages-Cmw-8nDf.mjs +1 -0
- package/dist/packem_shared/wire-codec-BkjZMyIW.mjs +1 -0
- package/package.json +12 -2
- package/dist/packem_shared/DEFAULT_REGISTRY_CACHE_TTL_MS-BpCwo_mo.mjs +0 -75
- package/dist/packem_shared/LunoraError-CL0aOtpo.mjs +0 -46
- package/dist/packem_shared/analyticsEngineSink-DqEvrQs0.mjs +0 -141
- package/dist/packem_shared/composeWorker-BmfKl1ei.mjs +0 -2501
- package/dist/packem_shared/createCrossShardRelationCapabilities-C0KOf7er.mjs +0 -61
- package/dist/packem_shared/createQueryCoordinator-DbxC7iUz.mjs +0 -838
- package/dist/packem_shared/decorateResponse-DbISh_Wi.mjs +0 -233
- package/dist/packem_shared/emitLogEvent-pEdtqAK8.mjs +0 -20
- package/dist/packem_shared/resolveShard-DDkzWtrU.mjs +0 -9
- package/dist/packem_shared/toAirbyteMessages-DrHdplb4.mjs +0 -39
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{c as O,d as k,e as P,a as B,f as j}from"./rest-cache-CmHN871W.mjs";import{LunoraError as h}from"./LunoraError-DksAgIpa.mjs";import{m as x}from"./method-guard-BG_vJNTl.mjs";const D=async(e,r)=>{if(!e.body)return"";const t=e.body.getReader(),o=new TextDecoder;let s=0,c="";for(;;){const{done:n,value:a}=await t.read();if(n)break;if(a){if(s+=a.byteLength,s>r)return await t.cancel().catch(()=>{}),null;c+=o.decode(a,{stream:!0})}}return c+=o.decode(),c},p=1048576,L=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),C=async(e,r=p)=>{const t=await D(e,r);if(t===null)throw new h("Body too large",{code:"PAYLOAD_TOO_LARGE",status:413});return t},re=async(e,r=p)=>{if(!e.body)return new ArrayBuffer(0);const t=e.body.getReader(),o=[];let s=0;for(;;){const{done:a,value:i}=await t.read();if(a)break;if(i){if(s+=i.byteLength,s>r)throw await t.cancel().catch(()=>{}),new h("Body too large",{code:"PAYLOAD_TOO_LARGE",status:413});o.push(i)}}const c=new Uint8Array(s);let n=0;for(const a of o)c.set(a,n),n+=a.byteLength;return c.buffer},ae=(e,r=p)=>{const t=e.body;let o=!1;if(t===null)return{overflowed:()=>o,request:e};let s,c=0;const n=new ReadableStream({cancel:async a=>{await(s??t).cancel(a)},pull:async a=>{s??=t.getReader();const{done:i,value:d}=await s.read();if(i){a.close();return}if(c+=d.byteLength,c>r){o=!0,a.error(new h("Body too large",{code:"PAYLOAD_TOO_LARGE",status:413})),s.cancel().catch(()=>{});return}a.enqueue(d)}},{highWaterMark:0});return{overflowed:()=>o,request:new Request(e,{body:n,duplex:"half"})}},U=async(e,r,t=p)=>{try{const o=await C(e,t);return o===""?{}:JSON.parse(o)}catch(o){throw o instanceof h?o:new h(`${r} body must be valid JSON`,{code:"BAD_REQUEST",status:400})}},oe=async(e,r=p)=>{const t=await U(e,"Request",r);if(!L(t))throw new h("Request body must be an object",{code:"BAD_REQUEST",status:400});return t},G=(e,r)=>{if(!L(e))throw new h(`${r} \`args\` must be an object`,{code:"BAD_REQUEST",status:400})},I=/^[\d.:a-f]+$/iu,N=e=>{const r=e.indexOf("%"),t=r===-1?e:e.slice(0,r);if(!t.includes(":")||!I.test(t))return e;let o;try{o=new URL(`http://[${t}]/`).hostname.slice(1,-1)}catch{return e}const[s="",c]=o.split("::"),n=s===""?[]:s.split(":"),a=c===void 0||c===""?[]:c.split(":"),i=[...n,...Array.from({length:8-n.length-a.length},()=>"0"),...a].map(d=>Number.parseInt(d,16));if(i.slice(0,5).every(d=>d===0)&&i[5]===65535){const[d=0,u=0]=i.slice(6);return[d>>8,d&255,u>>8,u&255].join(".")}return`${i.slice(0,4).map(d=>d.toString(16)).join(":")}::/64`},g="__lunora_vary",M="x-lunora-edge-cache",$=["x-d1-bookmark","x-lunora-shard-key"],H=()=>{try{return globalThis.caches?.default}catch{return}},S=e=>e.split(",").map(r=>r.trim().toLowerCase()).filter(r=>r!==""),K=(e,r)=>{const t=e.headers.get("vary");return t===null?!0:S(t).every(o=>o!=="*"&&r.includes(o))},Y=(e,r)=>{if(e===void 0||r===null||e.scope!=="public"||O(e.maxAge)<=0)return;const t=()=>r??H(),o=S(k(e)??""),s=n=>{const a=new URL(n.url);return a.searchParams.delete(g),o.length>0&&a.searchParams.set(g,o.map(i=>`${i}=${n.headers.get(i)??""}`).join("\0")),new Request(a.toString(),{method:"GET"})},c=(n,a)=>n.method==="GET"&&P(e,n,a)==="public";return{lookup:async(n,a)=>{const i=t();if(i===void 0||!c(n,a))return;let d;try{d=await i.match(s(n))}catch{return}if(d===void 0)return;const u=new Response(d.body,d);return u.headers.set(M,"hit"),u},store:(n,a,i)=>{const d=t();if(d===void 0||!c(a,i)||n.status!==200||n.headers.has("set-cookie")||n.headers.has("payment-response")||n.headers.has("x-payment-response")||!K(n,o))return n;try{const u=new Response(n.clone().body,n);for(const l of $)u.headers.delete(l);const f=Promise.resolve(d.put(s(a),u)).catch(()=>{});i?.waitUntil&&i.waitUntil(f)}catch{}return n}}},F=()=>globalThis.navigator?.userAgent==="Cloudflare-Workers",J=(e,r)=>{if(F())return e.get("cf-connecting-ip")??void 0;if(r===void 0)return;const t=e.get(r)?.trim();return t===void 0||t===""||t.includes(",")?void 0:t},Q=(e,r)=>{if(r.x402!==void 0&&r.expose?.cache?.scope==="public")throw new h(`REST route "${e}" is paid (.x402) but declares \`cache: { scope: "public" }\`: a shared cache would replay the paid response to callers who never paid. Use \`scope: "private"\` or drop the cache policy.`,{code:"MISCONFIGURED",status:500})},W=e=>j(Object.entries(e).map(([r,t])=>({exposure:t.expose,functionPath:r,kind:t.kind}))),V=(e,r)=>{const t=e.searchParams.get("shardKey");if(t!==null&&t!=="")return t;const o=r.headers.get("x-lunora-shard-key");return o===null||o===""?void 0:o},X=e=>{const r=Object.create(null);for(const[t,o]of e.searchParams.entries())if(!(t==="shardKey"||t===g))try{r[t]=JSON.parse(o)}catch{r[t]=o}return r},ne=e=>{const{edgeCache:r,functions:t,invoke:o,rateLimit:s,readJsonBody:c}=e,n={};for(const a of W(t)){const i=a.kind==="query"?["GET","POST"]:["POST"],d=t[a.functionPath];Q(a.functionPath,d);const u=d.expose?.cache,f=Y(u,r);n[a.path]=async(l,T,Z,y)=>{const R=x(l,i);if(R)return R;const v=new URL(l.url);if(s){const m=await s(l,a.functionPath);if(m)return m}const b=await f?.lookup(l,y);if(b)return b;let w;l.method==="GET"?w=X(v):w=l.body===null?{}:await c(l),G(w,"REST");const E=V(v,l),_=await o({args:w,env:T,functionPath:a.functionPath,request:l,...E===void 0?{}:{shardKey:E},...y?.waitUntil===void 0?{}:{waitUntil:m=>y.waitUntil?.(m)}}),A=B(_,u,l,y);return f?f.store(A,l,y):A}}return n},z=()=>Response.json({error:{code:"INTERNAL",message:"REST rate limit: no caller key could be resolved. Declare `trustedClientIpHeader` (a header your proxy stamps and callers cannot write), or pass `key` to identify callers by something unforgeable."}},{headers:{"content-type":"application/json"},status:500}),se=(e,r)=>async(t,o)=>{let s;if(r.key)s=r.key(t,o);else{const a=J(t.headers,r.trustedClientIpHeader);s=a===void 0?void 0:N(a)}if(s===void 0||s==="")return z();const c=await e.limit(r.name,{key:s});if(c.ok)return;if(c.reason==="deny")return Response.json({error:{code:"FORBIDDEN",message:"Request denied"}},{headers:{"content-type":"application/json"},status:403});const n=Math.max(1,Math.ceil(c.retryAfter/1e3));return Response.json({error:{code:"RATE_LIMITED",message:"Rate limit exceeded"}},{headers:{"content-type":"application/json","retry-after":String(n)},status:429})};export{p as M,X as a,ne as b,se as c,oe as d,U as e,re as f,G as g,C as h,W as r,J as t,ae as w};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{t as b}from"./portable-json-CTm_YALS.mjs";const p="_id",f=(t,n=p)=>{const o=Object.create(null),s=Object.create(null),c=Object.create(null),a=Object.create(null),h=e=>typeof n=="string"?n:n[e]??p,l=(e,r)=>{const u=e[r];if(u)return u;const d=[];return e[r]=d,d};for(const e of t.changes){a[e.table]??={primary_key:[h(e.table)]};const r=b(e.doc);e.op==="delete"?l(c,e.table).push(r):e.op==="update"?l(s,e.table).push(r):l(o,e.table).push(r)}return{delete:c,hasMore:t.hasMore,insert:o,schema:a,state:{cursor:t.nextCursor},update:s}},m=(t,n=Date.now())=>{const o=[];for(const s of t.changes){const c=b(s.doc),a=s.op==="delete"?{...c,_lunora_deleted:!0}:c;o.push({record:{data:a,emitted_at:n,stream:s.table},type:"RECORD"})}return o.push({state:{data:{cursor:t.nextCursor}},type:"STATE"}),o};export{m as toAirbyteMessages,f as toFivetranResponse};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as d,b as E}from"./base64-Bl1_r2k1.mjs";const o="$lunora.wire$",w=64,g=1024,p="__proto__",A={BigInt64Array,BigUint64Array,Float32Array,Float64Array,Int8Array,Int16Array,Int32Array,Uint8Array,Uint8ClampedArray,Uint16Array,Uint32Array},l={Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError},O=e=>{if(e===null||typeof e!="object")return!1;const n=Object.getPrototypeOf(e);return n===null||n===Object.prototype},y=(e,n=0)=>{if(n>w)throw new RangeError(`wire-codec: value nesting exceeds the ${w}-level limit`);if(e===void 0)return[o,"undefined"];if(e===null)return null;const b=typeof e;if(b==="bigint"){const r=e.toString();if(r.length>g)throw new RangeError(`wire-codec: over-long bigint (max ${g} digits)`);return[o,"bigint",r]}if(b==="number"){const r=e;return Number.isNaN(r)?[o,"nan"]:r===1/0?[o,"inf"]:r===-1/0?[o,"-inf"]:r}if(b!=="object")return e;if(e instanceof Date)return[o,"date",y(e.getTime(),n+1)];if(e instanceof Error){const r=e,t=y(Object.fromEntries(Object.keys(r).map(c=>[c,r[c]])),n+1),i=[o,"error",String(r.name),String(r.message),t];return r.cause!==void 0&&i.push(y(r.cause,n+1)),i}if(e instanceof URL)return[o,"url",e.href];if(e instanceof Map)return[o,"map",[...e.entries()].map(([r,t])=>[y(r,n+1),y(t,n+1)])];if(e instanceof Set)return[o,"set",[...e].map(r=>y(r,n+1))];if(e instanceof ArrayBuffer)return[o,"bytes",d(new Uint8Array(e)),"ArrayBuffer"];if(ArrayBuffer.isView(e)){const r=e,t=r.constructor.name,i=new Uint8Array(r.buffer,r.byteOffset,r.byteLength);return t==="Uint8Array"?[o,"bytes",d(i)]:[o,"bytes",d(i),t]}if(Array.isArray(e)){const r=e.map(t=>y(t,n+1));return r.length>0&&r[0]===o?[o,"arr",r]:r}if(!O(e)){const r=e.constructor?.name??"value";throw new TypeError(`wire-codec: cannot encode a ${r} over the Lunora wire — only plain objects, arrays, and the supported built-ins (Date, Error, URL, Map, Set, ArrayBuffer/typed arrays, bigint) round-trip`)}const s=e,a={};for(const r of Object.keys(s)){const t=s[r];if(t===void 0)continue;const i=y(t,n+1);r===p?Object.defineProperty(a,r,{configurable:!0,enumerable:!0,value:i,writable:!0}):a[r]=i}return a},f=(e,n=0)=>{if(n>w)throw new RangeError(`wire-codec: value nesting exceeds the ${w}-level limit`);if(e===null||typeof e!="object")return e;if(Array.isArray(e)){if(e[0]===o)switch(e[1]){case"-inf":return-1/0;case"arr":return e[2].map(r=>f(r,n+1));case"bigint":{const r=e[2];if(typeof r!="string"||r.length>g||!/^-?\d+$/.test(r))throw new RangeError(`wire-codec: invalid or over-long bigint (max ${g} digits)`);return BigInt(r)}case"date":{const r=f(e[2],n+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(t=>{if(!Array.isArray(t)||t.length!==2)throw new TypeError("wire-codec: malformed map entry — expected a [key, value] pair");return[f(t[0],n+1),f(t[1],n+1)]}))}case"set":return new Set(e[2].map(r=>f(r,n+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],t=e[3];if(typeof r!="string"||typeof t!="string")throw new TypeError("wire-codec: malformed error — name and message must be strings");const i=(Object.hasOwn(l,r)?l[r]:void 0)??Error,c=new i(t);c.name!==r&&Object.defineProperty(c,"name",{configurable:!0,value:r,writable:!0});const u=f(e[4],n+1);if(u===null||typeof u!="object"||Array.isArray(u))throw new TypeError("wire-codec: malformed error — props must be an object");for(const m of Object.keys(u))m===p?Object.defineProperty(c,m,{configurable:!0,enumerable:!0,value:u[m],writable:!0}):c[m]=u[m];return e.length>5&&Object.defineProperty(c,"cause",{configurable:!0,value:f(e[5],n+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 t=E(r);if(d(t)!==r)throw new TypeError("wire-codec: malformed bytes — payload must be canonical padded base64");const i=e[3]??"Uint8Array";if(i==="ArrayBuffer")return t.buffer.byteLength===t.byteLength?t.buffer:t.slice().buffer;const c=Object.hasOwn(A,i)?A[i]:void 0;return c?new c(t.slice().buffer):t}case"inf":return 1/0;case"nan":return Number.NaN;case"undefined":return;default:return e.map(r=>f(r,n+1))}return e.map(a=>f(a,n+1))}const b=e,s={};for(const a of Object.keys(b)){const r=f(b[a],n+1);a===p?Object.defineProperty(s,a,{configurable:!0,enumerable:!0,value:r,writable:!0}):s[a]=r}return s},T=(e,n,b)=>{try{return y(b)}catch(s){throw new TypeError(`${e}: cannot encode args for '${n}' — ${s instanceof Error?s.message:String(s)}`,s instanceof Error?{cause:s}:void 0)}};export{T as a,f as d,y as e,O as i};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/runtime",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.200",
|
|
4
4
|
"description": "Lunora Worker runtime: the RPC router, shard resolver, and query coordinator",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"directory": "packages/runtime"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
|
-
"dist",
|
|
28
|
+
"./dist",
|
|
29
29
|
"README.md",
|
|
30
30
|
"LICENSE.md",
|
|
31
31
|
"__assets__"
|
|
@@ -45,6 +45,16 @@
|
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@lunora/bindings": "1.0.0-alpha.112",
|
|
50
|
+
"@lunora/errors": ">=1.0.0-alpha.51 <2.0.0-0",
|
|
51
|
+
"@lunora/observability": "1.0.0-alpha.139",
|
|
52
|
+
"@lunora/platform": "1.0.0-alpha.64"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@lunora/shard-engine": ">=1.0.0-alpha.24 <2.0.0-0",
|
|
56
|
+
"@lunora/workflow": ">=1.0.0-alpha.24 <2.0.0-0"
|
|
57
|
+
},
|
|
48
58
|
"engines": {
|
|
49
59
|
"node": "^22.15.0 || >=24.11.0"
|
|
50
60
|
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
const SHARD_REGISTRY_DO_NAME = "__lunora_shard_registry__";
|
|
2
|
-
const DEFAULT_REGISTRY_CACHE_TTL_MS = 3e4;
|
|
3
|
-
const REGISTRY_BASE_URL = "https://shard-registry.internal";
|
|
4
|
-
const decodeJson = async (response) => (
|
|
5
|
-
// Response.json() throws on non-JSON; the DO always returns JSON so this
|
|
6
|
-
// only surfaces if the DO route is misbehaving — let it bubble.
|
|
7
|
-
await response.json()
|
|
8
|
-
);
|
|
9
|
-
const createDynamicShardRegistry = (options) => {
|
|
10
|
-
const instanceName = options.instanceName ?? SHARD_REGISTRY_DO_NAME;
|
|
11
|
-
const cacheTtlMs = options.cacheTtlMs ?? DEFAULT_REGISTRY_CACHE_TTL_MS;
|
|
12
|
-
const cache = /* @__PURE__ */ new Map();
|
|
13
|
-
let cachedStub;
|
|
14
|
-
const stub = () => {
|
|
15
|
-
cachedStub ??= options.namespace.get(options.namespace.idFromName(instanceName));
|
|
16
|
-
return cachedStub;
|
|
17
|
-
};
|
|
18
|
-
const post = async (path, body) => stub().fetch(
|
|
19
|
-
new Request(`${REGISTRY_BASE_URL}${path}`, {
|
|
20
|
-
body: JSON.stringify(body),
|
|
21
|
-
headers: { "content-type": "application/json" },
|
|
22
|
-
method: "POST"
|
|
23
|
-
})
|
|
24
|
-
);
|
|
25
|
-
const get = async (path) => stub().fetch(new Request(`${REGISTRY_BASE_URL}${path}`, { method: "GET" }));
|
|
26
|
-
return {
|
|
27
|
-
invalidate(table) {
|
|
28
|
-
if (table === void 0) {
|
|
29
|
-
cache.clear();
|
|
30
|
-
} else {
|
|
31
|
-
cache.delete(table);
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
async listShardKeys(table) {
|
|
35
|
-
const now = Date.now();
|
|
36
|
-
const cached = cache.get(table);
|
|
37
|
-
if (cached && cached.expiresAt > now) {
|
|
38
|
-
return cached.shardKeys;
|
|
39
|
-
}
|
|
40
|
-
const response = await get(`/list?table=${encodeURIComponent(table)}`);
|
|
41
|
-
if (!response.ok) {
|
|
42
|
-
throw new Error(`shard registry /list returned ${String(response.status)}`);
|
|
43
|
-
}
|
|
44
|
-
const { shardKeys } = await decodeJson(response);
|
|
45
|
-
if (cacheTtlMs > 0) {
|
|
46
|
-
cache.set(table, { expiresAt: now + cacheTtlMs, shardKeys });
|
|
47
|
-
}
|
|
48
|
-
return shardKeys;
|
|
49
|
-
},
|
|
50
|
-
async register(table, shardKey) {
|
|
51
|
-
const response = await post("/register", { shardKey, table });
|
|
52
|
-
if (!response.ok) {
|
|
53
|
-
throw new Error(`shard registry /register returned ${String(response.status)}`);
|
|
54
|
-
}
|
|
55
|
-
cache.delete(table);
|
|
56
|
-
},
|
|
57
|
-
async snapshot() {
|
|
58
|
-
const response = await get("/snapshot");
|
|
59
|
-
if (!response.ok) {
|
|
60
|
-
throw new Error(`shard registry /snapshot returned ${String(response.status)}`);
|
|
61
|
-
}
|
|
62
|
-
const { tables } = await decodeJson(response);
|
|
63
|
-
return tables;
|
|
64
|
-
},
|
|
65
|
-
async unregister(table, shardKey) {
|
|
66
|
-
const response = await post("/unregister", { shardKey, table });
|
|
67
|
-
if (!response.ok) {
|
|
68
|
-
throw new Error(`shard registry /unregister returned ${String(response.status)}`);
|
|
69
|
-
}
|
|
70
|
-
cache.delete(table);
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
export { DEFAULT_REGISTRY_CACHE_TTL_MS, SHARD_REGISTRY_DO_NAME, createDynamicShardRegistry };
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
class LunoraError extends Error {
|
|
2
|
-
code;
|
|
3
|
-
status;
|
|
4
|
-
constructor(message, options) {
|
|
5
|
-
super(message, { cause: options?.cause });
|
|
6
|
-
this.name = "LunoraError";
|
|
7
|
-
this.code = options?.code ?? "INTERNAL";
|
|
8
|
-
this.status = options?.status ?? 500;
|
|
9
|
-
}
|
|
10
|
-
toResponse() {
|
|
11
|
-
const body = { error: { code: this.code, message: this.message } };
|
|
12
|
-
return Response.json(body, {
|
|
13
|
-
headers: { "content-type": "application/json" },
|
|
14
|
-
status: this.status
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
const hasErrorShape = (error, name) => {
|
|
19
|
-
if (!error || typeof error !== "object") {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
const candidate = error;
|
|
23
|
-
return candidate.name === name && typeof candidate.code === "string" && typeof candidate.status === "number" && typeof candidate.message === "string";
|
|
24
|
-
};
|
|
25
|
-
const isStructuralConflictError = (error) => hasErrorShape(error, "ConflictError");
|
|
26
|
-
const isStructuralLunoraError = (error) => hasErrorShape(error, "LunoraError");
|
|
27
|
-
const toErrorResponse = (error) => {
|
|
28
|
-
if (error instanceof LunoraError) {
|
|
29
|
-
return error.toResponse();
|
|
30
|
-
}
|
|
31
|
-
if (isStructuralLunoraError(error) || isStructuralConflictError(error)) {
|
|
32
|
-
const body2 = { error: { code: error.code, message: error.message } };
|
|
33
|
-
return Response.json(body2, {
|
|
34
|
-
headers: { "content-type": "application/json" },
|
|
35
|
-
status: error.status
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
console.error("[lunora] unhandled error:", error);
|
|
39
|
-
const body = { error: { code: "INTERNAL", message: "Internal error" } };
|
|
40
|
-
return Response.json(body, {
|
|
41
|
-
headers: { "content-type": "application/json" },
|
|
42
|
-
status: 500
|
|
43
|
-
});
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export { LunoraError, isStructuralConflictError, isStructuralLunoraError, toErrorResponse };
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
const shouldSkip = (event, onlyErrors) => onlyErrors === true && event.ok;
|
|
2
|
-
const mergeHeaders = (defaults, overrides) => {
|
|
3
|
-
if (!overrides) {
|
|
4
|
-
return { ...defaults };
|
|
5
|
-
}
|
|
6
|
-
const merged = {};
|
|
7
|
-
const seen = /* @__PURE__ */ new Map();
|
|
8
|
-
for (const [name, value] of Object.entries(defaults)) {
|
|
9
|
-
const lower = name.toLowerCase();
|
|
10
|
-
seen.set(lower, name);
|
|
11
|
-
merged[name] = value;
|
|
12
|
-
}
|
|
13
|
-
for (const [name, value] of Object.entries(overrides)) {
|
|
14
|
-
const lower = name.toLowerCase();
|
|
15
|
-
const existing = seen.get(lower);
|
|
16
|
-
if (existing === void 0) {
|
|
17
|
-
seen.set(lower, name);
|
|
18
|
-
merged[name] = value;
|
|
19
|
-
} else {
|
|
20
|
-
merged[existing] = value;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return merged;
|
|
24
|
-
};
|
|
25
|
-
const consoleSink = (options = {}) => {
|
|
26
|
-
const { onlyErrors } = options;
|
|
27
|
-
return {
|
|
28
|
-
onLog: (event) => {
|
|
29
|
-
if (event.level === "error") {
|
|
30
|
-
console.error("[lunora:log]", event.functionPath, event.message);
|
|
31
|
-
} else {
|
|
32
|
-
console.log("[lunora:log]", event.functionPath, event.message);
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
onRpc: (event) => {
|
|
36
|
-
if (shouldSkip(event, onlyErrors)) {
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
if (event.ok) {
|
|
40
|
-
console.log("[lunora:rpc]", event);
|
|
41
|
-
} else {
|
|
42
|
-
console.error("[lunora:rpc]", event);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
};
|
|
47
|
-
const webhookSink = (options) => {
|
|
48
|
-
const { headers, onlyErrors, transform, url } = options;
|
|
49
|
-
const mergedHeaders = mergeHeaders({ "content-type": "application/json" }, headers);
|
|
50
|
-
return {
|
|
51
|
-
onRpc: (event, context) => {
|
|
52
|
-
if (shouldSkip(event, onlyErrors)) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
try {
|
|
56
|
-
let payload = event;
|
|
57
|
-
if (transform) {
|
|
58
|
-
try {
|
|
59
|
-
payload = transform(event);
|
|
60
|
-
} catch {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
if (payload === null || payload === void 0) {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
const sent = fetch(url, {
|
|
68
|
-
body: JSON.stringify(payload),
|
|
69
|
-
headers: mergedHeaders,
|
|
70
|
-
method: "POST"
|
|
71
|
-
}).catch(() => {
|
|
72
|
-
});
|
|
73
|
-
if (context?.waitUntil) {
|
|
74
|
-
context.waitUntil(sent);
|
|
75
|
-
}
|
|
76
|
-
} catch {
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
};
|
|
81
|
-
const sentrySink = (options) => {
|
|
82
|
-
const { capture } = options;
|
|
83
|
-
const onlyErrors = options.onlyErrors ?? true;
|
|
84
|
-
return {
|
|
85
|
-
onRpc: (event) => {
|
|
86
|
-
if (shouldSkip(event, onlyErrors)) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
try {
|
|
90
|
-
capture(event);
|
|
91
|
-
} catch {
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
};
|
|
96
|
-
const analyticsEngineSink = (options) => {
|
|
97
|
-
const { dataset, onlyErrors } = options;
|
|
98
|
-
return {
|
|
99
|
-
onRpc: (event) => {
|
|
100
|
-
if (shouldSkip(event, onlyErrors)) {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
try {
|
|
104
|
-
dataset.writeDataPoint({
|
|
105
|
-
blobs: [event.functionPath, event.ok ? "ok" : "error", event.shardKey ?? "", event.error?.code ?? "", event.fanOut?.table ?? ""],
|
|
106
|
-
doubles: [event.durationMs, event.ok ? 0 : 1, event.fanOut?.shards ?? 0, event.fanOut?.failed ?? 0],
|
|
107
|
-
indexes: [event.functionPath]
|
|
108
|
-
});
|
|
109
|
-
} catch {
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
};
|
|
114
|
-
const combineSinks = (...sinks) => {
|
|
115
|
-
return {
|
|
116
|
-
onLog: (event, context) => {
|
|
117
|
-
for (const sink of sinks) {
|
|
118
|
-
if (!sink.onLog) {
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
try {
|
|
122
|
-
sink.onLog(event, context);
|
|
123
|
-
} catch {
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
onRpc: (event, context) => {
|
|
128
|
-
for (const sink of sinks) {
|
|
129
|
-
if (!sink.onRpc) {
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
try {
|
|
133
|
-
sink.onRpc(event, context);
|
|
134
|
-
} catch {
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
export { analyticsEngineSink, combineSinks, consoleSink, sentrySink, webhookSink };
|