palgres 0.5.0 → 0.5.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/testing.js CHANGED
@@ -1,2 +1,2 @@
1
- /*! palgres 0.5.0 — 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/EQQRETM4.js";import"./chunks/TDJFB3V5.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.5.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/DDZIA3MX.js";import"./chunks/PBLTTHEI.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "palgres",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Palgres: the typed Postgres layer of Palbase's DB machines — schema, migrations and queries.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://palbase.studio",
@@ -201,6 +201,17 @@ declare const __colEnumValues: unique symbol;
201
201
  declare const __colPayload: unique symbol;
202
202
  declare const __colTransform: unique symbol;
203
203
  declare const __colWrite: unique symbol;
204
+ declare const __colKey: unique symbol;
205
+ /**
206
+ * The keys a column declares, for findUnique's types (TablesOf): `'primary'`,
207
+ * `'unique'`; `'ignored'` for a column marked .ignored(), which is no key
208
+ * whatever it declares (the run time leaves it out); and `'plain'` — which only
209
+ * the undeclared union carries, so a column that has declared none is told
210
+ * from one that has.
211
+ */
212
+ export type ColumnKey = "primary" | "unique" | "ignored" | "plain";
213
+ /** Q once `key` is declared: the undeclared union becomes the key alone; an ignored column stays ignored. */
214
+ type KeyAdded<Q extends ColumnKey, key extends "primary" | "unique"> = "plain" extends Q ? key : "ignored" extends Q ? Q : Q | key;
204
215
  /**
205
216
  * Fluent column builder with phantom type params:
206
217
  * K — ColumnType literal (e.g. "text", "integer")
@@ -211,15 +222,19 @@ declare const __colWrite: unique symbol;
211
222
  * T — transform target type (`never` when the column declares no transform;
212
223
  * `never` is the sentinel because it is the only type that survives
213
224
  * `[T] extends [never]` and never collides with a real target type)
225
+ * W — what writes it: `'readonly'` for a column only the database writes
226
+ * Q — the keys it declares (ColumnKey): `'primary'` after .primaryKey(),
227
+ * `'unique'` after .unique(); the whole union until one is declared, so a
228
+ * bare `ColumnBuilder` constraint still takes a keyed column
214
229
  *
215
- * All six params have defaults so bare `ColumnBuilder` (no args) still
230
+ * All params have defaults so bare `ColumnBuilder` (no args) still
216
231
  * satisfies `Record<string, ColumnBuilder>` in schema.ts without modification.
217
232
  *
218
- * The six `declare readonly` brand fields carry the phantom types into the
233
+ * The `declare readonly` brand fields carry the phantom types into the
219
234
  * structural shape so that conditional types like ColValue<C> can discriminate
220
235
  * on K without requiring runtime values on those fields.
221
236
  */
