@murumets-ee/db 0.1.4 → 0.1.5

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.
@@ -1,11 +1,12 @@
1
- import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
2
- import { PgTableWithColumns } from 'drizzle-orm/pg-core';
1
+ import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
+ import { PgTableWithColumns } from "drizzle-orm/pg-core";
3
3
 
4
+ //#region src/client.d.ts
4
5
  interface DbConfig {
5
- url: string;
6
- readOnlyUrl?: string;
7
- poolMin?: number;
8
- poolMax?: number;
6
+ url: string;
7
+ readOnlyUrl?: string;
8
+ poolMin?: number;
9
+ poolMax?: number;
9
10
  }
10
11
  /**
11
12
  * Create a full read-write database client
@@ -16,15 +17,16 @@ declare function createDbClient(config: DbConfig): PostgresJsDatabase;
16
17
  * Sets default_transaction_read_only=on to prevent writes even if SQL injection occurs
17
18
  */
18
19
  declare function createReadOnlyClient(config: DbConfig): PostgresJsDatabase;
19
-
20
+ //#endregion
21
+ //#region src/migrate.d.ts
20
22
  interface MigrationSource {
21
- path: string;
22
- namespace: 'toolkit' | 'project';
23
- name: string;
23
+ path: string;
24
+ namespace: 'toolkit' | 'project';
25
+ name: string;
24
26
  }
25
27
  interface MigrationStatus {
26
- applied: MigrationSource[];
27
- pending: MigrationSource[];
28
+ applied: MigrationSource[];
29
+ pending: MigrationSource[];
28
30
  }
29
31
  /**
30
32
  * Discover migrations from both toolkit (.toolkit/) and project (migrations/) directories
@@ -38,7 +40,8 @@ declare function getMigrationStatus(db: PostgresJsDatabase, projectRoot: string)
38
40
  * Run pending migrations
39
41
  */
40
42
  declare function runMigrations(db: PostgresJsDatabase, projectRoot: string): Promise<void>;
41
-
43
+ //#endregion
44
+ //#region src/schema-registry.d.ts
42
45
  /**
43
46
  * Schema registry for storing Drizzle schemas
44
47
  * This will be populated by @org/entity when schemas are generated from entity definitions
@@ -49,17 +52,17 @@ declare function runMigrations(db: PostgresJsDatabase, projectRoot: string): Pro
49
52
  */
50
53
  type AnyTable = PgTableWithColumns<any>;
51
54
  interface SchemaRegistry {
52
- register(name: string, schema: AnyTable): void;
53
- get(name: string): AnyTable | undefined;
54
- all(): Record<string, AnyTable>;
55
- has(name: string): boolean;
55
+ register(name: string, schema: AnyTable): void;
56
+ get(name: string): AnyTable | undefined;
57
+ all(): Record<string, AnyTable>;
58
+ has(name: string): boolean;
56
59
  }
57
60
  declare class SchemaRegistryImpl implements SchemaRegistry {
58
- private schemas;
59
- register(name: string, schema: AnyTable): void;
60
- get(name: string): AnyTable | undefined;
61
- all(): Record<string, AnyTable>;
62
- has(name: string): boolean;
61
+ private schemas;
62
+ register(name: string, schema: AnyTable): void;
63
+ get(name: string): AnyTable | undefined;
64
+ all(): Record<string, AnyTable>;
65
+ has(name: string): boolean;
63
66
  }
64
67
  /**
65
68
  * Global schema registry instance
@@ -70,5 +73,6 @@ declare const schemaRegistry: SchemaRegistryImpl;
70
73
  * Create a new isolated schema registry (useful for testing)
71
74
  */
72
75
  declare function createSchemaRegistry(): SchemaRegistry;
73
-
76
+ //#endregion
74
77
  export { type DbConfig, type MigrationSource, type MigrationStatus, type SchemaRegistry, createDbClient, createReadOnlyClient, createSchemaRegistry, discoverMigrations, getMigrationStatus, runMigrations, schemaRegistry };
