@lunora/bindings 1.0.0-alpha.62 → 1.0.0-alpha.64

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.
@@ -1,6 +1,6 @@
1
- import{c as x,U as I}from"./concurrent-vRmSvRpF.mjs";const B=(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},C=(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}},v=new Set,w=s=>{v.has(s)||(v.add(s),console.warn(`[@lunora/bindings/vectors] index "${s}" syncs vectors without a namespace — in a
1
+ import{c as b,U as g}from"./concurrent-vRmSvRpF.mjs";const x=(s,i)=>{if(!i.includes("."))return s[i];let n=s;for(const d of i.split(".")){if(n===null||typeof n!="object"||Array.isArray(n))return;n=n[d]}return n},S=(s,i)=>{const n=i?.namespace,d=new Set(i?.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,c)=>{const r=await s.getByIds(t,a);return c===void 0?r:r.filter(m=>m.namespace===c)};return{deleteByIds:async(t,a,c)=>{const r=l(t,c);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,c)=>{const r=l(t,c);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 c=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:c.count,matches:c.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
2
  multi-tenant/sharded app this exposes one tenant's vectors (and any captured
3
3
  metadata) to every other tenant, since Vectorize indexes are account-global.
4
4
  Pass \`namespace\` (the shard/tenant key) on both write and query — query-side
5
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,c)=>{const n={};for(const d of c)d in s&&(n[d]=s[d]);return n},E=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:B(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 f=[],b=[...y.map(e=>async()=>{await l.deleteByIds(e.index.name,[o.id])}),...u.map(e=>async()=>{!c&&n===void 0&&w(e.index.name),await l.upsert(e.index.name,{embed:e.index.embed,id:o.id,input:e.value,metadata:e.index.metadata?S(r,e.index.metadata):void 0,namespace:n}),f.push(e.index.name)}),...a.map(([e,p])=>async()=>{!c&&n===void 0&&w(e),await l.upsert(e,{embed:p.embed,id:o.id,input:p.select(r),metadata:p.metadata?.(r),namespace:n}),f.push(e)})];try{await x(b,I,async e=>e())}catch(e){const p=o.op==="insert"?i:f;throw await Promise.allSettled(p.map(g=>l.deleteByIds(g,[o.id]))),e}}};export{C as createContextVectors,E as createVectorSyncHook};
6
+ legitimately have no tenant key suppress this via { allowSharedNamespace: true }.`))},I=(s,i)=>{const n={};for(const d of i)d in s&&(n[d]=s[d]);return n},D=s=>{const{allowSharedNamespace:i,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 c=[...t.map(e=>e.name),...a.map(([e])=>e)];if(o.op==="delete"){await Promise.all(c.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()=>{!i&&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()=>{!i&&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})})];await b(w,g,async e=>e())}};export{S as createContextVectors,D as createVectorSyncHook};
@@ -243,8 +243,8 @@ interface SchemaLike {
243
243
  * AND the identical `shardedIndexNames` — so `ctx.vectors.query`/`getByIds`/
244
244
  * `deleteByIds` are scoped without any app code changes. One consequence of
245
245
  * sharing that instance: this hook's own internal `deleteByIds` calls (on row
246
- * delete, on a cleared inline field, and on compensation after a failed
247
- * upsert) now also go through the namespace-verifying path described on
246
+ * delete and on a cleared inline field) now also go through the
247
+ * namespace-verifying path described on
248
248
  * {@link createContextVectors} — an extra `getByIds` subrequest per