222
- export declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean = boolean, D extends boolean = boolean, E = unknown, P = unknown, T = unknown, W extends ColumnWrite = ColumnWrite> {
237
+ export declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends boolean = boolean, D extends boolean = boolean, E = unknown, P = unknown, T = unknown, W extends ColumnWrite = ColumnWrite, Q extends ColumnKey = ColumnKey> {
223
238
  readonly [__colKind]: K;
224
239
  readonly [__colNullable]: N;
225
240
  readonly [__colHasDefault]: D;
@@ -227,26 +242,27 @@ export declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends
227
242
  readonly [__colPayload]: P;
228
243
  readonly [__colTransform]: T;
229
244
  readonly [__colWrite]: W;
245
+ readonly [__colKey]: Q;
230
246
  readonly _def: ColumnDef;
231
247
  constructor(type: K, existingDef?: ColumnDef);
232
248
  /** Mark this column as the primary key. */
233
- primaryKey(): ColumnBuilder<K, N, D, E, P, T, W>;
249
+ primaryKey(): ColumnBuilder<K, N, D, E, P, T, W, KeyAdded<Q, "primary">>;
234
250
  /** Mark this column as NOT NULL (default). */
235
- notNull(): ColumnBuilder<K, false, D, E, P, T, W>;
251
+ notNull(): ColumnBuilder<K, false, D, E, P, T, W, Q>;
236
252
  /** Allow NULL values. */
237
- nullable(): ColumnBuilder<K, true, D, E, P, T, W>;
253
+ nullable(): ColumnBuilder<K, true, D, E, P, T, W, Q>;
238
254
  /** Set a default value. */
239
- default(value: unknown): ColumnBuilder<K, N, true, E, P, T, W>;
255
+ default(value: unknown): ColumnBuilder<K, N, true, E, P, T, W, Q>;
240
256
  /** UUID: generate a random default (gen_random_uuid()). */
241
- defaultRandom(): ColumnBuilder<K, N, true, E, P, T, W>;
257
+ defaultRandom(): ColumnBuilder<K, N, true, E, P, T, W, Q>;
242
258
  /** Timestamp: default to now(). */
243
- defaultNow(): ColumnBuilder<K, N, true, E, P, T, W>;
259
+ defaultNow(): ColumnBuilder<K, N, true, E, P, T, W, Q>;
244
260
  /**
245
261
  * A default the database computes, in SQL as it is: `nextval('items_id_seq'::regclass)`,
246
262
  * `CURRENT_DATE + 30`. What `palgres pull` writes for a default the other
247
263
  * builders cannot say; a value the schema itself gives is `default()`.
248
264
  */
249
- defaultSql(expr: string): ColumnBuilder<K, N, true, E, P, T, W>;
265
+ defaultSql(expr: string): ColumnBuilder<K, N, true, E, P, T, W, Q>;
250
266
  /**
251
267
  * The DATABASE assigns this column's value — a trigger, a rule, an identity.
252
268
  *
@@ -256,7 +272,7 @@ export declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends
256
272
  *
257
273
  * This is for a value something ELSE writes — a trigger in a raw() unit.
258
274
  */
259
- dbAssigned(): ColumnBuilder<K, N, true, E, P, T, W>;
275
+ dbAssigned(): ColumnBuilder<K, N, true, E, P, T, W, Q>;
260
276
  /**
261
277
  * An array of this type: `text().array()` is `text[]`, surfaced as `string[]`;
262
278
  * `enumType('Role', [...]).array()` is `"Role"[]`, with the enum's values as
@@ -269,7 +285,7 @@ export declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends
269
285
  * Filter with `contains` (every given element is in the array) and
270
286
  * `overlaps` (any of them is).
271
287
  */
272
- array(): ColumnBuilder<`${K & ArrayElementType}[]`, N, D, K extends 'enum' ? E : never, P, T, W>;
288
+ array(): ColumnBuilder<`${K & ArrayElementType}[]`, N, D, K extends 'enum' ? E : never, P, T, W, Q>;
273
289
  /**
274
290
  * The database numbers this column from a sequence: `GENERATED ALWAYS AS
275
291
  * IDENTITY`. Only on integer(), bigint() and sqlType('smallint'); the column
@@ -286,13 +302,13 @@ export declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends
286
302
  * the table under a lock to number them): add a bigint column with a default
287
303
  * and fill it.
288
304
  */
289
- identity(): ColumnBuilder<K, false, true, E, P, T, 'readonly'>;
305
+ identity(): ColumnBuilder<K, false, true, E, P, T, 'readonly', Q>;
290
306
  identity(opts: {
291
307
  always: true;
292
- }): ColumnBuilder<K, false, true, E, P, T, 'readonly'>;
308
+ }): ColumnBuilder<K, false, true, E, P, T, 'readonly', Q>;
293
309
  identity(opts: {
294
310
  always: false;
295
- }): ColumnBuilder<K, false, true, E, P, T, W>;
311
+ }): ColumnBuilder<K, false, true, E, P, T, W, Q>;
296
312
  /**
297
313
  * The column is `expr`, an SQL expression over this row's other columns —
298
314
  * `GENERATED ALWAYS AS (expr)`. Off every insert and update shape: Postgres
@@ -304,7 +320,7 @@ export declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends
304
320
  */
305
321
  generated(expr: string, opts?: {
306
322
  stored?: boolean;
307
- }): ColumnBuilder<K, N, true, E, P, T, 'readonly'>;
323
+ }): ColumnBuilder<K, N, true, E, P, T, 'readonly', Q>;
308
324
  /**
309
325
  * Fill this NOT NULL column for every row that does not supply it: the rows
310
326
  * already in the table AND every write of the release that is still serving,
@@ -321,7 +337,7 @@ export declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends
321
337
  * `defineTable` refuses any other column and names it. For a constant value,
322
338
  * use `.default()`.
323
339
  */
324
- fillWith(expr: string): ColumnBuilder<K, N, D, E, P, T, W>;
340
+ fillWith(expr: string): ColumnBuilder<K, N, D, E, P, T, W, Q>;
325
341
  /**
326
342
  * Declares that this column used to be called `previous`.
327
343
  *
@@ -333,7 +349,7 @@ export declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends
333
349
  * Once the rename has been applied the annotation is inert (the old name is no
334
350
  * longer there to rename), so it can be deleted at your leisure.
335
351
  */
