@lunora/bindings 1.0.0-alpha.71 → 1.0.0-alpha.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ import{c as w,U as v}from"./concurrent-vRmSvRpF.mjs";const M=(s,r)=>{if(!r.includes("."))return s[r];let d=s;for(const n of r.split(".")){if(d===null||typeof d!="object"||Array.isArray(d))return;d=d[n]}return d},A=(s,r)=>{const d=r?.namespace,n=new Set(r?.shardedIndexNames),c=(a,i)=>{if(i!==void 0)return i;if(n.has(a)){if(d!==void 0)return d;throw new Error(`@lunora/bindings/vectors: index "${a}" 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.`)}},e=r?.deferAfterCommit,l=async(a,i,m)=>{await s.upsert(a,{embed:i.embed,id:i.id,input:i.input,metadata:i.metadata,namespace:m})},f=async(a,i)=>{await l(a,i,c(a,i.namespace))},o=e===void 0?f:async(a,i)=>{const m=c(a,i.namespace);await e(async()=>l(a,i,m))},t=async(a,i,m)=>{const u=await s.getByIds(a,i);return m===void 0?u:u.filter(h=>h.namespace===m)};return{deleteByIds:async(a,i,m)=>{const u=c(a,m);if(u===void 0){await s.deleteByIds(a,i);return}const h=await t(a,i,u);h.length!==0&&await s.deleteByIds(a,h.map(p=>p.id))},getByIds:async(a,i,m)=>{const u=c(a,m);return(await t(a,i,u)).map(p=>({id:p.id,metadata:p.metadata,namespace:p.namespace,values:[...p.values]}))},query:async(a,i)=>{const m=await s.query(a,{embed:i.embed,filter:i.filter,input:i.input,namespace:c(a,i.namespace),returnMetadata:i.returnMetadata??"indexed",topK:i.topK,vector:i.vector});return{count:m.count,matches:m.matches.map(u=>({id:u.id,metadata:u.metadata,score:u.score}))}},upsert:o,upsertNow:f}},y=new Set,b=s=>{y.has(s)||(y.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 }.`))},S=(s,r)=>{const d={};for(const n of r)n in s&&(d[n]=s[n]);return d},g=(s,r)=>{const d=s.tables[r.table],n=d?.vectorIndexes??[],c=Object.entries(s.vectorIndexes).filter(([,t])=>t.table===r.table);if(n.length===0&&c.length===0)return;const e=d?.softDeleteMode?.field,l=e!==void 0&&r.doc?.[e]!==void 0&&r.doc[e]!==null;if(r.op==="delete"||l)return{deletes:[...n.map(t=>t.name),...c.map(([t])=>t)],upserts:[]};const f=r.doc;if(!f)return;const o={deletes:[],upserts:[]};for(const t of n){const a=M(f,t.field);if(a==null)o.deletes.push(t.name);else if(typeof a=="string")o.upserts.push({embed:t.embed,input:a,metadata:t.metadata?S(f,t.metadata):void 0,name:t.name});else throw new TypeError(`@lunora/bindings/vectors: inline index "${t.name}" expects a string source at "${t.field}" on table "${r.table}" (got ${typeof a}); use a standalone defineVectorIndex with a select() to derive text from non-string columns`)}for(const[t,a]of c)o.upserts.push({embed:a.embed,input:a.select(f),metadata:a.metadata?.(f),name:t});return o},D=s=>{const{allowSharedNamespace:r,namespace:d,schema:n,vectors:c}=s;return async e=>{const l=g(n,e);if(!l)return;const f=[...l.deletes.map(o=>async()=>{await c.deleteByIds(o,[e.id])}),...l.upserts.map(o=>async()=>{!r&&d===void 0&&b(o.name),await c.upsertNow(o.name,{embed:o.embed,id:e.id,input:o.input,metadata:o.metadata,namespace:d})})];await w(f,v,async o=>o())}},I=1e3,x=(s,r)=>{const d=[];for(let n=0;n<s.length;n+=r)d.push(s.slice(n,n+r));return d},k=async(s,r)=>{const d=await w(s,v,async e=>{try{return{item:e,ok:!0,value:await r(e)}}catch(l){return{error:l,item:e,ok:!1}}}),n=d.flatMap(e=>e.ok?[{item:e.item,value:e.value}]:[]),c=d.flatMap(e=>e.ok?[]:[{error:e.error,item:e.item}]);if(s.length>=2&&n.length===0)throw c[0]?.error;return{failed:c,ok:n}},B=(s,r,d,n)=>{const c=new Map,e=[];for(const{doc:l,id:f}of d){let o;try{o=g(s,{doc:l,id:f,op:"update",table:r})}catch(t){n.set(f,t);continue}for(const t of o?.deletes??[])c.set(t,[...c.get(t)??[],f]);for(const t of o?.upserts??[])e.push({id:f,upsert:t})}return{deletes:c,pending:e}},C=async(s,r,d,n)=>{const{allowSharedNamespace:c,namespace:e,upsertMany:l,vectors:f}=s;!c&&e===void 0&&b(r);const o=d.map(({id:t,upsert:a,values:i})=>({embed:()=>i,id:t,input:a.input,metadata:a.metadata,namespace:e}));for(const t of x(o,I))try{await l(r,t)}catch{const a=await k(t,async i=>f.upsertNow(r,i));for(const{error:i,item:m}of a.failed)n.set(m.id,i)}},E=s=>async(r,d)=>{const n=new Map,{deletes:c,pending:e}=B(s.schema,r,d,n),l=await k(e,async({upsert:o})=>o.embed(o.input)),f=new Map;for(const{error:o,item:t}of l.failed)n.set(t.id,o);for(const{item:o,value:t}of l.ok)f.set(o.upsert.name,[...f.get(o.upsert.name)??[],{...o,values:t}]);for(const[o,t]of c)for(const a of x(t,I))await s.vectors.deleteByIds(o,a);for(const[o,t]of f)await C(s,o,t,n);return[...n].map(([o,t])=>({error:t,id:o}))},V=s=>{const r=new Map,d=(n,c,e)=>{const l=e===void 0?c:[...c,e];r.set(n,[...r.get(n)??[],JSON.stringify(l)])};for(const[n,c]of Object.entries(s.tables))for(const e of c.vectorIndexes??[])d(n,[e.name,e.field,e.dimensions,e.metric,e.metadata??[]],e.model);for(const[n,c]of Object.entries(s.vectorIndexes))d(c.table,[n,"(select)",c.dimensions,c.metric],c.model);return[...r].map(([n,c])=>({profile:c.toSorted((e,l)=>e.localeCompare(l)).join("|"),table:n}))};export{A as createContextVectors,E as createVectorBackfillSync,D as createVectorSyncHook,V as vectorBackfillTargets};
@@ -219,6 +219,8 @@ interface TableVectorIndexLike {
219
219
  field: string;
220
220
  metadata?: ReadonlyArray<string>;
221
221
  metric?: string;
222
+ /** Declared identifier of what `embed` produces; part of the backfill fingerprint. */
223
+ model?: string;
222
224
  name: string;
223
225
  }
224
226
  interface TableDefinitionLike {
@@ -234,6 +236,8 @@ interface VectorIndexDefinitionLike {
234
236
  embed: VectorEmbedderLike;
235
237
  metadata?: (row: Record<string, unknown>) => Record<string, unknown>;
236
238
  metric?: string;
239
+ /** Declared identifier of what `embed` produces; part of the backfill fingerprint. */
240
+ model?: string;
237
241
  select: (row: Record<string, unknown>) => string;
238
242
  table: string;
239
243
  }
@@ -357,11 +361,15 @@ declare const createVectorBackfillSync: (options: BackfillSyncOptions) => Vector
357
361
  * backfill, which re-walks a table whose fingerprint changed.
358
362
  *
359
363
  * Covers what the schema can see: index names, the inline source field,
360
- * dimensions, metric and inline metadata fields. A function (`embed`, a Shape B
361
- * `select`/`metadata`) has no stable identity to fingerprint — its source text
362
- * changes with unrelated rebuilds of the bundle, which would re-embed whole
363
- * tables for nothing — so changing one is announced by calling the backfill with
364
- * `restart: true`.
364
+ * dimensions, metric, inline metadata fields and the declared `model`. A
365
+ * function (`embed`, a Shape B `select`/`metadata`) has no stable identity to
366
+ * fingerprint — its source text changes with unrelated rebuilds of the bundle,
367
+ * which would re-embed whole tables for nothing — so the declared `model` string
368
+ * stands in for `embed`, and any other change is announced by calling the
369
+ * backfill with `restart: true`.
370
+ *
371
+ * `model` joins a descriptor only when declared, so an index without one keeps
372
+ * the fingerprint it was recorded under and is not re-embedded for it.
365
373
  */
366
374
  declare const vectorBackfillTargets: (schema: SchemaLike) => {
367
375
  profile: string;
@@ -219,6 +219,8 @@ interface TableVectorIndexLike {
219
219
  field: string;
220
220
  metadata?: ReadonlyArray<string>;
221
221
  metric?: string;
222
+ /** Declared identifier of what `embed` produces; part of the backfill fingerprint. */
223
+ model?: string;
222
224
  name: string;
223
225
  }
224
226
  interface TableDefinitionLike {
@@ -234,6 +236,8 @@ interface VectorIndexDefinitionLike {
234
236
  embed: VectorEmbedderLike;
235
237
  metadata?: (row: Record<string, unknown>) => Record<string, unknown>;
236
238
  metric?: string;
239
+ /** Declared identifier of what `embed` produces; part of the backfill fingerprint. */
240
+ model?: string;
237
241
  select: (row: Record<string, unknown>) => string;
238
242
  table: string;
239
243
  }
@@ -357,11 +361,15 @@ declare const createVectorBackfillSync: (options: BackfillSyncOptions) => Vector
357
361
  * backfill, which re-walks a table whose fingerprint changed.
358
362
  *
359
363
  * Covers what the schema can see: index names, the inline source field,
360
- * dimensions, metric and inline metadata fields. A function (`embed`, a Shape B
361
- * `select`/`metadata`) has no stable identity to fingerprint — its source text
362
- * changes with unrelated rebuilds of the bundle, which would re-embed whole
363
- * tables for nothing — so changing one is announced by calling the backfill with
364
- * `restart: true`.
364
+ * dimensions, metric, inline metadata fields and the declared `model`. A
365
+ * function (`embed`, a Shape B `select`/`metadata`) has no stable identity to
366
+ * fingerprint — its source text changes with unrelated rebuilds of the bundle,
367
+ * which would re-embed whole tables for nothing — so the declared `model` string
368
+ * stands in for `embed`, and any other change is announced by calling the
369
+ * backfill with `restart: true`.
370
+ *
371
+ * `model` joins a descriptor only when declared, so an index without one keeps
372
+ * the fingerprint it was recorded under and is not re-embedded for it.
365
373
  */
366
374
  declare const vectorBackfillTargets: (schema: SchemaLike) => {
367
375
  profile: string;
@@ -1 +1 @@
1
- import{createContextVectors as t,createVectorBackfillSync as o,createVectorSyncHook as c,vectorBackfillTargets as a}from"../packem_shared/createContextVectors-Bz5Me4s6.mjs";import{createVectorAdminIntrospector as l}from"../packem_shared/createVectorAdminIntrospector-Ct8v6PxJ.mjs";import{default as s}from"../packem_shared/createVectors-Dzv0ilKE.mjs";export{t as createContextVectors,l as createVectorAdminIntrospector,o as createVectorBackfillSync,c as createVectorSyncHook,s as createVectors,a as vectorBackfillTargets};
1
+ import{createContextVectors as t,createVectorBackfillSync as o,createVectorSyncHook as c,vectorBackfillTargets as a}from"../packem_shared/createContextVectors-fqM-VsVd.mjs";import{createVectorAdminIntrospector as l}from"../packem_shared/createVectorAdminIntrospector-Ct8v6PxJ.mjs";import{default as s}from"../packem_shared/createVectors-Dzv0ilKE.mjs";export{t as createContextVectors,l as createVectorAdminIntrospector,o as createVectorBackfillSync,c as createVectorSyncHook,s as createVectors,a as vectorBackfillTargets};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/bindings",
3
- "version": "1.0.0-alpha.71",
3
+ "version": "1.0.0-alpha.72",
4
4
  "description": "Lightweight Cloudflare binding helpers for Lunora — ctx.kv, ctx.images, ctx.analytics, ctx.pipelines, ctx.vectors, ctx.r2sql — one install, per-binding subpaths",
5
5
  "keywords": [
6
6
  "analytics",
@@ -65,7 +65,7 @@
65
65
  },
66
66
  "dependencies": {
67
67
  "@lunora/errors": "1.0.0-alpha.41",
68
- "@lunora/platform": "1.0.0-alpha.36"
68
+ "@lunora/platform": "1.0.0-alpha.37"
69
69
  },
70
70
  "engines": {
71
71
  "node": "^22.15.0 || >=24.11.0"
@@ -1,6 +0,0 @@
1
- import{c as w,U as v}from"./concurrent-vRmSvRpF.mjs";const M=(s,r)=>{if(!r.includes("."))return s[r];let i=s;for(const a of r.split(".")){if(i===null||typeof i!="object"||Array.isArray(i))return;i=i[a]}return i},A=(s,r)=>{const i=r?.namespace,a=new Set(r?.shardedIndexNames),d=(t,c)=>{if(c!==void 0)return c;if(a.has(t)){if(i!==void 0)return i;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.`)}},n=r?.deferAfterCommit,f=async(t,c,u)=>{await s.upsert(t,{embed:c.embed,id:c.id,input:c.input,metadata:c.metadata,namespace:u})},l=async(t,c)=>{await f(t,c,d(t,c.namespace))},o=n===void 0?l:async(t,c)=>{const u=d(t,c.namespace);await n(async()=>f(t,c,u))},e=async(t,c,u)=>{const m=await s.getByIds(t,c);return u===void 0?m:m.filter(h=>h.namespace===u)};return{deleteByIds:async(t,c,u)=>{const m=d(t,u);if(m===void 0){await s.deleteByIds(t,c);return}const h=await e(t,c,m);h.length!==0&&await s.deleteByIds(t,h.map(p=>p.id))},getByIds:async(t,c,u)=>{const m=d(t,u);return(await e(t,c,m)).map(p=>({id:p.id,metadata:p.metadata,namespace:p.namespace,values:[...p.values]}))},query:async(t,c)=>{const u=await s.query(t,{embed:c.embed,filter:c.filter,input:c.input,namespace:d(t,c.namespace),returnMetadata:c.returnMetadata??"indexed",topK:c.topK,vector:c.vector});return{count:u.count,matches:u.matches.map(m=>({id:m.id,metadata:m.metadata,score:m.score}))}},upsert:o,upsertNow:l}},y=new Set,b=s=>{y.has(s)||(y.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 }.`))},S=(s,r)=>{const i={};for(const a of r)a in s&&(i[a]=s[a]);return i},g=(s,r)=>{const i=s.tables[r.table],a=i?.vectorIndexes??[],d=Object.entries(s.vectorIndexes).filter(([,e])=>e.table===r.table);if(a.length===0&&d.length===0)return;const n=i?.softDeleteMode?.field,f=n!==void 0&&r.doc?.[n]!==void 0&&r.doc[n]!==null;if(r.op==="delete"||f)return{deletes:[...a.map(e=>e.name),...d.map(([e])=>e)],upserts:[]};const l=r.doc;if(!l)return;const o={deletes:[],upserts:[]};for(const e of a){const t=M(l,e.field);if(t==null)o.deletes.push(e.name);else if(typeof t=="string")o.upserts.push({embed:e.embed,input:t,metadata:e.metadata?S(l,e.metadata):void 0,name:e.name});else throw new TypeError(`@lunora/bindings/vectors: inline index "${e.name}" expects a string source at "${e.field}" on table "${r.table}" (got ${typeof t}); use a standalone defineVectorIndex with a select() to derive text from non-string columns`)}for(const[e,t]of d)o.upserts.push({embed:t.embed,input:t.select(l),metadata:t.metadata?.(l),name:e});return o},D=s=>{const{allowSharedNamespace:r,namespace:i,schema:a,vectors:d}=s;return async n=>{const f=g(a,n);if(!f)return;const l=[...f.deletes.map(o=>async()=>{await d.deleteByIds(o,[n.id])}),...f.upserts.map(o=>async()=>{!r&&i===void 0&&b(o.name),await d.upsertNow(o.name,{embed:o.embed,id:n.id,input:o.input,metadata:o.metadata,namespace:i})})];await w(l,v,async o=>o())}},I=1e3,x=(s,r)=>{const i=[];for(let a=0;a<s.length;a+=r)i.push(s.slice(a,a+r));return i},k=async(s,r)=>{const i=await w(s,v,async n=>{try{return{item:n,ok:!0,value:await r(n)}}catch(f){return{error:f,item:n,ok:!1}}}),a=i.flatMap(n=>n.ok?[{item:n.item,value:n.value}]:[]),d=i.flatMap(n=>n.ok?[]:[{error:n.error,item:n.item}]);if(s.length>=2&&a.length===0)throw d[0]?.error;return{failed:d,ok:a}},B=(s,r,i,a)=>{const d=new Map,n=[];for(const{doc:f,id:l}of i){let o;try{o=g(s,{doc:f,id:l,op:"update",table:r})}catch(e){a.set(l,e);continue}for(const e of o?.deletes??[])d.set(e,[...d.get(e)??[],l]);for(const e of o?.upserts??[])n.push({id:l,upsert:e})}return{deletes:d,pending:n}},C=async(s,r,i,a)=>{const{allowSharedNamespace:d,namespace:n,upsertMany:f,vectors:l}=s;!d&&n===void 0&&b(r);const o=i.map(({id:e,upsert:t,values:c})=>({embed:()=>c,id:e,input:t.input,metadata:t.metadata,namespace:n}));for(const e of x(o,I))try{await f(r,e)}catch{const t=await k(e,async c=>l.upsertNow(r,c));for(const{error:c,item:u}of t.failed)a.set(u.id,c)}},E=s=>async(r,i)=>{const a=new Map,{deletes:d,pending:n}=B(s.schema,r,i,a),f=await k(n,async({upsert:o})=>o.embed(o.input)),l=new Map;for(const{error:o,item:e}of f.failed)a.set(e.id,o);for(const{item:o,value:e}of f.ok)l.set(o.upsert.name,[...l.get(o.upsert.name)??[],{...o,values:e}]);for(const[o,e]of d)for(const t of x(e,I))await s.vectors.deleteByIds(o,t);for(const[o,e]of l)await C(s,o,e,a);return[...a].map(([o,e])=>({error:e,id:o}))},V=s=>{const r=new Map,i=(a,d)=>{r.set(a,[...r.get(a)??[],JSON.stringify(d)])};for(const[a,d]of Object.entries(s.tables))for(const n of d.vectorIndexes??[])i(a,[n.name,n.field,n.dimensions,n.metric,n.metadata??[]]);for(const[a,d]of Object.entries(s.vectorIndexes))i(d.table,[a,"(select)",d.dimensions,d.metric]);return[...r].map(([a,d])=>({profile:d.toSorted((n,f)=>n.localeCompare(f)).join("|"),table:a}))};export{A as createContextVectors,E as createVectorBackfillSync,D as createVectorSyncHook,V as vectorBackfillTargets};