@effectstream/db 0.100.10 → 0.100.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effectstream/db",
3
- "version": "0.100.10",
3
+ "version": "0.100.12",
4
4
  "license": "MIT",
5
5
  "homepage": "https://effectstream.github.io/docs/",
6
6
  "repository": {
@@ -21,11 +21,11 @@
21
21
  "./version": "./migrations/system-version.ts"
22
22
  },
23
23
  "dependencies": {
24
- "@effectstream/coroutine": "0.100.10",
25
- "@effectstream/log": "0.100.10",
26
- "@effectstream/runtime": "0.100.10",
27
- "@effectstream/sync": "0.100.10",
28
- "@effectstream/utils": "0.100.10",
24
+ "@effectstream/coroutine": "0.100.12",
25
+ "@effectstream/log": "0.100.12",
26
+ "@effectstream/runtime": "0.100.12",
27
+ "@effectstream/sync": "0.100.12",
28
+ "@effectstream/utils": "0.100.12",
29
29
  "@electric-sql/pglite": "^0.3.14",
30
30
  "@pgtyped/runtime": "2.4.2",
31
31
  "@pgtyped/parser": "2.4.2",
@@ -1,119 +1,150 @@
1
1
  #!/usr/bin/env bun
2
2
  /**
3
- * Runs database startup and pgtyped generation concurrently
3
+ * Starts PGLite, applies system + user migrations, runs pgtyped codegen.
4
4
  */
5
5
 
6
6
  import { spawn } from "node:child_process";
7
- import { dirname, join } from "node:path";
8
- import { fileURLToPath } from "node:url";
7
+ import { resolve } from "node:path";
8
+ import { existsSync } from "node:fs";
9
9
 
10
- const __dirname = dirname(import.meta.url?.startsWith("file:") ? fileURLToPath(import.meta.url) : import.meta.url.replace("file://", ""));
11
-
12
- interface ProcessInfo {
13
- name: string;
14
- command: string;
15
- args: string[];
16
- wait: boolean;
17
- }
10
+ import pg from "pg";
11
+ import { startPglite, type PgliteHandle } from "./start-pglite.ts";
12
+ import { waitForDb } from "./wait-for-db.ts";
13
+ import { applyMigrations } from "./apply-migrations.ts";
14
+ import { getConnection } from "../src/pg-connection.ts";
15
+ import { getMigrations } from "../migrations/system-version.ts";
18
16
 
19
17
  function createPrefix(name: string): string {
20
- const colors = {
21
- "db:up": "\x1b[36m", // cyan
22
- "pgtyped:internal": "\x1b[33m", // yellow
18
+ const colors: Record<string, string> = {
19
+ "db:up": "\x1b[36m",
20
+ "user-migrations": "\x1b[35m",
21
+ "pgtyped": "\x1b[33m",
23
22
  };
24
23
  const reset = "\x1b[0m";
25
- const color = colors[name as keyof typeof colors] || "\x1b[37m"; // white
24
+ const color = colors[name] || "\x1b[37m";
26
25
  return `${color}[${name}]${reset} `;
27
26
  }
28
27
 
29
- async function runProcess(processInfo: ProcessInfo): Promise<void> {
30
- const prefix = createPrefix(processInfo.name);
28
+ export interface UserMigration {
29
+ name: string;
30
+ sql: string;
31
+ }
31
32
 
32
- console.log(`${prefix}Starting...`);
33
+ export interface PgtypedUpdateOptions {
34
+ userMigrations?: UserMigration[];
35
+ pgtypedConfigPath?: string;
36
+ pgtypedBin?: string;
37
+ }
38
+
39
+ async function resolveUserMigrations(): Promise<UserMigration[]> {
40
+ const candidates = [
41
+ resolve(process.cwd(), "migration-order.ts"),
42
+ resolve(process.cwd(), "src/migration-order.ts"),
43
+ ];
33
44
 
34
- const child = spawn(processInfo.command, processInfo.args, {
35
- stdio: ["inherit", "pipe", "pipe"],
36
- shell: processInfo.command === "npx",
45
+ const migrationFile = candidates.find((p) => existsSync(p));
46
+ if (!migrationFile) return [];
47
+
48
+ const mod = await import(migrationFile);
49
+ return mod.migrationTable ?? [];
50
+ }
51
+
52
+ async function applyUserMigrations(migrations: UserMigration[]): Promise<void> {
53
+ const prefix = createPrefix("user-migrations");
54
+
55
+ if (migrations.length === 0) {
56
+ console.log(`${prefix}No user migrations to apply, skipping`);
57
+ return;
58
+ }
59
+
60
+ const client = new pg.Client({
61
+ host: "localhost",
62
+ port: 5432,
63
+ user: "postgres",
64
+ database: "postgres",
37
65
  });
66
+ await client.connect();
67
+
68
+ try {
69
+ for (const migration of migrations) {
70
+ console.log(`${prefix}Applying ${migration.name}`);
71
+ await client.query(migration.sql);
72
+ }
73
+ console.log(`${prefix}✅ Applied ${migrations.length} user migration(s)`);
74
+ } finally {
75
+ await client.end();
76
+ }
77
+ }
78
+
79
+ async function applySystemMigrations(): Promise<void> {
80
+ const prefix = createPrefix("apply-migrations");
81
+ const db = getConnection();
82
+ const migrations = await getMigrations();
83
+ for (const migration of migrations) {
84
+ await applyMigrations(db as any, 0, migration.version, migration.sql, true);
85
+ }
86
+ console.log(`${prefix}✅ System migrations applied`);
87
+ await db.end();
88
+ }
89
+
90
+ async function runPgtyped(pgtypedBin: string, pgtypedConfigPath: string): Promise<void> {
91
+ const prefix = createPrefix("pgtyped");
92
+ console.log(`${prefix}Starting...`);
93
+
94
+ return new Promise((resolve, reject) => {
95
+ const child = spawn("node", [pgtypedBin, "-c", pgtypedConfigPath], {
96
+ stdio: ["ignore", "pipe", "pipe"],
97
+ shell: false,
98
+ });
38
99
 
39
- const stdoutPromise = new Promise<void>((resolve) => {
40
100
  child.stdout?.on("data", (chunk: Buffer | string) => {
41
- const text = chunk.toString();
42
- text.split("\n").forEach((line) => {
101
+ chunk.toString().split("\n").forEach((line) => {
43
102
  if (line.trim()) console.log(`${prefix}${line}`);
44
103
  });
45
104
  });
46
- child.stdout?.on("end", resolve);
47
- if (!child.stdout) resolve();
48
- });
49
105
 
50
- const stderrPromise = new Promise<void>((resolve) => {
51
106
  child.stderr?.on("data", (chunk: Buffer | string) => {
52
- const text = chunk.toString();
53
- text.split("\n").forEach((line) => {
107
+ chunk.toString().split("\n").forEach((line) => {
54
108
  if (line.trim()) console.error(`${prefix}${line}`);
55
109
  });
56
110
  });
57
- child.stderr?.on("end", resolve);
58
- if (!child.stderr) resolve();
59
- });
60
111
 
61
- const exitCode = new Promise<number | null>((resolve) => {
62
- child.on("close", (code) => resolve(code));
112
+ child.on("close", (code) => {
113
+ if (code === 0) {
114
+ console.log(`${prefix}✅ Completed successfully`);
115
+ resolve();
116
+ } else {
117
+ reject(new Error(`pgtyped failed with exit code ${code}`));
118
+ }
119
+ });
63
120
  });
64
-
65
- if (processInfo.wait) {
66
- const [code] = await Promise.all([exitCode, stdoutPromise, stderrPromise]);
67
- if (code === 0) {
68
- console.log(`${prefix}✅ Completed successfully`);
69
- } else {
70
- console.error(`${prefix}❌ Failed with exit code ${code}`);
71
- throw new Error(`Process ${processInfo.name} failed`);
72
- }
73
- }
74
121
  }
75
122
 
76
- async function main() {
77
- const processes: ProcessInfo[] = [
78
- {
79
- name: "db:up",
80
- command: "bun",
81
- args: ["run", join(__dirname, "start-pglite.ts")],
82
- wait: false,
83
- },
84
- {
85
- name: "db:wait",
86
- command: "bun",
87
- args: ["run", join(__dirname, "wait-for-db.ts")],
88
- wait: true,
89
- },
90
- {
91
- name: "apply-migrations",
92
- command: "bun",
93
- args: ["run", join(__dirname, "apply-migrations.ts")],
94
- wait: true,
95
- },
96
- {
97
- name: "pgtyped",
98
- command: "npx",
99
- args: ["pgtyped", "-c", "./pgtypedconfig.json"],
100
- wait: true,
101
- },
102
- ];
123
+ export async function main(options?: PgtypedUpdateOptions) {
124
+ const userMigrations = options?.userMigrations ?? await resolveUserMigrations();
125
+ const pgtypedConfigPath = options?.pgtypedConfigPath ?? "./pgtypedconfig.json";
126
+ const pgtypedBin = options?.pgtypedBin ?? resolve(process.cwd(), "node_modules/@paima/pgtyped-cli/lib/index.js");
103
127
 
104
- console.log("🚀 Starting concurrent processes...\n");
128
+ console.log("🚀 Starting pgtyped-update...\n");
105
129
 
130
+ let pglite: PgliteHandle | undefined;
106
131
  try {
107
- for (const process of processes) {
108
- await runProcess(process);
109
- }
110
- console.log("\n✅ All processes completed successfully");
111
- } catch (error) {
112
- console.error("\n❌ One or more processes failed:", error);
113
- process.exit(1);
132
+ pglite = await startPglite();
133
+ await waitForDb();
134
+ await applySystemMigrations();
135
+ await applyUserMigrations(userMigrations);
136
+ await runPgtyped(pgtypedBin, pgtypedConfigPath);
137
+ console.log("\n✅ All steps completed successfully");
138
+ } finally {
139
+ await pglite?.close();
114
140
  }
115
141
  }
116
142
 
117
143
  if (import.meta.main) {
118
- await main();
144
+ try {
145
+ await main();
146
+ } catch (error) {
147
+ console.error("\n❌ One or more steps failed:", error);
148
+ process.exit(1);
149
+ }
119
150
  }
@@ -7,35 +7,30 @@ import { fromNodeSocket } from "pg-gateway/node";
7
7
  import { ENV } from "@effectstream/utils/node-env";
8
8
  import { args, cwd } from "@effectstream/utils/runtime";
9
9
 
10
- // TODO PORT be a ENV variable
11
- // Get port from arguments.
12
- const portArgName = "--port";
13
- const argv = args();
14
- const portArgIndex = argv.indexOf(portArgName);
15
- const portValue = portArgIndex !== -1 ? argv[portArgIndex + 1] : "5432";
16
- const port = parseInt(portValue);
17
- if (isNaN(port)) {
18
- throw new Error(`Port argument ${portArgName} is not a number`);
10
+ export interface PgliteHandle {
11
+ server: net.Server;
12
+ db: PGlite;
13
+ close: () => Promise<void>;
19
14
  }
20
15
 
21
- // Resolve pg_ivm extension path via import.meta.resolve (works with bun's hoisting)
22
- const pgliteEntry = import.meta.resolve("@electric-sql/pglite");
23
- const pgliteDir = pgliteEntry.replace(/\/dist\/[^/]+$/, "");
24
- const pgIvmUrl = new URL(`${pgliteDir}/dist/pg_ivm.tar.gz`);
16
+ export async function startPglite(port = 5432): Promise<PgliteHandle> {
17
+ // Resolve pg_ivm extension path via import.meta.resolve (works with bun's hoisting)
18
+ const pgliteEntry = import.meta.resolve("@electric-sql/pglite");
19
+ const pgliteDir = pgliteEntry.replace(/\/dist\/[^/]+$/, "");
20
+ const pgIvmUrl = new URL(`${pgliteDir}/dist/pg_ivm.tar.gz`);
25
21
 
26
- const db = new PGlite(
27
- ENV.PGLITE_DATA_DIR,
28
- {
29
- username: ENV.DB_USER,
30
- database: ENV.DB_NAME,
31
- extensions: {
32
- pg_ivm: pgIvmUrl,
22
+ const db = new PGlite(
23
+ ENV.PGLITE_DATA_DIR,
24
+ {
25
+ username: ENV.DB_USER,
26
+ database: ENV.DB_NAME,
27
+ extensions: {
28
+ pg_ivm: pgIvmUrl,
29
+ },
30
+ debug: (ENV.DEBUG_PGLITE as DebugLevel) || 0,
33
31
  },
34
- debug: (ENV.DEBUG_PGLITE as DebugLevel) || 0,
35
- },
36
- );
32
+ );
37
33
 
38
- {
39
34
  // TODO: consider switching to pglite-socket once it works
40
35
  // https://discord.com/channels/933657521581858818/1371976702674075780/1371992712076595250
41
36
  const server = net.createServer(async (socket) => {
@@ -65,7 +60,33 @@ const db = new PGlite(
65
60
  });
66
61
  });
67
62
 
68
- server.listen(port, () => {
69
- console.info(`database: server listening on port ${port}`);
63
+ await new Promise<void>((resolve) => {
64
+ server.listen(port, () => {
65
+ console.info(`database: server listening on port ${port}`);
66
+ resolve();
67
+ });
70
68
  });
69
+
70
+ return {
71
+ server,
72
+ db,
73
+ close: async () => {
74
+ server.close();
75
+ await db.close();
76
+ },
77
+ };
78
+ }
79
+
80
+ if (import.meta.main) {
81
+ // TODO PORT be a ENV variable
82
+ // Get port from arguments.
83
+ const portArgName = "--port";
84
+ const argv = args();
85
+ const portArgIndex = argv.indexOf(portArgName);
86
+ const portValue = portArgIndex !== -1 ? argv[portArgIndex + 1] : "5432";
87
+ const port = parseInt(portValue);
88
+ if (isNaN(port)) {
89
+ throw new Error(`Port argument ${portArgName} is not a number`);
90
+ }
91
+ await startPglite(port);
71
92
  }