palgres 0.10.2 → 0.11.1
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/dist/chunks/{K37PKOI6.js → 35ZESSWK.js} +2 -2
- package/dist/chunks/BVMZDED5.js +5 -0
- package/dist/chunks/GE6QDZGJ.js +2 -0
- package/dist/dsl.js +2 -2
- package/dist/index.js +88 -84
- package/dist/testing.js +2 -2
- package/package.json +1 -1
- package/types/palgres/src/dsl/columns.d.ts +28 -0
- package/types/palgres/src/fields.d.ts +63 -0
- package/types/palgres/src/index.d.ts +1 -0
- package/types/palgres/src/migrate.d.ts +30 -0
- package/types/palgres/src/model.d.ts +12 -0
- package/types/palgres/src/plan.d.ts +27 -4
- package/types/palgres/src/query/catalog.d.ts +24 -1
- package/types/palgres/src/query/index.d.ts +23 -0
- package/types/palgres/src/query/typed.d.ts +25 -5
- package/dist/chunks/4JEMNLS3.js +0 -2
- package/dist/chunks/Q53F7ZFX.js +0 -5
package/dist/testing.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! palgres 0.
|
|
2
|
-
import{$ as wn,D as U,E as fn,F as Y,J as Z,K as V,L as z,M as dn,O as P,W as _,aa as $n,e as cn,g as ln}from"./chunks/K37PKOI6.js";import"./chunks/Q53F7ZFX.js";import{randomUUID as Rn}from"node:crypto";var On=/^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}(:\d{2}(\.\d+)?)?)?(Z|[+-]\d{2}(:?\d{2})?)?$/,bn=/^([+-]?)(\d*)(?:\.(\d*))?$/;function q(e){if(e instanceof Date)return e.getTime();if(typeof e=="string"&&On.test(e)){let t=Date.parse(e);return Number.isNaN(t)?null:t}return null}function B(e){if(typeof e=="number")return Number.isFinite(e)?B(String(e)):null;if(typeof e!="string"||/e/i.test(e))return null;let t=bn.exec(e.trim());if(t===null||t[2]===""&&(t[3]??"")==="")return null;let a=t[3]??"";return{unit:BigInt(`${t[1]==="-"?"-":""}${t[2]===""?"0":t[2]}${a}`),scale:a.length}}function K(e,t){return e.unit*10n**BigInt(t-e.scale)}function tn(e,t){if(e==null||t===null||t===void 0)return null;let a=q(e),c=q(t);if(a!==null&&c!==null)return Math.sign(a-c);let w=B(e),f=B(t);if(w!==null&&f!==null){let d=Math.max(w.scale,f.scale),h=K(w,d)-K(f,d);return h===0n?0:h<0n?-1:1}return typeof e==typeof t&&(typeof e=="string"||typeof e=="boolean"||typeof e=="number")?e<t?-1:e>t?1:0:null}function N(e,t){return e==null||t===null||t===void 0?!1:e instanceof Date||t instanceof Date?q(e)!==null&&q(e)===q(t):Array.isArray(e)&&Array.isArray(t)?e.length===t.length&&e.every((a,c)=>N(a,t[c])):e===t}function H(e){let t=((e?.days??0)*86400+(e?.hours??0)*3600+(e?.minutes??0)*60+(e?.seconds??0))*1e3;return new Date(Date.now()-t).toISOString()}function v(e,t,a){if(e==null)return null;if(typeof e=="number"&&typeof t=="number")return e+a*t;let c=B(e),w=B(t);if(c===null||w===null)throw new Error(`increment()/decrement() on a value that is not a number (${JSON.stringify(e)}): Postgres refuses it ("operator does not exist")`);let f=Math.max(c.scale,w.scale),d=K(c,f)+BigInt(a)*K(w,f);if(f===0)return typeof e=="number"?Number(d):d.toString();let h=d<0n,R=(h?-d:d).toString().padStart(f+1,"0"),k=`${h?"-":""}${R.slice(0,-f)}.${R.slice(-f)}`;return typeof e=="number"?Number(k):k}function kn(e,t){let a=A=>{let C=(A.unit<0n?-A.unit:A.unit).toString().padStart(A.scale+1,"0"),J=C.slice(0,C.length-A.scale),n=C.slice(C.length-A.scale),r=J.padStart(Math.ceil(J.length/4)*4,"0").match(/.{4}/g)??[],s=n.padEnd(Math.ceil(n.length/4)*4,"0").match(/.{4}/g)??[],i=[...r.map((u,l)=>[r.length-1-l,Number(u)]),...s.map((u,l)=>[-1-l,Number(u)])].find(([,u])=>u!==0);return i?[i[0],i[1]]:[0,0]},[c,w]=a(e),[f,d]=a(t),h=c-f-(w<=d?1:0),R=Math.min(Math.max(16-h*4,e.scale,t.scale,0),1e3),k=e.unit*10n**BigInt(R+t.scale),E=t.unit*10n**BigInt(e.scale),T=k/E,x=k%E;2n*(x<0n?-x:x)>=(E<0n?-E:E)&&(T+=k<0n!=E<0n?-1n:1n);let I=T<0n,W=(I?-T:T).toString().padStart(R+1,"0");return`${I?"-":""}${R===0?W:`${W.slice(0,-R)}.${W.slice(-R)}`}`}var $=e=>structuredClone(e);function rn(e,t,a){if(fn(a))throw new Error(`${e}(${t}): the fake database runs no SQL, so a sqlFragment cannot be evaluated \u2014 write the filter in the filter language (gt, gte, lt, lte, neq, in, contains, isNull, OR, AND, NOT), or test this against Postgres`);if(!(a===null||typeof a!="object"))for(let[c,w]of Object.entries(a))if(c==="OR"||c==="AND")for(let f of Array.isArray(w)?w:[])rn(e,t,f);else c==="NOT"&&rn(e,t,w)}function D(e,t,a){return cn(new ln({fields:[{field:t,message:a}]},`${e}: ${t} ${a}`))}function on(e,t,a="where"){for(let[c,w]of Object.entries(t))if(c==="OR"||c==="AND"){let f=w;if(c==="AND"&&f.length===0)throw D(e,`${a}.AND`,"is an empty list \u2014 no condition at all. Leave it out, or name a condition in it.");f.forEach((d,h)=>{if(Object.keys(d).length===0)throw D(e,`${a}.${c}[${h}]`,"holds no condition \u2014 a branch that matches every row. Name a condition in it, or leave it out.");on(e,d,`${a}.${c}[${h}]`)})}else if(c==="NOT"){if(Array.isArray(w))throw D(e,`${a}.NOT`,"is a list \u2014 NOT takes one filter object. For several conditions, NOT: { AND: [...] } or NOT: { OR: [...] }.");if(Object.keys(w).length===0)throw D(e,`${a}.NOT`,"holds no condition \u2014 NOT of nothing is not a filter. Name the condition to exclude.");on(e,w,`${a}.NOT`)}else if(c==="has"&&Object.keys(w??{}).length===0)throw D(e,`${a}.has`,"names no relation \u2014 no condition. Name one: has: { <relation>: { \u2026 } }.")}function F(e,t){if(Object.keys(t).length===0)throw D(e,"where",`holds no condition, so ${e} would reach EVERY row of the table. Name a condition.`)}function sn(e){let t=!1;for(let a of e){if(a===!1)return!1;a===null&&(t=!0)}return t?null:!0}function mn(e){let t=!1;for(let a of e){if(a===!0)return!0;a===null&&(t=!0)}return t?null:!1}var j=e=>e==null;function L(e,t,a,c){return sn(Object.entries(c).map(([w,f])=>{if(w==="OR")return mn(f.map(d=>L(e,t,a,d)));if(w==="AND")return sn(f.map(d=>L(e,t,a,d)));if(w==="NOT"){let d=L(e,t,a,f);return d===null?null:!d}if(w==="has")throw new Error(`${e}(${t}): the fake database has no relations, so a \`has\` filter cannot be decided for a row \u2014 read the related table first and filter by its ids ({ id: { in: [\u2026] } }), or test this against Postgres`);return xn(e,t,a,w,f)}))}var gn=["neq","gt","gte","lt","lte"];function Q(e,t){return j(e)||j(t)?null:N(e,t)}function xn(e,t,a,c,w){let f=a[c];return w===null?j(f):U(w)?Q(f,a[w.$col]):V(w)?Q(f,H(_(w)?.minus)):typeof w=="object"&&!Array.isArray(w)&&!(w instanceof Date)?sn(Object.entries(w).map(([d,h])=>{if(V(h)){if(!gn.includes(d))throw new Error(`${e}(${t}): where.${c}.${d} cannot take now()`);return G(d,f,H(_(h)?.minus))}if(Z(h))throw new Error(`${e}(${t}): where.${c}.${d} was given increment()/decrement() \u2014 a write, not a value to compare with`);if(U(h)){if(!gn.includes(d))throw new Error(`${e}(${t}): where.${c}.${d} cannot take col()`);return G(d,f,a[h.$col])}switch(d){case"in":{if(!Array.isArray(h))throw new Error(`${e}(${t}): where.${c}.in is a list`);return mn(h.map(R=>Q(f,R)))}case"neq":return h===null?!j(f):G(d,f,h);case"gt":case"gte":case"lt":case"lte":return G(d,f,h);case"isNull":if(typeof h!="boolean")throw new Error(`${e}(${t}): where.${c}.isNull is true or false`);return h?j(f):!j(f);case"overlaps":if(!Array.isArray(h))throw new Error(`${e}(${t}): where.${c}.overlaps is a list`);if(f!=null&&!Array.isArray(f))throw new Error(`${e}(${t}): where.${c}: "overlaps" is an array column's operator`);return j(f)?null:h.some(R=>f.some(k=>N(R,k)));case"contains":case"icontains":case"startsWith":case"endsWith":{if(d==="contains"&&Array.isArray(h))return j(f)?null:Array.isArray(f)&&h.every(R=>f.some(k=>N(R,k)));if(typeof h!="string")throw new Error(`${e}(${t}): where.${c}.${d} is text`);return j(f)?null:typeof f!="string"?!1:d==="contains"?f.includes(h):d==="icontains"?f.toLowerCase().includes(h.toLowerCase()):d==="startsWith"?f.startsWith(h):f.endsWith(h)}default:throw new Error(`${e}(${t}): where.${c} has no operator "${d}"`)}})):Q(f,w)}function G(e,t,a){if(j(t)||j(a))return null;if(e==="neq")return!N(t,a);let c=tn(t,a);return c===null?!1:e==="gt"?c>0:e==="gte"?c>=0:e==="lt"?c<0:c<=0}function En(){let e=new Map,t=new Map,a=new Map,c=new Map,w=[],f=n=>{let r=e.get(n);return r||e.set(n,r=[]),r},d=(n,r,s)=>{let o=n.get(r);o?o.push(s):n.set(r,[s])},h=n=>n.id===void 0?{id:Rn(),...n}:{...n},R=()=>{let n={store:new Map([...e].map(([r,s])=>[r,s.map($)])),inserted:hn(t),updated:hn(a),deleted:new Map([...c].map(([r,s])=>[r,[...s]]))};return()=>{e=n.store,t=n.inserted,a=n.updated,c=n.deleted}};function k(n,r,s){let o={};for(let[i,u]of Object.entries(s)){let l=_(u);if(l!==null&&l.fn==="now")o[i]=H(l.minus);else{if(l!==null)throw new Error(`${n}(${r}): "${i}" was given ${l.fn==="inc"?"increment()":"decrement()"} \u2014 the row does not exist yet, so there is no current value to change`);o[i]=u}}return P(n,r,Object.keys(o),o),z(n,r,Object.keys(o),o),o}function E(n,r,s,o){P(n,r,Object.keys(o),o);let i={};for(let[u,l]of Object.entries(o)){let g=_(l);g!==null&&g.fn==="now"?i[u]=H(g.minus):g!==null?i[u]=v(s[u],g.by,g.fn==="inc"?1:-1):i[u]=l}return z(n,r,Object.keys(i),i),i}function T(n,r,s){let o=h(k(n,r,s));return f(r).push(o),d(t,r,$(o)),$(o)}function x(n,r,s){return dn(n,r,s),rn(n,r,s),s!==void 0&&on(n,s),(e.get(r)??[]).filter(o=>s===void 0||L(n,r,o,s)===!0)}let I={diagnostics:()=>null,async query(n,r=[]){return w.push({sql:n,params:r}),[]},async findUnique(){throw new Error("the fake database knows no table's declared unique keys \u2014 find the row with findMany, or test this against Postgres")},async page(){throw new Error("the fake database cannot reproduce Postgres' cursor ordering, collation or row-level security \u2014 test pagination against Postgres")},async search(n){throw new Error(`search(${n}): the fake database has neither embeddings nor full text \u2014 test search against Postgres`)},async findById(n,r){let s=(e.get(n)??[]).find(o=>o.id===r);return s?$(s):null},async findMany(n,r,s){let{limit:o,offset:i}=s??{};if(o!==void 0&&(!Number.isInteger(o)||o<0))throw new Error(`findMany(${n}): limit is a whole number, 0 or more (got ${String(o)})`);if(i!==void 0){if(!Number.isInteger(i)||i<0)throw new Error(`findMany(${n}): offset is a whole number, 0 or more (got ${String(i)})`);if(o===void 0)throw new Error(`findMany(${n}): offset comes with limit \u2014 an offset into an unbounded result is not a page`)}if(s?.with!==void 0&&Object.keys(s.with).length>0)throw new Error(`findMany(${n}): the fake database has no relations, so \`with\` cannot be read \u2014 seed and query the related table on its own, or test this against Postgres`);let u=x("findMany",n,r),l=s?.orderBy===void 0?[]:Array.isArray(s.orderBy)?s.orderBy:[s.orderBy];l.length>0&&(u=[...u].sort((y,p)=>{for(let O of l){let b=O.direction==="desc"?-1:1,M=y[O.column],S=p[O.column],X=M==null,an=S==null;if(X||an){if(X&&an)continue;let pn=O.nulls===void 0?b===-1:O.nulls==="first";return(X?1:-1)*(pn?-1:1)}let un=tn(M,S)??(String(M)<String(S)?-1:String(M)>String(S)?1:0);if(un!==0)return un*b}return 0}));let g=o===void 0?u:u.slice(i??0,(i??0)+o),m=s?.select;return g.map(y=>m===void 0||m.length===0?$(y):Object.fromEntries(m.map(p=>[p,structuredClone(y[p])])))},async count(n,r={}){return x("count",n,r).length},async aggregate(n,r){let s=x("aggregate",n,r.where),o=r.groupBy??[],i=l=>{let g={},m=y=>l.map(p=>p[y]).filter(p=>p!=null);for(let y of r.sum??[]){let p=m(y);(g.sum??={})[y]=p.length===0?null:String(p.reduce((O,b)=>v(O,b,1),"0"))}for(let y of r.avg??[]){let p=m(y),O=p.some(b=>typeof b=="number"&&!Number.isInteger(b));(g.avg??={})[y]=p.length===0?null:O?p.reduce((b,M)=>b+M,0)/p.length:kn(B(p.reduce((b,M)=>v(b,M,1),"0")),{unit:BigInt(p.length),scale:0})}for(let[y,p]of[["min",-1],["max",1]])for(let O of r[y]??[]){let b=m(O);(g[y]??={})[O]=b.length===0?null:b.reduce((M,S)=>(tn(S,M)??0)*p>0?S:M)}return r.count===!0?g.count=l.length:r.count!==void 0&&r.count!==!1&&(g.count=Object.fromEntries(Object.entries(r.count).map(([y,p])=>[y,l.filter(O=>L("aggregate",n,O,p)===!0).length]))),g};if(o.length===0)return i(s);let u=new Map;for(let l of s){let g=JSON.stringify(o.map(m=>l[m]));u.set(g,[...u.get(g)??[],l])}return[...u.values()].map(l=>({...i(l),...Object.fromEntries(o.map(g=>[g,l[0][g]]))}))},async insert(n,r){return T("insert",n,r)},insertMany:(async(n,r,s)=>{if(wn(s),s?.onConflict?.length||s?.action!==void 0)throw new Error(`insertMany(${n}): the fake database knows no unique constraint to collide with \u2014 test onConflict against Postgres`);if(r.length===0)return s?.returning===!1?0:[];let o=Object.keys(r[0]);r.forEach((u,l)=>{let g=o.filter(y=>!(y in u)),m=Object.keys(u).filter(y=>!o.includes(y));if(g.length>0||m.length>0)throw new Error(`insertMany(${n}): row ${l} has not the first row's columns${g.length?` (missing ${g.join(", ")})`:""}${m.length?` (extra ${m.join(", ")})`:""} \u2014 write null for an empty one, or insert rows of another shape in a call of their own`)});let i=r.map(u=>T("insertMany",n,u));return s?.returning===!1?i.length:i}),async put(n,r,s){if(!s?.onConflict?.length)throw new Error(`put(${n}): onConflict names at least one column`);let o=k("put",n,r),i=f(n).find(u=>s.onConflict.every(l=>N(u[l],o[l])));if(!i)return T("put",n,r);for(let[u,l]of Object.entries(o))s.onConflict.includes(u)||(i[u]=l);return d(a,n,$(i)),$(i)},async update(n,r,s){let o=e.get(n)??[],i=o.findIndex(u=>u.id===r);return i<0?(P("update",n,Object.keys(s),s),null):(o[i]={...o[i],...E("update",n,o[i],s)},d(a,n,$(o[i])),$(o[i]))},async updateMany(n,r,s,o){F("updateMany",r);let i=x("updateMany",n,r),u=f(n),l=[];for(let g of i){let m=u.indexOf(g);u[m]={...g,...E("updateMany",n,g,s)},d(a,n,$(u[m])),l.push($(u[m]))}return i.length===0&&P("updateMany",n,Object.keys(s),s),o?.returning===!1?l.length:l},async delete(n,r){let s=e.get(n)??[],o=s.findIndex(i=>i.id===r);o<0||(s.splice(o,1),d(c,n,r))},async deleteMany(n,r){F("deleteMany",r);let s=x("deleteMany",n,r);e.set(n,(e.get(n)??[]).filter(o=>!s.includes(o)));for(let o of s)d(c,n,String(o.id));return s.length},async claim(n,r,s={}){let o=Object.keys(r);if(o.length===0)throw new Error(`claim(${n}): no unique field \u2014 claim takes a key; without one, insert(${n}, \u2026)`);P("claim",n,o,r);let i=(e.get(n)??[]).find(u=>o.every(l=>N(u[l],r[l])));return i?{inserted:!1,row:$(i)}:{inserted:!0,row:T("claim",n,{...r,...s})}},async lockRows(n,r){if(r.length===0)return;let s=e.get(n)??[],o=[...new Set(r)].filter(i=>!s.some(u=>u.id===i));if(o.length>0&&s.length>0)throw new Error(`lockRows(${n}): no row with id ${o.join(", ")}`)},async lockRowsWhere(n,r,s){let o=s?.mode??"update";if(!["update","share","noKeyUpdate"].includes(o))throw new Error(`lockRowsWhere(${n}): mode is "update", "share" or "noKeyUpdate" (got "${String(o)}")`);F("lockRowsWhere",r),x("lockRowsWhere",n,r)},async advisoryXactLock(){}};function W(n){let r=R(),s=[];try{for(let o of n.ops){let i=A(o,s);s.push(i);let u=Tn(o.guard,i.rows.length);if(u)throw u}}catch(o){throw r(),o}return{results:s}}function A(n,r){let s=i=>Object.fromEntries(Object.entries(i??{}).map(([u,l])=>[u,yn(l,r)])),o=`transaction plan ${n.op}`;switch(n.op){case"insert":return nn(T(o,n.table,s(n.values)));case"insertMany":{let i=(n.rows??[]).map(g=>s(g)),u=n.onConflict??[];if(n.action!==void 0&&u.length===0)throw new Error(`${o}(${n.table}): the fake database knows no unique key to collide with \u2014 name onConflict columns, or test this against Postgres`);let l=[];for(let g of i){let m=u.length>0?f(n.table).find(y=>u.every(p=>N(y[p],g[p]))):void 0;if(!(m&&n.action==="ignore")){if(m&&n.action==="update"){Object.assign(m,E(o,n.table,m,Object.fromEntries(Object.entries(g).filter(([y])=>!u.includes(y))))),d(a,n.table,$(m)),l.push($(m));continue}l.push(T(o,n.table,g))}}return{rows:l,rows_affected:l.length}}case"upsert":{let i=s(n.values),u=n.onConflict??[];if(Object.keys(i).length===0||u.length===0)throw Object.assign(new Error(`put on ${n.table} needs columns and onConflict`),{error_code:"tx_bad_upsert"});let l=f(n.table).find(g=>u.every(m=>N(g[m],i[m])));return l?(Object.assign(l,E(o,n.table,l,Object.fromEntries(Object.entries(i).filter(([g])=>!u.includes(g))))),d(a,n.table,$(l)),nn($(l))):nn(T(o,n.table,i))}case"update":{let i=s(n.set);if(Object.keys(i).length===0)throw new Error(`update ${n.table}: nothing to set`);let u=en(n.where,r);F(o,u);let l=f(n.table),g=[];for(let m of x(o,n.table,u)){let y=l.indexOf(m);l[y]={...m,...E(o,n.table,m,i)},d(a,n.table,$(l[y])),g.push($(l[y]))}return{rows:g,rows_affected:g.length}}case"delete":{let i=en(n.where,r);F(o,i);let u=x(o,n.table,i);e.set(n.table,f(n.table).filter(l=>!u.includes(l)));for(let l of u)d(c,n.table,String(l.id));return{rows:u.map($),rows_affected:u.length}}case"select":{let i=x(o,n.table,en(n.where,r));return n.limit!==void 0&&(i=i.slice(0,n.limit)),{rows:i.map($),rows_affected:i.length}}default:throw new Error(`unknown operation "${String(n.op)}" in a transaction plan`)}}let C={...I,async txPlan(n){return W(n)},async attempt(n){let r=R();try{return await n(I)}catch(s){throw r(),s}},async atomic(){throw new Error("the fake database cannot reproduce a transaction's isolation, COMMIT or retry \u2014 test $atomic against Postgres")}};return{...C,asService:()=>C,inserted:n=>(t.get(n)??[]).map($),updated:n=>(a.get(n)??[]).map($),deleted:n=>[...c.get(n)??[]],seed(n,r){e.set(n,r.map($))},queries:w}}function hn(e){return new Map([...e].map(([t,a])=>[t,a.map($)]))}function nn(e){return{rows:[e],rows_affected:1}}function yn(e,t){if(!Y(e))return e;let a=t[e.$ref.op]?.rows[0];if(!a)throw Object.assign(new Error(`transaction plan: operation ${e.$ref.op} produced no row to reference`),{error_code:"tx_ref_unresolved"});return a[e.$ref.field]}function en(e,t){let a=c=>{if(Y(c)){let w=yn(c,t);if(w==null)throw Object.assign(new Error(`transaction plan: a filter's reference to operation ${c.$ref.op}'s "${c.$ref.field}" is NULL`),{error_code:"tx_ref_null"});return w}return Array.isArray(c)?c.map(a):c!==null&&typeof c=="object"&&!U(c)&&!Z(c)&&!V(c)&&!(c instanceof Date)?Object.fromEntries(Object.entries(c).map(([w,f])=>[w,a(f)])):c};return a(e??{})}function Tn(e,t){return e?(e.kind==="one"?t===1:e.kind==="none"?t===0:e.kind==="atLeast"?t>=e.n:t<=e.n)?null:Object.assign(new Error(`transaction expectation failed: ${e.kind} (${t} row(s))`),{error_code:"tx_guard_failed",slot:e.slot}):null}export{En as createFakeClient,$n as typedDatabase};
|
|
1
|
+
/*! palgres 0.11.1 — Palbase SDK License (see LICENSE) */
|
|
2
|
+
import{$ as wn,D as U,E as fn,F as Y,J as Z,K as V,L as z,M as dn,O as P,W as _,aa as $n,e as cn,g as ln}from"./chunks/35ZESSWK.js";import"./chunks/BVMZDED5.js";import{randomUUID as Rn}from"node:crypto";var On=/^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}(:\d{2}(\.\d+)?)?)?(Z|[+-]\d{2}(:?\d{2})?)?$/,bn=/^([+-]?)(\d*)(?:\.(\d*))?$/;function q(e){if(e instanceof Date)return e.getTime();if(typeof e=="string"&&On.test(e)){let t=Date.parse(e);return Number.isNaN(t)?null:t}return null}function B(e){if(typeof e=="number")return Number.isFinite(e)?B(String(e)):null;if(typeof e!="string"||/e/i.test(e))return null;let t=bn.exec(e.trim());if(t===null||t[2]===""&&(t[3]??"")==="")return null;let a=t[3]??"";return{unit:BigInt(`${t[1]==="-"?"-":""}${t[2]===""?"0":t[2]}${a}`),scale:a.length}}function K(e,t){return e.unit*10n**BigInt(t-e.scale)}function tn(e,t){if(e==null||t===null||t===void 0)return null;let a=q(e),c=q(t);if(a!==null&&c!==null)return Math.sign(a-c);let w=B(e),f=B(t);if(w!==null&&f!==null){let d=Math.max(w.scale,f.scale),h=K(w,d)-K(f,d);return h===0n?0:h<0n?-1:1}return typeof e==typeof t&&(typeof e=="string"||typeof e=="boolean"||typeof e=="number")?e<t?-1:e>t?1:0:null}function N(e,t){return e==null||t===null||t===void 0?!1:e instanceof Date||t instanceof Date?q(e)!==null&&q(e)===q(t):Array.isArray(e)&&Array.isArray(t)?e.length===t.length&&e.every((a,c)=>N(a,t[c])):e===t}function H(e){let t=((e?.days??0)*86400+(e?.hours??0)*3600+(e?.minutes??0)*60+(e?.seconds??0))*1e3;return new Date(Date.now()-t).toISOString()}function v(e,t,a){if(e==null)return null;if(typeof e=="number"&&typeof t=="number")return e+a*t;let c=B(e),w=B(t);if(c===null||w===null)throw new Error(`increment()/decrement() on a value that is not a number (${JSON.stringify(e)}): Postgres refuses it ("operator does not exist")`);let f=Math.max(c.scale,w.scale),d=K(c,f)+BigInt(a)*K(w,f);if(f===0)return typeof e=="number"?Number(d):d.toString();let h=d<0n,R=(h?-d:d).toString().padStart(f+1,"0"),k=`${h?"-":""}${R.slice(0,-f)}.${R.slice(-f)}`;return typeof e=="number"?Number(k):k}function kn(e,t){let a=A=>{let C=(A.unit<0n?-A.unit:A.unit).toString().padStart(A.scale+1,"0"),J=C.slice(0,C.length-A.scale),n=C.slice(C.length-A.scale),r=J.padStart(Math.ceil(J.length/4)*4,"0").match(/.{4}/g)??[],s=n.padEnd(Math.ceil(n.length/4)*4,"0").match(/.{4}/g)??[],i=[...r.map((u,l)=>[r.length-1-l,Number(u)]),...s.map((u,l)=>[-1-l,Number(u)])].find(([,u])=>u!==0);return i?[i[0],i[1]]:[0,0]},[c,w]=a(e),[f,d]=a(t),h=c-f-(w<=d?1:0),R=Math.min(Math.max(16-h*4,e.scale,t.scale,0),1e3),k=e.unit*10n**BigInt(R+t.scale),E=t.unit*10n**BigInt(e.scale),T=k/E,x=k%E;2n*(x<0n?-x:x)>=(E<0n?-E:E)&&(T+=k<0n!=E<0n?-1n:1n);let I=T<0n,W=(I?-T:T).toString().padStart(R+1,"0");return`${I?"-":""}${R===0?W:`${W.slice(0,-R)}.${W.slice(-R)}`}`}var $=e=>structuredClone(e);function rn(e,t,a){if(fn(a))throw new Error(`${e}(${t}): the fake database runs no SQL, so a sqlFragment cannot be evaluated \u2014 write the filter in the filter language (gt, gte, lt, lte, neq, in, contains, isNull, OR, AND, NOT), or test this against Postgres`);if(!(a===null||typeof a!="object"))for(let[c,w]of Object.entries(a))if(c==="OR"||c==="AND")for(let f of Array.isArray(w)?w:[])rn(e,t,f);else c==="NOT"&&rn(e,t,w)}function D(e,t,a){return cn(new ln({fields:[{field:t,message:a}]},`${e}: ${t} ${a}`))}function on(e,t,a="where"){for(let[c,w]of Object.entries(t))if(c==="OR"||c==="AND"){let f=w;if(c==="AND"&&f.length===0)throw D(e,`${a}.AND`,"is an empty list \u2014 no condition at all. Leave it out, or name a condition in it.");f.forEach((d,h)=>{if(Object.keys(d).length===0)throw D(e,`${a}.${c}[${h}]`,"holds no condition \u2014 a branch that matches every row. Name a condition in it, or leave it out.");on(e,d,`${a}.${c}[${h}]`)})}else if(c==="NOT"){if(Array.isArray(w))throw D(e,`${a}.NOT`,"is a list \u2014 NOT takes one filter object. For several conditions, NOT: { AND: [...] } or NOT: { OR: [...] }.");if(Object.keys(w).length===0)throw D(e,`${a}.NOT`,"holds no condition \u2014 NOT of nothing is not a filter. Name the condition to exclude.");on(e,w,`${a}.NOT`)}else if(c==="has"&&Object.keys(w??{}).length===0)throw D(e,`${a}.has`,"names no relation \u2014 no condition. Name one: has: { <relation>: { \u2026 } }.")}function F(e,t){if(Object.keys(t).length===0)throw D(e,"where",`holds no condition, so ${e} would reach EVERY row of the table. Name a condition.`)}function sn(e){let t=!1;for(let a of e){if(a===!1)return!1;a===null&&(t=!0)}return t?null:!0}function mn(e){let t=!1;for(let a of e){if(a===!0)return!0;a===null&&(t=!0)}return t?null:!1}var j=e=>e==null;function L(e,t,a,c){return sn(Object.entries(c).map(([w,f])=>{if(w==="OR")return mn(f.map(d=>L(e,t,a,d)));if(w==="AND")return sn(f.map(d=>L(e,t,a,d)));if(w==="NOT"){let d=L(e,t,a,f);return d===null?null:!d}if(w==="has")throw new Error(`${e}(${t}): the fake database has no relations, so a \`has\` filter cannot be decided for a row \u2014 read the related table first and filter by its ids ({ id: { in: [\u2026] } }), or test this against Postgres`);return xn(e,t,a,w,f)}))}var gn=["neq","gt","gte","lt","lte"];function Q(e,t){return j(e)||j(t)?null:N(e,t)}function xn(e,t,a,c,w){let f=a[c];return w===null?j(f):U(w)?Q(f,a[w.$col]):V(w)?Q(f,H(_(w)?.minus)):typeof w=="object"&&!Array.isArray(w)&&!(w instanceof Date)?sn(Object.entries(w).map(([d,h])=>{if(V(h)){if(!gn.includes(d))throw new Error(`${e}(${t}): where.${c}.${d} cannot take now()`);return G(d,f,H(_(h)?.minus))}if(Z(h))throw new Error(`${e}(${t}): where.${c}.${d} was given increment()/decrement() \u2014 a write, not a value to compare with`);if(U(h)){if(!gn.includes(d))throw new Error(`${e}(${t}): where.${c}.${d} cannot take col()`);return G(d,f,a[h.$col])}switch(d){case"in":{if(!Array.isArray(h))throw new Error(`${e}(${t}): where.${c}.in is a list`);return mn(h.map(R=>Q(f,R)))}case"neq":return h===null?!j(f):G(d,f,h);case"gt":case"gte":case"lt":case"lte":return G(d,f,h);case"isNull":if(typeof h!="boolean")throw new Error(`${e}(${t}): where.${c}.isNull is true or false`);return h?j(f):!j(f);case"overlaps":if(!Array.isArray(h))throw new Error(`${e}(${t}): where.${c}.overlaps is a list`);if(f!=null&&!Array.isArray(f))throw new Error(`${e}(${t}): where.${c}: "overlaps" is an array column's operator`);return j(f)?null:h.some(R=>f.some(k=>N(R,k)));case"contains":case"icontains":case"startsWith":case"endsWith":{if(d==="contains"&&Array.isArray(h))return j(f)?null:Array.isArray(f)&&h.every(R=>f.some(k=>N(R,k)));if(typeof h!="string")throw new Error(`${e}(${t}): where.${c}.${d} is text`);return j(f)?null:typeof f!="string"?!1:d==="contains"?f.includes(h):d==="icontains"?f.toLowerCase().includes(h.toLowerCase()):d==="startsWith"?f.startsWith(h):f.endsWith(h)}default:throw new Error(`${e}(${t}): where.${c} has no operator "${d}"`)}})):Q(f,w)}function G(e,t,a){if(j(t)||j(a))return null;if(e==="neq")return!N(t,a);let c=tn(t,a);return c===null?!1:e==="gt"?c>0:e==="gte"?c>=0:e==="lt"?c<0:c<=0}function En(){let e=new Map,t=new Map,a=new Map,c=new Map,w=[],f=n=>{let r=e.get(n);return r||e.set(n,r=[]),r},d=(n,r,s)=>{let o=n.get(r);o?o.push(s):n.set(r,[s])},h=n=>n.id===void 0?{id:Rn(),...n}:{...n},R=()=>{let n={store:new Map([...e].map(([r,s])=>[r,s.map($)])),inserted:hn(t),updated:hn(a),deleted:new Map([...c].map(([r,s])=>[r,[...s]]))};return()=>{e=n.store,t=n.inserted,a=n.updated,c=n.deleted}};function k(n,r,s){let o={};for(let[i,u]of Object.entries(s)){let l=_(u);if(l!==null&&l.fn==="now")o[i]=H(l.minus);else{if(l!==null)throw new Error(`${n}(${r}): "${i}" was given ${l.fn==="inc"?"increment()":"decrement()"} \u2014 the row does not exist yet, so there is no current value to change`);o[i]=u}}return P(n,r,Object.keys(o),o),z(n,r,Object.keys(o),o),o}function E(n,r,s,o){P(n,r,Object.keys(o),o);let i={};for(let[u,l]of Object.entries(o)){let g=_(l);g!==null&&g.fn==="now"?i[u]=H(g.minus):g!==null?i[u]=v(s[u],g.by,g.fn==="inc"?1:-1):i[u]=l}return z(n,r,Object.keys(i),i),i}function T(n,r,s){let o=h(k(n,r,s));return f(r).push(o),d(t,r,$(o)),$(o)}function x(n,r,s){return dn(n,r,s),rn(n,r,s),s!==void 0&&on(n,s),(e.get(r)??[]).filter(o=>s===void 0||L(n,r,o,s)===!0)}let I={diagnostics:()=>null,async query(n,r=[]){return w.push({sql:n,params:r}),[]},async findUnique(){throw new Error("the fake database knows no table's declared unique keys \u2014 find the row with findMany, or test this against Postgres")},async page(){throw new Error("the fake database cannot reproduce Postgres' cursor ordering, collation or row-level security \u2014 test pagination against Postgres")},async search(n){throw new Error(`search(${n}): the fake database has neither embeddings nor full text \u2014 test search against Postgres`)},async findById(n,r){let s=(e.get(n)??[]).find(o=>o.id===r);return s?$(s):null},async findMany(n,r,s){let{limit:o,offset:i}=s??{};if(o!==void 0&&(!Number.isInteger(o)||o<0))throw new Error(`findMany(${n}): limit is a whole number, 0 or more (got ${String(o)})`);if(i!==void 0){if(!Number.isInteger(i)||i<0)throw new Error(`findMany(${n}): offset is a whole number, 0 or more (got ${String(i)})`);if(o===void 0)throw new Error(`findMany(${n}): offset comes with limit \u2014 an offset into an unbounded result is not a page`)}if(s?.with!==void 0&&Object.keys(s.with).length>0)throw new Error(`findMany(${n}): the fake database has no relations, so \`with\` cannot be read \u2014 seed and query the related table on its own, or test this against Postgres`);let u=x("findMany",n,r),l=s?.orderBy===void 0?[]:Array.isArray(s.orderBy)?s.orderBy:[s.orderBy];l.length>0&&(u=[...u].sort((y,p)=>{for(let O of l){let b=O.direction==="desc"?-1:1,M=y[O.column],S=p[O.column],X=M==null,an=S==null;if(X||an){if(X&&an)continue;let pn=O.nulls===void 0?b===-1:O.nulls==="first";return(X?1:-1)*(pn?-1:1)}let un=tn(M,S)??(String(M)<String(S)?-1:String(M)>String(S)?1:0);if(un!==0)return un*b}return 0}));let g=o===void 0?u:u.slice(i??0,(i??0)+o),m=s?.select;return g.map(y=>m===void 0||m.length===0?$(y):Object.fromEntries(m.map(p=>[p,structuredClone(y[p])])))},async count(n,r={}){return x("count",n,r).length},async aggregate(n,r){let s=x("aggregate",n,r.where),o=r.groupBy??[],i=l=>{let g={},m=y=>l.map(p=>p[y]).filter(p=>p!=null);for(let y of r.sum??[]){let p=m(y);(g.sum??={})[y]=p.length===0?null:String(p.reduce((O,b)=>v(O,b,1),"0"))}for(let y of r.avg??[]){let p=m(y),O=p.some(b=>typeof b=="number"&&!Number.isInteger(b));(g.avg??={})[y]=p.length===0?null:O?p.reduce((b,M)=>b+M,0)/p.length:kn(B(p.reduce((b,M)=>v(b,M,1),"0")),{unit:BigInt(p.length),scale:0})}for(let[y,p]of[["min",-1],["max",1]])for(let O of r[y]??[]){let b=m(O);(g[y]??={})[O]=b.length===0?null:b.reduce((M,S)=>(tn(S,M)??0)*p>0?S:M)}return r.count===!0?g.count=l.length:r.count!==void 0&&r.count!==!1&&(g.count=Object.fromEntries(Object.entries(r.count).map(([y,p])=>[y,l.filter(O=>L("aggregate",n,O,p)===!0).length]))),g};if(o.length===0)return i(s);let u=new Map;for(let l of s){let g=JSON.stringify(o.map(m=>l[m]));u.set(g,[...u.get(g)??[],l])}return[...u.values()].map(l=>({...i(l),...Object.fromEntries(o.map(g=>[g,l[0][g]]))}))},async insert(n,r){return T("insert",n,r)},insertMany:(async(n,r,s)=>{if(wn(s),s?.onConflict?.length||s?.action!==void 0)throw new Error(`insertMany(${n}): the fake database knows no unique constraint to collide with \u2014 test onConflict against Postgres`);if(r.length===0)return s?.returning===!1?0:[];let o=Object.keys(r[0]);r.forEach((u,l)=>{let g=o.filter(y=>!(y in u)),m=Object.keys(u).filter(y=>!o.includes(y));if(g.length>0||m.length>0)throw new Error(`insertMany(${n}): row ${l} has not the first row's columns${g.length?` (missing ${g.join(", ")})`:""}${m.length?` (extra ${m.join(", ")})`:""} \u2014 write null for an empty one, or insert rows of another shape in a call of their own`)});let i=r.map(u=>T("insertMany",n,u));return s?.returning===!1?i.length:i}),async put(n,r,s){if(!s?.onConflict?.length)throw new Error(`put(${n}): onConflict names at least one column`);let o=k("put",n,r),i=f(n).find(u=>s.onConflict.every(l=>N(u[l],o[l])));if(!i)return T("put",n,r);for(let[u,l]of Object.entries(o))s.onConflict.includes(u)||(i[u]=l);return d(a,n,$(i)),$(i)},async update(n,r,s){let o=e.get(n)??[],i=o.findIndex(u=>u.id===r);return i<0?(P("update",n,Object.keys(s),s),null):(o[i]={...o[i],...E("update",n,o[i],s)},d(a,n,$(o[i])),$(o[i]))},async updateMany(n,r,s,o){F("updateMany",r);let i=x("updateMany",n,r),u=f(n),l=[];for(let g of i){let m=u.indexOf(g);u[m]={...g,...E("updateMany",n,g,s)},d(a,n,$(u[m])),l.push($(u[m]))}return i.length===0&&P("updateMany",n,Object.keys(s),s),o?.returning===!1?l.length:l},async delete(n,r){let s=e.get(n)??[],o=s.findIndex(i=>i.id===r);o<0||(s.splice(o,1),d(c,n,r))},async deleteMany(n,r){F("deleteMany",r);let s=x("deleteMany",n,r);e.set(n,(e.get(n)??[]).filter(o=>!s.includes(o)));for(let o of s)d(c,n,String(o.id));return s.length},async claim(n,r,s={}){let o=Object.keys(r);if(o.length===0)throw new Error(`claim(${n}): no unique field \u2014 claim takes a key; without one, insert(${n}, \u2026)`);P("claim",n,o,r);let i=(e.get(n)??[]).find(u=>o.every(l=>N(u[l],r[l])));return i?{inserted:!1,row:$(i)}:{inserted:!0,row:T("claim",n,{...r,...s})}},async lockRows(n,r){if(r.length===0)return;let s=e.get(n)??[],o=[...new Set(r)].filter(i=>!s.some(u=>u.id===i));if(o.length>0&&s.length>0)throw new Error(`lockRows(${n}): no row with id ${o.join(", ")}`)},async lockRowsWhere(n,r,s){let o=s?.mode??"update";if(!["update","share","noKeyUpdate"].includes(o))throw new Error(`lockRowsWhere(${n}): mode is "update", "share" or "noKeyUpdate" (got "${String(o)}")`);F("lockRowsWhere",r),x("lockRowsWhere",n,r)},async advisoryXactLock(){}};function W(n){let r=R(),s=[];try{for(let o of n.ops){let i=A(o,s);s.push(i);let u=Tn(o.guard,i.rows.length);if(u)throw u}}catch(o){throw r(),o}return{results:s}}function A(n,r){let s=i=>Object.fromEntries(Object.entries(i??{}).map(([u,l])=>[u,yn(l,r)])),o=`transaction plan ${n.op}`;switch(n.op){case"insert":return nn(T(o,n.table,s(n.values)));case"insertMany":{let i=(n.rows??[]).map(g=>s(g)),u=n.onConflict??[];if(n.action!==void 0&&u.length===0)throw new Error(`${o}(${n.table}): the fake database knows no unique key to collide with \u2014 name onConflict columns, or test this against Postgres`);let l=[];for(let g of i){let m=u.length>0?f(n.table).find(y=>u.every(p=>N(y[p],g[p]))):void 0;if(!(m&&n.action==="ignore")){if(m&&n.action==="update"){Object.assign(m,E(o,n.table,m,Object.fromEntries(Object.entries(g).filter(([y])=>!u.includes(y))))),d(a,n.table,$(m)),l.push($(m));continue}l.push(T(o,n.table,g))}}return{rows:l,rows_affected:l.length}}case"upsert":{let i=s(n.values),u=n.onConflict??[];if(Object.keys(i).length===0||u.length===0)throw Object.assign(new Error(`put on ${n.table} needs columns and onConflict`),{error_code:"tx_bad_upsert"});let l=f(n.table).find(g=>u.every(m=>N(g[m],i[m])));return l?(Object.assign(l,E(o,n.table,l,Object.fromEntries(Object.entries(i).filter(([g])=>!u.includes(g))))),d(a,n.table,$(l)),nn($(l))):nn(T(o,n.table,i))}case"update":{let i=s(n.set);if(Object.keys(i).length===0)throw new Error(`update ${n.table}: nothing to set`);let u=en(n.where,r);F(o,u);let l=f(n.table),g=[];for(let m of x(o,n.table,u)){let y=l.indexOf(m);l[y]={...m,...E(o,n.table,m,i)},d(a,n.table,$(l[y])),g.push($(l[y]))}return{rows:g,rows_affected:g.length}}case"delete":{let i=en(n.where,r);F(o,i);let u=x(o,n.table,i);e.set(n.table,f(n.table).filter(l=>!u.includes(l)));for(let l of u)d(c,n.table,String(l.id));return{rows:u.map($),rows_affected:u.length}}case"select":{let i=x(o,n.table,en(n.where,r));return n.limit!==void 0&&(i=i.slice(0,n.limit)),{rows:i.map($),rows_affected:i.length}}default:throw new Error(`unknown operation "${String(n.op)}" in a transaction plan`)}}let C={...I,async txPlan(n){return W(n)},async attempt(n){let r=R();try{return await n(I)}catch(s){throw r(),s}},async atomic(){throw new Error("the fake database cannot reproduce a transaction's isolation, COMMIT or retry \u2014 test $atomic against Postgres")}};return{...C,asService:()=>C,inserted:n=>(t.get(n)??[]).map($),updated:n=>(a.get(n)??[]).map($),deleted:n=>[...c.get(n)??[]],seed(n,r){e.set(n,r.map($))},queries:w}}function hn(e){return new Map([...e].map(([t,a])=>[t,a.map($)]))}function nn(e){return{rows:[e],rows_affected:1}}function yn(e,t){if(!Y(e))return e;let a=t[e.$ref.op]?.rows[0];if(!a)throw Object.assign(new Error(`transaction plan: operation ${e.$ref.op} produced no row to reference`),{error_code:"tx_ref_unresolved"});return a[e.$ref.field]}function en(e,t){let a=c=>{if(Y(c)){let w=yn(c,t);if(w==null)throw Object.assign(new Error(`transaction plan: a filter's reference to operation ${c.$ref.op}'s "${c.$ref.field}" is NULL`),{error_code:"tx_ref_null"});return w}return Array.isArray(c)?c.map(a):c!==null&&typeof c=="object"&&!U(c)&&!Z(c)&&!V(c)&&!(c instanceof Date)?Object.fromEntries(Object.entries(c).map(([w,f])=>[w,a(f)])):c};return a(e??{})}function Tn(e,t){return e?(e.kind==="one"?t===1:e.kind==="none"?t===0:e.kind==="atLeast"?t>=e.n:t<=e.n)?null:Object.assign(new Error(`transaction expectation failed: ${e.kind} (${t} row(s))`),{error_code:"tx_guard_failed",slot:e.slot}):null}export{En as createFakeClient,$n as typedDatabase};
|
package/package.json
CHANGED
|
@@ -156,6 +156,17 @@ export interface ColumnDef {
|
|
|
156
156
|
* the type the application actually works with.
|
|
157
157
|
*/
|
|
158
158
|
transform?: ColumnTransform;
|
|
159
|
+
/**
|
|
160
|
+
* `.encrypted()`: the value is sealed by the app before it is written and
|
|
161
|
+
* opened after it is read, with the project's field keys (palbase/server's
|
|
162
|
+
* fields) — the database, its backups and a dump of it hold ciphertext. With
|
|
163
|
+
* `lookup`, a keyed hash of the value is kept beside it, in `<column>_lookup`
|
|
164
|
+
* (model.ts' lookupColumnOf): what equality finds a row by. Absent when the
|
|
165
|
+
* column declares none, so the wire stays byte-identical.
|
|
166
|
+
*/
|
|
167
|
+
encrypted?: {
|
|
168
|
+
lookup: boolean;
|
|
169
|
+
};
|
|
159
170
|
}
|
|
160
171
|
/**
|
|
161
172
|
* The read/write pair a column may declare with `.transform()`.
|
|
@@ -360,6 +371,23 @@ export declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends
|
|
|
360
371
|
* column built from a shared base does not mark the others built from it.
|
|
361
372
|
*/
|
|
362
373
|
ignored(): ColumnBuilder<K, N, D, E, P, T, W, "ignored">;
|
|
374
|
+
/**
|
|
375
|
+
* Keeps this text column encrypted in the database: the app seals each value
|
|
376
|
+
* before it is written and opens it when it is read, with the project's
|
|
377
|
+
* field keys — a dump, a backup or a stolen database connection reads
|
|
378
|
+
* ciphertext. Nothing else changes for the code: rows hold the plain value.
|
|
379
|
+
*
|
|
380
|
+
* What the database cannot see it cannot compare: an encrypted column is
|
|
381
|
+
* never sorted, ranged, LIKE-matched, searched or indexed. `{ lookup: true }`
|
|
382
|
+
* keeps a keyed hash beside it so equality still finds a row —
|
|
383
|
+
* `where: { tc: "…" }`, `in`, `findUnique` and `.unique()` — and nothing
|
|
384
|
+
* more. A value that is not encrypted where one belongs is refused when read.
|
|
385
|
+
*
|
|
386
|
+
* It returns a new column and leaves the one it was called on as it was.
|
|
387
|
+
*/
|
|
388
|
+
encrypted(this: ColumnBuilder<"text", N, D, E, P, T, W, Q>, opts?: {
|
|
389
|
+
lookup?: boolean;
|
|
390
|
+
}): ColumnBuilder<"text", N, D, E, P, T, W, Q>;
|
|
363
391
|
/**
|
|
364
392
|
* Foreign key onto another table's column.
|
|
365
393
|
*
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** What seals and opens a project's fields: palbase/server's `fields` is one. */
|
|
2
|
+
export interface FieldCipher {
|
|
3
|
+
/** The value sealed as the field at address, with the key in force. */
|
|
4
|
+
encrypt(address: string, value: string): string;
|
|
5
|
+
/** The field at address opened; a plain value, another column's or a tampered one is refused. */
|
|
6
|
+
decrypt(address: string, value: string): string;
|
|
7
|
+
/** What the field at address holding value is found by. */
|
|
8
|
+
lookup(address: string, value: string): string;
|
|
9
|
+
/** Whether value is a sealed field, without opening it. */
|
|
10
|
+
isEncrypted(value: string): boolean;
|
|
11
|
+
/** The id of the key a value sealed now is sealed with (pbe1.<id>.…). */
|
|
12
|
+
currentKey(): string;
|
|
13
|
+
}
|
|
14
|
+
/** The cipher, or what loads it once it is needed: a version, or a migration, with no encrypted column to read asks for no key. */
|
|
15
|
+
export type FieldsSource = FieldCipher | (() => Promise<FieldCipher>);
|
|
16
|
+
export declare function resolveFields(source: FieldsSource): Promise<FieldCipher>;
|
|
17
|
+
/** What a sealed field begins with (pbe1.<key id>.…). */
|
|
18
|
+
export declare const FIELD_PREFIX = "pbe1.";
|
|
19
|
+
/** A column's address, what its fields are bound to: "table.column" in public, "schema.table.column" elsewhere. */
|
|
20
|
+
export declare function fieldAddress(schema: string, table: string, column: string): string;
|
|
21
|
+
/** The ledger of columns going over: palgres.sealing (migrate.ts makes it). */
|
|
22
|
+
export declare const SEALING = "palgres.sealing";
|
|
23
|
+
export interface Going {
|
|
24
|
+
/** 'seal': the column becomes encrypted; 'open': it stops being. */
|
|
25
|
+
direction: 'seal' | 'open';
|
|
26
|
+
/** 'mixed' while the old version may still write; 'final' once it is gone. */
|
|
27
|
+
phase: 'mixed' | 'final';
|
|
28
|
+
}
|
|
29
|
+
/** How often a version reads which of its columns are going over: well inside a start's settling (SETTLE_MS). */
|
|
30
|
+
export declare const SEALING_REFRESH_MS = 5000;
|
|
31
|
+
/** How long a start that announces a column going over waits before it ends: every version serving has read it by then. */
|
|
32
|
+
export declare const SETTLE_MS: number;
|
|
33
|
+
/** The columns of a database going over now, by address, as last read. */
|
|
34
|
+
export declare class Sealing {
|
|
35
|
+
private going;
|
|
36
|
+
get(address: string): Going | undefined;
|
|
37
|
+
get size(): number;
|
|
38
|
+
/** Reads palgres.sealing; a database Palgres has not made it in reads as none. */
|
|
39
|
+
read(query: (sql: string) => PromiseLike<unknown>): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
/** A column's rows to take to one form: sealed with the key in force (and their lookup written), or opened. */
|
|
42
|
+
export interface SweepTarget {
|
|
43
|
+
/** The table as SQL names it — itself, never a version's view: its rows are gone over by where they are (ctid). */
|
|
44
|
+
table: string;
|
|
45
|
+
column: string;
|
|
46
|
+
lookup: string | null;
|
|
47
|
+
address: string;
|
|
48
|
+
direction: 'seal' | 'open';
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* One batch of a sweep, in one transaction the caller holds: the rows after
|
|
52
|
+
* `after` (a ctid) still to do, taken to the target's form — each written
|
|
53
|
+
* only if it still holds what was read: a write meanwhile is in the new form,
|
|
54
|
+
* or the next pass's. `last` is where the next batch starts; null at the end.
|
|
55
|
+
*/
|
|
56
|
+
export declare function sweepBatch(query: (sql: string, params: unknown[]) => PromiseLike<unknown>, t: SweepTarget, cipher: FieldCipher, after: string | null, size: number): Promise<{
|
|
57
|
+
done: number;
|
|
58
|
+
last: string | null;
|
|
59
|
+
}>;
|
|
60
|
+
/** A plain value a column going over is not given: one that begins as a sealed field does, which a reader could not tell from one. */
|
|
61
|
+
export declare function refusePlainLikeSealed(address: string, value: unknown): void;
|
|
62
|
+
/** Whether a row of the target is still to do. */
|
|
63
|
+
export declare function sweepLeft(query: (sql: string, params: unknown[]) => PromiseLike<unknown>, t: SweepTarget, cipher: FieldCipher): Promise<boolean>;
|
|
@@ -12,4 +12,5 @@ export { introspect, type Introspected } from './introspect.js';
|
|
|
12
12
|
export { pullSchema, type Pulled } from './pull.js';
|
|
13
13
|
export { SEARCH_SCHEMA, vectorTable, type SearchGeneration } from './search.js';
|
|
14
14
|
export { createSearchWorker, type SearchWorker, type SearchWorkerOptions } from './search-worker.js';
|
|
15
|
+
export { SEALING, SEALING_REFRESH_MS, SETTLE_MS, Sealing, type Going, type SweepTarget } from './fields.js';
|
|
15
16
|
export * from './query/index.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Schema } from './model.js';
|
|
2
2
|
import { type Adoption, type Plan, type Roles } from './plan.js';
|
|
3
|
+
import { type FieldsSource } from './fields.js';
|
|
3
4
|
/** A pool that hands out one connection of its own: Bun's SQL and postgres.js both are. */
|
|
4
5
|
export interface Pool {
|
|
5
6
|
reserve(): Promise<Reserved>;
|
|
@@ -43,6 +44,13 @@ export interface MigratorOptions {
|
|
|
43
44
|
* finds another's is told whose it is.
|
|
44
45
|
*/
|
|
45
46
|
openedBy?: string;
|
|
47
|
+
/**
|
|
48
|
+
* What seals and opens the rows of a column that becomes encrypted or stops
|
|
49
|
+
* being (fields.ts) — the project's field keys (palbase/server's `fields`,
|
|
50
|
+
* loaded). A migration that has such a column is refused without it; a
|
|
51
|
+
* loader is called only by one that has.
|
|
52
|
+
*/
|
|
53
|
+
fields?: FieldsSource;
|
|
46
54
|
}
|
|
47
55
|
export interface Version {
|
|
48
56
|
n: number;
|
|
@@ -120,6 +128,8 @@ export declare class Migrator {
|
|
|
120
128
|
private readonly disconnectTimeoutMs;
|
|
121
129
|
private readonly log;
|
|
122
130
|
private readonly shadow;
|
|
131
|
+
private readonly fieldsSource;
|
|
132
|
+
private fields;
|
|
123
133
|
constructor(pool: Pool, options?: MigratorOptions);
|
|
124
134
|
status(): Promise<Status>;
|
|
125
135
|
/**
|
|
@@ -165,6 +175,26 @@ export declare class Migrator {
|
|
|
165
175
|
private keyedBatch;
|
|
166
176
|
private pageBatch;
|
|
167
177
|
private retrying;
|
|
178
|
+
/** The project's field keys, loaded only for a plan that seals or opens a column's rows — refused without them before anything is written. */
|
|
179
|
+
private cipherFor;
|
|
180
|
+
/**
|
|
181
|
+
* An encrypted column's rows taken to their new form (fields.ts), in the
|
|
182
|
+
* order they lie (ctid), a batch a transaction — each row written only if it still holds
|
|
183
|
+
* what was read (a write meanwhile is the new version's, in the new form).
|
|
184
|
+
* First the transactions begun before the phase turned final are waited
|
|
185
|
+
* out: they may still write the old form. A pass ends at the table's end;
|
|
186
|
+
* another follows while a row still holds the old form.
|
|
187
|
+
*/
|
|
188
|
+
private seal;
|
|
189
|
+
/**
|
|
190
|
+
* Waits until every transaction that had written when the phase turned
|
|
191
|
+
* final has ended: one may have written the old form. They are the
|
|
192
|
+
* snapshot's running transactions — read through pg_current_snapshot and
|
|
193
|
+
* pg_xact_status, which need no right over another role's sessions (an
|
|
194
|
+
* app's xact_start in pg_stat_activity does). One that writes later reads
|
|
195
|
+
* the final phase; a write that still slips in is a later pass's.
|
|
196
|
+
*/
|
|
197
|
+
private waitBegunBefore;
|
|
168
198
|
private waitGone;
|
|
169
199
|
private adopt;
|
|
170
200
|
private made;
|
|
@@ -71,7 +71,19 @@ export interface Column {
|
|
|
71
71
|
onDelete: 'cascade' | 'set null';
|
|
72
72
|
owns: boolean;
|
|
73
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* The app seals the column's value (.encrypted(): the project's field keys)
|
|
76
|
+
* and the database holds ciphertext; `lookup` names the column its keyed
|
|
77
|
+
* hash is kept in, what equality finds a row by.
|
|
78
|
+
*/
|
|
79
|
+
encrypted?: {
|
|
80
|
+
lookup?: string;
|
|
81
|
+
};
|
|
82
|
+
/** The encrypted column this one keeps the lookup of: the engine writes it, the app never names it. */
|
|
83
|
+
lookupOf?: string;
|
|
74
84
|
}
|
|
85
|
+
/** Where an encrypted column's lookup is kept: `<column>_lookup`, a column of the table the app never names. */
|
|
86
|
+
export declare function lookupColumnOf(column: string): string;
|
|
75
87
|
export interface Unique {
|
|
76
88
|
name: string;
|
|
77
89
|
columns: string[];
|
|
@@ -33,6 +33,26 @@ export type Step =
|
|
|
33
33
|
set: string;
|
|
34
34
|
where: string;
|
|
35
35
|
why: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* An encrypted column's rows taken to its new form, in the app (fields.ts):
|
|
39
|
+
* sealed (direction 'seal'; its lookup written beside) or opened ('open') —
|
|
40
|
+
* once the transactions begun before the phase turned final are over.
|
|
41
|
+
*/
|
|
42
|
+
| {
|
|
43
|
+
kind: 'seal';
|
|
44
|
+
table: string;
|
|
45
|
+
column: string;
|
|
46
|
+
lookup: string | null;
|
|
47
|
+
address: string;
|
|
48
|
+
direction: 'seal' | 'open';
|
|
49
|
+
why: string;
|
|
50
|
+
}
|
|
51
|
+
/** A wait, ms long: every version serving reads what the step before it wrote (fields.ts' SETTLE_MS). */
|
|
52
|
+
| {
|
|
53
|
+
kind: 'settle';
|
|
54
|
+
ms: number;
|
|
55
|
+
why: string;
|
|
36
56
|
};
|
|
37
57
|
/**
|
|
38
58
|
* The kinds of step this Palgres runs. A plan is kept in the database while
|
|
@@ -42,11 +62,14 @@ export type Step =
|
|
|
42
62
|
*/
|
|
43
63
|
export declare const STEP_KINDS: ReadonlySet<Step['kind']>;
|
|
44
64
|
/**
|
|
45
|
-
* The plan
|
|
46
|
-
* format or before it, and refuses a newer one by name. A plan
|
|
47
|
-
*
|
|
65
|
+
* The newest plan format this Palgres runs: a Palgres runs a plan of its
|
|
66
|
+
* format or before it, and refuses a newer one by name. A plan says the
|
|
67
|
+
* oldest format its steps need (formatOf) — one without the seal and settle
|
|
68
|
+
* of an encrypted column going over (format 2) is format 1, run by a Palgres
|
|
69
|
+
* that knows nothing of them. A plan written before plans named theirs is of
|
|
70
|
+
* format 1.
|
|
48
71
|
*/
|
|
49
|
-
export declare const PLAN_FORMAT =
|
|
72
|
+
export declare const PLAN_FORMAT = 2;
|
|
50
73
|
export interface Plan {
|
|
51
74
|
/** Its format (PLAN_FORMAT as it was written); absent: 1. */
|
|
52
75
|
format?: number;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Binding } from '../connect.js';
|
|
2
2
|
import { type ColumnDef } from '../dsl/columns.js';
|
|
3
3
|
import { type SchemaDef } from '../dsl/schema.js';
|
|
4
|
+
import { type FieldCipher, type Sealing } from '../fields.js';
|
|
4
5
|
import { type Relation } from './relations.js';
|
|
5
6
|
export interface Transform {
|
|
6
7
|
fromDb?: (v: unknown) => unknown;
|
|
@@ -32,6 +33,20 @@ export interface TableInfo {
|
|
|
32
33
|
key: string;
|
|
33
34
|
from: readonly string[];
|
|
34
35
|
};
|
|
36
|
+
/** Its .encrypted() columns: the address their fields are bound to, and the column their lookup is kept in. */
|
|
37
|
+
encrypted: Map<string, EncryptedColumn>;
|
|
38
|
+
/** What seals and opens them (Catalog.fields). */
|
|
39
|
+
fields: CatalogFields | null;
|
|
40
|
+
}
|
|
41
|
+
export interface EncryptedColumn {
|
|
42
|
+
address: string;
|
|
43
|
+
/** The lookup's column (`<column>_lookup`), or null: equality does not find this one. */
|
|
44
|
+
lookup: string | null;
|
|
45
|
+
}
|
|
46
|
+
/** What the engine seals and opens a version's encrypted columns with: the cipher, and which columns are going over now. */
|
|
47
|
+
export interface CatalogFields {
|
|
48
|
+
cipher: FieldCipher;
|
|
49
|
+
sealing: Sealing;
|
|
35
50
|
}
|
|
36
51
|
export declare const quoteIdent: (name: string) => string;
|
|
37
52
|
/** A column's type as the engine reads it: the DSL's, an sqlType() as "sql:<type>". */
|
|
@@ -47,7 +62,15 @@ export declare class Catalog {
|
|
|
47
62
|
private readonly ignoredTables;
|
|
48
63
|
private readonly schemas;
|
|
49
64
|
private deferrals?;
|
|
50
|
-
|
|
65
|
+
/** The cipher and the columns going over; null for a version with no encrypted column and none going over. */
|
|
66
|
+
readonly fields: CatalogFields | null;
|
|
67
|
+
constructor(schemas: readonly SchemaDef[], binding: Binding, fields?: CatalogFields | null);
|
|
68
|
+
/** Every encrypted column of this version, with its table. */
|
|
69
|
+
encryptedColumns(): {
|
|
70
|
+
table: TableInfo;
|
|
71
|
+
column: string;
|
|
72
|
+
encrypted: EncryptedColumn;
|
|
73
|
+
}[];
|
|
51
74
|
/** A table of this version, or a refusal that names why it is not one. */
|
|
52
75
|
table(key: string, caller: string): TableInfo;
|
|
53
76
|
/**
|
|
@@ -2,6 +2,7 @@ import type { Binding } from '../connect.js';
|
|
|
2
2
|
import type { Roles } from '../plan.js';
|
|
3
3
|
import type { SchemaDef } from '../dsl/schema.js';
|
|
4
4
|
import { type DatabaseAdmissionOptions } from './admission.js';
|
|
5
|
+
import { type FieldsSource } from '../fields.js';
|
|
5
6
|
import type { DBClient } from './client.js';
|
|
6
7
|
import { type SqlDriver } from './driver.js';
|
|
7
8
|
import { type Identity, type RequestOptions } from './request.js';
|
|
@@ -35,6 +36,13 @@ export interface DatabaseOptions {
|
|
|
35
36
|
embed?: Embed;
|
|
36
37
|
/** What the engine has to say outside any request's answer: a search answered by word alone because its query could not be embedded. */
|
|
37
38
|
log?: (line: string) => void;
|
|
39
|
+
/**
|
|
40
|
+
* What seals and opens the schema's .encrypted() columns — the project's
|
|
41
|
+
* field keys (palbase/server's `fields`, loaded); palgres holds no key. A
|
|
42
|
+
* schema with an encrypted column, or a migration taking a column's
|
|
43
|
+
* encryption off, is refused without it. A loader is called only then.
|
|
44
|
+
*/
|
|
45
|
+
fields?: FieldsSource;
|
|
38
46
|
}
|
|
39
47
|
/** One request's — or one job's — database. `T`, `S`: the project's tables (the framework's `Tables`, `Schemas`). */
|
|
40
48
|
export interface RequestDatabase<T, S> {
|
|
@@ -50,10 +58,25 @@ export interface RequestDatabase<T, S> {
|
|
|
50
58
|
export interface AppDatabase<T, S> {
|
|
51
59
|
readonly binding: Binding;
|
|
52
60
|
request(identity: Identity, options?: RequestOptions): RequestDatabase<T, S>;
|
|
61
|
+
/**
|
|
62
|
+
* Seals again, with the key in force, every encrypted value sealed with an
|
|
63
|
+
* older one — what follows a rotation of the project's field keys, once the
|
|
64
|
+
* new key is in force. A batch a transaction, as service_role; a value
|
|
65
|
+
* written meanwhile is left as it is (it is the new key's). Nothing while a
|
|
66
|
+
* migration has the version reading through its views, and nothing of a
|
|
67
|
+
* column going over (its migration seals it).
|
|
68
|
+
*/
|
|
69
|
+
reseal(options?: {
|
|
70
|
+
batchSize?: number;
|
|
71
|
+
}): Promise<{
|
|
72
|
+
address: string;
|
|
73
|
+
resealed: number;
|
|
74
|
+
}[]>;
|
|
53
75
|
}
|
|
54
76
|
/** The engine over `binding`'s version, its `Database` typed by `T` (public's tables) and `S` (the other schemas'). */
|
|
55
77
|
export declare function createDatabase<T = Record<never, never>, S = Record<never, never>>(options: DatabaseOptions): Promise<AppDatabase<T, S>>;
|
|
56
78
|
export type { DatabaseAdmissionOptions } from './admission.js';
|
|
79
|
+
export { fieldAddress, type FieldCipher, type FieldsSource } from '../fields.js';
|
|
57
80
|
export type { AtomicClient, DBClient, DBOps, FindManyOptions, RowLock, SearchOptions } from './client.js';
|
|
58
81
|
export type { Embed } from './search.js';
|
|
59
82
|
export type { DatabaseBudget, DatabaseDiagnostics, DatabaseDiagnosticsOptions, DatabaseQueryEvent } from './diagnostics.js';
|
|
@@ -108,7 +108,11 @@ type PgTag<V> = [NonNullable<V>] extends [{
|
|
|
108
108
|
type PgKind<P> = P extends 'integer' | 'bigint' | 'numeric' ? 'num' : P;
|
|
109
109
|
type BothBranded<A, B> = '?' extends PgTag<A> ? false : '?' extends PgTag<B> ? false : true;
|
|
110
110
|
type PgSameKind<A, B> = [PgKind<PgTag<A>>] extends [PgKind<PgTag<B>>] ? ([PgKind<PgTag<B>>] extends [PgKind<PgTag<A>>] ? true : false) : false;
|
|
111
|
-
|
|
111
|
+
/** An encrypted column (.encrypted()): 'lookup' when equality finds it, 'sealed' when nothing does; never for any other column. */
|
|
112
|
+
type Encrypted<V> = PgTag<V> extends `encrypted${infer L}` ? (L extends ':lookup' ? 'lookup' : 'sealed') : never;
|
|
113
|
+
/** Not an encrypted column: the database holds its value to compare and order. */
|
|
114
|
+
type Plain<V> = [Encrypted<V>] extends [never] ? true : false;
|
|
115
|
+
type ColComparable<A, B> = Plain<A> extends false ? false : Plain<B> extends false ? false : BothBranded<A, B> extends true ? PgSameKind<A, B> : [PgFamily<A>] extends [PgFamily<B>] ? true : false;
|
|
112
116
|
type ColumnsComparableTo<Row, V> = {
|
|
113
117
|
[K in keyof Row]-?: ColComparable<Row[K], V> extends true ? K : never;
|
|
114
118
|
}[keyof Row];
|
|
@@ -178,7 +182,19 @@ type WhereOpBody<V, C> = {
|
|
|
178
182
|
/** IS NULL / IS NOT NULL — `= NULL` is never true. */
|
|
179
183
|
isNull?: boolean;
|
|
180
184
|
} & ListOrTextOps<V>;
|
|
181
|
-
export type WhereOp<V, Row = unknown> = WhereOpWith<V, ColRefOf<Row, V
|
|
185
|
+
export type WhereOp<V, Row = unknown> = Plain<V> extends true ? WhereOpWith<V, ColRefOf<Row, V>> : EncryptedOp<V>;
|
|
186
|
+
/**
|
|
187
|
+
* What an encrypted column is asked: whether it holds a value, and — with a
|
|
188
|
+
* lookup — equality. Its value is ciphertext in the database: nothing orders,
|
|
189
|
+
* ranges or matches a pattern there.
|
|
190
|
+
*/
|
|
191
|
+
type EncryptedOp<V> = (null extends V ? null : never) | (Encrypted<V> extends 'lookup' ? NonNullable<V> | {
|
|
192
|
+
in?: NonNullable<V>[];
|
|
193
|
+
neq?: NonNullable<V> | null;
|
|
194
|
+
isNull?: boolean;
|
|
195
|
+
} : {
|
|
196
|
+
isNull?: boolean;
|
|
197
|
+
});
|
|
182
198
|
/** The operator vocabulary with the column-reference position given from outside — the plan adds `Ref` there. */
|
|
183
199
|
export type WhereOpWith<V, C> = V | C | NowComparable<V> | WhereOpBody<V, C>;
|
|
184
200
|
/** THE filter: findMany, updateMany, deleteMany, count and the plans take this one. */
|
|
@@ -189,8 +205,12 @@ export type WhereFilter<Row> = {
|
|
|
189
205
|
AND?: (WhereFilter<Row> | SqlFragment)[];
|
|
190
206
|
NOT?: WhereFilter<Row> | SqlFragment;
|
|
191
207
|
};
|
|
208
|
+
/** The columns a row can be ordered by: not an encrypted one, whose order in the database is its ciphertext's. */
|
|
209
|
+
type OrderColumns<Row> = {
|
|
210
|
+
[K in keyof Row]-?: Plain<Row[K]> extends true ? K : never;
|
|
211
|
+
}[keyof Row];
|
|
192
212
|
export type OrderBySpec<Row> = {
|
|
193
|
-
column: Extract<
|
|
213
|
+
column: Extract<OrderColumns<Row>, string>;
|
|
194
214
|
direction?: 'asc' | 'desc';
|
|
195
215
|
/** Where NULLs go; Postgres' default changes with the direction. */
|
|
196
216
|
nulls?: 'first' | 'last';
|
|
@@ -286,7 +306,7 @@ type SummableCols<Row> = {
|
|
|
286
306
|
[K in keyof Row]-?: '?' extends PgTag<Row[K]> ? (NonNullable<Row[K]> extends number | string ? K : never) : PgKind<PgTag<Row[K]>> extends 'num' ? K : never;
|
|
287
307
|
}[keyof Row];
|
|
288
308
|
type OrderableCols<Row> = {
|
|
289
|
-
[K in keyof Row]-?: '?' extends PgTag<Row[K]> ? (NonNullable<Row[K]> extends number | string | Date ? K : never) : PgTag<Row[K]> extends 'jsonb' ? never : K;
|
|
309
|
+
[K in keyof Row]-?: '?' extends PgTag<Row[K]> ? (NonNullable<Row[K]> extends number | string | Date ? K : never) : PgTag<Row[K]> extends 'jsonb' ? never : Plain<Row[K]> extends true ? K : never;
|
|
290
310
|
}[keyof Row];
|
|
291
311
|
type AggCols<Row> = Extract<SummableCols<Row>, string>;
|
|
292
312
|
type AggOrderCols<Row> = Extract<OrderableCols<Row>, string>;
|
|
@@ -305,7 +325,7 @@ export type AggregateInput<Row, Rels = unknown, E extends Env = NoEnv> = {
|
|
|
305
325
|
readonly [name: string]: WhereFilter<Row> | SqlFragment | HasBranch<Row, Rels, E>;
|
|
306
326
|
};
|
|
307
327
|
/** Given: rows, each carrying its group's columns. */
|
|
308
|
-
groupBy?: readonly Extract<
|
|
328
|
+
groupBy?: readonly Extract<OrderColumns<Row>, string>[];
|
|
309
329
|
};
|
|
310
330
|
type AggPart<Q, Row, F extends 'sum' | 'avg' | 'min' | 'max'> = Q extends {
|
|
311
331
|
[K in F]: readonly (infer C extends keyof Row)[];
|
package/dist/chunks/4JEMNLS3.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
/*! palgres 0.10.2 — Palbase SDK License (see LICENSE) */
|
|
2
|
-
import{a as b,e as g,f as p,z as c}from"./Q53F7ZFX.js";var w={uuid:"uuid",text:"text",integer:"integer",bigint:"bigint",numeric:"numeric",boolean:"boolean",timestamp:"timestamp with time zone",date:"date",jsonb:"jsonb"};function O(e){let n=Array.isArray(e)?e:[e],t={tables:[],enums:[],extensions:[],raw:[]},i=new Map;for(let s of n){for(let o of Object.values(s.tables))t.tables.push(D(s.name,o,i));t.extensions.push(...s.extensions);for(let o of s.raw??[])t.raw.push({name:o.name,schema:s.name,sql:o.up,...o.drop!==void 0?{drop:o.drop}:{},...o.before===!0?{before:!0}:{}})}t.enums=[...i.values()];for(let s of n)for(let o of Object.values(s.tables))for(let f of o.raw??[])t.raw.push({name:f.name,table:`${s.name}.${o.name}`,sql:f.up});return t}function _(e,n){return!n&&b(e.onDelete,e.onUpdate)?{...e,deferrable:!0}:e}function K(e){return e._def}function D(e,n,t){let i=[],s=(n.unique??[]).map(r=>({name:r.name,columns:[...r.columns]})),o=[],f=[],l=new Set;for(let[r,y]of Object.entries(n.columns)){let u=K(y),m={name:r,type:N(e,r,n.name,u,t),nullable:u.nullable},h=P(u);if(h!==void 0&&(m.default=h),u.renamedFrom!==void 0&&(m.renamedFrom=u.renamedFrom),u.ignored&&(m.ignored=!0),u.fill!==void 0&&(m.fill=u.fill),u.identity!==void 0&&(m.identity=u.identity==="always"?"always":"by default"),u.generated!==void 0&&(m.generated={...u.generated}),u.user!==void 0&&(m.user={...u.user}),u.primaryKey&&f.push(r),u.unique&&s.push({name:`${n.name}_${r}_key`,columns:[r]}),u.references!==void 0){let[T,E]=C(u.references.table);o.push(_({name:`${Q(e,n.name)}_${r}_fkey`,columns:[r],refSchema:T,refTable:E,refColumns:[u.references.column],onDelete:u.onDeleteAction??"no action",onUpdate:u.onUpdateAction??"no action"},u.deferrable===!1)),u.index===!1&&l.add(r)}i.push(m)}for(let r of n.foreignKeys??[]){r.index===!1&&r.columns.length===1&&l.add(r.columns[0]);let[y,u]=C(r.refTable);o.push(_({name:r.name,columns:[...r.columns],refSchema:y,refTable:u,refColumns:[...r.refColumns],onDelete:r.onDelete??"no action",onUpdate:r.onUpdate??"no action",...r.match==="full"?{match:"full"}:{}},r.deferrable===!1))}let a={schema:e,name:n.name,columns:i,primaryKey:n.primaryKey!==void 0?[...n.primaryKey]:f,uniques:s,indexes:(n.indexes??[]).map(S),checks:(n.checks??[]).map(r=>({name:r.name,expr:r.expr})),foreignKeys:o,rls:n.rls,policies:n.policies.map(A)};return n.ignored&&(a.ignored=!0),n.search!==void 0&&(a.search={...n.search,from:[...n.search.from]}),a.uniques=k(a.uniques),q(a,l,new Set((n.raw??[]).map(r=>r.name)),new Set(n.policies.filter(r=>r.indexes===!1).map(r=>r.name))),a}function N(e,n,t,i,s){let o=i.type;if(o==="enum"||o==="enum[]"){let f=i.enumName,l=`${e}.${f}`,a=i.enumValues??[],r=s.get(l);if(r&&r.values.join()!==a.join())throw new Error(`enum ${l} is declared with different values by ${t}.${n}: (${r.values.join(", ")}) and (${a.join(", ")}) \u2014 one type has one list`);return s.set(l,{schema:e,name:f,values:[...a]}),o==="enum[]"?`${p(e,f)}[]`:p(e,f)}return o==="sql"?g(i.sqlType):o==="numeric"&&i.precision!==void 0?`numeric(${i.precision},${i.scale??0})`:o.endsWith("[]")?`${w[o.slice(0,-2)]??o.slice(0,-2)}[]`:w[o]??o}function P(e){if(e.defaultSql!==void 0)return e.defaultSql;if(e.defaultRandom)return"gen_random_uuid()";if(e.defaultNow)return e.type==="date"?"CURRENT_DATE":"now()";let n=e.defaultValue;if(n!==void 0){if(Array.isArray(n)&&n.length===0&&e.type.endsWith("[]"))return"'{}'";if(typeof n=="string")return`'${n.replace(/'/g,"''")}'`;if(typeof n=="boolean")return n?"true":"false";if(typeof n=="number"){if(!Number.isFinite(n))throw new Error(`default ${n} is not a number Postgres can store`);return String(n)}return n===null?"NULL":`'${JSON.stringify(n).replace(/'/g,"''")}'::jsonb`}}function S(e){let n=e.expression!==void 0?[{expr:e.expression}]:(e.columns??[]).map(s=>({column:s}));for(let s of n)e.sort==="desc"&&(s.desc=!0),e.nulls!==void 0&&(s.nulls=e.nulls);let t={name:e.name,keys:n,unique:e.unique===!0},i=e.where===void 0?void 0:c(e.where);return i!==void 0&&(t.where=i),e.include?.length&&(t.include=[...e.include]),t}function A(e){let n={name:e.name,for:e.command,to:e.roles.length===0?["public"]:[...e.roles]},t=c(e.using),i=c(e.withCheck);return t!==void 0&&(n.using=t),i!==void 0&&(n.withCheck=i),e.permissive||(n.restrictive=!0),n}function q(e,n,t,i){let s=new Set,o=l=>l[0]!==void 0&&s.add(l[0]);o(e.primaryKey);for(let l of e.uniques)o(l.columns);for(let l of e.indexes){let a=l.keys[0];a&&"column"in a&&l.where===void 0&&s.add(a.column)}let f=l=>{if(s.has(l)||e.columns.find(r=>r.name===l)?.ignored)return;let a=`${e.name}_${l}_idx`;Buffer.byteLength(a)>63||e.indexes.some(r=>r.name===a)||t.has(a)||(e.indexes.push({name:a,keys:[{column:l}],unique:!1}),s.add(l))};for(let l of e.foreignKeys)l.columns.length===1&&!n.has(l.columns[0])&&f(l.columns[0]);for(let l of e.policies)if(!i.has(l.name))for(let a of B(e,l))f(a)}function B(e,n){let t=[];for(let i of[n.using,n.withCheck])if(i!==void 0)for(let s of i.matchAll(/(?<![.\w"])"((?:[^"]|"")+)"(?!\s*\.)/g)){let o=s[1].replace(/""/g,'"');e.columns.some(f=>f.name===o)&&!t.includes(o)&&t.push(o)}return t}function C(e){let n=e.indexOf(".");return n<0?["public",e]:[e.slice(0,n),e.slice(n+1)]}function Q(e,n){return e==="public"?n:`${e}_${n}`}function k(e){let n=new Map;return e.filter(t=>{let i=n.get(t.name);if(i===void 0)return n.set(t.name,t),!0;if(i.columns.join()!==t.columns.join())throw new Error(`palgres: unique ${t.name} is declared over (${i.columns.join(", ")}) and over (${t.columns.join(", ")}): give one of them another name`);return!1})}var x=new Set(["uuid","text","integer","bigint","numeric","boolean","timestamp","date","enum"]),U={number:{fromDb:e=>e==null?e:Number(e),toDb:e=>{if(e==null||typeof e!="number")return e==null?e:String(e);if(!Number.isFinite(e))throw new Error(`asNumber() cannot write ${e}: it is not a finite number, and the column holds one.`);if(Number.isInteger(e)&&!Number.isSafeInteger(e))throw new Error(`asNumber() cannot write ${e}: it is outside the safe integer range and would be stored as a different number. Declare .asDecimal() and hand this column a string.`);return String(e)},tsType:"number"},decimal:{fromDb:e=>e==null?e:String(e),toDb:e=>e==null?e:String(e),tsType:"string"}},d=class e{_def;constructor(n,t){this._def=t??{type:n,nullable:!1,primaryKey:!1}}primaryKey(){return this._def.primaryKey=!0,new e(this._def.type,this._def)}notNull(){return this._def.nullable=!1,new e(this._def.type,this._def)}nullable(){if(this._def.identity!==void 0)throw new Error(".nullable() on an identity column: an identity is always NOT NULL");return this._def.nullable=!0,new e(this._def.type,this._def)}default(n){return this._def.defaultValue=n,new e(this._def.type,this._def)}defaultRandom(){return this._def.defaultRandom=!0,new e(this._def.type,this._def)}defaultNow(){return this._def.defaultNow=!0,new e(this._def.type,this._def)}defaultSql(n){if(typeof n!="string"||n.trim()==="")throw new Error("defaultSql() takes the SQL of the default");return this._def.defaultSql=n,new e(this._def.type,this._def)}dbAssigned(){return this._def.dbAssigned=!0,new e(this._def.type,this._def)}array(){let n=this._def.type;if(!x.has(n))throw new Error(`${n} column: .array() is available on uuid(), text(), integer(), bigint(), numeric(), boolean(), timestamp(), date() and enumType()`);if(this._def.defaultValue!==void 0||this._def.defaultNow||this._def.defaultRandom||this._def.defaultSql!==void 0)throw new Error(`.array() comes before the default \u2014 write ${n}().array().default([])`);if(this._def.codec!==void 0||this._def.transform!==void 0)throw new Error(".array() cannot follow a codec or a transform \u2014 they convert one value, not a list of them");let t=`${n}[]`;return new e(t,{...this._def,type:t})}identity(n){if(this._def.type!=="integer"&&this._def.type!=="bigint"&&!(this._def.type==="sql"&&this._def.sqlType==="smallint"))throw new Error(`${this._def.type} column: .identity() is for integer(), bigint() and sqlType('smallint') columns`);if(this._def.defaultValue!==void 0||this._def.defaultNow||this._def.defaultRandom||this._def.defaultSql!==void 0||this._def.fill!==void 0)throw new Error(".identity() is the column's value \u2014 it cannot also have a default or a fill");let t=n?.always===!1?"byDefault":"always";return new e(this._def.type,{...this._def,nullable:!1,identity:t})}generated(n,t={}){if(typeof n!="string"||n.trim()==="")throw new Error(".generated() takes the SQL expression the column is");if(this._def.defaultValue!==void 0||this._def.defaultNow||this._def.defaultRandom||this._def.defaultSql!==void 0||this._def.fill!==void 0||this._def.identity!==void 0)throw new Error(".generated() is the column's value \u2014 it cannot also have a default, a fill or an identity");return new e(this._def.type,{...this._def,generated:{expr:n,stored:t.stored===!0}})}fillWith(n){return new e(this._def.type,{...this._def,fill:n})}renamedFrom(n){return this._def.renamedFrom=n,this}ignored(){return new e(this._def.type,{...this._def,ignored:!0})}references(n,t){if(typeof n!="function")throw new Error('references(...) takes a callback: write references(() => otherTable.column). The two-string form references("table", "column") is gone \u2014 a string cannot be type-checked and cannot point at a table that does not exist yet.');return this._def.referencesThunk=n,t?.as!==void 0&&(this._def.refAs=t.as),t?.reverseAs!==void 0&&(this._def.reverseAs=t.reverseAs),t?.onDelete!==void 0&&(this._def.onDeleteAction=t.onDelete),t?.onUpdate!==void 0&&(this._def.onUpdateAction=t.onUpdate),t?.index===!1&&(this._def.index=!1),t?.deferrable===!1&&(this._def.deferrable=!1),new e(this._def.type,this._def)}selfReferences(n,t){return this._def.selfRefColumn=n,t?.as!==void 0&&(this._def.refAs=t.as),t?.onDelete!==void 0&&(this._def.onDeleteAction=t.onDelete),new e(this._def.type,this._def)}onDelete(n){return this._def.onDeleteAction=n,new e(this._def.type,this._def)}unique(){return this._def.unique=!0,new e(this._def.type,this._def)}asNumber(){return this.withCodec("number","asNumber")}asDecimal(){return this.withCodec("decimal","asDecimal")}withCodec(n,t){if(this._def.type!=="bigint"&&this._def.type!=="numeric")throw new Error(`${this._def.type} column: .${t}() is only available on bigint()/numeric() \u2014 those are the exact-precision types that surface as string.`);if(this._def.transform!==void 0)throw new Error(`.${t}() and .transform() are two ways to say the same thing on one column \u2014 keep .${t}().`);if(this._def.codec!==void 0)throw new Error(`column already declares .as${this._def.codec==="number"?"Number":"Decimal"}(); a column has ONE codec.`);return new e(this._def.type,{...this._def,codec:n})}transform(n){if(this._def.codec!==void 0)throw new Error("column already declares a named codec; .transform() and .as*() are two ways to say the same thing \u2014 keep the codec.");return new e(this._def.type,{...this._def,transform:n})}};function V(){return new d("uuid")}function j(){return new d("text")}function L(){return new d("integer")}function F(){return new d("bigint")}function I(e){if(e===void 0)return new d("numeric");let{precision:n,scale:t=0}=e;if(!Number.isInteger(n)||n<1||n>1e3)throw new Error(`numeric({ precision: ${n} }): precision is a whole number from 1 to 1000`);if(!Number.isInteger(t)||t<0||t>n)throw new Error(`numeric({ precision: ${n}, scale: ${t} }): scale is a whole number from 0 to the precision`);return new d("numeric",{type:"numeric",nullable:!1,primaryKey:!1,precision:n,scale:t})}function M(){return new d("date")}var W={uuid:"uuid()",text:"text()",integer:"integer()",int:"integer()",int4:"integer()",bigint:"bigint()",int8:"bigint()",numeric:"numeric()",decimal:"numeric()",boolean:"boolean()",bool:"boolean()",timestamptz:"timestamp()","timestamp with time zone":"timestamp()",date:"date()",jsonb:"jsonb()"};function Y(e){let n=typeof e=="string"?e.trim():"";if(n==="")throw new Error('sqlType() takes the Postgres type, e.g. sqlType("inet")');let t=n.toLowerCase().replace(/\[\]$/,""),i=W[t];if(i!==void 0&&!(n.endsWith("[]")&&!x.has(i.slice(0,-2))))throw new Error(`sqlType(${JSON.stringify(n)}): the DSL has this type \u2014 declare it as ${i}${n.endsWith("[]")?".array()":""}`);return new d("sql",{type:"sql",nullable:!1,primaryKey:!1,sqlType:n})}var H=new Set(["smallint","real","double precision"]),z=new Set(["json"]);function J(){return new d("boolean")}function G(){return new d("timestamp")}function X(){return new d("jsonb")}function Z(e,n){let t=new d("enum");return t._def.enumName=e,t._def.enumValues=[...n],t}function ee(){let e=new d("text");return e._def.nullable=!1,e._def.user={onDelete:"cascade",owns:!0},e}function ne(e){let n=new d("text");return n._def.user={onDelete:e.onDelete,owns:!1},e.as!==void 0&&(n._def.refAs=e.as),n}export{O as a,B as b,U as c,d,V as e,j as f,L as g,F as h,I as i,M as j,Y as k,H as l,z as m,J as n,G as o,X as p,Z as q,ee as r,ne as s};
|
package/dist/chunks/Q53F7ZFX.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
/*! palgres 0.10.2 — Palbase SDK License (see LICENSE) */
|
|
2
|
-
import{createHash as z}from"node:crypto";function fe(n,e){let t=r=>r==="cascade"||r==="set null"||r==="set default";return t(n)||t(e)}var L=class extends Error{problems;constructor(e){super(`the schema is not valid:
|
|
3
|
-
- ${e.join(`
|
|
4
|
-
- `)}`),this.name="SchemaError",this.problems=e}},ue=()=>({tables:[],enums:[],extensions:[],raw:[]}),b=n=>`${n.schema}.${n.name}`,Q={int:"integer",int4:"integer",int2:"smallint",int8:"bigint",float4:"real",float8:"double precision",bool:"boolean",timestamptz:"timestamp with time zone",timestamp:"timestamp without time zone","timestamp without time zone":"timestamp without time zone",timetz:"time with time zone",time:"time without time zone",varchar:"character varying",char:"character",decimal:"numeric"};function D(n){let e=n.trim().replace(/\s+/g," "),t="";for(;e.endsWith("[]");)t+="[]",e=e.slice(0,-2).trim();let r=/^((?:"(?:[^"]|"")+"|[A-Za-z_][A-Za-z0-9_ ]*?)(?:\.(?:"(?:[^"]|"")+"|[A-Za-z_][A-Za-z0-9_]*))?)\s*(\(.*\))?$/.exec(e);if(!r)return n.trim()+t;let c=r[2]?r[2].replace(/\s+/g,""):"",o=W(r[1]);return o===void 0?n.trim()+t:o.length===1&&o[0].quoted?`"${o[0].name.replace(/"/g,'""')}"${c}${t}`:o.length===1?`${Q[o[0].name]??o[0].name}${c}${t}`:`${o.map(l=>N(l.name)).join(".")}${c}${t}`}var N=n=>/^[a-z_][a-z0-9_$]*$/.test(n)?n:`"${n.replace(/"/g,'""')}"`,de=(n,e)=>`${N(n)}.${N(e)}`;function W(n){let e=[],t=0;for(;t<n.length;){if(n[t]==='"'){let r=t+1,c="";for(;;){if(r>=n.length)return;if(n[r]==='"'){if(n[r+1]==='"'){c+='"',r+=2;continue}break}c+=n[r++]}e.push({name:c,quoted:!0}),t=r+1}else{let r=/^[A-Za-z_][A-Za-z0-9_ ]*/.exec(n.slice(t));if(!r)return;e.push({name:r[0].trim().toLowerCase(),quoted:!1}),t+=r[0].length}if(t<n.length){if(n[t]!==".")return;t++}}return e}var J=/^[A-Za-z_][A-Za-z0-9_$]*$/,Z=63;function y(n,e,t=!1){if(typeof e!="string"||e==="")return`${n} has no name`;if(t?e.includes("\0"):!J.test(e))return`${n} "${e}": a name is letters, digits and _ (starting with a letter or _)`;if(Buffer.byteLength(e)>Z)return`${n} "${e}": longer than Postgres' 63 bytes, it would be cut`;if(e.startsWith("palgres_")||e.startsWith("_palgres"))return`${n} "${e}": names starting with palgres_ are Palgres' own`}var G="vector",F=["cosine","euclidean","inner_product"],A=1536,X=2e3,R=4e3,Y=/^(text|citext|public\.citext|character varying(\(\d+\))?)$/;function q(n,e,t){let r=[];e.primaryKey.length!==1&&r.push(`${n} declares search and has ${e.primaryKey.length===0?"no primary key":`a primary key of ${e.primaryKey.length} columns`}: a row's vector is kept by its key \u2014 give the table a single-column primary key (uuid().primaryKey())`),(!Array.isArray(t.from)||t.from.length===0)&&r.push(`${n}: search.from is empty \u2014 name the text columns a row is found by (search: { from: ['title', 'body'], model })`);let c=new Set;for(let o of Array.isArray(t.from)?t.from:[]){c.has(o)&&r.push(`${n}: search.from names "${o}" twice \u2014 name each column once`),c.add(o);let l=e.columns.find(i=>i.name===o);l?l.ignored?r.push(`${n}: search.from names "${o}", which is ignored \u2014 take it out of search.from, or keep the column`):Y.test(D(l.type))?l.generated&&r.push(`${n}: search.from names "${o}", a generated column \u2014 no write names it, so nothing would say when to embed the row again: name the columns it is computed from`):r.push(`${n}: search.from names "${o}", which is ${l.type} \u2014 search reads text (text, varchar, citext): name a text column, or keep the text in one`):r.push(`${n}: search.from names "${o}", which the table does not have \u2014 name its text columns`)}return(typeof t.model!="string"||t.model.trim()==="")&&r.push(`${n}: search.model is empty \u2014 name the embedding model (search: { from, model: 'embedding' })`),F.includes(t.metric)||r.push(`${n}: search.metric is ${JSON.stringify(t.metric)} \u2014 it is ${F.map(o=>`'${o}'`).join(", ")}`),(!Number.isInteger(t.dimensions)||t.dimensions<1||t.dimensions>R)&&r.push(`${n}: search.dimensions is ${String(t.dimensions)} \u2014 it is 1 to ${R}: pgvector indexes a vector of at most ${X} dimensions and a half-precision one of at most ${R}; ask the model for fewer (search: { dimensions: ${A} })`),t.staleness!=="null"&&t.staleness!=="keep"&&r.push(`${n}: search.staleness is ${JSON.stringify(t.staleness)} \u2014 it is 'null' (a changed row is not found by its old meaning) or 'keep' (it is, until embedded again)`),e.columns.some(o=>o.name==="_score"&&!o.ignored)&&r.push(`${n} declares search and has a column "_score": search() answers each row with its _score \u2014 rename the column`),r}function me(n){let e=[],t=i=>i&&e.push(i),r=new Map,c=new Map,o=(i,a,u)=>{let f=`${i}.${a}`,d=c.get(f);d?e.push(`${u} "${a}" has the name of ${d} in schema ${i}`):c.set(f,u)};for(let i of n.enums)t(y("an enum",i.name)),i.values.length===0&&e.push(`enum ${i.schema}.${i.name} has no values`),new Set(i.values).size!==i.values.length&&e.push(`enum ${i.schema}.${i.name} names a value twice`);for(let i of n.tables){let a=b(i);t(y("a schema",i.schema)),t(y("a table",i.name)),r.has(a)&&e.push(`table ${a} is declared twice`),r.set(a,i),o(i.schema,i.name,`table ${a}`);let u=new Map,f=(s,m)=>{let C=u.get(s);C?e.push(`${m} "${s}" has the name of a ${C} of table ${a}`):u.set(s,m)},d=new Map;for(let s of i.columns)t(y(`column ${a}.${s.name}`,s.name)),d.has(s.name)&&e.push(`column ${a}.${s.name} is declared twice`),d.set(s.name,s),s.fill!==void 0&&s.nullable&&e.push(`column ${a}.${s.name}: fillWith is for a column that is NOT NULL`),s.identity&&s.default!==void 0&&e.push(`column ${a}.${s.name}: an identity column has no default`),s.generated&&(s.default!==void 0||s.identity||s.fill!==void 0)&&e.push(`column ${a}.${s.name}: a generated column has no default, identity or fill`),s.generated&&!s.generated.expr.trim()&&e.push(`column ${a}.${s.name}: a generated column's expression is empty`),s.identity&&!["smallint","integer","bigint"].includes(D(s.type))&&e.push(`column ${a}.${s.name}: an identity column is smallint, integer or bigint`),s.identity&&s.nullable&&e.push(`column ${a}.${s.name}: an identity column is NOT NULL`);let g=(s,m)=>{let C=d.get(s);C?C.ignored&&e.push(`${m} names column "${s}", which is ignored`):e.push(`${m} names column "${s}", which ${a} does not have`)};for(let s of i.primaryKey)g(s,`the primary key of ${a}`);for(let s of i.primaryKey)d.get(s)?.nullable&&e.push(`column ${a}.${s} is in the primary key, so it is not nullable`);for(let s of i.uniques){t(y(`a unique constraint of ${a}`,s.name,!0)),o(i.schema,s.name,`unique constraint ${a}.${s.name}`),f(s.name,"unique constraint"),s.columns.length===0&&e.push(`unique constraint ${s.name} names no column`);for(let m of s.columns)g(m,`unique constraint ${s.name}`)}for(let s of i.indexes){t(y(`an index of ${a}`,s.name,!0)),o(i.schema,s.name,`index ${a}.${s.name}`),s.keys.length===0&&e.push(`index ${s.name} has no key`);for(let m of s.keys)"column"in m&&g(m.column,`index ${s.name}`);for(let m of s.include??[])g(m,`index ${s.name}`)}for(let s of i.checks)t(y(`a check of ${a}`,s.name,!0)),f(s.name,"check"),s.expr.trim()||e.push(`check ${s.name} has no expression`);for(let s of i.foreignKeys){t(y(`a foreign key of ${a}`,s.name,!0)),f(s.name,"foreign key"),(s.columns.length===0||s.columns.length!==s.refColumns.length)&&e.push(`foreign key ${s.name} names ${s.columns.length} column(s) and references ${s.refColumns.length}`);for(let m of s.columns)g(m,`foreign key ${s.name}`)}let k=new Set;for(let s of i.policies)t(y(`a policy of ${a}`,s.name,!0)),k.has(s.name)&&e.push(`policy ${a}.${s.name} is declared twice`),k.add(s.name),s.to.length===0&&e.push(`policy ${a}.${s.name} names no role`),s.for==="insert"&&s.using!==void 0&&e.push(`policy ${a}.${s.name}: an insert policy has only withCheck`),(s.for==="select"||s.for==="delete")&&s.withCheck!==void 0&&e.push(`policy ${a}.${s.name}: a ${s.for} policy has only using`);i.policies.length>0&&!i.rls&&e.push(`table ${a} has policies but row-level security off: they would do nothing`),i.search!==void 0&&e.push(...q(`table ${a}`,i,i.search))}for(let i of n.tables)for(let a of i.foreignKeys){let u=r.get(`${a.refSchema}.${a.refTable}`);if(!u){e.push(`foreign key ${a.name} of ${b(i)} references ${a.refSchema}.${a.refTable}, which is not declared`);continue}u.ignored&&e.push(`foreign key ${a.name} of ${b(i)} references ${b(u)}, which is ignored`);for(let f of a.refColumns)u.columns.some(d=>d.name===f&&!d.ignored)||e.push(`foreign key ${a.name} references ${b(u)}.${f}, which it does not have`)}let l=new Set;for(let i of n.raw)t(y("a raw unit",i.name)),l.has(i.name)&&e.push(`raw unit ${i.name} is declared twice`),l.add(i.name),i.sql.trim()||e.push(`raw unit ${i.name} has no SQL`),i.before&&i.table!==void 0&&e.push(`raw unit ${i.name}: before is for a schema's unit, not a table's \u2014 a table's is made after its table`);return e}function j(n){let e=t=>[...t].sort((r,c)=>r.name<c.name?-1:r.name>c.name?1:0);return{tables:[...n.tables].map(t=>({...t,schema:t.schema||"public",columns:t.columns.map(r=>({...r,type:D(r.type)})),uniques:e(t.uniques),indexes:e(t.indexes),checks:e(t.checks),foreignKeys:e(t.foreignKeys),policies:e(t.policies).map(r=>({...r,to:[...r.to].sort()}))})).sort((t,r)=>b(t)<b(r)?-1:b(t)>b(r)?1:0),enums:[...n.enums].sort((t,r)=>`${t.schema}.${t.name}`<`${r.schema}.${r.name}`?-1:1),extensions:[...new Set([...n.extensions,...n.tables.some(t=>t.search!==void 0)?[G]:[]])].sort(),raw:e(n.raw)}}function pe(n){let e=j(n),t=new Map(n.raw.map((r,c)=>[r.name,c]));return e.raw.sort((r,c)=>(t.get(r.name)??0)-(t.get(c.name)??0)),e}function O(n){return Array.isArray(n)?`[${n.map(O).join(",")}]`:n&&typeof n=="object"?`{${Object.entries(n).filter(([,t])=>t!==void 0).sort(([t],[r])=>t<r?-1:1).map(([t,r])=>`${JSON.stringify(t)}:${O(r)}`).join(",")}}`:JSON.stringify(n)}function he(n){return z("sha256").update(O(j(n))).digest("hex").slice(0,32)}var M=Symbol.for("palbase.table.meta"),x=Symbol.for("palbase.ref.target"),S=class{_def;constructor(e){if(e.trim()==="")throw new Error("foreignKey(name): a foreign key's name cannot be empty \u2014 Palbase reconciles constraints by name.");this._def={name:e,columns:[],refTable:"",refColumns:[]}}on(...e){return this._def.columns=e.map(t=>{let r=t.expr;if(r.kind!=="col")throw new Error(`foreignKey(${this._def.name}).on(): takes column references only \u2014 an expression cannot be part of a foreign key.`);return r.name}),this}references(e,...t){if(t.length===0)throw new Error(`foreignKey(${this._def.name}).references(): needs at least one target column.`);return this._def.refColumns=[...t],typeof e=="function"?(Object.defineProperty(this._def,x,{value:()=>e()[M],enumerable:!1,configurable:!0}),this):(this._def.refTable=e[M].name,Object.defineProperty(this._def,x,{value:e[M],enumerable:!1,configurable:!0}),this)}onUpdate(e){return this._def.onUpdate=e,this}onDelete(e){return this._def.onDelete=e,this}match(e){return this._def.match=e,this}index(e){if(e!==!1)throw new Error(`foreignKey(${this._def.name}).index(): only index(false) \u2014 an index is made unasked`);return this._def.index=!1,this}deferrable(e){if(e!==!1)throw new Error(`foreignKey(${this._def.name}).deferrable(): only deferrable(false) \u2014 a key with an action is deferrable unasked`);return this._def.deferrable=!1,this}};function V(n){return new S(n)}function B(n){let e=n instanceof S?n._def:n;if(e.columns.length===0)throw new Error(`foreignKey(${e.name}): needs at least one local column \u2014 call .on(...).`);let t=typeof e[x]=="function";if(e.refColumns.length===0||e.refTable===""&&!t)throw new Error(`foreignKey(${e.name}): needs the target table and its columns \u2014 call .references(...).`);if(e.columns.length!==e.refColumns.length)throw new Error(`foreignKey(${e.name}): ${e.columns.length} local column(s) (${e.columns.join(", ")}) but ${e.refColumns.length} target column(s) (${e.refColumns.join(", ")}) \u2014 the two lists must be the same length.`);return e}var h=class n{expr;constructor(e){this.expr=e}bin(e,t){return new n({kind:"binop",op:e,left:this.expr,right:ee(t)})}eq(e){return this.bin("eq",e)}neq(e){return this.bin("neq",e)}gt(e){return this.bin("gt",e)}gte(e){return this.bin("gte",e)}lt(e){return this.bin("lt",e)}lte(e){return this.bin("lte",e)}isNull(e=!1){return new n({kind:"isNull",expr:this.expr,negated:e})}and(...e){return new n({kind:"and",parts:[this.expr,...e.map(t=>t.expr)]})}or(...e){return new n({kind:"or",parts:[this.expr,...e.map(t=>t.expr)]})}not(){return new n({kind:"not",expr:this.expr})}};function ee(n){return n instanceof h?n.expr:{kind:"lit",value:n}}function E(n,e=0,t=""){let r=`pb_e${e}`;return{col:c=>new h(e===0?{kind:"col",name:c}:{kind:"col",name:c,alias:r}),auth:{uid:()=>new h({kind:"authUid"})},lit:c=>new h({kind:"lit",value:c}),and:(...c)=>new h({kind:"and",parts:c.map(o=>o.expr)}),or:(...c)=>new h({kind:"or",parts:c.map(o=>o.expr)}),not:c=>new h({kind:"not",expr:c.expr}),existsIn:(c,o)=>{let l=c[_],i=E(Object.keys(l.columns),e+1,t),a={kind:"exists",table:l.name,alias:`pb_e${e+1}`,outer:t,predicate:o(i).expr};return Object.defineProperty(a,x,{value:l,enumerable:!1,configurable:!0}),new h(a)}}}function p(n){return`"${n.replace(/"/g,'""')}"`}var P=class{_def;constructor(e){this._def={name:e,command:"all",roles:["authenticated"],using:null,withCheck:null,permissive:!0}}for(e){return this._def.command=e,this}to(...e){return this._def.roles=e,this}using(e){return this._def.using=e instanceof h?e.expr:e,this}memberOf(e,t,r={}){if(this._def.using!==null)throw new Error(`policy(${this._def.name}).memberOf(): this policy already has a using() expression. Write one or the other \u2014 memberOf IS the using expression.`);let c=r.column??"id",o=r.userColumn??"user_id";return this._def.using=`${p(c)} IN (SELECT ${p(t)} FROM ${p(e)} WHERE ${p(o)} = (select auth.uid()))`,this}withCheck(e){return this._def.withCheck=e instanceof h?e.expr:e,this}withoutIndexes(){return this._def.indexes=!1,this}as(e){return this._def.permissive=e==="permissive",this}};function $e(n){return new P(n)}function T(n,e){switch(n.kind){case"exists":throw new Error(`check(${e}): \`existsIn(...)\` is a subquery and PostgreSQL forbids subqueries in a CHECK constraint \u2014 express a cross-table rule as a composite FOREIGN KEY (foreignKeys), or as a guard.`);case"authUid":throw new Error(`check(${e}): \`auth.uid()\` renders as a subquery \`(SELECT auth.uid())\` and PostgreSQL forbids subqueries in a CHECK constraint \u2014 a caller-dependent rule belongs in an RLS policy, not a CHECK.`);case"binop":T(n.left,e),T(n.right,e);return;case"and":case"or":for(let t of n.parts)T(t,e);return;case"not":case"isNull":T(n.expr,e);return;default:return}}var ne={eq:"=",neq:"<>",gt:">",gte:">=",lt:"<",lte:"<="};function U(n){if(n===null)return"NULL";if(typeof n=="boolean")return n?"true":"false";if(typeof n=="number"){if(!Number.isFinite(n))throw new Error(`check(): ${n} is not an SQL number literal.`);return String(n)}return`'${n.replace(/'/g,"''")}'`}function w(n,e){switch(n.kind){case"col":return p(n.name);case"lit":return U(n.value);case"binop":{let t=ne[n.op];if(t===void 0)throw new Error(`check(${e}): \`${n.op}\` is not supported in a CHECK expression.`);return`(${w(n.left,e)} ${t} ${w(n.right,e)})`}case"and":case"or":{if(n.parts.length===0)return n.kind==="and"?"(true)":"(false)";let t=n.kind==="and"?" AND ":" OR ";return`(${n.parts.map(r=>w(r,e)).join(t)})`}case"not":return`(NOT ${w(n.expr,e)})`;case"isNull":return`(${w(n.expr,e)} IS ${n.negated?"NOT NULL":"NULL"})`;default:{let t=n.kind;throw new Error(`check(${e}): a \`${t}\` node is not one a CHECK expression takes.`)}}}function te(n,e){if(n.trim()==="")throw new Error("check(): the constraint's name cannot be empty \u2014 Palbase reconciles CHECKs by name.");return T(e.expr,n),{name:n,expr:w(e.expr,n)}}function we(n){if(n!==null)return typeof n=="string"?n:$(n,"")}var re={eq:"=",neq:"<>",gt:">",gte:">=",lt:"<",lte:"<="};function H(n){let e=n.indexOf(".");return e<0?`${p("public")}.${p(n)}`:`${p(n.slice(0,e))}.${p(n.slice(e+1))}`}function $(n,e){switch(n.kind){case"col":return n.alias!==void 0?`${p(n.alias)}.${p(n.name)}`:e===""?p(n.name):`${H(e)}.${p(n.name)}`;case"authUid":return"(SELECT auth.uid())";case"lit":return U(n.value);case"binop":{let t=re[n.op];if(t===void 0)throw new Error(`policy expression: \`${n.op}\` is not an operator a policy can use`);return`(${$(n.left,e)} ${t} ${$(n.right,e)})`}case"and":case"or":return n.parts.length===0?n.kind==="and"?"(true)":"(false)":`(${n.parts.map(t=>$(t,e)).join(n.kind==="and"?" AND ":" OR ")})`;case"not":return`(NOT ${$(n.expr,e)})`;case"isNull":return`(${$(n.expr,e)} IS ${n.negated?"NOT NULL":"NULL"})`;case"exists":if(n.outer==="")throw new Error("policy expression: an existsIn carries no outer table \u2014 its correlation would collapse to x = x and filter nothing");return`EXISTS (SELECT 1 FROM ${H(n.table)} ${p(n.alias)} WHERE (${$(n.predicate,n.outer)}))`}}function v(n,e){return n===""||n==="public"?e:`${n}.${e}`}var K=class{_def;constructor(e){if(e.trim()==="")throw new Error("index(name): an index's name cannot be empty \u2014 Palbase reconciles indexes by name.");this._def={name:e}}unique(){return this._def.unique=!0,this}on(...e){return this._def.columns=e.map(t=>{let r=t.expr;if(r.kind!=="col")throw new Error(`index(${this._def.name}).on(): takes column references only \u2014 for an expression, use onExpression("\u2026").`);return r.name}),this}onExpression(e){return this._def.expression=e,this}where(e){return this._def.where=e.expr,this}desc(){return this._def.sort="desc",this}asc(){return this._def.sort="asc",this}nulls(e){return this._def.nulls=e,this}include(e){return this._def.include=e,this}};function Se(n){return new K(n)}function ie(n){return n instanceof P?n._def:n}var _=Symbol.for("palbase.table.meta");function _e(n,e){if(n.trim()==="")throw new Error("defineTable(name, \u2026): name must not be empty");let t=(e.policies?.(E(Object.keys(e.columns),0,n))??[]).map(ie),r=t.length>0||e.rls!==!1,c={};for(let[f,d]of Object.entries(e.columns)){let g=d;ae(n,f,g._def);let k=Object.create(Object.getPrototypeOf(g));Object.defineProperty(k,"_def",{value:{...g._def},enumerable:!0,writable:!0}),c[f]=k}let o={name:n,columns:c,rls:r,policies:t};if(e.primaryKey!==void 0&&(o.primaryKey=[...e.primaryKey]),e.search!==void 0&&(o.search=se(n,e.search,c,o.primaryKey)),e.unique!==void 0&&(o.unique=e.unique.map(f=>({name:f.name,columns:[...f.columns]}))),e.raw!==void 0&&e.raw.length>0){for(let f of e.raw)if(f.drop!==void 0)throw new Error(`defineTable(${JSON.stringify(n)}): raw ${JSON.stringify(f.name)} carries a drop, but a table-level raw() has no teardown \u2014 a drop is run only for a schema-level unit: defineSchema(name, { tables, raw: [raw(name, up, { drop })] })`);o.raw=e.raw.slice()}let l=typeof e.checks=="function"?e.checks(E(Object.keys(e.columns),0,n)):e.checks?.slice()??[];l.length>0&&(o.checks=l);let i=(e.indexes?.(E(Object.keys(e.columns),0,n))??[]).map(f=>f instanceof K?f._def:f);i.length>0&&(o.indexes=i);let a=(e.foreignKeys?.(E(Object.keys(e.columns),0,n))??[]).map(B);a.length>0&&(o.foreignKeys=a),e.ignored===!0&&(o.ignored=!0);for(let f of Object.values(c))f._def.ownerTable=o;let u={...c};return Object.defineProperty(u,_,{value:o,enumerable:!1}),u}function se(n,e,t,r){if(e===null||typeof e!="object")throw new Error(`defineTable(${JSON.stringify(n)}): search is { from: [...columns], model } \u2014 got ${e===null?"null":typeof e}`);let c={from:Array.isArray(e.from)?[...e.from]:e.from,model:e.model,dimensions:e.dimensions??A,metric:e.metric??"cosine",staleness:e.staleness??"null"},o=Object.entries(t).map(([i,a])=>({name:i,def:a._def})),l=q(`defineTable(${JSON.stringify(n)})`,{columns:o.map(({name:i,def:a})=>({name:i,type:a.type==="sql"?a.sqlType??"sql":a.type,...a.ignored?{ignored:!0}:{},...a.generated?{generated:a.generated}:{}})),primaryKey:r??o.filter(({def:i})=>i.primaryKey===!0).map(({name:i})=>i)},c);if(l.length>0)throw new Error(l.join(`
|
|
5
|
-
`));return c}var I=new WeakMap;function oe(n,e){for(let t of Object.values(n))le(t,`table "${t.name}"`,e);for(let t of Object.values(n))for(let[r,c]of Object.entries(t.columns)){let o=c._def;if(o.selfRefColumn!==void 0){if(!(o.selfRefColumn in t.columns))throw new Error(`table "${t.name}" column "${r}": selfReferences("${o.selfRefColumn}") \u2014 no such column on this table`);o.references={table:v(e,t.name),column:o.selfRefColumn};continue}if(o.referencesThunk===void 0)continue;let l=o.referencesThunk(),i=l?._def?.ownerTable;if(i===void 0)throw new Error(`table "${t.name}" column "${r}": references(() => \u2026) must point at a column of a table declared with defineTable(...)`);let a=Object.entries(i.columns).find(([,f])=>f._def===l._def)?.[0];if(a===void 0)throw new Error(`table "${t.name}" column "${r}": the referenced column was not found on table "${i.name}"`);let u=I.get(i);if(u===void 0)throw new Error(`table "${t.name}" column "${r}": references(() => \u2026) points at table "${i.name}", which no defineSchema(...) has claimed yet \u2014 a foreign key cannot be written without its target's schema. Declare that table's schema first (one file per schema: db/public.ts, db/billing.ts \u2026, imported by this one).`);o.references={table:v(u,i.name),column:a}}}function ae(n,e,t){if(t.fill===void 0)return;let r=t.nullable===!0?"the column is .nullable() \u2014 .fillWith() fills a NOT NULL column; keep one of the two":t.ignored===!0?"the column is .ignored() \u2014 this release never writes it, so there is nothing to fill for it":t.defaultValue!==void 0||t.defaultRandom===!0||t.defaultNow===!0||t.defaultSql!==void 0?"the column has a default \u2014 the default already fills every row; keep one of the two":null;if(r!==null)throw new Error(`defineTable(${JSON.stringify(n)}): column "${e}" \u2014 .fillWith(): ${r}`)}function Ke(n,e){if(typeof n!="string")throw new Error(`defineSchema(...) takes the schema NAME first: defineSchema("public", { tables: [todos, lists] }). The one-argument form defineSchema({ tables: { \u2026 } }) is gone \u2014 a schema that does not say its own name cannot be told apart from another schema's table of the same name, which is what a cross-schema foreign key has to do. Migrate db/public.ts (one file per schema: db/public.ts, db/billing.ts \u2026): name the schema, and lift each table key onto the table itself with defineTable("todos", { columns: { \u2026 } }), then list the tables in the array.`);if(n.trim()==="")throw new Error("defineSchema(name, \u2026): name must not be empty");let t={};for(let l of e.tables){let i=l[_];if(t[i.name]!==void 0)throw new Error(`defineSchema("${n}"): two tables declare the name "${i.name}" \u2014 table names must be unique within a schema`);t[i.name]=i,I.set(i,n)}oe(t,n);let r=[...new Set(e.extensions??[])],c=(e.raw??[]).slice(),o=new Set;for(let l of c){if(typeof l.name!="string"||l.name.trim()==="")throw new Error(`defineSchema("${n}"): a raw unit needs a name`);if(o.has(l.name))throw new Error(`defineSchema("${n}"): two raw units are named "${l.name}" \u2014 a unit's name identifies it`);o.add(l.name)}return{name:n,tables:t,extensions:r,raw:c}}function le(n,e,t){let r=(l,i)=>{let a=l[x];if(a===void 0)return;let u;try{u=typeof a=="function"?a():a}catch(g){throw new Error(`${e}: ${i} names its target through a callback that failed: ${g.message}`)}if(u==null||typeof u.name!="string")throw new Error(`${e}: ${i}'s callback does not answer a table \u2014 write references(() => otherTable, \u2026)`);let f=I.get(u);if(f===void 0)throw new Error(`${e}: ${i} points at table "${u.name}", which no defineSchema(...) has claimed yet \u2014 a cross-table reference cannot be written without its target's schema. Declare that table's schema first (one file per schema: db/public.ts, db/billing.ts \u2026, imported by this one).`);let d=v(f,u.name);l.refTable!==void 0&&(l.refTable=d),l.table!==void 0&&(l.table=d)};for(let l of n.foreignKeys??[])r(l,`foreignKey("${l.name}")`);let c=`${t}.${n.name}`,o=(l,i)=>{if(!(l==null||typeof l=="string"))switch(l.kind){case"exists":r(l,i),l.table.includes(".")||(l.table=`public.${l.table}`),l.outer=c,o(l.predicate,i);return;case"binop":o(l.left,i),o(l.right,i);return;case"and":case"or":for(let a of l.parts)o(a,i);return;case"not":case"isNull":o(l.expr,i);return;default:return}};for(let l of n.policies)o(l.using,`policy("${l.name}")`),o(l.withCheck,`policy("${l.name}")`);for(let l of n.indexes??[])o(l.where,`index("${l.name}")`)}export{fe as a,L as b,ue as c,b as d,D as e,de as f,X as g,q as h,me as i,j,pe as k,he as l,S as m,V as n,v as o,K as p,Se as q,_ as r,_e as s,Ke as t,h as u,E as v,P as w,$e as x,te as y,we as z};
|