@prisma/dev 0.21.0 → 0.22.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/index.d.ts CHANGED
@@ -1,10 +1,55 @@
1
- import { P as ProgrammaticServer } from './programmatic-QofJRfwC.js';
2
- import { ServerOptions } from './state.js';
1
+ import { Exports, ServerOptions } from './state.js';
3
2
  export { ServerAlreadyRunningError } from './state.js';
4
- import { DBServerPurpose } from './db.js';
3
+ import { WalEvent, DBServerPurpose } from './db.js';
5
4
  import 'valibot';
6
5
  import '@electric-sql/pglite';
7
6
 
7
+ /**
8
+ * A readonly batch of WAL events emitted together.
9
+ *
10
+ * @experimental This API may change without notice.
11
+ */
12
+ type ExperimentalWalEventBatch = readonly WalEvent[];
13
+ /**
14
+ * Callback invoked when a batch of WAL events is available.
15
+ *
16
+ * @experimental This API may change without notice.
17
+ */
18
+ type ExperimentalWalEventCallback = (events: ExperimentalWalEventBatch) => void;
19
+ /**
20
+ * The experimental public WAL event API returned from `startPrismaDevServer()`.
21
+ *
22
+ * @experimental This API may change without notice.
23
+ */
24
+ interface ExperimentalWalEvents {
25
+ /**
26
+ * Subscribe to WAL event batches. The returned function unsubscribes the callback.
27
+ */
28
+ subscribe(callback: ExperimentalWalEventCallback): () => void;
29
+ /**
30
+ * Create an async iterator that yields WAL event batches as they arrive.
31
+ */
32
+ stream(): AsyncIterableIterator<ExperimentalWalEventBatch>;
33
+ }
34
+ /**
35
+ * Experimental capabilities exposed on the public programmatic server.
36
+ *
37
+ * @experimental This API may change without notice.
38
+ */
39
+ interface ExperimentalServerFeatures {
40
+ readonly wal: ExperimentalWalEvents;
41
+ }
42
+ interface ProgrammaticServer extends Exports {
43
+ close(): Promise<void>;
44
+ /**
45
+ * Experimental capabilities that may change without notice.
46
+ *
47
+ * @experimental
48
+ */
49
+ experimental: ExperimentalServerFeatures;
50
+ name: string;
51
+ }
52
+
8
53
  declare const DEFAULT_DATABASE_PORT = 51214;
9
54
  declare const DEFAULT_SERVER_PORT = 51213;
10
55
  declare const DEFAULT_SHADOW_DATABASE_PORT = 51215;
@@ -20,6 +65,9 @@ type ReadonlyServer = Omit<ProgrammaticServer, "close">;
20
65
  * Starts a `prisma dev` server instance programmatically.
21
66
  *
22
67
  * DO NOT USE IN PRODUCTION. This is only intended for development and testing purposes.
68
+ *
69
+ * The returned server also includes experimental capabilities under `server.experimental`,
70
+ * including WAL event subscriptions at `server.experimental.wal`.
23
71
  */
24
72
  declare function startPrismaDevServer(options?: ServerOptions): Promise<ProgrammaticServer>;
25
73
  /**
@@ -27,4 +75,4 @@ declare function startPrismaDevServer(options?: ServerOptions): Promise<Programm
27
75
  */
28
76
  declare function unstable_startServer(options?: ServerOptions): Promise<ProgrammaticServer>;
29
77
 