336
- renamedFrom(previous: string): ColumnBuilder<K, N, D, E, P, T, W>;
352
+ renamedFrom(previous: string): ColumnBuilder<K, N, D, E, P, T, W, Q>;
337
353
  /**
338
354
  * Marks this column as no longer used by this release: the first step of
339
355
  * removing it from a table that holds rows. See {@link ColumnDef.ignored}.
@@ -341,7 +357,7 @@ export declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends
341
357
  * It returns a new column and leaves the one it was called on unmarked, so a
342
358
  * column built from a shared base does not mark the others built from it.
343
359
  */
344
- ignored(): ColumnBuilder<K, N, D, E, P, T, W>;
360
+ ignored(): ColumnBuilder<K, N, D, E, P, T, W, "ignored">;
345
361
  /**
346
362
  * Foreign key onto another table's column.
347
363
  *
@@ -381,7 +397,7 @@ export declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends
381
397
  * column that CHANGES is indexed, not because the table has indexes.
382
398
  */
383
399
  index?: boolean;
384
- }): ColumnBuilder<K, N, D, E, P, T, W>;
400
+ }): ColumnBuilder<K, N, D, E, P, T, W, Q>;
385
401
  /**
386
402
  * Foreign key onto THIS table (`parent_id → id`) — category trees, comment
387
403
  * replies, org charts.
@@ -394,11 +410,11 @@ export declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends
394
410
  selfReferences(column: string, opts?: {
395
411
  as?: string;
396
412
  onDelete?: OnDeleteAction;
397
- }): ColumnBuilder<K, N, D, E, P, T, W>;
413
+ }): ColumnBuilder<K, N, D, E, P, T, W, Q>;
398
414
  /** Set the ON DELETE action for a foreign key reference. */
399
- onDelete(action: OnDeleteAction): ColumnBuilder<K, N, D, E, P, T, W>;
415
+ onDelete(action: OnDeleteAction): ColumnBuilder<K, N, D, E, P, T, W, Q>;
400
416
  /** Add a single-column UNIQUE constraint. */
401
- unique(): ColumnBuilder<K, N, D, E, P, T, W>;
417
+ unique(): ColumnBuilder<K, N, D, E, P, T, W, KeyAdded<Q, "unique">>;
402
418
  /**
403
419
  * Surface this exact-precision column (`bigint()` or `numeric()`) as a JS
404
420
  * `number`.
@@ -417,13 +433,13 @@ export declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends
417
433
  * `Infinity`. For values a double cannot hold, use `.asDecimal()`. A column
418
434
  * has one codec, and not together with `.transform()`.
419
435
  */
420
- asNumber(): ColumnBuilder<K, N, D, E, P, number, W>;
436
+ asNumber(): ColumnBuilder<K, N, D, E, P, number, W, Q>;
421
437
  /**
422
438
  * Surface this exact-precision column (`bigint()` or `numeric()`) as a
423
439
  * `string`: the value Postgres sent, DECLARED rather than left as the
424
440
  * default.
425
441
  */
426
- asDecimal(): ColumnBuilder<K, N, D, E, P, string, W>;
442
+ asDecimal(): ColumnBuilder<K, N, D, E, P, string, W, Q>;
427
443
  private withCodec;
428
444
  /**
429
445
  * Declare how this column's value is projected in and out of the process.
@@ -443,7 +459,7 @@ export declare class ColumnBuilder<K extends ColumnType = ColumnType, N extends
443
459
  * A transform is a PROJECTION, never a constraint: it lives only in this
444
460
  * process, so it can neither validate nor migrate what is stored.
445
461
  */
446
- transform<Target>(fns: ColumnTransform<Target>): ColumnBuilder<K, N, D, E, P, Target, W>;
462
+ transform<Target>(fns: ColumnTransform<Target>): ColumnBuilder<K, N, D, E, P, Target, W, Q>;
447
463
  }
