@lunora/bindings 1.0.0-alpha.4 → 1.0.0-alpha.41
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 +6 -0
- package/dist/analytics/index.d.mts +73 -84
- package/dist/analytics/index.d.ts +73 -84
- package/dist/analytics/index.mjs +1 -2
- package/dist/images/index.d.mts +173 -149
- package/dist/images/index.d.ts +173 -149
- package/dist/images/index.mjs +1 -3
- package/dist/kv/index.d.mts +74 -151
- package/dist/kv/index.d.ts +74 -151
- package/dist/kv/index.mjs +1 -2
- package/dist/packem_shared/AnalyticsSqlError-BEKML_Dg.mjs +1 -0
- package/dist/packem_shared/R2SqlError-CJ_zgztT.mjs +1 -0
- package/dist/packem_shared/SelectBuilder-Dnqem9T0.mjs +1 -0
- package/dist/packem_shared/SetOperation-kiI5Wnlm.mjs +1 -0
- package/dist/packem_shared/Sql-BfnxRway.mjs +1 -0
- package/dist/packem_shared/WindowExpression-VX7EEV3h.mjs +1 -0
- package/dist/packem_shared/WindowFunction-CL4jYy2l.mjs +1 -0
- package/dist/packem_shared/asc-DP_WFiAE.mjs +1 -0
- package/dist/packem_shared/buildImageDeliveryUrl-Brqs-dcZ.mjs +1 -0
- package/dist/packem_shared/buildSignedImageUrl-DbSDmjWK.mjs +4 -0
- package/dist/packem_shared/concurrent-vRmSvRpF.mjs +1 -0
- package/dist/packem_shared/createAnalytics-BXTNc57d.mjs +1 -0
- package/dist/packem_shared/createContextVectors-Bk6ZVCqQ.mjs +6 -0
- package/dist/packem_shared/createImages-CHgMZP4g.mjs +1 -0
- package/dist/packem_shared/createKv-B0RfMMal.mjs +1 -0
- package/dist/packem_shared/createKvIntrospector-DT1k8mCG.mjs +1 -0
- package/dist/packem_shared/createPipelines-CIvqrc7E.mjs +1 -0
- package/dist/packem_shared/createVectorAdminIntrospector-DrsigTJs.mjs +1 -0
- package/dist/packem_shared/createVectors-DirhBYpw.mjs +1 -0
- package/dist/pipelines/index.d.mts +24 -24
- package/dist/pipelines/index.d.ts +24 -24
- package/dist/pipelines/index.mjs +1 -1
- package/dist/r2sql/index.d.mts +148 -123
- package/dist/r2sql/index.d.ts +148 -123
- package/dist/r2sql/index.mjs +1 -7
- package/dist/vectors/index.d.mts +185 -134
- package/dist/vectors/index.d.ts +185 -134
- package/dist/vectors/index.mjs +1 -3
- package/package.json +3 -2
- package/dist/packem_shared/AnalyticsSqlError-C2nz3jpH.mjs +0 -41
- package/dist/packem_shared/R2SqlError-Boygg3S0.mjs +0 -65
- package/dist/packem_shared/SelectBuilder-DHaXZwn_.mjs +0 -167
- package/dist/packem_shared/SetOperation-RDHcxccj.mjs +0 -80
- package/dist/packem_shared/Sql-DceGtcUd.mjs +0 -68
- package/dist/packem_shared/WindowExpression-Cg9s2xcr.mjs +0 -44
- package/dist/packem_shared/WindowFunction-DA3pGC3N.mjs +0 -82
- package/dist/packem_shared/asc-Cur-xO8v.mjs +0 -16
- package/dist/packem_shared/buildImageDeliveryUrl-B6_QU91n.mjs +0 -33
- package/dist/packem_shared/buildSignedImageUrl-6iibA9v1.mjs +0 -113
- package/dist/packem_shared/concurrent-Dj5sOibv.mjs +0 -23
- package/dist/packem_shared/createAnalytics-CEEI69o9.mjs +0 -57
- package/dist/packem_shared/createContextVectors-BSizpmu5.mjs +0 -140
- package/dist/packem_shared/createImages-BzRnsz3H.mjs +0 -85
- package/dist/packem_shared/createKv-7lNrS1W2.mjs +0 -143
- package/dist/packem_shared/createKvIntrospector-CrAgQp1w.mjs +0 -77
- package/dist/packem_shared/createPipelines-CfyJ6VGu.mjs +0 -10
- package/dist/packem_shared/createVectorAdminIntrospector-C-qTDWA1.mjs +0 -53
- package/dist/packem_shared/createVectors-DqxgEPQb.mjs +0 -95
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
const v=e=>{const t=atob(e),n=new Uint8Array(t.length);for(let r=0;r<t.length;r+=1)n[r]=t.codePointAt(r)??0;return n},a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",E=e=>{let t="",n=0;const r=e.length-2;for(;n<r;n+=3){const s=e[n]<<16|e[n+1]<<8|e[n+2];t+=a.charAt(s>>18&63)+a.charAt(s>>12&63)+a.charAt(s>>6&63)+a.charAt(s&63)}const o=e.length-n;if(o===1){const s=e[n]<<16;t+=a.charAt(s>>18&63)+a.charAt(s>>12&63)}else if(o===2){const s=e[n]<<16|e[n+1]<<8;t+=a.charAt(s>>18&63)+a.charAt(s>>12&63)+a.charAt(s>>6&63)}return t},R=e=>{const t=e.replaceAll("-","+").replaceAll("_","/"),n=t+"=".repeat((4-t.length%4)%4);return v(n)},$=(e,t)=>{if(e.size<t)return;const n=e.keys().next().value;n!==void 0&&e.delete(n)},_=(e,t,n,r)=>{const o=e.get(t);if(o!==void 0)return o;$(e,r);const s=n().catch(i=>{throw e.get(t)===s&&e.delete(t),i});return e.set(t,s),s},u=new TextEncoder,x=10080*60,C=/^[a-z][a-z0-9+\-.]*:\/\//i,T=Array.from({length:32},(e,t)=>t),U=new RegExp(`[${T.map(e=>String.fromCodePoint(e)).join("")}]`,"u"),N=64,O=new Map,h=async e=>_(O,e,async()=>crypto.subtle.importKey("raw",u.encode(e),{hash:"SHA-256",name:"HMAC"},!1,["sign","verify"]),N),p=e=>{try{return new URL(e).host}catch{return e.replace(C,"").split("/")[0]??""}},y=e=>{if(U.test(e))throw new TypeError("hmac-url: value must not contain control characters (including CR/LF)")},L=/^\/+$/u,H=e=>L.test(e),I=(e,t)=>{if(!Number.isFinite(e)||e<=0)return"expiresInSeconds must be a positive finite number";if(e>t){const n=` (${String(t/86400)} days)`;return`expiresInSeconds must not exceed ${String(t)} seconds${n}`}},K=async(e,t)=>{const n=await h(e),r=await crypto.subtle.sign("HMAC",n,u.encode(t));return E(new Uint8Array(r))},P=async(e,t,n)=>{const r=await h(e);return crypto.subtle.verify("HMAC",r,n,u.encode(t))},b=/^\//,M=e=>{let t=e.length;for(;t>0&&e[t-1]==="/";)t-=1;return e.slice(0,t)},j=e=>e===void 0?"":Object.entries(e).filter(([,t])=>t!==void 0).toSorted(([t],[n])=>(t>n?1:0)-(t<n?1:0)).map(([t,n])=>`${t}=${typeof n=="object"?JSON.stringify(n):String(n)}`).join("&"),D={background:"string",blur:"number",brightness:"number",contrast:"number",draw:"json",fit:"string",flip:"string",gamma:"number",gravity:"string-or-json",height:"number",rotate:"number",saturation:"number",segment:"string",sharpen:"number",upscale:"string",width:"number"},w=new Map(Object.entries(D)),k=e=>{const t=[];for(const n of e.split("&")){const r=n.indexOf("="),o=r!==-1&&w.has(n.slice(0,r)),s=t.at(-1);o||s===void 0?t.push(n):t[t.length-1]=`${s}&${n}`}return t},z=(e,t,n)=>{if(t==="string"||t==="string-or-json"&&!n.startsWith("{"))return n;if(t==="number"){const r=Number(n);if(n===""||!Number.isFinite(r))throw new TypeError(`@lunora/bindings/images: transform key "${e}" expects a number, got "${n}"`);return r}try{return JSON.parse(n)}catch{throw new TypeError(`@lunora/bindings/images: transform key "${e}" carries malformed JSON`)}},S=(e,t,n,r)=>`${e.toLowerCase()}
|
|
2
|
+
${t}
|
|
3
|
+
${String(n)}
|
|
4
|
+
${r}`,B=e=>e.split("/").map(t=>encodeURIComponent(t)).join("/"),F=e=>{if(e==="")return{};const t={};for(const n of k(e)){const r=n.indexOf("=");if(r===-1)throw new TypeError(`@lunora/bindings/images: malformed transform segment "${n}" — expected key=value`);const o=n.slice(0,r),s=w.get(o);if(s===void 0)throw new TypeError(`@lunora/bindings/images: unknown transform key "${o}" — the serialized transform does not match this version's TransformOptions`);t[o]=z(o,s,n.slice(r+1))}return t},Y=async e=>{const t=e.expiresInSeconds??3600,n=I(t,x);if(n!==void 0)throw new TypeError(`@lunora/bindings/images: ${n}`);let r="";try{r=new URL(e.baseUrl).pathname}catch{}if(r!==""&&!H(r))throw new TypeError(`@lunora/bindings/images: baseUrl must not carry a path ("${r}") — the key is verified from the full URL pathname, so a subpath base would make every signed URL fail verification`);const o=Math.floor(Date.now()/1e3)+t,s=p(e.baseUrl),i=j(e.transform),c=e.key.replace(b,"");y(c);const l=await K(e.secret,S(s,c,o,i)),m=M(e.baseUrl),d=B(c),g=i===""?"":`&t=${encodeURIComponent(i)}`;return`${m}/${d}?exp=${String(o)}&sig=${l}${g}`},J=async(e,t,n)=>{let r;try{r=e instanceof URL?e:new URL(e)}catch{return{reason:"malformed",valid:!1}}const o=r.searchParams.get("exp"),s=o===null?Number.NaN:Number(o),i=r.searchParams.get("sig"),c=r.searchParams.get("t")??"";if(!i||!Number.isInteger(s))return{reason:"malformed",valid:!1};if(s<Math.floor(Date.now()/1e3))return{reason:"expired",valid:!1};let l,m;try{l=r.pathname.replace(b,"").split("/").map(A=>decodeURIComponent(A)).join("/"),y(l),m=R(i)}catch{return{reason:"malformed",valid:!1}}const d=n?.expectedHost===void 0?r.host:p(n.expectedHost);if(!await P(t,S(d,l,s,c),m))return{reason:"bad_signature",valid:!1};if(c==="")return{key:l,valid:!0};let f;try{f=F(c)}catch{f=void 0}return{key:l,transform:c,transformOptions:f,valid:!0}};export{Y as buildSignedImageUrl,F as parseSignedTransform,J as verifySignedImageUrl};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const h=8,u=async(r,c,l)=>{if(r.length===0)return[];const f=Math.max(1,Math.min(c,r.length)),n=Array.from({length:r.length});let a=0,t=!1,o;const s=Array.from({length:f},async()=>{for(;;){if(t)return;const e=a;if(a+=1,e>=r.length)return;try{n[e]=await l(r[e],e)}catch(i){t||(t=!0,o=i);return}}});if(await Promise.all(s),t)throw o;return n};export{h as U,u as c};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const _=new TextEncoder,l=n=>n===null?0:typeof n=="string"?_.encode(n).length:n.byteLength,a=(n,o,r)=>{if(o>r)throw new RangeError(`@lunora/bindings/analytics: a data point may carry at most ${String(r)} ${n} (got ${String(o)}).`)},b=(n,o,r)=>{if(o>r)throw new RangeError(`@lunora/bindings/analytics: a data point's ${n} may total at most ${String(r)} bytes (got ${String(o)}).`)},m=n=>{const o=t=>{a("blobs",t.blobs?.length??0,20),a("doubles",t.doubles?.length??0,20),a("indexes",t.indexes?.length??0,1);const s=(t.blobs??[]).reduce((e,c)=>e+l(c),0);b("blobs",s,16384);for(const e of t.indexes??[])b("index",l(e),96);n.writeDataPoint(t)};return{track:(t,s={})=>{const e=Object.entries(s.dimensions??{}),c=Object.entries(s.metrics??{}),E=[t,...e.map(([,i])=>i)],g=c.map(([,i])=>i),u=s.index===void 0?[]:[s.index];o({blobs:E,doubles:g,indexes:u});const B=e.map(([i],d)=>({column:`blob${String(d+2)}`,field:i})),S=c.map(([i],d)=>({column:`double${String(d+1)}`,field:i})),X=s.index===void 0?null:{column:"index1",field:"index"};return{dimensions:B,index:X,metrics:S,name:t}},writeDataPoint:o}};export{m as createAnalytics};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{c as b,U as g}from"./concurrent-vRmSvRpF.mjs";const x=(s,c)=>{if(!c.includes("."))return s[c];let n=s;for(const d of c.split(".")){if(n===null||typeof n!="object"||Array.isArray(n))return;n=n[d]}return n},S=(s,c)=>{const n=c?.namespace,d=new Set(c?.shardedIndexNames),l=(t,a)=>{if(a!==void 0)return a;if(d.has(t)){if(n!==void 0)return n;throw new Error(`@lunora/bindings/vectors: index "${t}" belongs to a sharded table, but this DO instance has no shard key (it is the root/default DO) and no explicit namespace was given. A namespace-less operation here would reach every tenant's vectors — Vectorize indexes are account-global. Pass an explicit namespace, or issue this call from the sharded DO instance that owns the tenant.`)}},o=async(t,a)=>{await s.upsert(t,{embed:a.embed,id:a.id,input:a.input,metadata:a.metadata,namespace:l(t,a.namespace)})},h=async(t,a,i)=>{const r=await s.getByIds(t,a);return i===void 0?r:r.filter(m=>m.namespace===i)};return{deleteByIds:async(t,a,i)=>{const r=l(t,i);if(r===void 0){await s.deleteByIds(t,a);return}const m=await h(t,a,r);m.length!==0&&await s.deleteByIds(t,m.map(u=>u.id))},getByIds:async(t,a,i)=>{const r=l(t,i);return(await h(t,a,r)).map(u=>({id:u.id,metadata:u.metadata,namespace:u.namespace,values:u.values}))},query:async(t,a)=>{const i=await s.query(t,{embed:a.embed,filter:a.filter,input:a.input,namespace:l(t,a.namespace),returnMetadata:a.returnMetadata??"indexed",topK:a.topK,vector:a.vector});return{count:i.count,matches:i.matches.map(r=>({id:r.id,metadata:r.metadata,score:r.score}))}},upsert:o,upsertNow:o}},f=new Set,v=s=>{f.has(s)||(f.add(s),console.warn(`[@lunora/bindings/vectors] index "${s}" syncs vectors without a namespace — in a
|
|
2
|
+
multi-tenant/sharded app this exposes one tenant's vectors (and any captured
|
|
3
|
+
metadata) to every other tenant, since Vectorize indexes are account-global.
|
|
4
|
+
Pass \`namespace\` (the shard/tenant key) on both write and query — query-side
|
|
5
|
+
namespace filtering is mandatory for multi-tenant apps. Single-tenant apps that
|
|
6
|
+
legitimately have no tenant key suppress this via { allowSharedNamespace: true }.`))},I=(s,c)=>{const n={};for(const d of c)d in s&&(n[d]=s[d]);return n},D=s=>{const{allowSharedNamespace:c,namespace:n,schema:d,vectors:l}=s;return async o=>{const t=d.tables[o.table]?.vectorIndexes??[],a=Object.entries(d.vectorIndexes).filter(([,e])=>e.table===o.table);if(t.length===0&&a.length===0)return;const i=[...t.map(e=>e.name),...a.map(([e])=>e)];if(o.op==="delete"){await Promise.all(i.map(e=>l.deleteByIds(e,[o.id])));return}const r=o.doc;if(!r)return;const m=t.map(e=>({index:e,value:x(r,e.field)})),u=m.filter(e=>e.value!==void 0&&e.value!==null),y=m.filter(e=>e.value===void 0||e.value===null);for(const{index:e,value:p}of u)if(typeof p!="string")throw new TypeError(`@lunora/bindings/vectors: inline index "${e.name}" expects a string source at "${e.field}" on table "${o.table}" (got ${typeof p}); use a standalone defineVectorIndex with a select() to derive text from non-string columns`);const w=[...y.map(e=>async()=>{await l.deleteByIds(e.index.name,[o.id])}),...u.map(e=>async()=>{!c&&n===void 0&&v(e.index.name),await l.upsert(e.index.name,{embed:e.index.embed,id:o.id,input:e.value,metadata:e.index.metadata?I(r,e.index.metadata):void 0,namespace:n})}),...a.map(([e,p])=>async()=>{!c&&n===void 0&&v(e),await l.upsert(e,{embed:p.embed,id:o.id,input:p.select(r),metadata:p.metadata?.(r),namespace:n})})];try{await b(w,g,async e=>e())}catch(e){throw await Promise.allSettled(i.map(p=>l.deleteByIds(p,[o.id]))),e}}};export{S as createContextVectors,D as createVectorSyncHook};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as d}from"@lunora/errors";const m=new Set(["image/avif","image/gif","image/jpeg","image/png","image/webp"]),T=1e4,A="image/webp",O=e=>typeof e!="object"?!1:"body"in e&&!(e instanceof ArrayBuffer)&&!(e instanceof Uint8Array),i=e=>{if(e instanceof ReadableStream)return e;if(O(e)){if(e.body===null)throw new d("INTERNAL","@lunora/bindings/images: R2 object body is null (object missing or already consumed)");return e.body}if(e instanceof Blob)return e.stream();const t=e instanceof Uint8Array?e:new Uint8Array(e);return new ReadableStream({start(n){n.enqueue(t),n.close()}})},f=(e,t)=>{if(e===void 0)return{};const n=r=>{if(!Number.isFinite(r)||r<=0)throw new TypeError("@lunora/bindings/images: width/height must be a positive finite number");return Math.min(Math.floor(r),t)},o={...e};return delete o.draw,{...o,...e.width===void 0?{}:{width:n(e.width)},...e.height===void 0?{}:{height:n(e.height)}}},v=e=>{const{image:t,transform:n,...o}=e;return{drawOptions:o,image:t,transform:n}},U=e=>{const t=e?.format??A;if(!m.has(t))throw new d("INTERNAL",`@lunora/bindings/images: unsupported output format "${t}" (allowed: ${[...m].join(", ")})`);return{...e,format:t}},p=e=>{const{binding:t}=e,n=e.maxDimension??T;return{info:async o=>t.info(i(o)),transform:async(o,r,g,b)=>{const u=f(r,n),l=U(g);let a=t.input(i(o)).transform(u);for(const w of b??[]){const{drawOptions:y,image:s,transform:c}=v(w),h=c===void 0?i(s):t.input(i(s)).transform(f(c,n));a=a.draw(h,y)}return a.output(l)}}};export{p as createImages};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as c}from"@lunora/errors";const d=512,T=1e3,v=1024,f=new TextEncoder,m=t=>f.encode(t).length,h=(t,i)=>{if(m(t)>d)throw new c("INTERNAL",`@lunora/bindings/kv: ${i} exceeds ${String(d)}-byte limit`);if(t.includes("\0"))throw new c("INTERNAL",`@lunora/bindings/kv: ${i} contains NUL byte`);for(const a of t.split("/"))if(a==="."||a==="..")throw new c("INTERNAL",`@lunora/bindings/kv: ${i} contains a \`.\`/\`..\` path component`)},u=t=>{if(typeof t!="string"||t.length===0)throw new TypeError("@lunora/bindings/kv: key must be a non-empty string");h(t,"key")},g=t=>{t.length>0&&h(t,"prefix")},b=(t,i)=>{u(t),u(i);const o=`${t.endsWith("/")?t.slice(0,-1):t}/${i}`;if(m(o)>d)throw new c("INTERNAL",`@lunora/bindings/kv: scoped key exceeds ${String(d)}-byte limit`);return o},x=t=>{const i={};if(t.expiration!==void 0&&t.expirationTtl!==void 0)throw new c("INTERNAL","@lunora/bindings/kv: `expiration` and `expirationTtl` are mutually exclusive");if(t.expiration!==void 0&&(i.expiration=t.expiration),t.expirationTtl!==void 0&&(i.expirationTtl=t.expirationTtl),t.metadata!==void 0){let a;try{a=JSON.stringify(t.metadata)}catch{throw new c("INTERNAL","@lunora/bindings/kv: metadata is not JSON-serializable (cyclic value, bigint, or similar)")}if(m(a)>v)throw new c("INTERNAL",`@lunora/bindings/kv: metadata exceeds ${String(v)}-byte limit`);i.metadata=t.metadata}return Object.keys(i).length>0?i:void 0},k=t=>{if(!t.namespace)throw new TypeError("@lunora/bindings/kv: `namespace` is required");const{keyPrefix:i,namespace:a}=t;i!==void 0&&u(i);const o=e=>i===void 0?(u(e),e):b(i,e),y=e=>{if(i===void 0)return e;const n=i.endsWith("/")?i:`${i}/`;return e.startsWith(n)?e.slice(n.length):e};return{delete:async e=>{await a.delete(o(e))},get:async(e,n={})=>await a.get(o(e),{cacheTtl:n.cacheTtl,type:"json"})??null,getRaw:async(e,n={})=>await a.get(o(e),{cacheTtl:n.cacheTtl,type:n.type??"text"})??null,getWithMetadata:async(e,n={})=>{const r=await a.getWithMetadata(o(e),{cacheTtl:n.cacheTtl,type:"json"});return{metadata:r.metadata??null,value:r.value??null}},list:async(e={})=>{if(e.limit!==void 0&&(!Number.isInteger(e.limit)||e.limit<=0))throw new TypeError("@lunora/bindings/kv: `limit` must be a positive integer");let{prefix:n}=e;if(n!==void 0&&g(n),i!==void 0){const l=i.endsWith("/")?i:`${i}/`;n=e.prefix===void 0?l:`${l}${e.prefix}`,g(n)}const r=e.limit===void 0?void 0:Math.min(e.limit,T),s=await a.list({cursor:e.cursor,limit:r,prefix:n}),w=s.keys.map(l=>({...l,name:y(l.name)}));return{cursor:s.list_complete?void 0:s.cursor,keys:w,listComplete:s.list_complete}},put:async(e,n,r={})=>{const s=r.raw?n:JSON.stringify(n);await a.put(o(e),s,x(r))}}};export{k as createKv,b as scopeKey};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as l}from"@lunora/errors";const u=a=>{const{namespaces:t}=a,s=e=>{const r=t[e];if(!Object.hasOwn(t,e)||r===void 0)throw new l("BAD_REQUEST",`@lunora/bindings/kv: no namespace registered under binding "${e}"`);return r};return{deleteKey:async e=>{await s(e.namespace).delete(e.key)},getValue:async e=>{const n=await s(e.namespace).getWithMetadata(e.key,"text");return{metadata:n.metadata??null,value:n.value??null}},listKeys:async e=>{const n=await s(e.namespace).list({cursor:e.cursor,limit:e.limit,prefix:e.prefix}),i=n.keys.map(o=>({expiration:o.expiration,metadata:o.metadata,name:o.name}));return{cursor:n.list_complete?void 0:n.cursor,keys:i,listComplete:n.list_complete}},listNamespaces:()=>Promise.resolve(Object.keys(t).map(e=>({binding:e}))),putValue:async e=>{await s(e.namespace).put(e.key,e.value,{expiration:e.expiration,expirationTtl:e.expirationTtl,metadata:e.metadata})}}},m=a=>{if(typeof a!="object"||a===null)return!1;const t=a;return typeof t.getWithMetadata=="function"&&typeof t.list=="function"&&typeof t.put=="function"&&typeof t.delete=="function"},v=a=>{const t={};if(typeof a=="object"&&a!==null)for(const[s,c]of Object.entries(a))m(c)&&(t[s]=c);return u({namespaces:t})};export{u as createKvIntrospector,v as createKvIntrospectorFromEnv};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const i=a=>{const{binding:e}=a;return{send:async n=>{await e.send(Array.isArray(n)?n:[n])}}};export{i as createPipelines};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as a}from"@lunora/errors";const m=20,p=10,f=u=>{const{embedders:r,indexes:n,registry:b}=u,i=async()=>Promise.all(b.map(async e=>{const s=n[e.name];if(s?.describe===void 0)return{...e};try{const t=await s.describe();return{...e,dimensions:e.dimensions??t.dimensions,processedUpToMutation:t.processedUpToMutation,vectorsCount:t.vectorsCount}}catch{return{...e}}}));return r!==void 0&&Object.keys(r).length>0?{listIndexes:i,queryIndex:async({name:e,text:s,topK:t})=>{const d=n[e];if(!Object.hasOwn(n,e)||d===void 0)throw new a("INTERNAL",`@lunora/bindings/vectors: no Vectorize binding registered for index "${e}"`);const c=r[e];if(!Object.hasOwn(r,e)||c===void 0)throw new a("INTERNAL",`@lunora/bindings/vectors: no embedder registered for index "${e}" — it lists read-only`);const l=await c(s);return{matches:(await d.query(l,{returnMetadata:"all",topK:Math.min(t??p,m)})).matches.map(o=>({id:o.id,metadata:o.metadata,score:o.score}))}}}:{listIndexes:i}};export{f as createVectorAdminIntrospector};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as b}from"@lunora/errors";import{c as v,U as h}from"./concurrent-vRmSvRpF.mjs";const a=(r,s)=>{const d=r[s];if(!Object.hasOwn(r,s)||d===void 0)throw new b("INTERNAL",`@lunora/bindings/vectors: no index registered for "${s}". Known indexes: ${Object.keys(r).join(", ")||"(none)"}`);return d},g=async r=>{const s=await r.embed(r.input);return{id:r.id,metadata:r.metadata,namespace:r.namespace,values:s}},m=100,u=50,c=1e3,y=1e3,K=r=>{if(Object.keys(r.indexes).length===0)throw new TypeError("@lunora/bindings/vectors: at least one index binding is required");return{deleteByIds:async(t,e)=>{const n=a(r.indexes,t);if(e.length>c)throw new RangeError(`@lunora/bindings/vectors: deleteByIds accepts at most ${String(c)} ids (got ${String(e.length)})`);return n.deleteByIds(e)},describe:async t=>{const e=a(r.indexes,t);if(!e.describe)throw new b("INTERNAL",`@lunora/bindings/vectors: binding for "${t}" does not implement describe()`);return e.describe()},getByIds:async(t,e)=>{const n=a(r.indexes,t);if(e.length>c)throw new RangeError(`@lunora/bindings/vectors: getByIds accepts at most ${String(c)} ids (got ${String(e.length)})`);return n.getByIds(e)},query:async(t,e)=>{const n=a(r.indexes,t),o=e.returnValues===!0||e.returnMetadata==="all",l=o?u:m;if(e.topK!==void 0&&(!Number.isInteger(e.topK)||e.topK<1||e.topK>l)){const w=o?` (lowered to ${String(u)} because returnValues/returnMetadata:"all" is set)`:"";throw new RangeError(`@lunora/bindings/vectors: topK must be an integer in [1, ${String(l)}]${w} (got ${String(e.topK)})`)}let i;if(e.vector&&e.vector.length>0)i=e.vector;else{if(!e.embed||e.input===void 0)throw new TypeError("@lunora/bindings/vectors: query requires either `vector` or both `input` and `embed`");i=await e.embed(e.input)}return n.query(i,{filter:e.filter,namespace:e.namespace,returnMetadata:e.returnMetadata,returnValues:e.returnValues,topK:e.topK})},upsert:async(t,e)=>{const n=a(r.indexes,t),o=await g(e);return n.upsert([o])},upsertMany:async(t,e)=>{const n=a(r.indexes,t);if(e.length>y)throw new RangeError(`@lunora/bindings/vectors: upsertMany batch exceeds ${String(y)} (got ${String(e.length)}) — split across calls`);const o=await v(e,h,g);return n.upsert(o)}}};export{K as default};
|
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Structural types for the Cloudflare Pipelines write path (R2-backed streaming
|
|
3
|
-
* ingestion). Pipelines is the other "emit data to a sink" surface alongside
|
|
4
|
-
* Analytics Engine — telemetry/events out, no in-handler read-back. The binding
|
|
5
|
-
* is mirrored structurally (`*Like`) so a plain-object fake satisfies it in unit
|
|
6
|
-
* tests, like `@lunora/bindings/analytics`'s `AnalyticsEngineDatasetLike`.
|
|
7
|
-
*/
|
|
2
|
+
* Structural types for the Cloudflare Pipelines write path (R2-backed streaming
|
|
3
|
+
* ingestion). Pipelines is the other "emit data to a sink" surface alongside
|
|
4
|
+
* Analytics Engine — telemetry/events out, no in-handler read-back. The binding
|
|
5
|
+
* is mirrored structurally (`*Like`) so a plain-object fake satisfies it in unit
|
|
6
|
+
* tests, like `@lunora/bindings/analytics`'s `AnalyticsEngineDatasetLike`.
|
|
7
|
+
*/
|
|
8
8
|
/** One Pipelines record — a JSON object matching the stream's schema. */
|
|
9
9
|
type PipelineRecord = Record<string, unknown>;
|
|
10
10
|
/**
|
|
11
|
-
* Minimal structural projection of workers-types' `Pipeline
|
|
12
|
-
* real binding's `send` takes an array of records and resolves once accepted.
|
|
13
|
-
*/
|
|
11
|
+
* Minimal structural projection of workers-types' `Pipeline<T>` binding. The
|
|
12
|
+
* real binding's `send` takes an array of records and resolves once accepted.
|
|
13
|
+
*/
|
|
14
14
|
interface PipelineBindingLike<T extends PipelineRecord = PipelineRecord> {
|
|
15
15
|
send: (records: T[]) => Promise<void>;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* The write-side client bound to `ctx.pipelines` (the generated context imports
|
|
19
|
-
* this exact type as `import("@lunora/bindings/pipelines").PipelineClient`).
|
|
20
|
-
* Ingestion is durable, batched, and fire-and-forget — never read a record back
|
|
21
|
-
* in-handler.
|
|
22
|
-
*/
|
|
18
|
+
* The write-side client bound to `ctx.pipelines` (the generated context imports
|
|
19
|
+
* this exact type as `import("@lunora/bindings/pipelines").PipelineClient`).
|
|
20
|
+
* Ingestion is durable, batched, and fire-and-forget — never read a record back
|
|
21
|
+
* in-handler.
|
|
22
|
+
*/
|
|
23
23
|
interface PipelineClient<T extends PipelineRecord = PipelineRecord> {
|
|
24
24
|
/** Ingest one record or an array of records into the R2-backed sink. */
|
|
25
25
|
send: (records: T | T[]) => Promise<void>;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* Wrap a Cloudflare Pipelines binding in the write-side {@link PipelineClient}
|
|
29
|
-
* bound to `ctx.pipelines`. The binding is `env.PIPELINES` (the `pipelines`
|
|
30
|
-
* binding the config layer recognizes; the remote pipeline name is minted with
|
|
31
|
-
* `wrangler pipelines create`).
|
|
32
|
-
*
|
|
33
|
-
* Ingestion is durable and batched: `send` accepts one record or an array and
|
|
34
|
-
* resolves once Cloudflare has accepted them for delivery to the R2-backed sink.
|
|
35
|
-
* There is no in-handler read-back — this is a fire-and-forget egress path, so
|
|
36
|
-
* it belongs on ActionCtx only (external I/O), mirroring `ctx.images`.
|
|
37
|
-
*/
|
|
28
|
+
* Wrap a Cloudflare Pipelines binding in the write-side {@link PipelineClient}
|
|
29
|
+
* bound to `ctx.pipelines`. The binding is `env.PIPELINES` (the `pipelines`
|
|
30
|
+
* binding the config layer recognizes; the remote pipeline name is minted with
|
|
31
|
+
* `wrangler pipelines create`).
|
|
32
|
+
*
|
|
33
|
+
* Ingestion is durable and batched: `send` accepts one record or an array and
|
|
34
|
+
* resolves once Cloudflare has accepted them for delivery to the R2-backed sink.
|
|
35
|
+
* There is no in-handler read-back — this is a fire-and-forget egress path, so
|
|
36
|
+
* it belongs on ActionCtx only (external I/O), mirroring `ctx.images`.
|
|
37
|
+
*/
|
|
38
38
|
declare const createPipelines: <T extends PipelineRecord = PipelineRecord>(options: {
|
|
39
39
|
binding: PipelineBindingLike<T>;
|
|
40
40
|
}) => PipelineClient<T>;
|
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Structural types for the Cloudflare Pipelines write path (R2-backed streaming
|
|
3
|
-
* ingestion). Pipelines is the other "emit data to a sink" surface alongside
|
|
4
|
-
* Analytics Engine — telemetry/events out, no in-handler read-back. The binding
|
|
5
|
-
* is mirrored structurally (`*Like`) so a plain-object fake satisfies it in unit
|
|
6
|
-
* tests, like `@lunora/bindings/analytics`'s `AnalyticsEngineDatasetLike`.
|
|
7
|
-
*/
|
|
2
|
+
* Structural types for the Cloudflare Pipelines write path (R2-backed streaming
|
|
3
|
+
* ingestion). Pipelines is the other "emit data to a sink" surface alongside
|
|
4
|
+
* Analytics Engine — telemetry/events out, no in-handler read-back. The binding
|
|
5
|
+
* is mirrored structurally (`*Like`) so a plain-object fake satisfies it in unit
|
|
6
|
+
* tests, like `@lunora/bindings/analytics`'s `AnalyticsEngineDatasetLike`.
|
|
7
|
+
*/
|
|
8
8
|
/** One Pipelines record — a JSON object matching the stream's schema. */
|
|
9
9
|
type PipelineRecord = Record<string, unknown>;
|
|
10
10
|
/**
|
|
11
|
-
* Minimal structural projection of workers-types' `Pipeline
|
|
12
|
-
* real binding's `send` takes an array of records and resolves once accepted.
|
|
13
|
-
*/
|
|
11
|
+
* Minimal structural projection of workers-types' `Pipeline<T>` binding. The
|
|
12
|
+
* real binding's `send` takes an array of records and resolves once accepted.
|
|
13
|
+
*/
|
|
14
14
|
interface PipelineBindingLike<T extends PipelineRecord = PipelineRecord> {
|
|
15
15
|
send: (records: T[]) => Promise<void>;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* The write-side client bound to `ctx.pipelines` (the generated context imports
|
|
19
|
-
* this exact type as `import("@lunora/bindings/pipelines").PipelineClient`).
|
|
20
|
-
* Ingestion is durable, batched, and fire-and-forget — never read a record back
|
|
21
|
-
* in-handler.
|
|
22
|
-
*/
|
|
18
|
+
* The write-side client bound to `ctx.pipelines` (the generated context imports
|
|
19
|
+
* this exact type as `import("@lunora/bindings/pipelines").PipelineClient`).
|
|
20
|
+
* Ingestion is durable, batched, and fire-and-forget — never read a record back
|
|
21
|
+
* in-handler.
|
|
22
|
+
*/
|
|
23
23
|
interface PipelineClient<T extends PipelineRecord = PipelineRecord> {
|
|
24
24
|
/** Ingest one record or an array of records into the R2-backed sink. */
|
|
25
25
|
send: (records: T | T[]) => Promise<void>;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* Wrap a Cloudflare Pipelines binding in the write-side {@link PipelineClient}
|
|
29
|
-
* bound to `ctx.pipelines`. The binding is `env.PIPELINES` (the `pipelines`
|
|
30
|
-
* binding the config layer recognizes; the remote pipeline name is minted with
|
|
31
|
-
* `wrangler pipelines create`).
|
|
32
|
-
*
|
|
33
|
-
* Ingestion is durable and batched: `send` accepts one record or an array and
|
|
34
|
-
* resolves once Cloudflare has accepted them for delivery to the R2-backed sink.
|
|
35
|
-
* There is no in-handler read-back — this is a fire-and-forget egress path, so
|
|
36
|
-
* it belongs on ActionCtx only (external I/O), mirroring `ctx.images`.
|
|
37
|
-
*/
|
|
28
|
+
* Wrap a Cloudflare Pipelines binding in the write-side {@link PipelineClient}
|
|
29
|
+
* bound to `ctx.pipelines`. The binding is `env.PIPELINES` (the `pipelines`
|
|
30
|
+
* binding the config layer recognizes; the remote pipeline name is minted with
|
|
31
|
+
* `wrangler pipelines create`).
|
|
32
|
+
*
|
|
33
|
+
* Ingestion is durable and batched: `send` accepts one record or an array and
|
|
34
|
+
* resolves once Cloudflare has accepted them for delivery to the R2-backed sink.
|
|
35
|
+
* There is no in-handler read-back — this is a fire-and-forget egress path, so
|
|
36
|
+
* it belongs on ActionCtx only (external I/O), mirroring `ctx.images`.
|
|
37
|
+
*/
|
|
38
38
|
declare const createPipelines: <T extends PipelineRecord = PipelineRecord>(options: {
|
|
39
39
|
binding: PipelineBindingLike<T>;
|
|
40
40
|
}) => PipelineClient<T>;
|
package/dist/pipelines/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import{createPipelines as i}from"../packem_shared/createPipelines-CIvqrc7E.mjs";export{i as createPipelines};
|
package/dist/r2sql/index.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { LunoraError } from '@lunora/errors';
|
|
2
2
|
/**
|
|
3
|
-
* A composed SQL fragment. Carries the finished `text`; `toString()` returns it
|
|
4
|
-
* so a fragment can be dropped straight into a template or `String(...)`.
|
|
5
|
-
*/
|
|
3
|
+
* A composed SQL fragment. Carries the finished `text`; `toString()` returns it
|
|
4
|
+
* so a fragment can be dropped straight into a template or `String(...)`.
|
|
5
|
+
*/
|
|
6
6
|
declare class Sql {
|
|
7
7
|
readonly text: string;
|
|
8
8
|
constructor(text: string);
|
|
@@ -11,32 +11,32 @@ declare class Sql {
|
|
|
11
11
|
/** True when `value` is a {@link Sql} fragment (an already-trusted, pre-escaped span). */
|
|
12
12
|
declare const isSql: (value: unknown) => value is Sql;
|
|
13
13
|
/**
|
|
14
|
-
* Wrap an already-trusted string as a {@link Sql} fragment so {@link sql}
|
|
15
|
-
* splices it **verbatim** (no escaping). Use ONLY for SQL you constructed
|
|
16
|
-
* yourself (identifiers, keywords, sub-fragments) — never for user input.
|
|
17
|
-
*/
|
|
14
|
+
* Wrap an already-trusted string as a {@link Sql} fragment so {@link sql}
|
|
15
|
+
* splices it **verbatim** (no escaping). Use ONLY for SQL you constructed
|
|
16
|
+
* yourself (identifiers, keywords, sub-fragments) — never for user input.
|
|
17
|
+
*/
|
|
18
18
|
declare const raw: (text: string) => Sql;
|
|
19
19
|
/** Resolve a `string | Sql` to its raw text. A bare string is taken as trusted SQL (callers pass identifiers/fragments here). */
|
|
20
20
|
declare const toText: (value: Sql | string) => string;
|
|
21
21
|
/**
|
|
22
|
-
* Render a JS value as an R2 SQL literal:
|
|
23
|
-
*
|
|
24
|
-
* - `null` / `undefined` → `NULL`
|
|
25
|
-
* - `boolean` → `true` / `false`
|
|
26
|
-
* - finite `number` / `bigint` → the numeric text (non-finite throws — `NaN`/`Infinity` have no SQL literal)
|
|
27
|
-
* - `Date` → an RFC3339 string literal (R2 SQL's `timestamp` form)
|
|
28
|
-
* - `string` → a single-quoted, escaped literal
|
|
29
|
-
* - `Array` → a parenthesised, comma-separated list of literals (for `IN (...)`)
|
|
30
|
-
*
|
|
31
|
-
* Anything else (object, symbol, function) throws — there is no safe SQL literal
|
|
32
|
-
* for it, and silently coercing would risk a malformed or injectable statement.
|
|
33
|
-
*/
|
|
22
|
+
* Render a JS value as an R2 SQL literal:
|
|
23
|
+
*
|
|
24
|
+
* - `null` / `undefined` → `NULL`
|
|
25
|
+
* - `boolean` → `true` / `false`
|
|
26
|
+
* - finite `number` / `bigint` → the numeric text (non-finite throws — `NaN`/`Infinity` have no SQL literal)
|
|
27
|
+
* - `Date` → an RFC3339 string literal (R2 SQL's `timestamp` form; an invalid `Date` throws)
|
|
28
|
+
* - `string` → a single-quoted, escaped literal
|
|
29
|
+
* - `Array` → a parenthesised, comma-separated list of literals (for `IN (...)`)
|
|
30
|
+
*
|
|
31
|
+
* Anything else (object, symbol, function) throws — there is no safe SQL literal
|
|
32
|
+
* for it, and silently coercing would risk a malformed or injectable statement.
|
|
33
|
+
*/
|
|
34
34
|
declare const lit: (value: unknown) => string;
|
|
35
35
|
/**
|
|
36
|
-
* Tagged template producing a safe {@link Sql} fragment. Each interpolation is
|
|
37
|
-
* escaped with {@link lit} unless it is already a {@link Sql} (spliced
|
|
38
|
-
* verbatim), so user values can never break out of their literal.
|
|
39
|
-
*/
|
|
36
|
+
* Tagged template producing a safe {@link Sql} fragment. Each interpolation is
|
|
37
|
+
* escaped with {@link lit} unless it is already a {@link Sql} (spliced
|
|
38
|
+
* verbatim), so user values can never break out of their literal.
|
|
39
|
+
*/
|
|
40
40
|
declare const sql: (strings: TemplateStringsArray, ...values: unknown[]) => Sql;
|
|
41
41
|
/** Join SQL fragments/strings with `separator` into one {@link Sql} (e.g. `AND`-ed conditions). */
|
|
42
42
|
declare const joinSql: (parts: ReadonlyArray<Sql | string>, separator: string) => Sql;
|
|
@@ -52,29 +52,29 @@ declare const desc: (expr: Sql | string) => OrderTerm;
|
|
|
52
52
|
/** Render one {@link OrderTerm} to SQL. */
|
|
53
53
|
declare const renderOrderTerm: (term: OrderTerm) => string;
|
|
54
54
|
/**
|
|
55
|
-
* Public types for `@lunora/bindings/r2sql`.
|
|
56
|
-
*
|
|
57
|
-
* R2 SQL is Cloudflare's serverless, distributed query engine over **Apache
|
|
58
|
-
* Iceberg** tables in [R2 Data Catalog](https://developers.cloudflare.com/r2/data-catalog/).
|
|
59
|
-
* It has **no Workers binding** — every query is an HTTPS round-trip to the REST
|
|
60
|
-
* endpoint (`POST …/r2-sql/query/{bucket}`). So, like Hyperdrive's `ctx.sql`, the
|
|
61
|
-
* client is **non-deterministic external I/O**: it is wired onto `ActionCtx`
|
|
62
|
-
* only (see the `r2sql_outside_action` advisor lint) and its reads are NOT
|
|
63
|
-
* tracked by Lunora live queries.
|
|
64
|
-
*
|
|
65
|
-
* Everything here is deliberately structural (no hard dependency on
|
|
66
|
-
* `@cloudflare/workers-types`) so unit tests can inject a plain `fetch` double
|
|
67
|
-
* and never touch the network — mirroring `AnalyticsSqlConfig` in
|
|
68
|
-
* `@lunora/bindings/analytics`.
|
|
69
|
-
*/
|
|
55
|
+
* Public types for `@lunora/bindings/r2sql`.
|
|
56
|
+
*
|
|
57
|
+
* R2 SQL is Cloudflare's serverless, distributed query engine over **Apache
|
|
58
|
+
* Iceberg** tables in [R2 Data Catalog](https://developers.cloudflare.com/r2/data-catalog/).
|
|
59
|
+
* It has **no Workers binding** — every query is an HTTPS round-trip to the REST
|
|
60
|
+
* endpoint (`POST …/r2-sql/query/{bucket}`). So, like Hyperdrive's `ctx.sql`, the
|
|
61
|
+
* client is **non-deterministic external I/O**: it is wired onto `ActionCtx`
|
|
62
|
+
* only (see the `r2sql_outside_action` advisor lint) and its reads are NOT
|
|
63
|
+
* tracked by Lunora live queries.
|
|
64
|
+
*
|
|
65
|
+
* Everything here is deliberately structural (no hard dependency on
|
|
66
|
+
* `@cloudflare/workers-types`) so unit tests can inject a plain `fetch` double
|
|
67
|
+
* and never touch the network — mirroring `AnalyticsSqlConfig` in
|
|
68
|
+
* `@lunora/bindings/analytics`.
|
|
69
|
+
*/
|
|
70
70
|
/**
|
|
71
|
-
* Configuration for a {@link import("./client").R2SqlClient | R2SqlClient}.
|
|
72
|
-
*
|
|
73
|
-
* `apiToken` is a **secret** — a Cloudflare API token scoped to R2 SQL (read),
|
|
74
|
-
* R2 Data Catalog, and R2 storage. It is never a binding and must never be
|
|
75
|
-
* auto-scaffolded with a real value; the caller provides it from
|
|
76
|
-
* env/`.dev.vars`.
|
|
77
|
-
*/
|
|
71
|
+
* Configuration for a {@link import("./client").R2SqlClient | R2SqlClient}.
|
|
72
|
+
*
|
|
73
|
+
* `apiToken` is a **secret** — a Cloudflare API token scoped to R2 SQL (read),
|
|
74
|
+
* R2 Data Catalog, and R2 storage. It is never a binding and must never be
|
|
75
|
+
* auto-scaffolded with a real value; the caller provides it from
|
|
76
|
+
* env/`.dev.vars`.
|
|
77
|
+
*/
|
|
78
78
|
interface R2SqlConfig {
|
|
79
79
|
/** Cloudflare account id that owns the bucket/catalog. */
|
|
80
80
|
accountId: string;
|
|
@@ -83,36 +83,46 @@ interface R2SqlConfig {
|
|
|
83
83
|
/** The R2 bucket (warehouse) whose Data Catalog the queries run against. */
|
|
84
84
|
bucket: string;
|
|
85
85
|
/**
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
* Override the REST base URL. Defaults to Cloudflare's public R2 SQL host
|
|
87
|
+
* (`https://api.sql.cloudflarestorage.com/api/v1/accounts`). Injected in
|
|
88
|
+
* tests, or pointed at a regional/preview host.
|
|
89
|
+
*/
|
|
90
90
|
endpoint?: string;
|
|
91
91
|
/**
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
* `fetch` implementation. Defaults to the global `fetch`; injected in tests
|
|
93
|
+
* so a query never touches the network.
|
|
94
|
+
*/
|
|
95
95
|
fetch?: typeof globalThis.fetch;
|
|
96
|
+
/**
|
|
97
|
+
* Milliseconds before an in-flight query (the fetch AND its body read) is
|
|
98
|
+
* aborted and surfaced as an `R2SqlError` with status 504. Defaults to
|
|
99
|
+
* 60_000 — analytical scans legitimately run tens of seconds. `undefined`
|
|
100
|
+
* means the default, not unbounded.
|
|
101
|
+
*
|
|
102
|
+
* The deadline is carried by the request's `signal`, so a custom `fetch`
|
|
103
|
+
* (above) that ignores `signal` leaves the query unbounded.
|
|
104
|
+
*/
|
|
105
|
+
timeoutMs?: number;
|
|
96
106
|
}
|
|
97
107
|
/**
|
|
98
|
-
* One column descriptor in a result's schema: the column `name` and, when the
|
|
99
|
-
* engine reports it, the Iceberg storage `type` (`integer`, `string`,
|
|
100
|
-
* `timestamp`, …). R2 SQL does not always echo a schema block, so `type` is
|
|
101
|
-
* optional and `columns` may be derived from the first row's keys.
|
|
102
|
-
*/
|
|
108
|
+
* One column descriptor in a result's schema: the column `name` and, when the
|
|
109
|
+
* engine reports it, the Iceberg storage `type` (`integer`, `string`,
|
|
110
|
+
* `timestamp`, …). R2 SQL does not always echo a schema block, so `type` is
|
|
111
|
+
* optional and `columns` may be derived from the first row's keys.
|
|
112
|
+
*/
|
|
103
113
|
interface R2SqlColumn {
|
|
104
114
|
name: string;
|
|
105
115
|
type?: string;
|
|
106
116
|
}
|
|
107
117
|
/**
|
|
108
|
-
* A parsed R2 SQL result. R2 SQL returns a Cloudflare envelope
|
|
109
|
-
* (`{ success, result, errors }`); we surface the `rows` (the `result` array of
|
|
110
|
-
* column→value records), the inferred/echoed `columns`, and the `rowCount`.
|
|
111
|
-
*
|
|
112
|
-
* `Row` defaults to an open record; supply it (`from
|
|
113
|
-
* `query
|
|
114
|
-
* not `defineSchema`, so the row type is caller-declared rather than inferred.
|
|
115
|
-
*/
|
|
118
|
+
* A parsed R2 SQL result. R2 SQL returns a Cloudflare envelope
|
|
119
|
+
* (`{ success, result, errors }`); we surface the `rows` (the `result` array of
|
|
120
|
+
* column→value records), the inferred/echoed `columns`, and the `rowCount`.
|
|
121
|
+
*
|
|
122
|
+
* `Row` defaults to an open record; supply it (`from<MyRow>(…)` /
|
|
123
|
+
* `query<MyRow>(…)`) to get typed result fields — R2 SQL tables live in Iceberg,
|
|
124
|
+
* not `defineSchema`, so the row type is caller-declared rather than inferred.
|
|
125
|
+
*/
|
|
116
126
|
interface R2SqlResult<Row = Record<string, unknown>> {
|
|
117
127
|
/** Column descriptors, echoed by the engine or inferred from the first row. */
|
|
118
128
|
columns: R2SqlColumn[];
|
|
@@ -124,25 +134,25 @@ interface R2SqlResult<Row = Record<string, unknown>> {
|
|
|
124
134
|
/** Options for {@link import("./client").R2SqlClient.explain | explain}. */
|
|
125
135
|
interface R2SqlExplainOptions {
|
|
126
136
|
/**
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
137
|
+
* `"json"` runs `EXPLAIN FORMAT JSON` (structured plan); `"text"` (default)
|
|
138
|
+
* runs a plain `EXPLAIN`.
|
|
139
|
+
*/
|
|
130
140
|
format?: "json" | "text";
|
|
131
141
|
}
|
|
132
142
|
/**
|
|
133
|
-
* Executes a finished SQL string against R2 SQL and returns the parsed result.
|
|
134
|
-
* Deliberately non-generic (the row type is a caller-side concern) — the typed
|
|
135
|
-
* `run()` / `query()` boundaries cast the open result to the declared row.
|
|
136
|
-
*/
|
|
143
|
+
* Executes a finished SQL string against R2 SQL and returns the parsed result.
|
|
144
|
+
* Deliberately non-generic (the row type is a caller-side concern) — the typed
|
|
145
|
+
* `run()` / `query()` boundaries cast the open result to the declared row.
|
|
146
|
+
*/
|
|
137
147
|
type QueryExecutor = (statement: string) => Promise<R2SqlResult>;
|
|
138
148
|
/** Anything that compiles to a statement and can run — a {@link import("./builder").SelectBuilder | SelectBuilder} or {@link import("./set-operation").SetOperation | SetOperation}. */
|
|
139
149
|
interface Queryable<Row = Record<string, unknown>> {
|
|
140
150
|
/**
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
151
|
+
* True when this query carries its own `ORDER BY`/`LIMIT`, so a set
|
|
152
|
+
* operation must parenthesise it (R2 SQL rejects a bare `LIMIT` before a set
|
|
153
|
+
* operator). Read structurally by the set-operation renderer to avoid an
|
|
154
|
+
* import cycle.
|
|
155
|
+
*/
|
|
146
156
|
readonly needsWrapForSetOperation?: boolean;
|
|
147
157
|
run: () => Promise<R2SqlResult<Row>>;
|
|
148
158
|
toSQL: () => string;
|
|
@@ -155,16 +165,16 @@ interface SetMember {
|
|
|
155
165
|
query: Queryable<unknown>;
|
|
156
166
|
}
|
|
157
167
|
/**
|
|
158
|
-
* A composition of queries via set operations. Chain more operations, or apply a
|
|
159
|
-
* single `ORDER BY` / `LIMIT` to the combined result.
|
|
160
|
-
*/
|
|
168
|
+
* A composition of queries via set operations. Chain more operations, or apply a
|
|
169
|
+
* single `ORDER BY` / `LIMIT` to the combined result.
|
|
170
|
+
*/
|
|
161
171
|
declare class SetOperation<Row = Record<string, unknown>> implements Queryable<Row> {
|
|
162
172
|
/**
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
173
|
+
* Always `true`: a nested set operation must be parenthesised when it is a
|
|
174
|
+
* member of another set operation, or mixed operators mis-associate — e.g.
|
|
175
|
+
* `a.union(b.except(c))` must render `a UNION (b EXCEPT c)`, not the flat
|
|
176
|
+
* `a UNION b EXCEPT c`.
|
|
177
|
+
*/
|
|
168
178
|
readonly needsWrapForSetOperation = true;
|
|
169
179
|
private readonly exec;
|
|
170
180
|
private readonly members;
|
|
@@ -192,9 +202,9 @@ declare class SetOperation<Row = Record<string, unknown>> implements Queryable<R
|
|
|
192
202
|
private add;
|
|
193
203
|
}
|
|
194
204
|
/**
|
|
195
|
-
* A fluent `SELECT` over one Iceberg table (`namespace.table`), generic over the
|
|
196
|
-
* caller-declared `Row` result type.
|
|
197
|
-
*/
|
|
205
|
+
* A fluent `SELECT` over one Iceberg table (`namespace.table`), generic over the
|
|
206
|
+
* caller-declared `Row` result type.
|
|
207
|
+
*/
|
|
198
208
|
declare class SelectBuilder<Row = Record<string, unknown>> implements Queryable<Row> {
|
|
199
209
|
private readonly exec;
|
|
200
210
|
private readonly table;
|
|
@@ -232,9 +242,9 @@ declare class SelectBuilder<Row = Record<string, unknown>> implements Queryable<
|
|
|
232
242
|
/** Add `HAVING` condition(s) over aggregates; multiple are `AND`-ed. */
|
|
233
243
|
having(...conditions: Condition[]): this;
|
|
234
244
|
/**
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
245
|
+
* `QUALIFY` — filter on a window function without a subquery, e.g.
|
|
246
|
+
* `.qualify(fn.rowNumber().over({ partitionBy: "region", orderBy: desc("total") }).lte(3))`.
|
|
247
|
+
*/
|
|
238
248
|
qualify(condition: Condition): this;
|
|
239
249
|
/** `ORDER BY` term(s) — bare strings (ASC) or {@link import("./order").asc | asc}/{@link import("./order").desc | desc} tags. */
|
|
240
250
|
orderBy(...terms: OrderTerm[]): this;
|
|
@@ -262,18 +272,18 @@ declare class SelectBuilder<Row = Record<string, unknown>> implements Queryable<
|
|
|
262
272
|
private setOperation;
|
|
263
273
|
}
|
|
264
274
|
/**
|
|
265
|
-
* Thrown when R2 SQL responds with a non-2xx status, an `success: false`
|
|
266
|
-
* envelope, or an unparseable body; carries the HTTP `status` and the raw body
|
|
267
|
-
* for the caller to surface.
|
|
268
|
-
*/
|
|
275
|
+
* Thrown when R2 SQL responds with a non-2xx status, an `success: false`
|
|
276
|
+
* envelope, or an unparseable body; carries the HTTP `status` and the raw body
|
|
277
|
+
* for the caller to surface.
|
|
278
|
+
*/
|
|
269
279
|
declare class R2SqlError extends LunoraError {
|
|
270
280
|
constructor(status: number, body: string);
|
|
271
281
|
}
|
|
272
282
|
/**
|
|
273
|
-
* The typed R2 SQL surface bound to `ctx.r2sql` on **`ActionCtx` only**. This is
|
|
274
|
-
* the exact type the generated ctx imports as
|
|
275
|
-
* `import("@lunora/bindings/r2sql").R2SqlClient` — keep the name and shape stable.
|
|
276
|
-
*/
|
|
283
|
+
* The typed R2 SQL surface bound to `ctx.r2sql` on **`ActionCtx` only**. This is
|
|
284
|
+
* the exact type the generated ctx imports as
|
|
285
|
+
* `import("@lunora/bindings/r2sql").R2SqlClient` — keep the name and shape stable.
|
|
286
|
+
*/
|
|
277
287
|
interface R2SqlClient {
|
|
278
288
|
/** Run `DESCRIBE namespace.table` — column names and Iceberg types. */
|
|
279
289
|
describe: (table: string) => Promise<R2SqlResult>;
|
|
@@ -289,16 +299,16 @@ interface R2SqlClient {
|
|
|
289
299
|
showTables: (namespace: string) => Promise<R2SqlResult>;
|
|
290
300
|
}
|
|
291
301
|
/**
|
|
292
|
-
* Build an {@link R2SqlClient}. Each query POSTs to the bucket's
|
|
293
|
-
* `r2-sql/query/{bucket}` endpoint with the bearer token, then normalises the
|
|
294
|
-
* envelope into {@link R2SqlResult}.
|
|
295
|
-
*/
|
|
302
|
+
* Build an {@link R2SqlClient}. Each query POSTs to the bucket's
|
|
303
|
+
* `r2-sql/query/{bucket}` endpoint with the bearer token, then normalises the
|
|
304
|
+
* envelope into {@link R2SqlResult}.
|
|
305
|
+
*/
|
|
296
306
|
declare const createR2Sql: (config: R2SqlConfig) => R2SqlClient;
|
|
297
307
|
/**
|
|
298
|
-
* A windowed expression. Extends {@link Sql}, so it is usable anywhere a raw
|
|
299
|
-
* fragment is; `.as(alias)` makes a `SELECT` item, and the comparison helpers
|
|
300
|
-
* (`.lte`, `.gt`, `.between`, …) make `QUALIFY` conditions.
|
|
301
|
-
*/
|
|
308
|
+
* A windowed expression. Extends {@link Sql}, so it is usable anywhere a raw
|
|
309
|
+
* fragment is; `.as(alias)` makes a `SELECT` item, and the comparison helpers
|
|
310
|
+
* (`.lte`, `.gt`, `.between`, …) make `QUALIFY` conditions.
|
|
311
|
+
*/
|
|
302
312
|
declare class WindowExpression extends Sql {
|
|
303
313
|
/** Alias the expression — `... AS alias` — for use in a `SELECT` list. */
|
|
304
314
|
as(alias: string): Sql;
|
|
@@ -310,18 +320,18 @@ declare class WindowExpression extends Sql {
|
|
|
310
320
|
gt(value: unknown): Sql;
|
|
311
321
|
/** `expr >= value`. */
|
|
312
322
|
gte(value: unknown): Sql;
|
|
313
|
-
/** `expr
|
|
323
|
+
/** `expr < value`. */
|
|
314
324
|
lt(value: unknown): Sql;
|
|
315
|
-
/** `expr
|
|
325
|
+
/** `expr <= value`. */
|
|
316
326
|
lte(value: unknown): Sql;
|
|
317
327
|
private compare;
|
|
318
328
|
}
|
|
319
329
|
/** The `OVER (...)` window specification. */
|
|
320
330
|
interface OverSpec {
|
|
321
331
|
/**
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
332
|
+
* A raw frame clause, e.g. `"ROWS BETWEEN 2 PRECEDING AND CURRENT ROW"`.
|
|
333
|
+
* Spliced verbatim — it is keyword-only SQL, not a value.
|
|
334
|
+
*/
|
|
325
335
|
frame?: string;
|
|
326
336
|
/** `ORDER BY` term(s) within the window. */
|
|
327
337
|
orderBy?: OrderTerm | OrderTerm[];
|
|
@@ -329,9 +339,9 @@ interface OverSpec {
|
|
|
329
339
|
partitionBy?: Sql | string | (Sql | string)[];
|
|
330
340
|
}
|
|
331
341
|
/**
|
|
332
|
-
* A window function awaiting its `OVER (...)`. Call {@link WindowFunction.over |
|
|
333
|
-
* .over} to bind a window and get a {@link WindowExpression}.
|
|
334
|
-
*/
|
|
342
|
+
* A window function awaiting its `OVER (...)`. Call {@link WindowFunction.over |
|
|
343
|
+
* .over} to bind a window and get a {@link WindowExpression}.
|
|
344
|
+
*/
|
|
335
345
|
declare class WindowFunction {
|
|
336
346
|
private readonly callText;
|
|
337
347
|
constructor(callText: string);
|
|
@@ -339,13 +349,13 @@ declare class WindowFunction {
|
|
|
339
349
|
over(spec?: OverSpec): WindowExpression;
|
|
340
350
|
}
|
|
341
351
|
/**
|
|
342
|
-
* Window-function builders. Each returns a {@link WindowFunction}; chain
|
|
343
|
-
* `.over(...)` to bind the window.
|
|
344
|
-
*
|
|
345
|
-
* Ranking: `rowNumber`, `rank`, `denseRank`, `percentRank`, `cumeDist`,
|
|
346
|
-
* `ntile`. Offset/value: `lag`, `lead`, `firstValue`, `lastValue`, `nthValue`.
|
|
347
|
-
* Aggregates used as windows: `sum`, `avg`, `count`, `min`, `max`.
|
|
348
|
-
*/
|
|
352
|
+
* Window-function builders. Each returns a {@link WindowFunction}; chain
|
|
353
|
+
* `.over(...)` to bind the window.
|
|
354
|
+
*
|
|
355
|
+
* Ranking: `rowNumber`, `rank`, `denseRank`, `percentRank`, `cumeDist`,
|
|
356
|
+
* `ntile`. Offset/value: `lag`, `lead`, `firstValue`, `lastValue`, `nthValue`.
|
|
357
|
+
* Aggregates used as windows: `sum`, `avg`, `count`, `min`, `max`.
|
|
358
|
+
*/
|
|
349
359
|
declare const fn: {
|
|
350
360
|
/** `AVG(column) OVER (...)`. */
|
|
351
361
|
avg: (column: Sql | string) => WindowFunction;
|
|
@@ -380,4 +390,19 @@ declare const fn: {
|
|
|
380
390
|
/** `SUM(column) OVER (...)`. */
|
|
381
391
|
sum: (column: Sql | string) => WindowFunction;
|
|
382
392
|
};
|
|
383
|
-
export { type Condition, type OrderTerm, type OverSpec, type QueryExecutor, type Queryable, type R2SqlClient, type R2SqlColumn, type R2SqlConfig, R2SqlError, type R2SqlExplainOptions, type R2SqlResult,
|
|
393
|
+
export { type Condition, type OrderTerm, type OverSpec, type QueryExecutor, type Queryable, type R2SqlClient, type R2SqlColumn, type R2SqlConfig, R2SqlError, type R2SqlExplainOptions, type R2SqlResult,
|
|
394
|
+
/**
|
|
395
|
+
* `@lunora/bindings/r2sql` — typed, chainable R2 SQL for Lunora.
|
|
396
|
+
*
|
|
397
|
+
* Query Cloudflare [R2 SQL](https://developers.cloudflare.com/r2-sql/) — the
|
|
398
|
+
* serverless engine over Apache Iceberg tables in R2 Data Catalog — from a
|
|
399
|
+
* Lunora **action** via `ctx.r2sql`. First-class support for the 2026-06-21
|
|
400
|
+
* features: window functions (`fn.rowNumber().over(...)`), `DISTINCT` /
|
|
401
|
+
* `DISTINCT ON`, `QUALIFY`, and set operations (`.union()/.intersect()/.except()`).
|
|
402
|
+
*
|
|
403
|
+
* R2 SQL is non-deterministic external I/O with no Workers binding, so — like
|
|
404
|
+
* `ctx.sql` (Hyperdrive) — it is wired onto `ActionCtx` only and its reads are
|
|
405
|
+
* not tracked by Lunora live queries (see the `r2sql_outside_action` advisor
|
|
406
|
+
* lint). See the README and `createR2Sql` JSDoc.
|
|
407
|
+
*/
|
|
408
|
+
SelectBuilder, SetOperation, Sql, WindowExpression, WindowFunction, asc, createR2Sql, desc, fn, isSql, joinSql, lit, raw, renderOrderTerm, sql, toText };
|