30
- export { DEFAULT_DATABASE_PORT, DEFAULT_SERVER_PORT, DEFAULT_SHADOW_DATABASE_PORT, type PortAssignableService, PortNotAvailableError, type ReadonlyServer, ProgrammaticServer as Server, ServerOptions, startPrismaDevServer, unstable_startServer };
78
+ export { DEFAULT_DATABASE_PORT, DEFAULT_SERVER_PORT, DEFAULT_SHADOW_DATABASE_PORT, type ExperimentalServerFeatures, WalEvent as ExperimentalWalEvent, type ExperimentalWalEventBatch, type ExperimentalWalEvents, type PortAssignableService, PortNotAvailableError, type ReadonlyServer, type ProgrammaticServer as Server, ServerOptions, startPrismaDevServer, unstable_startServer };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{a as f,b as g}from"./chunk-VXHP3HPZ.js";import"./chunk-DZGCLGSX.js";import{g as e}from"./chunk-FIY24ARL.js";import{a,b,c,g as d}from"./chunk-OTI5SWIV.js";import"./chunk-6ORCLJD5.js";import"./chunk-DGKV2DPF.js";export{a as DEFAULT_DATABASE_PORT,b as DEFAULT_SERVER_PORT,c as DEFAULT_SHADOW_DATABASE_PORT,d as PortNotAvailableError,e as ServerAlreadyRunningError,f as startPrismaDevServer,g as unstable_startServer};
1
+ import{a as f,b as g}from"./chunk-E7DPFNXK.js";import"./chunk-YYOOMJ67.js";import{g as e}from"./chunk-FIY24ARL.js";import{a,b,c,g as d}from"./chunk-OTI5SWIV.js";import"./chunk-6ORCLJD5.js";import"./chunk-DGKV2DPF.js";export{a as DEFAULT_DATABASE_PORT,b as DEFAULT_SERVER_PORT,c as DEFAULT_SHADOW_DATABASE_PORT,d as PortNotAvailableError,e as ServerAlreadyRunningError,f as startPrismaDevServer,g as unstable_startServer};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma/dev",
3
- "version": "0.21.0",
3
+ "version": "0.22.1",
4
4
  "description": "A local Prisma Postgres server for development and testing",
5
5
  "type": "module",
6
6
  "author": "Igal Klebanov <igalklebanov@gmail.com> (https://github.com/igalklebanov)",
@@ -77,16 +77,16 @@
77
77
  "common-stuff": "^0.0.0"
78
78
  },