448
464
  /**
449
465
  * Extracts the TypeScript value type for a column, respecting nullability.
@@ -1,5 +1,5 @@
1
1
  export { bigint, boolean, ColumnBuilder, date, enumType, integer, jsonb, numeric, ownedByUser, sqlType, text, timestamp, userRef, uuid } from './columns.js';
2
- export type { AnyColumn, ArrayElementType, AuthUserOnDelete, ColumnDef, ColumnType, ColumnWrite, ColValue, OnDeleteAction } from './columns.js';
2
+ export type { AnyColumn, ArrayElementType, AuthUserOnDelete, ColumnDef, ColumnKey, ColumnType, ColumnWrite, ColValue, OnDeleteAction } from './columns.js';
3
3
  export { foreignKey, ForeignKeyBuilder } from './foreign-key.js';
4
4
  export type { FkAction, ForeignKeyDef } from './foreign-key.js';
5
5
  export { check, exprCtx, policy, PolicyBuilder, PolicyExprRef, policySQL } from './policy.js';
@@ -7,5 +7,5 @@ export type { CheckDef, PolicyCommand, PolicyDef, PolicyExpr, PolicyExprCtx, Pol
7
7
  export { raw } from './raw.js';
8
8
  export type { RawConstraintDef, RawUnitDef } from './raw.js';
9
9
  export { defineSchema, defineTable, index, IndexBuilder, qualifiedTableKey, TABLE_META } from './schema.js';
10
- export type { ColumnMap, IndexDef, SchemaDef, SearchInput, TableDef, TableHandle, TableInput } from './schema.js';
10
+ export type { ColumnMap, IndexDef, SchemaDef, SearchInput, TableDef, TableHandle, TableInput, TableKeys } from './schema.js';
11
11
  export { toModel } from './to-model.js';
@@ -196,11 +196,11 @@ export interface TableInput<C extends ColumnMap = ColumnMap> {
196
196
  */
197
197
  ignored?: boolean;
198
198
  /** Composite/named primary key (ordered column names). Omit for single-column inline .primaryKey(). */
199
- primaryKey?: string[];
199
+ primaryKey?: readonly string[];
200
200
  /** Named multi-column UNIQUE constraints. */
201
- unique?: {
202
- name: string;
203
- columns: string[];
201
+ unique?: readonly {
202
+ readonly name: string;
203
+ readonly columns: readonly string[];
204
204
  }[];
205
205
  /** Named raw-SQL DDL objects (EXCLUDE, triggers, views) that the typed DSL cannot express. */
206
206
  raw?: RawConstraintDef[];
@@ -360,13 +360,39 @@ export declare const TABLE_META: unique symbol;
360
360
  * symbol. `S` is the table's search as declared: with one, the table's type
361
361
  * says so, and `search()` is typed for it.
362
362
  */
363
- export type TableHandle<C extends ColumnMap = ColumnMap, N extends string = string, S = undefined> = C & {
363
+ export type TableHandle<C extends ColumnMap = ColumnMap, N extends string = string, S = undefined, K = unknown> = C & {
364
364
  readonly [TABLE_META]: TableDef<C> & {
365
365
  name: N;
366
366
  } & ([S] extends [undefined] ? unknown : {
367
367
  search: Search;
368
- });
368
+ }) & TableKeys<K>;
369
369
  };
370
+ /**
371
+ * A table's unique keys in its type (DeclaredKeys): never set — the run time
372
+ * reads them from the table (uniqueKeysOf). A string key, `~` like Standard
373
+ * Schema's `~standard`, so a declaration that infers a table's type can name it.
374
+ */
375
+ export interface TableKeys<K> {
376
+ readonly "~keys"?: K;
377
+ }
378
+ type ColumnKeyOf<B> = B extends ColumnBuilder<infer _K, infer _N, infer _D, infer _E, infer _P, infer _T, infer _W, infer Q> ? Q : never;
379
+ /** The columns that declared `key` inline (`"plain"` is the union of a column that declared none; an ignored column's is `"ignored"`). */
380
+ type InlineKeyed<C extends ColumnMap, key extends "primary" | "unique"> = {
381
+ [Col in keyof C]: "plain" extends ColumnKeyOf<C[Col]> ? never : key extends ColumnKeyOf<C[Col]> ? Col : never;
382
+ }[keyof C] & string;
383
+ /** No key of no columns: an empty one is not declared (uniqueKeysOf finds none). */
384
+ type NonEmpty<Key> = Key extends readonly [] ? never : Key;
385
+ /**
386
+ * A table's unique keys as its declaration types them — the primary key (the
387
+ * table's, or its inline .primaryKey() columns), each inline .unique() column,
388
+ * each `unique` constraint — what uniqueKeysOf finds at run time, so
389
+ * findUnique takes exactly these.
390
+ */
391
+ type DeclaredKeys<C extends ColumnMap, PK, U> = readonly ((PK extends readonly string[] ? NonEmpty<PK> : [InlineKeyed<C, "primary">] extends [never] ? never : readonly InlineKeyed<C, "primary">[]) | (InlineKeyed<C, "unique"> extends infer Col extends string ? (Col extends unknown ? readonly [Col] : never) : never) | (U extends readonly {
392
+ readonly columns: infer Cols;
393
+ }[] ? NonEmpty<Cols> : never))[];
394
+ /** The unique keys a table's declaration typed (none for a TableDef not made by defineTable). */
395
+ export type UniqueKeysOf<T> = T extends TableKeys<infer K> ? (K extends readonly (readonly string[])[] ? K : readonly never[]) : readonly never[];
370
396
  /**
371
397
  * Declare one table as a standalone value that KNOWS ITS OWN NAME.
372
398
  *
@@ -378,6 +404,17 @@ export type TableHandle<C extends ColumnMap = ColumnMap, N extends string = stri
378
404
  * The returned object spreads its own columns, so a sibling can point at it:
379
405
  * `references(() => lists.id)`.
380
406
  */