78
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/client.ts","../src/migrate.ts","../src/schema-registry.ts"],"mappings":";;;;UAGiB,QAAA;EACf,GAAA;EACA,WAAA;EACA,OAAA;EACA,OAAA;AAAA;;;;iBAMc,cAAA,CAAe,MAAA,EAAQ,QAAA,GAAW,kBAAA;;;;AAAlD;iBAcgB,oBAAA,CAAqB,MAAA,EAAQ,QAAA,GAAW,kBAAA;;;UCrBvC,eAAA;EACf,IAAA;EACA,SAAA;EACA,IAAA;AAAA;AAAA,UAGe,eAAA;EACf,OAAA,EAAS,eAAA;EACT,OAAA,EAAS,eAAA;AAAA;;;;iBAqBW,kBAAA,CAAmB,WAAA,WAAsB,OAAA,CAAQ,eAAA;ADtBvE;;;AAAA,iBCwEsB,kBAAA,CACpB,EAAA,EAAI,kBAAA,EACJ,WAAA,WACC,OAAA,CAAQ,eAAA;;;;iBAiCW,aAAA,CAAc,EAAA,EAAI,kBAAA,EAAoB,WAAA,WAAsB,OAAA;;;;;;ADtHlF;;;;;KESK,QAAA,GAAW,kBAAA;AAAA,UAEC,cAAA;EACf,QAAA,CAAS,IAAA,UAAc,MAAA,EAAQ,QAAA;EAC/B,GAAA,CAAI,IAAA,WAAe,QAAA;EACnB,GAAA,IAAO,MAAA,SAAe,QAAA;EACtB,GAAA,CAAI,IAAA;AAAA;AAAA,cAGA,kBAAA,YAA8B,cAAA;EAAA,QAC1B,OAAA;EAER,QAAA,CAAS,IAAA,UAAc,MAAA,EAAQ,QAAA;EAI/B,GAAA,CAAI,IAAA,WAAe,QAAA;EAInB,GAAA,CAAA,GAAO,MAAA,SAAe,QAAA;EAQtB,GAAA,CAAI,IAAA;AAAA;AFbN;;;;AAAA,cEsBa,cAAA,EAAc,kBAAA;;;;iBAKX,oBAAA,CAAA,GAAwB,cAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,16 @@
1
+ import{drizzle as e}from"drizzle-orm/postgres-js";import t from"postgres";import{existsSync as n}from"node:fs";import{readFile as r,readdir as i}from"node:fs/promises";import{join as a}from"node:path";import{sql as o}from"drizzle-orm";function s(n){return e(t(n.url,{max:n.poolMax||10,idle_timeout:20,max_lifetime:1800}))}function c(n){let r=t(n.readOnlyUrl||n.url,{max:n.poolMax||10,idle_timeout:20,max_lifetime:1800,connection:{application_name:`toolkit_readonly`},onnotice:()=>{}});return r.unsafe(`SET default_transaction_read_only = on`).catch(()=>{}),e(r)}async function l(e){await e.execute(o`
2
+ CREATE TABLE IF NOT EXISTS _toolkit_migrations (
3
+ id SERIAL PRIMARY KEY,
4
+ namespace VARCHAR(50) NOT NULL,
5
+ name VARCHAR(255) NOT NULL,
6
+ applied_at TIMESTAMP NOT NULL DEFAULT NOW(),
7
+ UNIQUE(namespace, name)
8
+ )
9
+ `)}async function u(e){let t=[],r=a(e,`migrations`,`.toolkit`);if(n(r)){let e=await i(r);for(let n of e)(n.endsWith(`.sql`)||n.endsWith(`.ts`))&&t.push({path:a(r,n),namespace:`toolkit`,name:n})}let o=a(e,`migrations`);if(n(o)){let e=await i(o);for(let n of e)n!==`.toolkit`&&(n.endsWith(`.sql`)||n.endsWith(`.ts`))&&t.push({path:a(o,n),namespace:`project`,name:n})}let s=t.filter(e=>e.namespace===`toolkit`).sort((e,t)=>e.name.localeCompare(t.name)),c=t.filter(e=>e.namespace===`project`).sort((e,t)=>e.name.localeCompare(t.name));return[...s,...c]}async function d(e,t){await l(e);let n=await u(t),r=await e.execute(o`
10
+ SELECT namespace, name FROM _toolkit_migrations
11
+ ORDER BY applied_at ASC
12
+ `),i=new Set(r.map(e=>`${e.namespace}:${e.name}`)),a=[],s=[];for(let e of n){let t=`${e.namespace}:${e.name}`;i.has(t)?a.push(e):s.push(e)}return{applied:a,pending:s}}async function f(e,t){await l(e);let{pending:n}=await d(e,t);if(n.length===0){console.log(`No pending migrations`);return}console.log(`Running ${n.length} pending migrations...`);for(let t of n){console.log(` Applying ${t.namespace}/${t.name}...`);try{let n=await r(t.path,`utf-8`);await e.transaction(async e=>{await e.execute(o.raw(n)),await e.execute(o`
13
+ INSERT INTO _toolkit_migrations (namespace, name)
14
+ VALUES (${t.namespace}, ${t.name})
15
+ `)}),console.log(` ✓ Applied ${t.namespace}/${t.name}`)}catch(e){throw console.error(` ✗ Failed to apply ${t.namespace}/${t.name}:`,e),e}}console.log(`Successfully applied ${n.length} migrations`)}var p=class{schemas=new Map;register(e,t){this.schemas.set(e,t)}get(e){return this.schemas.get(e)}all(){let e={};for(let[t,n]of this.schemas.entries())e[t]=n;return e}has(e){return this.schemas.has(e)}};const m=new p;function h(){return new p}export{s as createDbClient,c as createReadOnlyClient,h as createSchemaRegistry,u as discoverMigrations,d as getMigrationStatus,f as runMigrations,m as schemaRegistry};
16
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/client.ts","../src/migrate.ts","../src/schema-registry.ts"],"sourcesContent":["import { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport postgres from 'postgres'\n\nexport interface DbConfig {\n url: string\n readOnlyUrl?: string\n poolMin?: number\n poolMax?: number\n}\n\n/**\n * Create a full read-write database client\n */\nexport function createDbClient(config: DbConfig): PostgresJsDatabase {\n const sql = postgres(config.url, {\n max: config.poolMax || 10,\n idle_timeout: 20,\n max_lifetime: 60 * 30,\n })\n\n return drizzle(sql)\n}\n\n/**\n * Create a read-only database client\n * Sets default_transaction_read_only=on to prevent writes even if SQL injection occurs\n */\nexport function createReadOnlyClient(config: DbConfig): PostgresJsDatabase {\n const url = config.readOnlyUrl || config.url\n\n const sql = postgres(url, {\n max: config.poolMax || 10,\n idle_timeout: 20,\n max_lifetime: 60 * 30,\n connection: {\n application_name: 'toolkit_readonly',\n },\n onnotice: () => {}, // Suppress NOTICE messages\n })\n\n // Set session to read-only\n // This enforces read-only at the PostgreSQL level, not just application level\n sql.unsafe('SET default_transaction_read_only = on').catch(() => {\n // Silently fail if already set\n })\n\n return drizzle(sql)\n}\n","import { existsSync } from 'node:fs'\nimport { readdir, readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { sql } from 'drizzle-orm'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\n\nexport interface MigrationSource {\n path: string\n namespace: 'toolkit' | 'project'\n name: string\n}\n\nexport interface MigrationStatus {\n applied: MigrationSource[]\n pending: MigrationSource[]\n}\n\n/**\n * Ensure the _toolkit_migrations table exists\n */\nasync function ensureMigrationsTable(db: PostgresJsDatabase): Promise<void> {\n await db.execute(sql`\n CREATE TABLE IF NOT EXISTS _toolkit_migrations (\n id SERIAL PRIMARY KEY,\n namespace VARCHAR(50) NOT NULL,\n name VARCHAR(255) NOT NULL,\n applied_at TIMESTAMP NOT NULL DEFAULT NOW(),\n UNIQUE(namespace, name)\n )\n `)\n}\n\n/**\n * Discover migrations from both toolkit (.toolkit/) and project (migrations/) directories\n */\nexport async function discoverMigrations(projectRoot: string): Promise<MigrationSource[]> {\n const migrations: MigrationSource[] = []\n\n // Discover toolkit migrations (.toolkit/ directory)\n const toolkitDir = join(projectRoot, 'migrations', '.toolkit')\n if (existsSync(toolkitDir)) {\n const files = await readdir(toolkitDir)\n for (const file of files) {\n if (file.endsWith('.sql') || file.endsWith('.ts')) {\n migrations.push({\n path: join(toolkitDir, file),\n namespace: 'toolkit',\n name: file,\n })\n }\n }\n }\n\n // Discover project migrations (migrations/ directory, excluding .toolkit/)\n const migrationsDir = join(projectRoot, 'migrations')\n if (existsSync(migrationsDir)) {\n const files = await readdir(migrationsDir)\n for (const file of files) {\n if (file !== '.toolkit' && (file.endsWith('.sql') || file.endsWith('.ts'))) {\n migrations.push({\n path: join(migrationsDir, file),\n namespace: 'project',\n name: file,\n })\n }\n }\n }\n\n // Sort migrations:\n // 1. Toolkit migrations by name (they should have numeric prefixes)\n // 2. Project migrations by name (timestamp prefixes)\n const toolkitMigrations = migrations\n .filter((m) => m.namespace === 'toolkit')\n .sort((a, b) => a.name.localeCompare(b.name))\n\n const projectMigrations = migrations\n .filter((m) => m.namespace === 'project')\n .sort((a, b) => a.name.localeCompare(b.name))\n\n return [...toolkitMigrations, ...projectMigrations]\n}\n\n/**\n * Get the status of migrations (applied vs pending)\n */\nexport async function getMigrationStatus(\n db: PostgresJsDatabase,\n projectRoot: string,\n): Promise<MigrationStatus> {\n await ensureMigrationsTable(db)\n\n const allMigrations = await discoverMigrations(projectRoot)\n\n // Get applied migrations from database\n const appliedRows = await db.execute<{ namespace: string; name: string }>(sql`\n SELECT namespace, name FROM _toolkit_migrations\n ORDER BY applied_at ASC\n `)\n\n const appliedSet = new Set(\n appliedRows.map((row: { namespace: string; name: string }) => `${row.namespace}:${row.name}`),\n )\n\n const applied: MigrationSource[] = []\n const pending: MigrationSource[] = []\n\n for (const migration of allMigrations) {\n const key = `${migration.namespace}:${migration.name}`\n if (appliedSet.has(key)) {\n applied.push(migration)\n } else {\n pending.push(migration)\n }\n }\n\n return { applied, pending }\n}\n\n/**\n * Run pending migrations\n */\nexport async function runMigrations(db: PostgresJsDatabase, projectRoot: string): Promise<void> {\n await ensureMigrationsTable(db)\n\n const { pending } = await getMigrationStatus(db, projectRoot)\n\n if (pending.length === 0) {\n console.log('No pending migrations')\n return\n }\n\n console.log(`Running ${pending.length} pending migrations...`)\n\n for (const migration of pending) {\n console.log(` Applying ${migration.namespace}/${migration.name}...`)\n\n try {\n // Read migration file\n const content = await readFile(migration.path, 'utf-8')\n\n // Execute migration in a transaction\n await db.transaction(async (tx) => {\n // Execute the migration SQL\n await tx.execute(sql.raw(content))\n\n // Record the migration as applied\n await tx.execute(sql`\n INSERT INTO _toolkit_migrations (namespace, name)\n VALUES (${migration.namespace}, ${migration.name})\n `)\n })\n\n console.log(` ✓ Applied ${migration.namespace}/${migration.name}`)\n } catch (error) {\n console.error(` ✗ Failed to apply ${migration.namespace}/${migration.name}:`, error)\n throw error\n }\n }\n\n console.log(`Successfully applied ${pending.length} migrations`)\n}\n","import type { PgTableWithColumns } from 'drizzle-orm/pg-core'\n\n/**\n * Schema registry for storing Drizzle schemas\n * This will be populated by @org/entity when schemas are generated from entity definitions\n *\n * We use PgTableWithColumns<any> because entity table schemas are dynamic (columns\n * not known at compile time) and we need runtime property access (.id, .status, etc.).\n * AnyPgTable won't work here — it doesn't expose column accessors.\n */\n\n// biome-ignore lint/suspicious/noExplicitAny: dynamic table columns require PgTableWithColumns<any> for property access\ntype AnyTable = PgTableWithColumns<any>\n\nexport interface SchemaRegistry {\n register(name: string, schema: AnyTable): void\n get(name: string): AnyTable | undefined\n all(): Record<string, AnyTable>\n has(name: string): boolean\n}\n\nclass SchemaRegistryImpl implements SchemaRegistry {\n private schemas: Map<string, AnyTable> = new Map()\n\n register(name: string, schema: AnyTable): void {\n this.schemas.set(name, schema)\n }\n\n get(name: string): AnyTable | undefined {\n return this.schemas.get(name)\n }\n\n all(): Record<string, AnyTable> {\n const result: Record<string, AnyTable> = {}\n for (const [name, schema] of this.schemas.entries()) {\n result[name] = schema\n }\n return result\n }\n\n has(name: string): boolean {\n return this.schemas.has(name)\n }\n}\n\n/**\n * Global schema registry instance\n * Entities will register their schemas here when they are defined\n */\nexport const schemaRegistry = new SchemaRegistryImpl()\n\n/**\n * Create a new isolated schema registry (useful for testing)\n */\nexport function createSchemaRegistry(): SchemaRegistry {\n return new SchemaRegistryImpl()\n}\n"],"mappings":"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"}
@@ -1,11 +1,12 @@
1
- import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
1
+ import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
2
 
3
+ //#region src/test-utils.d.ts
3
4
  interface TestDb {
4
- db: PostgresJsDatabase;
5
- schema: string;
6
- /** Push Drizzle table definitions to the test schema (uses drizzle-kit/api) */
7
- push: (schemas: Record<string, unknown>) => Promise<void>;
8
- cleanup: () => Promise<void>;
5
+ db: PostgresJsDatabase;
6
+ schema: string;
7
+ /** Push Drizzle table definitions to the test schema (uses drizzle-kit/api) */
8
+ push: (schemas: Record<string, unknown>) => Promise<void>;
9
+ cleanup: () => Promise<void>;
9
10
  }
10
11
  /**
11
12
  * Create an isolated test database using a unique PostgreSQL schema.
@@ -23,5 +24,6 @@ interface TestDb {
23
24
  * ```
24
25
  */
25
26
  declare function createTestDb(): Promise<TestDb>;
26
-
27
- export { type TestDb, createTestDb };
27
+ //#endregion
28
+ export { TestDb, createTestDb };
29
+ //# sourceMappingURL=test-utils.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-utils.d.mts","names":[],"sources":["../src/test-utils.ts"],"mappings":";;;UAIiB,MAAA;EACf,EAAA,EAAI,kBAAA;EACJ,MAAA;EAFqB;EAIrB,IAAA,GAAO,OAAA,EAAS,MAAA,sBAA4B,OAAA;EAC5C,OAAA,QAAe,OAAA;AAAA;;;;;;;;;;;;;;;;iBAkBK,YAAA,CAAA,GAAgB,OAAA,CAAQ,MAAA"}
@@ -0,0 +1,2 @@
1
+ import{drizzle as e}from"drizzle-orm/postgres-js";import t from"postgres";import{sql as n}from"drizzle-orm";async function r(){let r=process.env.DATABASE_TEST_URL||process.env.DATABASE_URL;if(!r)throw Error(`DATABASE_TEST_URL (or DATABASE_URL) env var is required for integration tests`);let i=`test_${crypto.randomUUID().replaceAll(`-`,``).slice(0,12)}`,a=t(r,{max:3,idle_timeout:10}),o=e(a);return await o.execute(n.raw(`CREATE SCHEMA "${i}"`)),await o.execute(n.raw(`SET search_path TO "${i}", public`)),{db:o,schema:i,async push(e){let{createRequire:t}=await import(`node:module`),{pushSchema:n}=t(import.meta.url)(`drizzle-kit/api`);await(await n(e,new Proxy(o,{get(e,t,n){return t===`execute`?async(...t)=>{let n=await e.execute(...t);return Array.isArray(n)&&!(`rows`in n)&&Object.defineProperty(n,`rows`,{value:[...n],enumerable:!1}),n}:Reflect.get(e,t,n)}}))).apply()},async cleanup(){await o.execute(n.raw(`DROP SCHEMA IF EXISTS "${i}" CASCADE`)),await a.end()}}}export{r as createTestDb};
2
+ //# sourceMappingURL=test-utils.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-utils.mjs","names":[],"sources":["../src/test-utils.ts"],"sourcesContent":["import { sql } from 'drizzle-orm'\nimport { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport postgres from 'postgres'\n\nexport interface TestDb {\n db: PostgresJsDatabase\n schema: string\n /** Push Drizzle table definitions to the test schema (uses drizzle-kit/api) */\n push: (schemas: Record<string, unknown>) => Promise<void>\n cleanup: () => Promise<void>\n}\n\n/**\n * Create an isolated test database using a unique PostgreSQL schema.\n * Each test suite gets its own schema so tests can run in parallel safely.\n *\n * Requires `DATABASE_TEST_URL` env var pointing to a running PostgreSQL instance.\n * Falls back to `DATABASE_URL` if test URL is not set.\n *\n * Usage:\n * ```ts\n * const testDb = await createTestDb()\n * await testDb.push({ articles: articlesTable, entity_refs: entityRefsTable })\n * // ... run tests with testDb.db ...\n * await testDb.cleanup()\n * ```\n */\nexport async function createTestDb(): Promise<TestDb> {\n const url = process.env.DATABASE_TEST_URL || process.env.DATABASE_URL\n if (!url) {\n throw new Error('DATABASE_TEST_URL (or DATABASE_URL) env var is required for integration tests')\n }\n\n const schemaName = `test_${crypto.randomUUID().replaceAll('-', '').slice(0, 12)}`\n\n const connection = postgres(url, {\n max: 3,\n idle_timeout: 10,\n })\n\n const db = drizzle(connection)\n\n // Create isolated schema\n await db.execute(sql.raw(`CREATE SCHEMA \"${schemaName}\"`))\n await db.execute(sql.raw(`SET search_path TO \"${schemaName}\", public`))\n\n return {\n db,\n schema: schemaName,\n\n async push(schemas: Record<string, unknown>) {\n // Use createRequire to load drizzle-kit/api via Node's CJS resolver.\n // drizzle-kit/api.mjs bundles CJS code with require('fs') — Vite's ESM\n // transform replaces require() with a polyfill that throws. Native CJS bypasses this.\n const { createRequire } = await import('node:module')\n const require = createRequire(import.meta.url)\n const { pushSchema } = require('drizzle-kit/api') as typeof import('drizzle-kit/api')\n\n // pushSchema internally does: res = drizzleInstance.execute(sql.raw(query)); return res.rows\n // postgres-js returns rows as a plain array — no .rows property. Patch execute to add it.\n const patchedDb = new Proxy(db, {\n get(target, prop, receiver) {\n if (prop === 'execute') {\n return async (...args: unknown[]) => {\n const result = await (target.execute as (...a: unknown[]) => Promise<unknown[]>)(\n ...args,\n )\n if (Array.isArray(result) && !('rows' in result)) {\n Object.defineProperty(result, 'rows', { value: [...result], enumerable: false })\n }\n return result\n }\n }\n return Reflect.get(target, prop, receiver)\n },\n })\n\n // Use default schemaFilters ([\"public\"]). Our tables are unqualified (= public),\n // and SET search_path ensures DDL actually creates them in the test schema.\n const result = await pushSchema(schemas, patchedDb as typeof db)\n await result.apply()\n },\n\n async cleanup() {\n await db.execute(sql.raw(`DROP SCHEMA IF EXISTS \"${schemaName}\" CASCADE`))\n await connection.end()\n },\n }\n}\n"],"mappings":"4GA2BA,eAAsB,GAAgC,CACpD,IAAM,EAAM,QAAQ,IAAI,mBAAqB,QAAQ,IAAI,aACzD,GAAI,CAAC,EACH,MAAU,MAAM,gFAAgF,CAGlG,IAAM,EAAa,QAAQ,OAAO,YAAY,CAAC,WAAW,IAAK,GAAG,CAAC,MAAM,EAAG,GAAG,GAEzE,EAAa,EAAS,EAAK,CAC/B,IAAK,EACL,aAAc,GACf,CAAC,CAEI,EAAK,EAAQ,EAAW,CAM9B,OAHA,MAAM,EAAG,QAAQ,EAAI,IAAI,kBAAkB,EAAW,GAAG,CAAC,CAC1D,MAAM,EAAG,QAAQ,EAAI,IAAI,uBAAuB,EAAW,WAAW,CAAC,CAEhE,CACL,KACA,OAAQ,EAER,MAAM,KAAK,EAAkC,CAI3C,GAAM,CAAE,iBAAkB,MAAM,OAAO,eAEjC,CAAE,cADQ,EAAc,OAAO,KAAK,IAAI,CACf,kBAAkB,CAwBjD,MADe,MAAM,EAAW,EAnBd,IAAI,MAAM,EAAI,CAC9B,IAAI,EAAQ,EAAM,EAAU,CAY1B,OAXI,IAAS,UACJ,MAAO,GAAG,IAAoB,CACnC,IAAM,EAAS,MAAO,EAAO,QAC3B,GAAG,EACJ,CAID,OAHI,MAAM,QAAQ,EAAO,EAAI,EAAE,SAAU,IACvC,OAAO,eAAe,EAAQ,OAAQ,CAAE,MAAO,CAAC,GAAG,EAAO,CAAE,WAAY,GAAO,CAAC,CAE3E,GAGJ,QAAQ,IAAI,EAAQ,EAAM,EAAS,EAE7C,CAAC,CAI8D,EACnD,OAAO,EAGtB,MAAM,SAAU,CACd,MAAM,EAAG,QAAQ,EAAI,IAAI,0BAA0B,EAAW,WAAW,CAAC,CAC1E,MAAM,EAAW,KAAK,EAEzB"}
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@murumets-ee/db",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "license": "Elastic-2.0",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {
8
- "types": "./dist/index.d.ts",
9
- "import": "./dist/index.js"
8
+ "types": "./dist/index.d.mts",
9
+ "import": "./dist/index.mjs"
10
10
  },
11
11
  "./test-utils": {
12
- "types": "./dist/test-utils.d.ts",
13
- "import": "./dist/test-utils.js"
12
+ "types": "./dist/test-utils.d.mts",
13
+ "import": "./dist/test-utils.mjs"
14
14
  }
15
15
  },
16
16
  "files": [
@@ -22,13 +22,13 @@
22
22
  },
23
23
  "devDependencies": {
24
24
  "drizzle-kit": "^0.31.10",
25
- "tsup": "^8.3.5",
25
+ "tsdown": "^0.21.7",
26
26
  "typescript": "^5.7.2",
27
27
  "vitest": "^2.1.8"
28
28
  },
29
29
  "scripts": {
30
- "build": "tsup",
31
- "dev": "tsup --watch",
30
+ "build": "tsdown",
31
+ "dev": "tsdown --watch",
32
32
  "test": "vitest"
33
33
  }
34
34
  }
package/dist/index.js DELETED
@@ -1,15 +0,0 @@
1
- import {drizzle}from'drizzle-orm/postgres-js';import g from'postgres';import {existsSync}from'fs';import {readdir,readFile}from'fs/promises';import {join}from'path';import {sql}from'drizzle-orm';function S(a){let t=g(a.url,{max:a.poolMax||10,idle_timeout:20,max_lifetime:1800});return drizzle(t)}function b(a){let t=a.readOnlyUrl||a.url,n=g(t,{max:a.poolMax||10,idle_timeout:20,max_lifetime:1800,connection:{application_name:"toolkit_readonly"},onnotice:()=>{}});return n.unsafe("SET default_transaction_read_only = on").catch(()=>{}),drizzle(n)}async function d(a){await a.execute(sql`
2
- CREATE TABLE IF NOT EXISTS _toolkit_migrations (
3
- id SERIAL PRIMARY KEY,
4
- namespace VARCHAR(50) NOT NULL,
5
- name VARCHAR(255) NOT NULL,
6
- applied_at TIMESTAMP NOT NULL DEFAULT NOW(),
7
- UNIQUE(namespace, name)
8
- )
9
- `);}async function h(a){let t=[],n=join(a,"migrations",".toolkit");if(existsSync(n)){let o=await readdir(n);for(let e of o)(e.endsWith(".sql")||e.endsWith(".ts"))&&t.push({path:join(n,e),namespace:"toolkit",name:e});}let i=join(a,"migrations");if(existsSync(i)){let o=await readdir(i);for(let e of o)e!==".toolkit"&&(e.endsWith(".sql")||e.endsWith(".ts"))&&t.push({path:join(i,e),namespace:"project",name:e});}let r=t.filter(o=>o.namespace==="toolkit").sort((o,e)=>o.name.localeCompare(e.name)),s=t.filter(o=>o.namespace==="project").sort((o,e)=>o.name.localeCompare(e.name));return [...r,...s]}async function y(a,t){await d(a);let n=await h(t),i=await a.execute(sql`
10
- SELECT namespace, name FROM _toolkit_migrations
11
- ORDER BY applied_at ASC
12
- `),r=new Set(i.map(e=>`${e.namespace}:${e.name}`)),s=[],o=[];for(let e of n){let M=`${e.namespace}:${e.name}`;r.has(M)?s.push(e):o.push(e);}return {applied:s,pending:o}}async function x(a,t){await d(a);let{pending:n}=await y(a,t);if(n.length===0){console.log("No pending migrations");return}console.log(`Running ${n.length} pending migrations...`);for(let i of n){console.log(` Applying ${i.namespace}/${i.name}...`);try{let r=await readFile(i.path,"utf-8");await a.transaction(async s=>{await s.execute(sql.raw(r)),await s.execute(sql`
13
- INSERT INTO _toolkit_migrations (namespace, name)
14
- VALUES (${i.namespace}, ${i.name})
15
- `);}),console.log(` \u2713 Applied ${i.namespace}/${i.name}`);}catch(r){throw console.error(` \u2717 Failed to apply ${i.namespace}/${i.name}:`,r),r}}console.log(`Successfully applied ${n.length} migrations`);}var m=class{schemas=new Map;register(t,n){this.schemas.set(t,n);}get(t){return this.schemas.get(t)}all(){let t={};for(let[n,i]of this.schemas.entries())t[n]=i;return t}has(t){return this.schemas.has(t)}},R=new m;function A(){return new m}export{S as createDbClient,b as createReadOnlyClient,A as createSchemaRegistry,h as discoverMigrations,y as getMigrationStatus,x as runMigrations,R as schemaRegistry};
@@ -1 +0,0 @@
1
- import {sql}from'drizzle-orm';import {drizzle}from'drizzle-orm/postgres-js';import T from'postgres';async function S(){let a=process.env.DATABASE_TEST_URL||process.env.DATABASE_URL;if(!a)throw new Error("DATABASE_TEST_URL (or DATABASE_URL) env var is required for integration tests");let r=`test_${crypto.randomUUID().replaceAll("-","").slice(0,12)}`,n=T(a,{max:3,idle_timeout:10}),e=drizzle(n);return await e.execute(sql.raw(`CREATE SCHEMA "${r}"`)),await e.execute(sql.raw(`SET search_path TO "${r}", public`)),{db:e,schema:r,async push(c){let{createRequire:u}=await import('module'),p=u(import.meta.url),{pushSchema:m}=p("drizzle-kit/api"),l=new Proxy(e,{get(o,i,w){return i==="execute"?async(...A)=>{let t=await o.execute(...A);return Array.isArray(t)&&!("rows"in t)&&Object.defineProperty(t,"rows",{value:[...t],enumerable:false}),t}:Reflect.get(o,i,w)}});await(await m(c,l)).apply();},async cleanup(){await e.execute(sql.raw(`DROP SCHEMA IF EXISTS "${r}" CASCADE`)),await n.end();}}}export{S as createTestDb};