79
79
  "dependencies": {
80
- "@electric-sql/pglite": "0.3.16",
81
- "@electric-sql/pglite-socket": "0.0.22",
82
- "@electric-sql/pglite-tools": "0.2.21",
83
- "@hono/node-server": "1.19.9",
84
- "@mrleebo/prisma-ast": "0.13.1",
80
+ "@electric-sql/pglite": "0.4.0",
81
+ "@electric-sql/pglite-socket": "0.1.0",
82
+ "@electric-sql/pglite-tools": "0.3.0",
83
+ "@hono/node-server": "1.19.11",
84
+ "@mrleebo/prisma-ast": "0.15.0",
85
85
  "@prisma/get-platform": "7.2.0",
86
86
  "@prisma/query-plan-executor": "7.2.0",
87
87
  "foreground-child": "3.3.1",
88
88
  "get-port-please": "3.2.0",
89
- "hono": "4.11.4",
89
+ "hono": "^4.12.8",
90
90
  "http-status-codes": "2.3.0",
91
91
  "pathe": "2.0.3",
92
92
  "proper-lockfile": "4.1.2",
@@ -1,84 +0,0 @@
1
- import{g as w}from"./chunk-OTI5SWIV.js";import{e as b}from"./chunk-DGKV2DPF.js";import{filename as k}from"pathe/utils";import{protocol as G}from"@electric-sql/pglite";var m="_prisma_dev_wal",p="events",_="install_all_triggers",f="capture_event",N="prisma_dev_wal_capture",E=new WeakMap,W=new Set(["ALTER","COMMIT","COPY","CREATE","DELETE","DROP","INSERT","MERGE","TRUNCATE","UPDATE"]);async function g(e,t){let o=E.get(e);if(o&&!o.closed)return o.bridge;let n=e.execProtocolRaw.bind(e),r={bridge:{close:async()=>{r.closed||(r.closed=!0,r.subscribers.clear(),e.execProtocolRaw===s&&(e.execProtocolRaw=n),await r.pollPromise,E.delete(e))},poll:async()=>{await S(r,e)},subscribe:d=>(r.subscribers.add(d),()=>{r.subscribers.delete(d)})},closed:!1,ensureInfrastructurePromise:null,pendingPoll:!1,pollPromise:null,subscribers:new Set,suppressDepth:0},s=async(d,l)=>{let i=await n(d,l);return!r.closed&&r.suppressDepth===0&&L(i)&&S(r,e),i};return e.execProtocolRaw=s,E.set(e,r),await D(r,e),r.bridge}async function R(e){let t=E.get(e);t&&await t.bridge.close()}function A(e){for(let t of e){if(t.name!=="commandComplete"||typeof t.text!="string")continue;let o=t.text.split(/\s+/,1)[0]?.toUpperCase();if(o&&W.has(o))return!0}return!1}function L(e){if(e.length===0)return!1;let t=[];return new G.Parser().parse(e,n=>{t.push(n)}),A(t)}async function D(e,t){e.ensureInfrastructurePromise??=T(e,t,async()=>{await t.exec(`CREATE SCHEMA IF NOT EXISTS "${m}"`),await t.exec(`
2
- CREATE TABLE IF NOT EXISTS "${m}"."${p}" (
3
- id BIGSERIAL PRIMARY KEY,
4
- txid BIGINT NOT NULL DEFAULT txid_current(),
5
- schema_name TEXT NOT NULL,
6
- table_name TEXT NOT NULL,
7
- op TEXT NOT NULL,
8
- row_data JSONB,
9
- old_row_data JSONB,
10
- created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp()
11
- )
12
- `),await t.exec(`
13
- CREATE OR REPLACE FUNCTION "${m}"."${f}"()
14
- RETURNS trigger
15
- LANGUAGE plpgsql
16
- AS $$
17
- BEGIN
18
- IF TG_TABLE_SCHEMA = '${m}' THEN
19
- RETURN COALESCE(NEW, OLD);
20
- END IF;
21
-
22
- INSERT INTO "${m}"."${p}" (
23
- txid,
24
- schema_name,
25
- table_name,
26
- op,
27
- row_data,
28
- old_row_data
29
- )
30
- VALUES (
31
- txid_current(),
32
- TG_TABLE_SCHEMA,
33
- TG_TABLE_NAME,
34
- lower(TG_OP),
35
- CASE WHEN TG_OP IN ('INSERT', 'UPDATE') THEN to_jsonb(NEW) ELSE NULL END,
36
- CASE WHEN TG_OP IN ('UPDATE', 'DELETE') THEN to_jsonb(OLD) ELSE NULL END
37
- );
38
-
39
- RETURN COALESCE(NEW, OLD);
40
- END;
41
- $$;
42
- `),await t.exec(`
43
- CREATE OR REPLACE FUNCTION "${m}"."${_}"()
44
- RETURNS void
45
- LANGUAGE plpgsql
46
- AS $$
47
- DECLARE
48
- target REGCLASS;
49
- BEGIN
50
- FOR target IN
51
- SELECT c.oid::regclass
52
- FROM pg_class AS c
53
- JOIN pg_namespace AS n ON n.oid = c.relnamespace
54
- WHERE c.relkind IN ('r', 'p')
55
- AND n.nspname NOT IN ('${m}', 'information_schema', 'pg_catalog')
56
- AND n.nspname NOT LIKE 'pg_temp_%'
57
- AND n.nspname NOT LIKE 'pg_toast%'
58
- LOOP
59
- IF EXISTS (
60
- SELECT 1
61
- FROM pg_trigger
62
- WHERE tgrelid = target
63
- AND tgname = '${N}'
64
- ) THEN
65
- CONTINUE;
66
- END IF;
67
-
68
- EXECUTE format(
69
- 'CREATE TRIGGER %I AFTER INSERT OR UPDATE OR DELETE ON %s FOR EACH ROW EXECUTE FUNCTION "${m}"."${f}"()',
70
- '${N}',
71
- target::text
72
- );
73
- END LOOP;
74
- END;
75
- $$;
76
- `),await v(e,t)}),await e.ensureInfrastructurePromise}async function v(e,t){await T(e,t,async()=>{await t.query(`SELECT "${m}"."${_}"()`)})}async function U(e,t){await D(e,t),await v(e,t);let o=await T(e,t,async()=>await t.query(`
77
- WITH drained AS (
78
- DELETE FROM "${m}"."${p}"
79
- RETURNING txid, schema_name, table_name, op, row_data, old_row_data, id
80
- )
81
- SELECT txid, schema_name, table_name, op, row_data, old_row_data
82
- FROM drained
83
- ORDER BY id
84
- `));if(o.rows.length===0||e.subscribers.size===0)return;let n=o.rows.map(M);for(let r of e.subscribers)queueMicrotask(()=>{if(!e.closed&&e.subscribers.has(r))try{r(n)}catch(s){console.error("[WAL bridge] subscriber failed",s)}})}async function S(e,t){if(!e.closed){if(e.pollPromise){e.pendingPoll=!0,await e.pollPromise;return}e.pollPromise=(async()=>{do e.pendingPoll=!1,await U(e,t);while(e.pendingPoll&&!e.closed)})().finally(()=>{e.pollPromise=null}),await e.pollPromise}}async function T(e,t,o){e.suppressDepth+=1;try{return await o()}finally{e.suppressDepth-=1,e.suppressDepth===0&&!e.closed&&E.get(t)!==e&&(e.closed=!0)}}function M(e){return{oldRecord:e.old_row_data,record:e.row_data,schema:e.schema_name,table:e.table_name,txid:String(e.txid),type:F(e.op)}}function F(e){switch(e.toLowerCase()){case"delete":return"delete";case"insert":return"insert";case"update":return"update";default:throw new Error(`Unsupported WAL bridge operation: ${e}`)}}var H=10,a={connectionLimit:H,connectTimeout:0,database:"template1",maxIdleConnectionLifetime:0,password:"postgres",poolTimeout:0,socketTimeout:0,sslMode:"disable",username:"postgres"},q=`postgres://${a.username}:${a.password}@localhost`,h=new URLSearchParams({sslmode:a.sslMode}),X=new URLSearchParams({...Object.fromEntries(h.entries()),connection_limit:String(a.connectionLimit),connect_timeout:String(a.connectTimeout),max_idle_connection_lifetime:String(a.maxIdleConnectionLifetime),pool_timeout:String(a.poolTimeout),socket_timeout:String(a.socketTimeout)});async function te(e,t){let o=e==="database"?t.databasePort:t.shadowDatabasePort;if(t.dryRun)return y(e,t,{db:null,port:o,server:null});let{debug:n}=t,s=await(e==="shadow_database"?Y:O)(t.pgliteDataDirPath,n);n&&s.onNotification((c,u)=>{console.debug(`[${e}][${c}] ${u}`)});let{PGLiteSocketServer:d}=await import("@electric-sql/pglite-socket"),l=e==="shadow_database"?t.shadowDatabaseIdleTimeoutMillis:t.databaseIdleTimeoutMillis,i=new d({db:s,debug:n,idleTimeout:Number.isFinite(l)?l:0,inspect:n,maxConnections:a.connectionLimit,port:o});n&&(i.addEventListener("listening",c=>{let{detail:u}=c;console.debug(`[${e}] server listening on ${JSON.stringify(u)}`)}),i.addEventListener("connection",c=>{let{clientAddress:u,clientPort:$}=c.detail;console.debug(`[${e}] client connected from ${u}:${$}`)}),i.addEventListener("error",c=>{let{detail:u}=c;console.error(`[${e}] server error:`,u)}));try{await i.start()}catch(c){throw c instanceof Error&&"code"in c&&c.code==="EADDRINUSE"?new w(o):c}let P=Number(i.getServerConn().split(":").at(1));return t[e==="database"?"databasePort":"shadowDatabasePort"]=P,y(e,t,{db:s,port:P,server:i})}function y(e,t,o){let{debug:n}=t,{db:r,port:s,server:d}=o||{};return n&&console.debug(`[${e}] server started on port ${s}`),{...a,attachWalEventBridge:async()=>{if(e!=="database"||!r)throw new Error("WAL bridge is only available for the primary database server");return await g(r)},close:async()=>{let l=[];try{await d?.stop(),n&&console.debug(`[${e}] server stopped on port ${s}`)}catch(i){console.error(`[${e}] server stop error`,i),l.push(i)}if(e==="database"){try{r&&await R(r),n&&console.debug(`[${e}] closed WAL bridge`)}catch(i){console.error(`[${e}] WAL bridge close error`,i),l.push(i)}try{await r?.syncToFs(),n&&console.debug(`[${e}] synced to filesystem`)}catch(i){console.error(`[${e}] sync error`,i),l.push(i)}}try{await r?.close(),n&&console.debug(`[${e}] closed`)}catch(i){console.error(`[${e}] close error`,i),l.push(i)}if(l.length>0)throw new AggregateError(l,`Failed to close ${e} properly`)},connectionString:I(s,h),dump:async l=>{e==="shadow_database"||!r||await Q({db:r,debug:n,destinationPath:l})},port:s,prismaORMConnectionString:I(s,X),terminalCommand:`PGPASSWORD=${a.password} PGSSLMODE=${a.sslMode} psql -h localhost -p ${s} -U ${a.username} -d ${a.database}`}}function I(e,t){return`${q}:${e}/${a.database}?${t.toString()}`}async function O(e,t){let{PGlite:o}=await import("@electric-sql/pglite");return await o.create({database:a.database,dataDir:e,debug:t?5:void 0,extensions:await B(),relaxedDurability:!1,username:a.username})}async function Y(e,t){let{PGlite:o}=await import("@electric-sql/pglite");return await o.create({database:a.database,dataDir:"memory://",debug:t?5:void 0,extensions:await B(),relaxedDurability:!1,username:a.username})}async function B(){let e=await Promise.all([import("@electric-sql/pglite/contrib/amcheck"),import("@electric-sql/pglite/contrib/bloom"),import("@electric-sql/pglite/contrib/btree_gin"),import("@electric-sql/pglite/contrib/btree_gist"),import("@electric-sql/pglite/contrib/citext"),import("@electric-sql/pglite/contrib/cube"),import("@electric-sql/pglite/contrib/dict_int"),import("@electric-sql/pglite/contrib/dict_xsyn"),import("@electric-sql/pglite/contrib/earthdistance"),import("@electric-sql/pglite/contrib/file_fdw"),import("@electric-sql/pglite/contrib/fuzzystrmatch"),import("@electric-sql/pglite/contrib/hstore"),import("@electric-sql/pglite/contrib/intarray"),import("@electric-sql/pglite/contrib/isn"),import("@electric-sql/pglite/contrib/lo"),import("@electric-sql/pglite/contrib/ltree"),import("@electric-sql/pglite/contrib/pageinspect"),import("@electric-sql/pglite/contrib/pg_buffercache"),import("@electric-sql/pglite/contrib/pg_freespacemap"),import("@electric-sql/pglite/contrib/pg_surgery"),import("@electric-sql/pglite/contrib/pg_trgm"),import("@electric-sql/pglite/contrib/pg_visibility"),import("@electric-sql/pglite/contrib/pg_walinspect"),import("@electric-sql/pglite/contrib/seg"),import("@electric-sql/pglite/contrib/tablefunc"),import("@electric-sql/pglite/contrib/tcn"),import("@electric-sql/pglite/contrib/tsm_system_rows"),import("@electric-sql/pglite/contrib/tsm_system_time"),import("@electric-sql/pglite/contrib/unaccent"),import("@electric-sql/pglite/contrib/uuid_ossp"),import("@electric-sql/pglite/vector")]);return Object.assign({},...e)}async function Q(e){let{dataDir:t,db:o,debug:n,destinationPath:r}=e,s=o||await O(t,n),{pgDump:d}=await import("@electric-sql/pglite-tools/pg_dump"),l=await d({args:["--schema-only","--no-owner"],fileName:r?k(r):void 0,pg:await s.clone()});return r?(n&&console.debug(`[DB] Dumping database to ${r}`),await b(l,r)):(n&&console.debug("[DB] Dumping database to memory"),await l.text())}export{g as a,A as b,L as c,te as d,Q as e};
@@ -1 +0,0 @@
1
- import{d as S}from"./chunk-DZGCLGSX.js";import{a as P}from"./chunk-FIY24ARL.js";import{g as p}from"./chunk-OTI5SWIV.js";import{a as v,c as u,d}from"./chunk-6ORCLJD5.js";import{createServer as D}from"http";import{promisify as A}from"util";import E from"@prisma/get-platform";async function w(t,r){let{port:e}=r;if(r.dryRun)return f(e,null);let o=await R(t,r),{promise:a,reject:l,resolve:s}=u(),{serve:c}=await import("@hono/node-server"),m=c({createServer:D,fetch:o.fetch,overrideGlobalObjects:!1,port:e},s);m.on("error",i=>{if(typeof i=="object"&&"code"in i&&i.code==="EADDRINUSE")return l(new p(e));console.error("[Accelerate]",i)});let{port:n}=await a;return r.port=n,f(n,m)}function f(t,r){return{async close(){r&&await Promise.allSettled([A(r.close.bind(r))(),d.stopAll()])},port:t,url:`http://localhost:${t}`}}async function R(t,r){let{debug:e}=r,[{Hono:o},{accelerateRoute:a},{utilityRoute:l}]=await Promise.all([import("hono/tiny"),import("./accelerate-HABH6RJU.js"),import("./utility-Q5A254LJ.js")]),s=new o,c=await E.getPlatformInfo();if(e&&console.debug("[Accelerate] platform info: %s",JSON.stringify(c)),e){let{logger:n}=await import("hono/logger");s.use("*",n((...i)=>console.log("[Accelerate]",...i)))}s.use("*",async(n,i)=>(n.set("databaseDumpPath",r.databaseDumpPath),n.set("db",t),n.set("debug",!!e),n.set("name",r.name),n.set("platform",c),n.set("shadowDBPort",r.shadowDatabasePort),await i()));let m=new o;return m.route("/",a),m.route("/",l),s.route("/",m),s}async function h(t){let r=await P.createExclusively(t),e=null,o=null,a=null;try{[e,a]=await Promise.all([S("database",r),S("shadow_database",r)]);let l=await e.attachWalEventBridge();o=await w(e,r);let s=b(e,a,o,r);await r.writeServerDump(s);let c=e,m=a,n=o,i=async()=>await T(r,[n,c,m]);return{close:i,dbServer:e,httpServer:o,server:{...s,close:i,name:r.name},serverState:r,shadowDbServer:a,walBridge:l}}catch(l){return await B(r,[o,e,a],l)}}function b(t,r,e,o){let a=`prisma+postgres://localhost:${e.port}/?${new URLSearchParams({api_key:v({databaseUrl:t.prismaORMConnectionString,name:o.name,shadowDatabaseUrl:r.prismaORMConnectionString})}).toString()}`;return{database:{connectionString:t.connectionString,prismaORMConnectionString:t.prismaORMConnectionString,terminalCommand:t.terminalCommand},http:{url:e.url},ppg:{url:a},shadowDatabase:{connectionString:r.prismaORMConnectionString,prismaORMConnectionString:r.prismaORMConnectionString,terminalCommand:r.terminalCommand}}}async function T(t,r){let o=(await Promise.allSettled(r.map(a=>a.close()))).filter(a=>a.status==="rejected").map(a=>new Error(a.reason));try{await t.close()}catch(a){o.push(a)}if(o.length>0)throw new AggregateError(o,"Failed to close some servers")}async function B(t,r,e){try{await T(t,r.filter(o=>o!==null))}catch(o){throw new AggregateError([e,o],"Failed to start Prisma Dev server cleanly")}throw e}async function O(t){let{server:r}=await h(t);return r}async function z(t){return await O(t)}export{O as a,z as b};
@@ -1,8 +0,0 @@
1
- import { Exports } from './state.cjs';
2
-
3
- interface ProgrammaticServer extends Exports {
4
- close(): Promise<void>;
5
- name: string;
6
- }
7
-
8
- export type { ProgrammaticServer as P };
@@ -1,8 +0,0 @@
1
- import { Exports } from './state.js';
2
-
3
- interface ProgrammaticServer extends Exports {
4
- close(): Promise<void>;
5
- name: string;
6
- }
7
-
8
- export type { ProgrammaticServer as P };