@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};
|
package/dist/vectors/index.d.mts
CHANGED
|
@@ -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
|
|
361
|
-
* `select`/`metadata`) has no stable identity to
|
|
362
|
-
* changes with unrelated rebuilds of the bundle,
|
|
363
|
-
* tables for nothing — so
|
|
364
|
-
* `
|
|
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;
|
package/dist/vectors/index.d.ts
CHANGED
|
@@ -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
|
|
361
|
-
* `select`/`metadata`) has no stable identity to
|
|
362
|
-
* changes with unrelated rebuilds of the bundle,
|
|
363
|
-
* tables for nothing — so
|
|
364
|
-
* `
|
|
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;
|
package/dist/vectors/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createContextVectors as t,createVectorBackfillSync as o,createVectorSyncHook as c,vectorBackfillTargets as a}from"../packem_shared/createContextVectors-
|
|
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.
|
|
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.
|
|
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};
|