palgres 0.0.0-stage → 0.1.0
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/LICENSE +27 -0
- package/README.md +2 -2
- package/dist/chunks/5DSZT7RI.js +5 -0
- package/dist/chunks/7676QADW.js +2 -0
- package/dist/chunks/W7VRQDZA.js +2 -0
- package/dist/dsl.js +2 -0
- package/dist/index.js +372 -0
- package/dist/testing.js +2 -0
- package/package.json +32 -4
- package/types/internal/src/data.d.ts +15 -0
- package/types/palgres/src/adopt.d.ts +17 -0
- package/types/palgres/src/connect.d.ts +37 -0
- package/types/palgres/src/dsl/columns.d.ts +388 -0
- package/types/palgres/src/dsl/foreign-key.d.ts +33 -0
- package/types/palgres/src/dsl/index.d.ts +11 -0
- package/types/palgres/src/dsl/policy.d.ts +181 -0
- package/types/palgres/src/dsl/raw.d.ts +43 -0
- package/types/palgres/src/dsl/schema.d.ts +288 -0
- package/types/palgres/src/dsl/to-model.d.ts +6 -0
- package/types/palgres/src/forget.d.ts +12 -0
- package/types/palgres/src/index.d.ts +15 -0
- package/types/palgres/src/introspect.d.ts +17 -0
- package/types/palgres/src/migrate.d.ts +189 -0
- package/types/palgres/src/model.d.ts +181 -0
- package/types/palgres/src/plan.d.ts +107 -0
- package/types/palgres/src/pull.d.ts +13 -0
- package/types/palgres/src/query/admission.d.ts +80 -0
- package/types/palgres/src/query/catalog.d.ts +50 -0
- package/types/palgres/src/query/client.d.ts +154 -0
- package/types/palgres/src/query/data-mark.d.ts +7 -0
- package/types/palgres/src/query/diagnostics.d.ts +76 -0
- package/types/palgres/src/query/driver.d.ts +299 -0
- package/types/palgres/src/query/env-gen.d.ts +14 -0
- package/types/palgres/src/query/errors.d.ts +168 -0
- package/types/palgres/src/query/guards.d.ts +62 -0
- package/types/palgres/src/query/index.d.ts +76 -0
- package/types/palgres/src/query/options.d.ts +75 -0
- package/types/palgres/src/query/relations.d.ts +15 -0
- package/types/palgres/src/query/repository.d.ts +42 -0
- package/types/palgres/src/query/request.d.ts +54 -0
- package/types/palgres/src/query/search.d.ts +55 -0
- package/types/palgres/src/query/surface.d.ts +9 -0
- package/types/palgres/src/query/tx-plan.d.ts +229 -0
- package/types/palgres/src/query/typed.d.ts +537 -0
- package/types/palgres/src/query/unique.d.ts +16 -0
- package/types/palgres/src/search-worker.d.ts +36 -0
- package/types/palgres/src/search.d.ts +107 -0
- package/types/palgres/src/testing/fake.d.ts +22 -0
- package/types/palgres/src/testing/index.d.ts +2 -0
- package/types/palgres/src/units.d.ts +24 -0
package/dist/testing.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! palgres 0.1.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/W7VRQDZA.js";import"./chunks/5DSZT7RI.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,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "palgres",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Palgres: the typed Postgres layer of Palbase's DB machines — schema, migrations and queries.",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"homepage": "https://palbase.studio",
|
|
7
|
+
"author": "Palbase",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=26"
|
|
11
|
+
},
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./types/palgres/src/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./dsl": {
|
|
19
|
+
"types": "./types/palgres/src/dsl/index.d.ts",
|
|
20
|
+
"default": "./dist/dsl.js"
|
|
21
|
+
},
|
|
22
|
+
"./testing": {
|
|
23
|
+
"types": "./types/palgres/src/testing/index.d.ts",
|
|
24
|
+
"default": "./dist/testing.js"
|
|
25
|
+
},
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"types",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"README.md"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The objects that are one value: a Date and binary data, which a filter
|
|
3
|
+
* compares and a column stores whole. Every other object is marked — the
|
|
4
|
+
* list says what is a value, never what is data, so an object a validator
|
|
5
|
+
* or a transform built in a shape nobody listed (a class's instance, a
|
|
6
|
+
* foreign prototype) is data, not a way around the mark.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isValue(v: object): boolean;
|
|
9
|
+
/** Marks every object and array in `value` as data, but a Date and binary data; returns it. */
|
|
10
|
+
export declare function markData<T>(value: T): T;
|
|
11
|
+
export declare function isData(v: unknown): boolean;
|
|
12
|
+
/** The values of each row marked — not the row: its keys are the columns the code selected. */
|
|
13
|
+
export declare function markRows<T>(rows: T): T;
|
|
14
|
+
/** A copy the engine makes of a marked object stays data: a plan's filter is copied twice before it compiles. */
|
|
15
|
+
export declare function carryMark<T extends object>(from: unknown, to: T): T;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Introspected } from './introspect.js';
|
|
2
|
+
import { type Schema } from './model.js';
|
|
3
|
+
export interface Baseline {
|
|
4
|
+
schema: Schema;
|
|
5
|
+
/** What adoption itself changes before the migration: tables whose RLS is FORCEd on their owner. */
|
|
6
|
+
unforce: string[];
|
|
7
|
+
refusals: string[];
|
|
8
|
+
/** Left as it is: the database's tables outside the schema and what the model cannot say. */
|
|
9
|
+
unmanaged: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare function adoptionBaseline(real: Introspected, shadow: Introspected, declaredSchema: Schema): Baseline;
|
|
12
|
+
/**
|
|
13
|
+
* Where a database differs from the declared schema, both read the same way:
|
|
14
|
+
* one line per table, column, constraint, index or policy of a declared table
|
|
15
|
+
* that the database does not hold as the shadow does. [] when it is the schema.
|
|
16
|
+
*/
|
|
17
|
+
export declare function drift(real: Introspected, shadow: Introspected): string[];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Schema } from './model.js';
|
|
2
|
+
/** What an app connection says about itself in every transaction. */
|
|
3
|
+
export interface Binding {
|
|
4
|
+
version: number;
|
|
5
|
+
/** Reads the tables directly (true), or through the version's views. */
|
|
6
|
+
direct: boolean;
|
|
7
|
+
searchPath: string;
|
|
8
|
+
applicationName: string;
|
|
9
|
+
/** Where a table of Postgres schema `name` is read by this version: the schema itself, or its version's views. */
|
|
10
|
+
schema(name: string): string;
|
|
11
|
+
}
|
|
12
|
+
export declare function binding(version: number, direct: boolean): Binding;
|
|
13
|
+
export declare class VersionNotMigrated extends Error {
|
|
14
|
+
readonly digest: string;
|
|
15
|
+
constructor(d: string);
|
|
16
|
+
}
|
|
17
|
+
interface Query {
|
|
18
|
+
unsafe(sql: string, params?: unknown[]): PromiseLike<unknown>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The version of `schema`, once it can serve: active, its views made. Waits
|
|
22
|
+
* up to `waitMs` (default 0) for a start still running.
|
|
23
|
+
*/
|
|
24
|
+
export declare function bind(sql: Query, schema: Schema, options?: {
|
|
25
|
+
waitMs?: number;
|
|
26
|
+
}): Promise<Binding>;
|
|
27
|
+
/**
|
|
28
|
+
* The statement a transaction of the app starts with, as text the driver
|
|
29
|
+
* sends in BEGIN's own round trip: role and claims for this transaction
|
|
30
|
+
* alone, the version's name for the connection (it outlives the transaction:
|
|
31
|
+
* an idle connection still says which version holds it). A SELECT list, so
|
|
32
|
+
* what a transaction adds (its timeouts, its cancel ticket) is more columns.
|
|
33
|
+
* The values are literals (sql.ts' textLiteral): a statement sent with BEGIN
|
|
34
|
+
* takes no parameters.
|
|
35
|
+
*/
|
|
36
|
+
export declare function bindStatement(b: Binding, role: string, claims: Record<string, unknown>): string;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/** On delete action for foreign key references. */
|
|
2
|
+
export type OnDeleteAction = 'cascade' | 'set null' | 'set default' | 'restrict' | 'no action';
|
|
3
|
+
/** What a foreign key does when the row it points at changes its key. */
|
|
4
|
+
export type OnUpdateAction = OnDeleteAction;
|
|
5
|
+
/**
|
|
6
|
+
* What happens to a user's rows when the user is deleted: they go (`cascade`)
|
|
7
|
+
* or forget the user (`set null`). Nothing else: a deletion is never blocked.
|
|
8
|
+
*/
|
|
9
|
+
export type AuthUserOnDelete = Extract<OnDeleteAction, 'cascade' | 'set null'>;
|
|
10
|
+
export type ArrayElementType = 'uuid' | 'text' | 'integer' | 'bigint' | 'numeric' | 'boolean' | 'timestamp' | 'date' | 'enum';
|
|
11
|
+
/** Column type identifiers. */
|
|
12
|
+
export type ColumnType = 'uuid' | 'text' | 'integer' | 'bigint' | 'numeric' | 'boolean' | 'timestamp' | 'date' | 'jsonb' | 'enum' | 'sql' | `${ArrayElementType}[]`;
|
|
13
|
+
export type ColumnWrite = 'write' | 'readonly';
|
|
14
|
+
/** Base column definition shared by all column types. */
|
|
15
|
+
export interface ColumnDef {
|
|
16
|
+
type: ColumnType;
|
|
17
|
+
nullable: boolean;
|
|
18
|
+
primaryKey: boolean;
|
|
19
|
+
defaultValue?: unknown;
|
|
20
|
+
defaultRandom?: boolean;
|
|
21
|
+
defaultNow?: boolean;
|
|
22
|
+
/** A default in SQL, written as it is: what a database taken over has (nextval(…), CURRENT_DATE + 1). */
|
|
23
|
+
defaultSql?: string;
|
|
24
|
+
references?: {
|
|
25
|
+
table: string;
|
|
26
|
+
column: string;
|
|
27
|
+
};
|
|
28
|
+
/** Pending FK target, resolved by `defineSchema` once every binding exists. */
|
|
29
|
+
referencesThunk?: () => ColumnBuilder;
|
|
30
|
+
/** FK onto THIS table — no thunk needed, the target is the declaring table. */
|
|
31
|
+
selfRefColumn?: string;
|
|
32
|
+
/**
|
|
33
|
+
* The column holds a user's id (ownedByUser(), userRef()). Users live in
|
|
34
|
+
* the platform, not in this database, so there is no foreign key: when a
|
|
35
|
+
* user is deleted, the app's rows of them go (`cascade`) or forget them
|
|
36
|
+
* (`set null`) — the backend does it on the platform's user.deleted.
|
|
37
|
+
*/
|
|
38
|
+
user?: {
|
|
39
|
+
onDelete: AuthUserOnDelete;
|
|
40
|
+
owns: boolean;
|
|
41
|
+
};
|
|
42
|
+
/** Explicit name for the FORWARD relation (child → parent), when the one
|
|
43
|
+
* derived from the column would be ambiguous or unclear. */
|
|
44
|
+
refAs?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Explicit name for the REVERSE relation (parent → children).
|
|
47
|
+
*
|
|
48
|
+
* Separate from {@link refAs} because the two directions are different facts:
|
|
49
|
+
* the forward name describes the parent this row points at (`author`), the
|
|
50
|
+
* reverse one describes the rows hanging off the parent (`posts`). One option
|
|
51
|
+
* naming both made an ordinary schema undeclarable — `posts.author_id` and
|
|
52
|
+
* `comments.author_id` both named `{ as: "author" }` collided on `users`, and
|
|
53
|
+
* the refusal asked for the `{ as }` they had both already written.
|
|
54
|
+
*/
|
|
55
|
+
reverseAs?: string;
|
|
56
|
+
/** The table this column belongs to; set by `defineTable`. */
|
|
57
|
+
ownerTable?: {
|
|
58
|
+
name: string;
|
|
59
|
+
columns: Record<string, ColumnBuilder>;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* The name this column used to have. A diff cannot tell a rename from a drop and
|
|
63
|
+
* an add — both leave one name gone and another present — so the intent has to be
|
|
64
|
+
* declared. Without it, renaming a column loses its data.
|
|
65
|
+
*/
|
|
66
|
+
renamedFrom?: string;
|
|
67
|
+
ignored?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* The per-row fill of a NOT NULL column (`.fillWith()`, D-1): one SQL scalar
|
|
70
|
+
* expression over this row's columns. The deploy fills every row that does not
|
|
71
|
+
* supply the column with it — the rows already in the table and every write of
|
|
72
|
+
* a release that does not know the column yet — and only then makes it NOT
|
|
73
|
+
* NULL. Absent when the column declares none, so the wire stays byte-identical.
|
|
74
|
+
*/
|
|
75
|
+
fill?: string;
|
|
76
|
+
onDeleteAction?: OnDeleteAction;
|
|
77
|
+
onUpdateAction?: OnUpdateAction;
|
|
78
|
+
index?: boolean;
|
|
79
|
+
enumName?: string;
|
|
80
|
+
enumValues?: string[];
|
|
81
|
+
unique?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* The value is written by the DATABASE — a trigger, a rule, an identity — not by
|
|
84
|
+
* the author and not by a DEFAULT this schema declares. It makes the column
|
|
85
|
+
* optional on INSERT without putting a DEFAULT in the DDL.
|
|
86
|
+
*
|
|
87
|
+
* Before this existed the only way to keep a trigger-filled column off the
|
|
88
|
+
* INSERT type was to give it a fake `default()`: a value the schema claimed to
|
|
89
|
+
* write and the trigger immediately overwrote. That made the schema lie about
|
|
90
|
+
* its own data.
|
|
91
|
+
*/
|
|
92
|
+
dbAssigned?: boolean;
|
|
93
|
+
precision?: number;
|
|
94
|
+
scale?: number;
|
|
95
|
+
sqlType?: string;
|
|
96
|
+
identity?: 'always' | 'byDefault';
|
|
97
|
+
/** .generated(expr): the column is `expr` over the row — computed when read (virtual) or on write (stored). */
|
|
98
|
+
generated?: {
|
|
99
|
+
expr: string;
|
|
100
|
+
stored: boolean;
|
|
101
|
+
};
|
|
102
|
+
codec?: CodecName;
|
|
103
|
+
transform?: ColumnTransform;
|
|
104
|
+
}
|
|
105
|
+
interface ColumnTransform<T = unknown> {
|
|
106
|
+
fromDb: (value: unknown) => T;
|
|
107
|
+
toDb: (value: T) => unknown;
|
|
108
|
+
}
|
|
109
|
+
export type CodecName = 'number' | 'decimal';
|
|
110
|
+
export declare const CODECS: Record<CodecName, {
|
|
111
|
+
fromDb: (value: unknown) => unknown;
|
|
112
|
+
toDb: (value: unknown) => unknown;
|
|
113
|
+
tsType: 'number' | 'string';
|
|
114
|
+
}>;
|
|
115
|
+
declare const __colKind: unique symbol;
|
|
116
|
+
declare const __colNullable: unique symbol;
|
|
117
|
+
declare const __colHasDefault: unique symbol;
|
|
118
|
+
declare const __colEnumValues: unique symbol;
|
|
119
|
+
declare const __colPayload: unique symbol;
|
|
120
|
+
declare const __colTransform: unique symbol;
|
|
121
|
+
declare const __colWrite: unique symbol;
|
|
122
|
+
/**
|
|
123
|
+
* Fluent column builder with phantom type params:
|
|
124
|
+
* K — ColumnType literal (e.g. "text", "integer")
|
|
125
|
+
* N — boolean: true when nullable() has been called last (false = NOT NULL)
|
|
126
|
+
* D — boolean: true when a default has been set
|
|
127
|
+
* E — enum value union (never for non-enum columns)
|
|
128
|
+
* P — jsonb payload shape (unknown unless jsonb<T>() supplied one)
|
|
129
|
+
* T — transform target type (`never` when the column declares no transform;
|
|
130
|
+
* `never` is the sentinel because it is the only type that survives
|
|
131
|
+
* `[T] extends [never]` and never collides with a real target type)
|
|
132
|
+
*
|
|
133
|
+
* All six params have defaults so bare `ColumnBuilder` (no args) still
|
|
134
|
+
* satisfies `Record<string, ColumnBuilder>` in schema.ts without modification.
|
|
135
|
+
*
|
|
136
|
+
* The six `declare readonly` brand fields carry the phantom types into the
|
|
137
|
+
* structural shape so that conditional types like ColValue<C> can discriminate
|
|
138
|
+
* on K without requiring runtime values on those fields.
|
|
139
|
+
*/
|
|
140
|
+
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> {
|
|
141
|
+
readonly [__colKind]: K;
|
|
142
|
+
readonly [__colNullable]: N;
|
|
143
|
+
readonly [__colHasDefault]: D;
|
|
144
|
+
readonly [__colEnumValues]: E;
|
|
145
|
+
readonly [__colPayload]: P;
|
|
146
|
+
readonly [__colTransform]: T;
|
|
147
|
+
readonly [__colWrite]: W;
|
|
148
|
+
readonly _def: ColumnDef;
|
|
149
|
+
constructor(type: K, existingDef?: ColumnDef);
|
|
150
|
+
/** Mark this column as the primary key. */
|
|
151
|
+
primaryKey(): ColumnBuilder<K, N, D, E, P, T, W>;
|
|
152
|
+
/** Mark this column as NOT NULL (default). */
|
|
153
|
+
notNull(): ColumnBuilder<K, false, D, E, P, T, W>;
|
|
154
|
+
/** Allow NULL values. */
|
|
155
|
+
nullable(): ColumnBuilder<K, true, D, E, P, T, W>;
|
|
156
|
+
/** Set a default value. */
|
|
157
|
+
default(value: unknown): ColumnBuilder<K, N, true, E, P, T, W>;
|
|
158
|
+
/** UUID: generate a random default (gen_random_uuid()). */
|
|
159
|
+
defaultRandom(): ColumnBuilder<K, N, true, E, P, T, W>;
|
|
160
|
+
/** Timestamp: default to now(). */
|
|
161
|
+
defaultNow(): ColumnBuilder<K, N, true, E, P, T, W>;
|
|
162
|
+
/**
|
|
163
|
+
* A default the database computes, in SQL as it is: `nextval('items_id_seq'::regclass)`,
|
|
164
|
+
* `CURRENT_DATE + 30`. What `palgres pull` writes for a default the other
|
|
165
|
+
* builders cannot say; a value the schema itself gives is `default()`.
|
|
166
|
+
*/
|
|
167
|
+
defaultSql(expr: string): ColumnBuilder<K, N, true, E, P, T, W>;
|
|
168
|
+
/**
|
|
169
|
+
* The DATABASE assigns this column's value — a trigger, a rule, an identity.
|
|
170
|
+
*
|
|
171
|
+
* The column becomes optional on INSERT (the author has nothing to send) while
|
|
172
|
+
* the DDL stays free of a DEFAULT this schema would not honour. It is NOT
|
|
173
|
+
* `default()`: that declares a value the schema promises to write.
|
|
174
|
+
*
|
|
175
|
+
* This is for a value something ELSE writes — a trigger in a raw() unit.
|
|
176
|
+
*/
|
|
177
|
+
dbAssigned(): ColumnBuilder<K, N, true, E, P, T, W>;
|
|
178
|
+
array(): ColumnBuilder<`${K & ArrayElementType}[]`, N, D, K extends 'enum' ? E : never, P, T, W>;
|
|
179
|
+
identity(): ColumnBuilder<K, false, true, E, P, T, 'readonly'>;
|
|
180
|
+
identity(opts: {
|
|
181
|
+
always: true;
|
|
182
|
+
}): ColumnBuilder<K, false, true, E, P, T, 'readonly'>;
|
|
183
|
+
identity(opts: {
|
|
184
|
+
always: false;
|
|
185
|
+
}): ColumnBuilder<K, false, true, E, P, T, W>;
|
|
186
|
+
/**
|
|
187
|
+
* The column is `expr`, an SQL expression over this row's other columns —
|
|
188
|
+
* `GENERATED ALWAYS AS (expr)`. Off every insert and update shape: Postgres
|
|
189
|
+
* refuses a value the application sends. VIRTUAL by default (Postgres 18):
|
|
190
|
+
* computed when read, and added to a table that holds rows at once.
|
|
191
|
+
* `{ stored: true }` keeps the value on disk, written with the row — what a
|
|
192
|
+
* search vector an index reads takes; added to a table with rows, Postgres
|
|
193
|
+
* would rewrite the table, which a deploy refuses.
|
|
194
|
+
*/
|
|
195
|
+
generated(expr: string, opts?: {
|
|
196
|
+
stored?: boolean;
|
|
197
|
+
}): ColumnBuilder<K, N, true, E, P, T, 'readonly'>;
|
|
198
|
+
fillWith(expr: string): ColumnBuilder<K, N, D, E, P, T, W>;
|
|
199
|
+
/** Add a foreign key reference. */
|
|
200
|
+
/**
|
|
201
|
+
* Declares that this column used to be called `previous`.
|
|
202
|
+
*
|
|
203
|
+
* A schema diff sees one name gone and another present; it cannot know whether
|
|
204
|
+
* you renamed a column or dropped one and added another, and the two are very
|
|
205
|
+
* different — the second loses every value. Saying so here turns the plan into
|
|
206
|
+
* `ALTER TABLE … RENAME COLUMN` instead.
|
|
207
|
+
*
|
|
208
|
+
* Once the rename has been applied the annotation is inert (the old name is no
|
|
209
|
+
* longer there to rename), so it can be deleted at your leisure.
|
|
210
|
+
*/
|
|
211
|
+
renamedFrom(previous: string): ColumnBuilder<K, N, D, E, P, T, W>;
|
|
212
|
+
/**
|
|
213
|
+
* See {@link ColumnDef.ignored}.
|
|
214
|
+
*
|
|
215
|
+
* COPIES the def rather than mutating it. The constructor takes an existing
|
|
216
|
+
* def BY REFERENCE, so every builder derived from another shares one object —
|
|
217
|
+
* `const a = slug.unique()` leaves `a._def === slug._def`. An in-place
|
|
218
|
+
* `ignored = true` therefore marks every column sharing that def, including
|
|
219
|
+
* one another table actively reads, and the gate would let THAT column be
|
|
220
|
+
* dropped. Measured before this copy existed.
|
|
221
|
+
*
|
|
222
|
+
* The aliasing is older than this method and other fields leak through it too.
|
|
223
|
+
* The reason this one cannot wait: every other leak produces a VISIBLE schema
|
|
224
|
+
* difference — the plan shows it, the DDL shows it. This one is invisible by
|
|
225
|
+
* design (no DDL, no diff, no plan line), so its only effect is to disarm a
|
|
226
|
+
* safety gate in silence.
|
|
227
|
+
*/
|
|
228
|
+
ignored(): ColumnBuilder<K, N, D, E, P, T, W>;
|
|
229
|
+
/**
|
|
230
|
+
* Foreign key onto another table's column.
|
|
231
|
+
*
|
|
232
|
+
* The target is a THUNK, not a direct reference. In a cycle (`x → y`, `y → x`)
|
|
233
|
+
* the second table does not exist yet when the first is built; a direct
|
|
234
|
+
* reference makes TypeScript chase its own tail (TS7022 — measured, and making
|
|
235
|
+
* the return type independent of the target does NOT help). The thunk is
|
|
236
|
+
* invoked in `defineSchema`, where every binding exists and every table
|
|
237
|
+
* already knows its name.
|
|
238
|
+
*
|
|
239
|
+
* In a cycle, ONE side needs an explicit return type:
|
|
240
|
+
* `references((): AnyColumn => y.id)`. One side is enough — measured.
|
|
241
|
+
* For a self-reference use `selfReferences(column)`: no thunk, no annotation.
|
|
242
|
+
*
|
|
243
|
+
* `as` names the FORWARD relation (`author_id` → `author` by default);
|
|
244
|
+
* `reverseAs` names the REVERSE one on the parent, whose default is this
|
|
245
|
+
* table's own name (`users.posts`). Two foreign keys from one table onto one
|
|
246
|
+
* parent therefore need a `reverseAs` on at least one of them — the reverse
|
|
247
|
+
* names would otherwise both be this table's name.
|
|
248
|
+
*/
|
|
249
|
+
references(target: () => AnyColumn, opts?: {
|
|
250
|
+
as?: string;
|
|
251
|
+
reverseAs?: string;
|
|
252
|
+
onDelete?: OnDeleteAction;
|
|
253
|
+
/** ON UPDATE — what Prisma writes for every relation (cascade). Default: no action. */
|
|
254
|
+
onUpdate?: OnUpdateAction;
|
|
255
|
+
index?: boolean;
|
|
256
|
+
}): ColumnBuilder<K, N, D, E, P, T, W>;
|
|
257
|
+
/**
|
|
258
|
+
* Foreign key onto THIS table (`parent_id → id`) — category trees, comment
|
|
259
|
+
* replies, org charts.
|
|
260
|
+
*
|
|
261
|
+
* No thunk and no type annotation: the target table is the one being declared,
|
|
262
|
+
* so there is nothing to defer and nothing for TypeScript to chase in a circle.
|
|
263
|
+
* Drizzle forces an explicit `(): AnyPgColumn =>` here because its reference
|
|
264
|
+
* always goes through a callback; measured, we do not need one.
|
|
265
|
+
*/
|
|
266
|
+
selfReferences(column: string, opts?: {
|
|
267
|
+
as?: string;
|
|
268
|
+
onDelete?: OnDeleteAction;
|
|
269
|
+
}): ColumnBuilder<K, N, D, E, P, T, W>;
|
|
270
|
+
/** Set the ON DELETE action for a foreign key reference. */
|
|
271
|
+
onDelete(action: OnDeleteAction): ColumnBuilder<K, N, D, E, P, T, W>;
|
|
272
|
+
/** Add a single-column UNIQUE constraint. */
|
|
273
|
+
unique(): ColumnBuilder<K, N, D, E, P, T, W>;
|
|
274
|
+
/**
|
|
275
|
+
* Declare how this column's value is projected in and out of the process.
|
|
276
|
+
*
|
|
277
|
+
* The DDL does not move: `numeric` stays `numeric`, and the driver still hands
|
|
278
|
+
* back what Postgres sent. What changes is the type the row surface exposes —
|
|
279
|
+
* it becomes `Target`:
|
|
280
|
+
*
|
|
281
|
+
* amount: numeric().transform<number>({ fromDb: Number, toDb: String })
|
|
282
|
+
*
|
|
283
|
+
* `numeric` surfacing as `string` is CORRECT (a JS number cannot hold
|
|
284
|
+
* arbitrary precision), and that is exactly why this exists: application code
|
|
285
|
+
* that does arithmetic on the column otherwise rewrites the same
|
|
286
|
+
* `Number(row.amount)` / `String(x)` pair in every controller that touches it,
|
|
287
|
+
* and each rewrite is a place the two directions can drift apart.
|
|
288
|
+
*
|
|
289
|
+
* A transform is a PROJECTION, never a constraint: it lives only in this
|
|
290
|
+
* process, so it can neither validate nor migrate what is stored.
|
|
291
|
+
*/
|
|
292
|
+
asNumber(): ColumnBuilder<K, N, D, E, P, number, W>;
|
|
293
|
+
asDecimal(): ColumnBuilder<K, N, D, E, P, string, W>;
|
|
294
|
+
private withCodec;
|
|
295
|
+
transform<Target>(fns: ColumnTransform<Target>): ColumnBuilder<K, N, D, E, P, Target, W>;
|
|
296
|
+
}
|
|
297
|
+
export type ColValue<C> = C extends ColumnBuilder<ColumnType, infer N, boolean, unknown, unknown, infer T> ? [unknown] extends [T] ? ColStoredValue<C> : N extends true ? T | null : T : never;
|
|
298
|
+
type ArrayElementValue<K, E> = K extends 'integer[]' ? number : K extends 'boolean[]' ? boolean : K extends 'enum[]' ? E : string;
|
|
299
|
+
/** The value as the DATABASE hands it over — the branch table above, before any
|
|
300
|
+
* transform. This is what a column's `fromDb` receives. */
|
|
301
|
+
type ColStoredValue<C> = C extends ColumnBuilder<infer K extends `${ArrayElementType}[]`, infer N, infer _D, infer E, infer _P> ? N extends true ? ArrayElementValue<K, E>[] | null : ArrayElementValue<K, E>[] : C extends ColumnBuilder<'sql', infer N, infer _D, infer _E, infer P> ? N extends true ? P | null : P : C extends ColumnBuilder<'uuid' | 'text' | 'timestamp' | 'bigint' | 'numeric' | 'date', infer N, infer _D, infer _E, infer _P> ? N extends true ? string | null : string : C extends ColumnBuilder<'integer', infer N, infer _D, infer _E, infer _P> ? N extends true ? number | null : number : C extends ColumnBuilder<'boolean', infer N, infer _D, infer _E, infer _P> ? N extends true ? boolean | null : boolean : C extends ColumnBuilder<'jsonb', infer N, infer _D, infer _E, infer P> ? N extends true ? P | null : P : C extends ColumnBuilder<'enum', infer N, infer _D, infer E, infer _P> ? N extends true ? E | null : E : never;
|
|
302
|
+
/**
|
|
303
|
+
* True when a column is optional on INSERT:
|
|
304
|
+
* - nullable columns (N = true) — the DB allows NULL so the field may be omitted
|
|
305
|
+
* - columns with a default (D = true) — the DB fills in the value when absent
|
|
306
|
+
*/
|
|
307
|
+
export type ColIsOptionalOnInsert<C> = C extends ColumnBuilder<ColumnType, true, boolean, unknown, unknown, unknown> ? true : C extends ColumnBuilder<ColumnType, boolean, true, unknown, unknown, unknown> ? true : false;
|
|
308
|
+
/** Create a UUID column. */
|
|
309
|
+
export declare function uuid(): ColumnBuilder<'uuid', false, false, never>;
|
|
310
|
+
/** Create a TEXT column. */
|
|
311
|
+
export declare function text(): ColumnBuilder<'text', false, false, never>;
|
|
312
|
+
/** Create an INTEGER column. Emits int4 (max ~2.1B). */
|
|
313
|
+
export declare function integer(): ColumnBuilder<'integer', false, false, never>;
|
|
314
|
+
/**
|
|
315
|
+
* Create a BIGINT column (Postgres int8, max ~9.2×10^18).
|
|
316
|
+
* Surfaces as `string` in row/insert types — JS number loses precision past 2^53
|
|
317
|
+
* and pgx/PostgREST serialize int8 as a JSON string. Use BigInt(row.column) in app code.
|
|
318
|
+
*/
|
|
319
|
+
export declare function bigint(): ColumnBuilder<'bigint', false, false, never>;
|
|
320
|
+
/**
|
|
321
|
+
* Create a NUMERIC column (Postgres `numeric`/`decimal`, arbitrary precision).
|
|
322
|
+
* For exact fractional values (money with cents as a decimal, rates, weights)
|
|
323
|
+
* where int4/int8 don't fit. Surfaces as `string` in row/insert types — JS
|
|
324
|
+
* number can't hold arbitrary-precision decimals without rounding, and
|
|
325
|
+
* pgx/PostgREST serialize numeric as a JSON string. Parse with a decimal lib
|
|
326
|
+
* (or BigInt for scaled integers) in app code.
|
|
327
|
+
*/
|
|
328
|
+
export declare function numeric(opts?: {
|
|
329
|
+
precision: number;
|
|
330
|
+
scale?: number;
|
|
331
|
+
}): ColumnBuilder<'numeric', false, false, never>;
|
|
332
|
+
export declare function date(): ColumnBuilder<'date', false, false, never>;
|
|
333
|
+
export declare function sqlType<const S extends string>(type: S): ColumnBuilder<'sql', false, false, never, SqlTypeValue<S>>;
|
|
334
|
+
export declare const SQL_NUMBER_TYPES: ReadonlySet<string>;
|
|
335
|
+
export declare const SQL_JSON_TYPES: ReadonlySet<string>;
|
|
336
|
+
/** What an sqlType() column surfaces as, from its Postgres type. */
|
|
337
|
+
export type SqlTypeValue<S extends string> = S extends 'smallint' | 'real' | 'double precision' ? number : S extends 'json' ? unknown : string;
|
|
338
|
+
/** Create a BOOLEAN column. */
|
|
339
|
+
export declare function boolean(): ColumnBuilder<'boolean', false, false, never>;
|
|
340
|
+
/** Create a TIMESTAMP column. */
|
|
341
|
+
export declare function timestamp(): ColumnBuilder<'timestamp', false, false, never>;
|
|
342
|
+
/**
|
|
343
|
+
* Create a JSONB column. Pass a payload type to make the generated row/insert
|
|
344
|
+
* type concrete instead of `unknown`:
|
|
345
|
+
*
|
|
346
|
+
* tags: jsonb<string[]>() // row.tags: string[]
|
|
347
|
+
* meta: jsonb<{ tier: string }>() // row.meta: { tier: string }
|
|
348
|
+
* raw: jsonb() // row.raw: unknown (back-compat)
|
|
349
|
+
*
|
|
350
|
+
* The runtime accepts a plain JS object/array directly (no JSON.stringify); the
|
|
351
|
+
* generic only refines the TYPE the env codegen emits.
|
|
352
|
+
*/
|
|
353
|
+
export declare function jsonb<T = unknown>(): ColumnBuilder<'jsonb', false, false, never, T>;
|
|
354
|
+
/**
|
|
355
|
+
* Create an ENUM column.
|
|
356
|
+
* @param name The PostgreSQL enum type name (used in DDL).
|
|
357
|
+
* @param values A readonly tuple of valid string values — kept `const` so the
|
|
358
|
+
* union `V[number]` is as narrow as possible.
|
|
359
|
+
*/
|
|
360
|
+
export declare function enumType<const V extends readonly string[]>(name: string, values: V): ColumnBuilder<'enum', false, false, V[number]>;
|
|
361
|
+
/**
|
|
362
|
+
* Any column, whatever its type parameters.
|
|
363
|
+
*
|
|
364
|
+
* Exported so a cycle can be broken from ONE side:
|
|
365
|
+
* `references((): AnyColumn => y.id)`.
|
|
366
|
+
*/
|
|
367
|
+
export type AnyColumn = ColumnBuilder;
|
|
368
|
+
/**
|
|
369
|
+
* The column that OWNS this row: the id of the user it belongs to — text, NOT
|
|
370
|
+
* NULL. When the user is deleted, their rows go.
|
|
371
|
+
*
|
|
372
|
+
* At most ONE per table: which column's user takes the row with them must not
|
|
373
|
+
* depend on the order columns are declared in.
|
|
374
|
+
*
|
|
375
|
+
* For a column that merely POINTS at a user without owning the row
|
|
376
|
+
* (`created_by`, `edited_by`), use `userRef({ onDelete })`.
|
|
377
|
+
*/
|
|
378
|
+
export declare function ownedByUser(): ColumnBuilder<'text', false, false, never>;
|
|
379
|
+
/**
|
|
380
|
+
* The id of a user the row points at without belonging to them
|
|
381
|
+
* (`created_by`, `edited_by`). When that user is deleted the row goes
|
|
382
|
+
* (`cascade`) or forgets them (`set null`, on a nullable column).
|
|
383
|
+
*/
|
|
384
|
+
export declare function userRef(opts: {
|
|
385
|
+
onDelete: AuthUserOnDelete;
|
|
386
|
+
as?: string;
|
|
387
|
+
}): ColumnBuilder<'text', boolean, false, never>;
|
|
388
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { PolicyExprRef } from "./policy.js";
|
|
2
|
+
import type { ColumnMap, TableHandle } from "./schema.js";
|
|
3
|
+
export declare const REF_TARGET: unique symbol;
|
|
4
|
+
export type FkAction = "cascade" | "restrict" | "set null" | "set default" | "no action";
|
|
5
|
+
export type FkMatch = "simple" | "full";
|
|
6
|
+
export interface ForeignKeyDef {
|
|
7
|
+
name: string;
|
|
8
|
+
columns: string[];
|
|
9
|
+
refTable: string;
|
|
10
|
+
refColumns: string[];
|
|
11
|
+
onUpdate?: FkAction;
|
|
12
|
+
onDelete?: FkAction;
|
|
13
|
+
match?: FkMatch;
|
|
14
|
+
/** Only `false`: no index derived for a one-column key (the column's references() has the same option). */
|
|
15
|
+
index?: false;
|
|
16
|
+
}
|
|
17
|
+
export declare class ForeignKeyBuilder {
|
|
18
|
+
readonly _def: ForeignKeyDef;
|
|
19
|
+
constructor(name: string);
|
|
20
|
+
on(...cols: PolicyExprRef[]): this;
|
|
21
|
+
references<C extends ColumnMap, N extends string>(table: TableHandle<C, N> | (() => TableHandle<C, N>), ...cols: (Extract<keyof C, string>)[]): this;
|
|
22
|
+
onUpdate(action: FkAction): this;
|
|
23
|
+
onDelete(action: FkAction): this;
|
|
24
|
+
match(m: FkMatch): this;
|
|
25
|
+
/**
|
|
26
|
+
* `.index(false)`: Palgres indexes a one-column foreign key's column unasked
|
|
27
|
+
* (as <table>_<column>_idx); this one stays without. What a database taken
|
|
28
|
+
* over had: its key unindexed, or indexed by an index of its own.
|
|
29
|
+
*/
|
|
30
|
+
index(on: false): this;
|
|
31
|
+
}
|
|
32
|
+
export declare function foreignKey(name: string): ForeignKeyBuilder;
|
|
33
|
+
export declare function toForeignKeyDef(b: ForeignKeyBuilder | ForeignKeyDef): ForeignKeyDef;
|
|
@@ -0,0 +1,11 @@
|
|
|
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';
|
|
3
|
+
export { foreignKey, ForeignKeyBuilder } from './foreign-key.js';
|
|
4
|
+
export type { FkAction, ForeignKeyDef } from './foreign-key.js';
|
|
5
|
+
export { check, exprCtx, policy, PolicyBuilder, PolicyExprRef, policySQL } from './policy.js';
|
|
6
|
+
export type { CheckDef, PolicyCommand, PolicyDef, PolicyExpr, PolicyExprCtx, PolicyMode, PolicyRole, TableRef } from './policy.js';
|
|
7
|
+
export { raw } from './raw.js';
|
|
8
|
+
export type { RawConstraintDef, RawUnitDef } from './raw.js';
|
|
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';
|
|
11
|
+
export { toModel } from './to-model.js';
|