407
+ export declare function defineTable<const N extends string, C extends ColumnMap, const S extends SearchInput<Extract<keyof C, string>> | undefined = undefined, const PK extends readonly Extract<keyof C, string>[] | undefined = undefined, const U extends readonly {
408
+ readonly name: string;
409
+ readonly columns: readonly Extract<keyof C, string>[];
410
+ }[] | undefined = undefined>(name: N, input: Omit<TableInput<C>, "primaryKey" | "unique"> & {
411
+ search?: S;
412
+ /** Composite/named primary key (ordered column names). Omit for single-column inline .primaryKey(). */
413
+ primaryKey?: PK;
414
+ /** Named multi-column UNIQUE constraints. */
415
+ unique?: U;
416
+ }): TableHandle<C, N, S, DeclaredKeys<C, PK, U>>;
417
+ /** A table whose keys are not written as its column names (a `string[]` built elsewhere): its type names no key, so findUnique takes none. */
381
418
  export declare function defineTable<const N extends string, C extends ColumnMap, const S extends SearchInput<Extract<keyof C, string>> | undefined = undefined>(name: N, input: TableInput<C> & {
382
419
  search?: S;
383
420
  }): TableHandle<C, N, S>;
@@ -1,5 +1,5 @@
1
1
  import type { ColIsOptionalOnInsert, ColumnBuilder, ColumnType, ColValue } from '../dsl/columns.js';
2
- import { type SchemaDef, type TableDef } from '../dsl/schema.js';
2
+ import { type SchemaDef, type TableDef, type UniqueKeysOf } from '../dsl/schema.js';
3
3
  import type { AtomicClient, DBClient, DBOps, RowLock, SearchOptions } from './client.js';
4
4
  import type { Search } from '../model.js';
5
5
  import { isColRef } from './guards.js';
@@ -41,6 +41,7 @@ export interface Env<T = unknown, S = unknown> {
41
41
  *
42
42
  * declare module "palbase/backend/env" { interface Tables extends TablesOf<typeof schema> {} }
43
43
  *
44
+ * Its unique keys are the ones the file declares, so findUnique takes them.
44
45
  * Relations cross files (a foreign key names another schema's table), so they
45
46
  * are not read this way: `with` and `has` take none.
46
47
  */
@@ -48,6 +49,7 @@ export type TablesOf<S extends SchemaDef> = {
48
49
  [K in keyof S['tables']]: {
49
50
  row: RowShape<S['tables'][K]>;
50
51
  insert: InsertShape<S['tables'][K]>;
52
+ uniqueKeys: UniqueKeysOf<S['tables'][K]>;
51
53
  relations: Record<never, never>;
52
54
  } & (S['tables'][K] extends {
53
55
  search: Search;
@@ -6,6 +6,14 @@ export declare function uniqueKeysOf(table: {
6
6
  columns: readonly string[];
7
7
  }[];
8
8
  }): string[][];
9
+ /** A table's unique keys as a version serves it: of its columns not marked .ignored(). */
10
+ export declare function tableUniqueKeys(table: {
11
+ columns: Record<string, unknown>;
12
+ primaryKey?: readonly string[];
13
+ unique?: readonly {
14
+ columns: readonly string[];
15
+ }[];
16
+ }): string[][];
9
17
  /** Exactly one declared key, every column of it equal to a non-null scalar: NULLs in a UNIQUE constraint are not unique. */
10
18
  export declare function assertUniqueLookup(table: string, keys: readonly (readonly string[])[], where: Record<string, unknown>): void;
11
19
  /** A union of exact keys, not a partial row that could match many. */