UNPKG

@electric-sql/pglite

Version:

PGlite is a WASM Postgres build packaged into a TypeScript client library that enables you to run Postgres in the browser, Node.js and Bun, with no need to install any other dependencies. It is only 3.7mb gzipped.

127 lines (125 loc) 10.9 kB
import{u as I,v as O,w as C}from"../chunk-M6G2OE44.js";import{j as P}from"../chunk-BTBUZ646.js";P();var M=5,U=async(u,w)=>{let g=new Set,A={async query(e,p,i){let R,c,r;if(typeof e!="string"&&(R=e.signal,p=e.params,i=e.callback,c=e.offset,r=e.limit,e=e.query),c===void 0!=(r===void 0))throw new Error("offset and limit must be provided together");let a=c!==void 0&&r!==void 0,f;if(a&&(typeof c!="number"||isNaN(c)||typeof r!="number"||isNaN(r)))throw new Error("offset and limit must be numbers");let d=i?[i]:[],_=I().replace(/-/g,""),T=!1,v,L,h,N=async()=>{await u.transaction(async t=>{let s=p&&p.length>0?await O(u,e,p,t):e;await t.exec(`CREATE OR REPLACE TEMP VIEW live_query_${_}_view AS ${s}`),L=await q(t,`live_query_${_}_view`),await F(t,L,g),a?(await t.exec(` PREPARE live_query_${_}_get(int, int) AS SELECT * FROM live_query_${_}_view LIMIT $1 OFFSET $2; `),await t.exec(` PREPARE live_query_${_}_get_total_count AS SELECT COUNT(*) FROM live_query_${_}_view; `),f=(await t.query(`EXECUTE live_query_${_}_get_total_count;`)).rows[0].count,v={...await t.query(`EXECUTE live_query_${_}_get(${r}, ${c});`),offset:c,limit:r,totalCount:f}):(await t.exec(` PREPARE live_query_${_}_get AS SELECT * FROM live_query_${_}_view; `),v=await t.query(`EXECUTE live_query_${_}_get;`)),h=await Promise.all(L.map(l=>t.listen(`"table_change__${l.schema_name}__${l.table_name}"`,async()=>{m()})))})};await N();let m=C(async({offset:t,limit:s}={})=>{if(!a&&(t!==void 0||s!==void 0))throw new Error("offset and limit cannot be provided for non-windowed queries");if(t&&(typeof t!="number"||isNaN(t))||s&&(typeof s!="number"||isNaN(s)))throw new Error("offset and limit must be numbers");c=t??c,r=s??r;let l=async(E=0)=>{if(d.length!==0){try{a?v={...await u.query(`EXECUTE live_query_${_}_get(${r}, ${c});`),offset:c,limit:r,totalCount:f}:v=await u.query(`EXECUTE live_query_${_}_get;`)}catch(n){let $=n.message;if($.startsWith(`prepared statement "live_query_${_}`)&&$.endsWith("does not exist")){if(E>M)throw n;await N(),l(E+1)}else throw n}if(S(d,v),a){let n=(await u.query(`EXECUTE live_query_${_}_get_total_count;`)).rows[0].count;n!==f&&(f=n,m())}}};await l()}),y=t=>{if(T)throw new Error("Live query is no longer active and cannot be subscribed to");d.push(t)},o=async t=>{t?d=d.filter(s=>s!==s):d=[],d.length===0&&!T&&(T=!0,await u.transaction(async s=>{await Promise.all(h.map(l=>l(s))),await s.exec(` DROP VIEW IF EXISTS live_query_${_}_view; DEALLOCATE live_query_${_}_get; `)}))};return R?.aborted?await o():R?.addEventListener("abort",()=>{o()},{once:!0}),S(d,v),{initialResults:v,subscribe:y,unsubscribe:o,refresh:m}},async changes(e,p,i,R){let c;if(typeof e!="string"&&(c=e.signal,p=e.params,i=e.key,R=e.callback,e=e.query),!i)throw new Error("key is required for changes queries");let r=R?[R]:[],a=I().replace(/-/g,""),f=!1,d,_=1,T,v,L=async()=>{await u.transaction(async o=>{let t=await O(u,e,p,o);await o.query(`CREATE OR REPLACE TEMP VIEW live_query_${a}_view AS ${t}`),d=await q(o,`live_query_${a}_view`),await F(o,d,g);let s=[...(await o.query(` SELECT column_name, data_type, udt_name FROM information_schema.columns WHERE table_name = 'live_query_${a}_view' `)).rows,{column_name:"__after__",data_type:"integer"}];await o.exec(` CREATE TEMP TABLE live_query_${a}_state1 (LIKE live_query_${a}_view INCLUDING ALL); CREATE TEMP TABLE live_query_${a}_state2 (LIKE live_query_${a}_view INCLUDING ALL); `);for(let l of[1,2]){let E=l===1?2:1;await o.exec(` PREPARE live_query_${a}_diff${l} AS WITH prev AS (SELECT LAG("${i}") OVER () as __after__, * FROM live_query_${a}_state${E}), curr AS (SELECT LAG("${i}") OVER () as __after__, * FROM live_query_${a}_state${l}), data_diff AS ( -- INSERT operations: Include all columns SELECT 'INSERT' AS __op__, ${s.map(({column_name:n})=>`curr."${n}" AS "${n}"`).join(`, `)}, ARRAY[]::text[] AS __changed_columns__ FROM curr LEFT JOIN prev ON curr.${i} = prev.${i} WHERE prev.${i} IS NULL UNION ALL -- DELETE operations: Include only the primary key SELECT 'DELETE' AS __op__, ${s.map(({column_name:n,data_type:$,udt_name:b})=>n===i?`prev."${n}" AS "${n}"`:`NULL${$==="USER-DEFINED"?`::${b}`:""} AS "${n}"`).join(`, `)}, ARRAY[]::text[] AS __changed_columns__ FROM prev LEFT JOIN curr ON prev.${i} = curr.${i} WHERE curr.${i} IS NULL UNION ALL -- UPDATE operations: Include only changed columns SELECT 'UPDATE' AS __op__, ${s.map(({column_name:n,data_type:$,udt_name:b})=>n===i?`curr."${n}" AS "${n}"`:`CASE WHEN curr."${n}" IS DISTINCT FROM prev."${n}" THEN curr."${n}" ELSE NULL${$==="USER-DEFINED"?`::${b}`:""} END AS "${n}"`).join(`, `)}, ARRAY(SELECT unnest FROM unnest(ARRAY[${s.filter(({column_name:n})=>n!==i).map(({column_name:n})=>`CASE WHEN curr."${n}" IS DISTINCT FROM prev."${n}" THEN '${n}' ELSE NULL END`).join(", ")}]) WHERE unnest IS NOT NULL) AS __changed_columns__ FROM curr INNER JOIN prev ON curr.${i} = prev.${i} WHERE NOT (curr IS NOT DISTINCT FROM prev) ) SELECT * FROM data_diff; `)}v=await Promise.all(d.map(l=>o.listen(`"table_change__${l.schema_name}__${l.table_name}"`,async()=>{h()})))})};await L();let h=C(async()=>{if(r.length===0&&T)return;let o=!1;for(let t=0;t<5;t++)try{await u.transaction(async s=>{await s.exec(` INSERT INTO live_query_${a}_state${_} SELECT * FROM live_query_${a}_view; `),T=await s.query(`EXECUTE live_query_${a}_diff${_};`),_=_===1?2:1,await s.exec(` TRUNCATE live_query_${a}_state${_}; `)});break}catch(s){if(s.message===`relation "live_query_${a}_state${_}" does not exist`){o=!0,await L();continue}else throw s}D(r,[...o?[{__op__:"RESET"}]:[],...T.rows])}),N=o=>{if(f)throw new Error("Live query is no longer active and cannot be subscribed to");r.push(o)},m=async o=>{o?r=r.filter(t=>t!==t):r=[],r.length===0&&!f&&(f=!0,await u.transaction(async t=>{await Promise.all(v.map(s=>s(t))),await t.exec(` DROP VIEW IF EXISTS live_query_${a}_view; DROP TABLE IF EXISTS live_query_${a}_state1; DROP TABLE IF EXISTS live_query_${a}_state2; DEALLOCATE live_query_${a}_diff1; DEALLOCATE live_query_${a}_diff2; `)}))};return c?.aborted?await m():c?.addEventListener("abort",()=>{m()},{once:!0}),await h(),{fields:T.fields.filter(o=>!["__after__","__op__","__changed_columns__"].includes(o.name)),initialChanges:T.rows,subscribe:N,unsubscribe:m,refresh:h}},async incrementalQuery(e,p,i,R){let c;if(typeof e!="string"&&(c=e.signal,p=e.params,i=e.key,R=e.callback,e=e.query),!i)throw new Error("key is required for incremental queries");let r=R?[R]:[],a=new Map,f=new Map,d=[],_=!0,{fields:T,unsubscribe:v,refresh:L}=await A.changes(e,p,i,m=>{for(let t of m){let{__op__:s,__changed_columns__:l,...E}=t;switch(s){case"RESET":a.clear(),f.clear();break;case"INSERT":a.set(E[i],E),f.set(E.__after__,E[i]);break;case"DELETE":{let n=a.get(E[i]);a.delete(E[i]),n.__after__!==null&&f.delete(n.__after__);break}case"UPDATE":{let n={...a.get(E[i])??{}};for(let $ of l)n[$]=E[$],$==="__after__"&&f.set(E.__after__,E[i]);a.set(E[i],n);break}}}let y=[],o=null;for(let t=0;t<a.size;t++){let s=f.get(o),l=a.get(s);if(!l)break;let E={...l};delete E.__after__,y.push(E),o=s}d=y,_||S(r,{rows:y,fields:T})});_=!1,S(r,{rows:d,fields:T});let h=m=>{r.push(m)},N=async m=>{m?r=r.filter(y=>y!==y):r=[],r.length===0&&await v()};return c?.aborted?await N():c?.addEventListener("abort",()=>{N()},{once:!0}),{initialResults:{rows:d,fields:T},subscribe:h,unsubscribe:N,refresh:L}}};return{namespaceObj:A}},j={name:"Live Queries",setup:U};async function q(u,w){return(await u.query(` WITH RECURSIVE view_dependencies AS ( -- Base case: Get the initial view's dependencies SELECT DISTINCT cl.relname AS dependent_name, n.nspname AS schema_name, cl.relkind = 'v' AS is_view FROM pg_rewrite r JOIN pg_depend d ON r.oid = d.objid JOIN pg_class cl ON d.refobjid = cl.oid JOIN pg_namespace n ON cl.relnamespace = n.oid WHERE r.ev_class = ( SELECT oid FROM pg_class WHERE relname = $1 AND relkind = 'v' ) AND d.deptype = 'n' UNION ALL -- Recursive case: Traverse dependencies for views SELECT DISTINCT cl.relname AS dependent_name, n.nspname AS schema_name, cl.relkind = 'v' AS is_view FROM view_dependencies vd JOIN pg_rewrite r ON vd.dependent_name = ( SELECT relname FROM pg_class WHERE oid = r.ev_class AND relkind = 'v' ) JOIN pg_depend d ON r.oid = d.objid JOIN pg_class cl ON d.refobjid = cl.oid JOIN pg_namespace n ON cl.relnamespace = n.oid WHERE d.deptype = 'n' ) SELECT DISTINCT dependent_name AS table_name, schema_name FROM view_dependencies WHERE NOT is_view; -- Exclude intermediate views `,[w])).rows.map(A=>({table_name:A.table_name,schema_name:A.schema_name}))}async function F(u,w,g){let A=w.filter(e=>!g.has(`${e.schema_name}_${e.table_name}`)).map(e=>` CREATE OR REPLACE FUNCTION "_notify_${e.schema_name}_${e.table_name}"() RETURNS TRIGGER AS $$ BEGIN PERFORM pg_notify('table_change__${e.schema_name}__${e.table_name}', ''); RETURN NULL; END; $$ LANGUAGE plpgsql; CREATE OR REPLACE TRIGGER "_notify_trigger_${e.schema_name}_${e.table_name}" AFTER INSERT OR UPDATE OR DELETE ON "${e.schema_name}"."${e.table_name}" FOR EACH STATEMENT EXECUTE FUNCTION "_notify_${e.schema_name}_${e.table_name}"(); `).join(` `);A.trim()!==""&&await u.exec(A),w.map(e=>g.add(`${e.schema_name}_${e.table_name}`))}var S=(u,w)=>{for(let g of u)g(w)},D=(u,w)=>{for(let g of u)g(w)};export{j as live}; //# sourceMappingURL=index.js.map