249
249
  * delete-shaped write, not a behavior change (the row being deleted was
250
250
  * written under this same shard's namespace, so the verification passes).
@@ -253,18 +253,19 @@ interface SchemaLike {
253
253
  * processes a write for a sharded index, this instance IS a real per-tenant
254
254
  * shard (not root) — `namespace` here is never `undefined` for that index.
255
255
  *
256
- * Consistency — IMPORTANT: this hook runs inline within the mutation but talks
257
- * to Vectorize, which is external and non-transactional. The per-index calls
258
- * fan out; if one fails after others have already applied, the SQLite write may
259
- * roll back while the applied Vectorize mutations cannot leaving SQLite and
260
- * Vectorize diverged. We mitigate, not eliminate: upserts/deletes are
261
- * idempotent (keyed by row id), so a retry of the same write converges; and on
262
- * a fan-out failure we attempt a best-effort compensating delete of the row's
263
- * id before re-throwing from every index of the table on an insert, and on an
264
- * update from the indexes this fan-out actually wrote, since the rollback leaves
265
- * the untouched ones holding the prior row's still-correct vector. A delete
266
- * after a failed upsert can itself fail this is best-effort, the authoritative
267
- * recovery is re-running the (idempotent) write.
256
+ * Consistency — IMPORTANT: Vectorize is external and non-transactional, so this
257
+ * hook runs AFTER the mutation's transaction has committed, never inside it (the
258
+ * shard host holds it `ShardDO.deferAfterCommit`). That ordering is what stops
259
+ * a rolled-back write from leaving a vector for a row that does not exist, and a
260
+ * rolled-back delete from leaving a live row with its vector already purged.
261
+ *
262
+ * What remains is the opposite divergence, and it is the one worth having: the
263
+ * row is committed and this hook may still fail fully, or partway through a
264
+ * fan-out that already applied to some indexes. The row is then indexed in some
265
+ * indexes and not others. Nothing is compensated, deliberately: the row SURVIVES
266
+ * a failure here, so purging the indexes that did apply would turn a partially
267
+ * indexed row into an unsearchable one. Upserts and deletes are idempotent
268
+ * (keyed by row id), so re-running the same write converges.
268
269
  */
269
270
  declare const createVectorSyncHook: (options: {
270
271
  allowSharedNamespace?: boolean;
@@ -243,8 +243,8 @@ interface SchemaLike {
243
243
  * AND the identical `shardedIndexNames` — so `ctx.vectors.query`/`getByIds`/
244
244
  * `deleteByIds` are scoped without any app code changes. One consequence of
245
245
  * sharing that instance: this hook's own internal `deleteByIds` calls (on row
246
- * delete, on a cleared inline field, and on compensation after a failed
247
- * upsert) now also go through the namespace-verifying path described on
246
+ * delete and on a cleared inline field) now also go through the
247
+ * namespace-verifying path described on
248
248
  * {@link createContextVectors} — an extra `getByIds` subrequest per
249
249
  * delete-shaped write, not a behavior change (the row being deleted was
250
250
  * written under this same shard's namespace, so the verification passes).
@@ -253,18 +253,19 @@ interface SchemaLike {
253
253
  * processes a write for a sharded index, this instance IS a real per-tenant
254
254
  * shard (not root) — `namespace` here is never `undefined` for that index.
255
255
  *
256
- * Consistency — IMPORTANT: this hook runs inline within the mutation but talks
257
- * to Vectorize, which is external and non-transactional. The per-index calls
258
- * fan out; if one fails after others have already applied, the SQLite write may
259
- * roll back while the applied Vectorize mutations cannot leaving SQLite and
260
- * Vectorize diverged. We mitigate, not eliminate: upserts/deletes are
261
- * idempotent (keyed by row id), so a retry of the same write converges; and on
262
- * a fan-out failure we attempt a best-effort compensating delete of the row's
263
- * id before re-throwing from every index of the table on an insert, and on an
264
- * update from the indexes this fan-out actually wrote, since the rollback leaves
265
- * the untouched ones holding the prior row's still-correct vector. A delete
266
- * after a failed upsert can itself fail this is best-effort, the authoritative
267
- * recovery is re-running the (idempotent) write.
256
+ * Consistency — IMPORTANT: Vectorize is external and non-transactional, so this
257
+ * hook runs AFTER the mutation's transaction has committed, never inside it (the
258
+ * shard host holds it `ShardDO.deferAfterCommit`). That ordering is what stops
259
+ * a rolled-back write from leaving a vector for a row that does not exist, and a
260
+ * rolled-back delete from leaving a live row with its vector already purged.
261
+ *
262
+ * What remains is the opposite divergence, and it is the one worth having: the
263
+ * row is committed and this hook may still fail fully, or partway through a
264
+ * fan-out that already applied to some indexes. The row is then indexed in some
265
+ * indexes and not others. Nothing is compensated, deliberately: the row SURVIVES
266
+ * a failure here, so purging the indexes that did apply would turn a partially
267
+ * indexed row into an unsearchable one. Upserts and deletes are idempotent
268
+ * (keyed by row id), so re-running the same write converges.
268
269
  */
269
270
  declare const createVectorSyncHook: (options: {
270
271
  allowSharedNamespace?: boolean;
@@ -1 +1 @@
1
- import{createContextVectors as t,createVectorSyncHook as o}from"../packem_shared/createContextVectors-BcfM30uc.mjs";import{createVectorAdminIntrospector as a}from"../packem_shared/createVectorAdminIntrospector-Ct8v6PxJ.mjs";import{default as m}from"../packem_shared/createVectors-Dzv0ilKE.mjs";export{t as createContextVectors,a as createVectorAdminIntrospector,o as createVectorSyncHook,m as createVectors};
1
+ import{createContextVectors as t,createVectorSyncHook as o}from"../packem_shared/createContextVectors-e9Y-syKl.mjs";import{createVectorAdminIntrospector as a}from"../packem_shared/createVectorAdminIntrospector-Ct8v6PxJ.mjs";import{default as m}from"../packem_shared/createVectors-Dzv0ilKE.mjs";export{t as createContextVectors,a as createVectorAdminIntrospector,o as createVectorSyncHook,m as createVectors};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/bindings",
3
- "version": "1.0.0-alpha.62",
3
+ "version": "1.0.0-alpha.64",
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",
@@ -64,7 +64,7 @@
64
64
  "access": "public"
65
65
  },
66
66
  "dependencies": {
67
- "@lunora/errors": "1.0.0-alpha.38",
67
+ "@lunora/errors": "1.0.0-alpha.39",
68
68
  "@lunora/platform": "1.0.0-alpha.32"
69
69
  },
70
70
  "engines": {