@softure-ai/db 0.1.5 → 0.1.7
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/CHANGELOG.md +25 -0
- package/README.md +261 -22
- package/dist/cli/run.d.ts +8 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +71 -21
- package/dist/cli/run.js.map +1 -1
- package/dist/client.d.ts +39 -8
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +64 -8
- package/dist/client.js.map +1 -1
- package/dist/configured.d.ts +31 -0
- package/dist/configured.d.ts.map +1 -0
- package/dist/configured.js +74 -0
- package/dist/configured.js.map +1 -0
- package/dist/driver-errors.d.ts +21 -0
- package/dist/driver-errors.d.ts.map +1 -0
- package/dist/driver-errors.js +25 -0
- package/dist/driver-errors.js.map +1 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/migrations/adopt.d.ts +8 -1
- package/dist/migrations/adopt.d.ts.map +1 -1
- package/dist/migrations/adopt.js +53 -87
- package/dist/migrations/adopt.js.map +1 -1
- package/dist/migrations/apply.d.ts +35 -0
- package/dist/migrations/apply.d.ts.map +1 -0
- package/dist/migrations/apply.js +115 -0
- package/dist/migrations/apply.js.map +1 -0
- package/dist/migrations/migrator.d.ts +55 -27
- package/dist/migrations/migrator.d.ts.map +1 -1
- package/dist/migrations/migrator.js +187 -103
- package/dist/migrations/migrator.js.map +1 -1
- package/dist/migrations/problems.d.ts +9 -0
- package/dist/migrations/problems.d.ts.map +1 -1
- package/dist/migrations/problems.js +9 -3
- package/dist/migrations/problems.js.map +1 -1
- package/dist/migrations/reference.d.ts +23 -0
- package/dist/migrations/reference.d.ts.map +1 -0
- package/dist/migrations/reference.js +64 -0
- package/dist/migrations/reference.js.map +1 -0
- package/dist/migrations/session.d.ts +2 -1
- package/dist/migrations/session.d.ts.map +1 -1
- package/dist/migrations/session.js +19 -2
- package/dist/migrations/session.js.map +1 -1
- package/dist/migrations/stubs.d.ts +23 -0
- package/dist/migrations/stubs.d.ts.map +1 -0
- package/dist/migrations/stubs.js +99 -0
- package/dist/migrations/stubs.js.map +1 -0
- package/dist/process.d.ts +23 -0
- package/dist/process.d.ts.map +1 -0
- package/dist/process.js +82 -0
- package/dist/process.js.map +1 -0
- package/dist/shared.d.ts +2 -0
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +9 -0
- package/dist/shared.js.map +1 -1
- package/dist/testing.d.ts +11 -2
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +28 -9
- package/dist/testing.js.map +1 -1
- package/package.json +22 -5
- package/src/cli/run.ts +84 -23
- package/src/client.ts +88 -16
- package/src/configured.ts +109 -0
- package/src/driver-errors.ts +40 -0
- package/src/index.ts +12 -1
- package/src/migrations/adopt.ts +65 -100
- package/src/migrations/apply.ts +135 -0
- package/src/migrations/migrator.ts +246 -128
- package/src/migrations/problems.ts +13 -5
- package/src/migrations/reference.ts +78 -0
- package/src/migrations/session.ts +28 -2
- package/src/migrations/stubs.ts +139 -0
- package/src/process.ts +110 -0
- package/src/shared.ts +9 -0
- package/src/testing.ts +38 -10
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
const SESSION_SETTINGS_SQL = "SELECT name, current_setting(name) AS value FROM pg_settings WHERE source = 'session'";
|
|
1
2
|
/**
|
|
2
3
|
* Runs `run` on one dedicated connection. A pg client is destroyed afterwards instead of going
|
|
3
4
|
* back to the pool, so a `SET` inside a migration file or a lock that failed to release cannot
|
|
4
|
-
* leak into the app's queries. PGlite has one connection, so
|
|
5
|
+
* leak into the app's queries. PGlite has one connection, shared with the app, so the settings the
|
|
6
|
+
* run changed are put back afterwards; the app's own (`TimeZone`, `search_path`) stay as they were.
|
|
5
7
|
*/
|
|
6
8
|
export async function withSession(handle, run) {
|
|
7
9
|
if (handle.kind === "pglite") {
|
|
8
10
|
const { client } = handle;
|
|
11
|
+
const before = (await client.query(SESSION_SETTINGS_SQL)).rows;
|
|
9
12
|
try {
|
|
10
13
|
return await run({
|
|
11
14
|
query: async (text, params) => (await client.query(text, params ? [...params] : undefined)).rows,
|
|
@@ -15,7 +18,7 @@ export async function withSession(handle, run) {
|
|
|
15
18
|
});
|
|
16
19
|
}
|
|
17
20
|
finally {
|
|
18
|
-
await client
|
|
21
|
+
await restoreSessionSettings(client, before);
|
|
19
22
|
}
|
|
20
23
|
}
|
|
21
24
|
const client = await handle.pool.connect();
|
|
@@ -36,4 +39,18 @@ export async function withSession(handle, run) {
|
|
|
36
39
|
client.off("error", ignoreClientError);
|
|
37
40
|
}
|
|
38
41
|
}
|
|
42
|
+
/** Puts back every value in `before` (also one the run reset) and resets every session setting the run added. */
|
|
43
|
+
async function restoreSessionSettings(client, before) {
|
|
44
|
+
const after = new Map((await client.query(SESSION_SETTINGS_SQL)).rows.map((setting) => [setting.name, setting.value]));
|
|
45
|
+
for (const setting of before) {
|
|
46
|
+
if (after.get(setting.name) !== setting.value) {
|
|
47
|
+
await client.query("SELECT set_config($1, $2, false)", [setting.name, setting.value]);
|
|
48
|
+
}
|
|
49
|
+
after.delete(setting.name);
|
|
50
|
+
}
|
|
51
|
+
for (const name of after.keys()) {
|
|
52
|
+
// Names come from pg_settings, not from input; quoted as identifiers all the same.
|
|
53
|
+
await client.exec(`RESET "${name.replaceAll('"', '""')}"`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
39
56
|
//# sourceMappingURL=session.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/migrations/session.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/migrations/session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { MigrationSession } from "./session.js";
|
|
2
|
+
/** One DDL statement for the scratch database, with the object it creates for reports. */
|
|
3
|
+
export interface StubStatement {
|
|
4
|
+
/** `schema.table`, `schema.table.column`, `schema.table.constraint` or `schema.type`, unquoted. */
|
|
5
|
+
readonly object: string;
|
|
6
|
+
readonly sql: string;
|
|
7
|
+
}
|
|
8
|
+
/** The app's tables as statements to replay, in a runnable order. */
|
|
9
|
+
export interface AppCatalog {
|
|
10
|
+
readonly statements: readonly StubStatement[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The app's tables in every schema except `pg_*`, `information_schema` and `excludedSchemas` (the
|
|
14
|
+
* ledger, the enabled modules' schemas, the app migrator's own). Runs in its own read-only transaction.
|
|
15
|
+
*/
|
|
16
|
+
export declare function readAppTables(session: MigrationSession, excludedSchemas: readonly string[]): Promise<AppCatalog>;
|
|
17
|
+
/**
|
|
18
|
+
* Replays the catalog on the scratch database, each statement on its own: a statement that fails
|
|
19
|
+
* (a type the scratch lacks, a key on a skipped column) is skipped, so a module that never
|
|
20
|
+
* references that table still builds its reference. Returns the skipped objects with the reason.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createAppStubs(session: MigrationSession, catalog: AppCatalog): Promise<string[]>;
|
|
23
|
+
//# sourceMappingURL=stubs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stubs.d.ts","sourceRoot":"","sources":["../../src/migrations/stubs.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,0FAA0F;AAC1F,MAAM,WAAW,aAAa;IAC5B,mGAAmG;IACnG,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,qEAAqE;AACrE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;CAC/C;AA2ED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAuBtH;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAUtG"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Names come back quoted by Postgres (`quote_ident`, `format('%I')`), so no DDL below concatenates
|
|
2
|
+
// a raw identifier. Each query takes the excluded schemas as $1.
|
|
3
|
+
const IN_APP_SCHEMA = (alias) => `${alias}.nspname <> ALL($1::text[]) AND ${alias}.nspname <> 'information_schema' AND ${alias}.nspname NOT LIKE 'pg\\_%'`;
|
|
4
|
+
const NOT_IN_EXTENSION = (catalog, alias) => `NOT EXISTS (SELECT 1 FROM pg_depend dep WHERE dep.classid = '${catalog}'::regclass AND dep.objid = ${alias}.oid AND dep.deptype = 'e')`;
|
|
5
|
+
const QUERIES = {
|
|
6
|
+
schemas: `
|
|
7
|
+
SELECT DISTINCT n.nspname AS name, quote_ident(n.nspname) AS quoted
|
|
8
|
+
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
9
|
+
WHERE c.relkind IN ('r', 'p') AND ${IN_APP_SCHEMA("n")} AND ${NOT_IN_EXTENSION("pg_class", "c")}
|
|
10
|
+
ORDER BY 1`,
|
|
11
|
+
enums: `
|
|
12
|
+
SELECT n.nspname || '.' || t.typname AS object, format('%I.%I', n.nspname, t.typname) AS quoted,
|
|
13
|
+
(SELECT string_agg(quote_literal(e.enumlabel), ', ' ORDER BY e.enumsortorder) FROM pg_enum e WHERE e.enumtypid = t.oid) AS labels
|
|
14
|
+
FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace
|
|
15
|
+
WHERE t.typtype = 'e' AND ${IN_APP_SCHEMA("n")} AND ${NOT_IN_EXTENSION("pg_type", "t")}
|
|
16
|
+
ORDER BY 1`,
|
|
17
|
+
tables: `
|
|
18
|
+
SELECT n.nspname || '.' || c.relname AS object, format('%I.%I', n.nspname, c.relname) AS quoted
|
|
19
|
+
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
20
|
+
WHERE c.relkind IN ('r', 'p') AND ${IN_APP_SCHEMA("n")} AND ${NOT_IN_EXTENSION("pg_class", "c")}
|
|
21
|
+
ORDER BY 1`,
|
|
22
|
+
columns: `
|
|
23
|
+
SELECT n.nspname || '.' || c.relname AS table_object, format('%I.%I', n.nspname, c.relname) AS table_quoted,
|
|
24
|
+
a.attname AS name, quote_ident(a.attname) AS quoted, format_type(a.atttypid, a.atttypmod) AS type
|
|
25
|
+
FROM pg_attribute a
|
|
26
|
+
JOIN pg_class c ON c.oid = a.attrelid
|
|
27
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
28
|
+
WHERE c.relkind IN ('r', 'p') AND a.attnum > 0 AND NOT a.attisdropped
|
|
29
|
+
AND ${IN_APP_SCHEMA("n")} AND ${NOT_IN_EXTENSION("pg_class", "c")}
|
|
30
|
+
ORDER BY 1, a.attnum`,
|
|
31
|
+
keys: `
|
|
32
|
+
SELECT n.nspname || '.' || c.relname AS table_object, format('%I.%I', n.nspname, c.relname) AS table_quoted,
|
|
33
|
+
con.conname AS name, quote_ident(con.conname) AS quoted, pg_get_constraintdef(con.oid) AS definition
|
|
34
|
+
FROM pg_constraint con
|
|
35
|
+
JOIN pg_class c ON c.oid = con.conrelid
|
|
36
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
37
|
+
WHERE con.contype IN ('p', 'u') AND c.relkind IN ('r', 'p')
|
|
38
|
+
AND ${IN_APP_SCHEMA("n")} AND ${NOT_IN_EXTENSION("pg_class", "c")}
|
|
39
|
+
ORDER BY 1, con.contype, 3`,
|
|
40
|
+
// A unique index that backs no key constraint. A foreign key records the index it references in
|
|
41
|
+
// `conindid` too, so only key constraints count.
|
|
42
|
+
uniqueIndexes: `
|
|
43
|
+
SELECT n.nspname || '.' || c.relname AS table_object, i.relname AS name, pg_get_indexdef(i.oid) AS definition
|
|
44
|
+
FROM pg_index x
|
|
45
|
+
JOIN pg_class i ON i.oid = x.indexrelid
|
|
46
|
+
JOIN pg_class c ON c.oid = x.indrelid
|
|
47
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
48
|
+
WHERE x.indisunique AND x.indpred IS NULL AND x.indexprs IS NULL AND c.relkind IN ('r', 'p')
|
|
49
|
+
AND NOT EXISTS (SELECT 1 FROM pg_constraint con WHERE con.conindid = x.indexrelid AND con.contype IN ('p', 'u', 'x'))
|
|
50
|
+
AND ${IN_APP_SCHEMA("n")} AND ${NOT_IN_EXTENSION("pg_class", "c")}
|
|
51
|
+
ORDER BY 1, 2`,
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The app's tables in every schema except `pg_*`, `information_schema` and `excludedSchemas` (the
|
|
55
|
+
* ledger, the enabled modules' schemas, the app migrator's own). Runs in its own read-only transaction.
|
|
56
|
+
*/
|
|
57
|
+
export async function readAppTables(session, excludedSchemas) {
|
|
58
|
+
await session.exec("BEGIN READ ONLY; SET LOCAL search_path = pg_catalog;");
|
|
59
|
+
try {
|
|
60
|
+
const params = [[...excludedSchemas]];
|
|
61
|
+
const schemas = await session.query(QUERIES.schemas, params);
|
|
62
|
+
const enums = await session.query(QUERIES.enums, params);
|
|
63
|
+
const tables = await session.query(QUERIES.tables, params);
|
|
64
|
+
const columns = await session.query(QUERIES.columns, params);
|
|
65
|
+
const keys = await session.query(QUERIES.keys, params);
|
|
66
|
+
const indexes = await session.query(QUERIES.uniqueIndexes, params);
|
|
67
|
+
return {
|
|
68
|
+
statements: [
|
|
69
|
+
...schemas.map((row) => ({ object: row.name, sql: `CREATE SCHEMA IF NOT EXISTS ${row.quoted}` })),
|
|
70
|
+
...enums.map((row) => ({ object: row.object, sql: `CREATE TYPE ${row.quoted} AS ENUM (${row.labels})` })),
|
|
71
|
+
...tables.map((row) => ({ object: row.object, sql: `CREATE TABLE ${row.quoted} ()` })),
|
|
72
|
+
...columns.map((row) => ({ object: `${row.table_object}.${row.name}`, sql: `ALTER TABLE ${row.table_quoted} ADD COLUMN ${row.quoted} ${row.type}` })),
|
|
73
|
+
...keys.map((row) => ({ object: `${row.table_object}.${row.name}`, sql: `ALTER TABLE ${row.table_quoted} ADD CONSTRAINT ${row.quoted} ${row.definition}` })),
|
|
74
|
+
...indexes.map((row) => ({ object: `${row.table_object}.${row.name}`, sql: row.definition })),
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
await session.exec("ROLLBACK");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Replays the catalog on the scratch database, each statement on its own: a statement that fails
|
|
84
|
+
* (a type the scratch lacks, a key on a skipped column) is skipped, so a module that never
|
|
85
|
+
* references that table still builds its reference. Returns the skipped objects with the reason.
|
|
86
|
+
*/
|
|
87
|
+
export async function createAppStubs(session, catalog) {
|
|
88
|
+
const skipped = [];
|
|
89
|
+
for (const statement of catalog.statements) {
|
|
90
|
+
try {
|
|
91
|
+
await session.exec(statement.sql);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
skipped.push(`${statement.object}: ${error instanceof Error ? error.message : String(error)}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return skipped;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=stubs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stubs.js","sourceRoot":"","sources":["../../src/migrations/stubs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Database, DatabaseHandle, DatabaseSchema, CreateDatabaseOptions } from "./client.js";
|
|
2
|
+
export interface ProcessDatabaseOptions<TSchema extends DatabaseSchema> extends CreateDatabaseOptions {
|
|
3
|
+
/** The app's drizzle schema: `db` is built with it, so `db.query.<table>` is typed and works. */
|
|
4
|
+
readonly schema?: TSchema;
|
|
5
|
+
}
|
|
6
|
+
export interface ProcessDatabase<TSchema extends DatabaseSchema = DatabaseSchema> {
|
|
7
|
+
/** The open database. Usable from module scope; a query before `open()` resolves throws "Database is not open". */
|
|
8
|
+
readonly db: Database<TSchema>;
|
|
9
|
+
/** Opens the process-wide handle for the URL (`getSharedDatabase`), once; later calls return the same handle. */
|
|
10
|
+
readonly open: () => Promise<DatabaseHandle>;
|
|
11
|
+
/** Closes the handle and forgets it, so `db` refuses queries again. Does nothing when not open. */
|
|
12
|
+
readonly close: () => Promise<void>;
|
|
13
|
+
/** For a script: opens, runs `main` with `db`, and closes whether `main` resolves or throws. */
|
|
14
|
+
readonly withDatabase: <T>(main: (db: Database<TSchema>) => Promise<T>) => Promise<T>;
|
|
15
|
+
readonly isOpen: () => boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A module-level database for the process. `url` may be a function, read on `open()`, so a script can load its
|
|
19
|
+
* environment after importing the module that creates it. The handle is the one `getSharedDatabase(url)` gives the
|
|
20
|
+
* modules, so the app and the modules share one pool (db README §3, "One handle per process").
|
|
21
|
+
*/
|
|
22
|
+
export declare function createProcessDatabase<TSchema extends DatabaseSchema = DatabaseSchema>(url: string | (() => string), options?: ProcessDatabaseOptions<TSchema>): ProcessDatabase<TSchema>;
|
|
23
|
+
//# sourceMappingURL=process.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../src/process.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAKnG,MAAM,WAAW,sBAAsB,CAAC,OAAO,SAAS,cAAc,CAAE,SAAQ,qBAAqB;IACnG,iGAAiG;IACjG,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe,CAAC,OAAO,SAAS,cAAc,GAAG,cAAc;IAC9E,mHAAmH;IACnH,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC/B,iHAAiH;IACjH,QAAQ,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7C,mGAAmG;IACnG,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,gGAAgG;IAChG,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,QAAQ,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACtF,QAAQ,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC;CAChC;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,SAAS,cAAc,GAAG,cAAc,EACnF,GAAG,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,EAC5B,OAAO,GAAE,sBAAsB,CAAC,OAAO,CAAM,GAC5C,eAAe,CAAC,OAAO,CAAC,CA0C1B"}
|
package/dist/process.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { closeSharedDatabase, getSharedDatabase } from "./shared.js";
|
|
2
|
+
const NOT_OPEN_MESSAGE = "Database is not open: call open() or withDatabase() at the process entry before querying";
|
|
3
|
+
/**
|
|
4
|
+
* A module-level database for the process. `url` may be a function, read on `open()`, so a script can load its
|
|
5
|
+
* environment after importing the module that creates it. The handle is the one `getSharedDatabase(url)` gives the
|
|
6
|
+
* modules, so the app and the modules share one pool (db README §3, "One handle per process").
|
|
7
|
+
*/
|
|
8
|
+
export function createProcessDatabase(url, options = {}) {
|
|
9
|
+
const { schema, ...databaseOptions } = options;
|
|
10
|
+
let opening;
|
|
11
|
+
let opened;
|
|
12
|
+
const open = () => {
|
|
13
|
+
opening ??= openHandle().catch((error) => {
|
|
14
|
+
opening = undefined;
|
|
15
|
+
throw error;
|
|
16
|
+
});
|
|
17
|
+
return opening;
|
|
18
|
+
};
|
|
19
|
+
async function openHandle() {
|
|
20
|
+
const resolvedUrl = typeof url === "function" ? url() : url;
|
|
21
|
+
const handle = await getSharedDatabase(resolvedUrl, databaseOptions);
|
|
22
|
+
opened = { url: resolvedUrl, db: await buildDatabase(handle, schema) };
|
|
23
|
+
return handle;
|
|
24
|
+
}
|
|
25
|
+
const close = async () => {
|
|
26
|
+
if (opening === undefined)
|
|
27
|
+
return;
|
|
28
|
+
const pending = opening;
|
|
29
|
+
opening = undefined;
|
|
30
|
+
await pending.catch(() => undefined);
|
|
31
|
+
const current = opened;
|
|
32
|
+
opened = undefined;
|
|
33
|
+
if (current !== undefined)
|
|
34
|
+
await closeSharedDatabase(current.url);
|
|
35
|
+
};
|
|
36
|
+
const withDatabase = async (main) => {
|
|
37
|
+
await open();
|
|
38
|
+
try {
|
|
39
|
+
return await main(db);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await close();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const db = createDatabaseProxy(() => opened?.db);
|
|
46
|
+
return { db, open, close, withDatabase, isOpen: () => opened !== undefined };
|
|
47
|
+
}
|
|
48
|
+
/** The handle's drizzle instance, or a new one over the same pool or client when the app passes its schema. */
|
|
49
|
+
async function buildDatabase(handle, schema) {
|
|
50
|
+
// The handle's own instance is typed with the default schema; without the app's schema the two types agree.
|
|
51
|
+
if (schema === undefined)
|
|
52
|
+
return handle.db;
|
|
53
|
+
if (handle.kind === "postgres") {
|
|
54
|
+
const { drizzle } = await import("drizzle-orm/node-postgres");
|
|
55
|
+
return drizzle({ client: handle.pool, schema });
|
|
56
|
+
}
|
|
57
|
+
const { drizzle } = await import("drizzle-orm/pglite");
|
|
58
|
+
return drizzle({ client: handle.client, schema });
|
|
59
|
+
}
|
|
60
|
+
/** A stand-in that forwards every property to the open database, or throws when none is open. */
|
|
61
|
+
function createDatabaseProxy(getDatabase) {
|
|
62
|
+
return new Proxy({}, {
|
|
63
|
+
get(_target, property) {
|
|
64
|
+
const database = getDatabase();
|
|
65
|
+
if (database === undefined) {
|
|
66
|
+
// `await db` and `Promise.resolve(db)` look for `then`: a closed database is just not a thenable.
|
|
67
|
+
if (property === "then")
|
|
68
|
+
return undefined;
|
|
69
|
+
throw new Error(NOT_OPEN_MESSAGE);
|
|
70
|
+
}
|
|
71
|
+
const value = Reflect.get(database, property, database);
|
|
72
|
+
return typeof value === "function" ? value.bind(database) : value;
|
|
73
|
+
},
|
|
74
|
+
has(_target, property) {
|
|
75
|
+
const database = getDatabase();
|
|
76
|
+
if (database === undefined)
|
|
77
|
+
throw new Error(NOT_OPEN_MESSAGE);
|
|
78
|
+
return Reflect.has(database, property);
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process.js","sourceRoot":"","sources":["../src/process.ts"],"names":[],"mappings":"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"}
|
package/dist/shared.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { type CreateDatabaseOptions, type DatabaseHandle } from "./client.js";
|
|
|
4
4
|
* A failed open is not kept, so the next call tries again.
|
|
5
5
|
*/
|
|
6
6
|
export declare function getSharedDatabase(url: string, options?: CreateDatabaseOptions): Promise<DatabaseHandle>;
|
|
7
|
+
/** Closes and forgets the shared handle for `url`, if one is open. For a script's end. */
|
|
8
|
+
export declare function closeSharedDatabase(url: string): Promise<void>;
|
|
7
9
|
/** Closes and forgets every shared handle. For tests and graceful shutdown. */
|
|
8
10
|
export declare function closeSharedDatabases(): Promise<void>;
|
|
9
11
|
//# sourceMappingURL=shared.d.ts.map
|
package/dist/shared.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,KAAK,qBAAqB,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAQ9F;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,cAAc,CAAC,CAS3G;AAED,+EAA+E;AAC/E,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAO1D"}
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,KAAK,qBAAqB,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAQ9F;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,cAAc,CAAC,CAS3G;AAED,0FAA0F;AAC1F,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMpE;AAED,+EAA+E;AAC/E,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAO1D"}
|
package/dist/shared.js
CHANGED
|
@@ -19,6 +19,15 @@ export function getSharedDatabase(url, options = {}) {
|
|
|
19
19
|
}
|
|
20
20
|
return handle;
|
|
21
21
|
}
|
|
22
|
+
/** Closes and forgets the shared handle for `url`, if one is open. For a script's end. */
|
|
23
|
+
export async function closeSharedDatabase(url) {
|
|
24
|
+
const handle = host[HANDLES_KEY]?.get(url);
|
|
25
|
+
if (handle === undefined)
|
|
26
|
+
return;
|
|
27
|
+
host[HANDLES_KEY]?.delete(url);
|
|
28
|
+
const opened = await handle.catch(() => undefined);
|
|
29
|
+
await opened?.close();
|
|
30
|
+
}
|
|
22
31
|
/** Closes and forgets every shared handle. For tests and graceful shutdown. */
|
|
23
32
|
export async function closeSharedDatabases() {
|
|
24
33
|
const handles = host[HANDLES_KEY];
|
package/dist/shared.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,oGAAoG;AACpG,8FAA8F;AAC9F,sFAAsF;AACtF,OAAO,EAAE,cAAc,EAAmD,MAAM,aAAa,CAAC;AAE9F,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;AAIhE,MAAM,IAAI,GAAG,UAAmC,CAAC;AAEjD;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW,EAAE,UAAiC,EAAE;IAChF,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,IAAI,GAAG,EAAmC,CAAC,CAAC;IACnF,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,GAAG,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACtC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACzB,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,oBAAoB;IACxC,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC;IAClC,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO;IAClC,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACrC,OAAO,CAAC,KAAK,EAAE,CAAC;IAChB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACjD,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAChH,CAAC"}
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,oGAAoG;AACpG,8FAA8F;AAC9F,sFAAsF;AACtF,OAAO,EAAE,cAAc,EAAmD,MAAM,aAAa,CAAC;AAE9F,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;AAIhE,MAAM,IAAI,GAAG,UAAmC,CAAC;AAEjD;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW,EAAE,UAAiC,EAAE;IAChF,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,IAAI,GAAG,EAAmC,CAAC,CAAC;IACnF,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,GAAG,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACtC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACzB,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,0FAA0F;AAC1F,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,GAAW;IACnD,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO;IACjC,IAAI,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACnD,MAAM,MAAM,EAAE,KAAK,EAAE,CAAC;AACxB,CAAC;AAED,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,oBAAoB;IACxC,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC;IAClC,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO;IAClC,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACrC,OAAO,CAAC,KAAK,EAAE,CAAC;IAChB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACjD,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAChH,CAAC"}
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import { PGlite } from "@electric-sql/pglite";
|
|
2
2
|
import type { AnySoftureModule } from "@softure-ai/core";
|
|
3
3
|
import { type PgliteClientDatabase } from "./client.js";
|
|
4
|
+
import { type AppMigrations } from "./migrations/migrator.js";
|
|
4
5
|
export interface TestDatabase {
|
|
5
6
|
readonly db: PgliteClientDatabase;
|
|
6
7
|
readonly client: PGlite;
|
|
7
8
|
readonly close: () => Promise<void>;
|
|
8
9
|
}
|
|
10
|
+
export interface CreateTestDatabaseOptions {
|
|
11
|
+
/**
|
|
12
|
+
* The app's own migrations, run around the module files as `migrate` runs them. The template is
|
|
13
|
+
* cached per hook object: define the hooks once, at module level, not inline per call.
|
|
14
|
+
*/
|
|
15
|
+
readonly app?: AppMigrations;
|
|
16
|
+
}
|
|
9
17
|
/**
|
|
10
18
|
* A fresh, migrated, in-memory database per call. List the module under test and the modules it
|
|
11
|
-
* depends on. Throws when a migration
|
|
19
|
+
* depends on. Throws when a migration (a module file or an app hook) fails: that is a bug in the
|
|
20
|
+
* module package or the app's migrations.
|
|
12
21
|
*/
|
|
13
|
-
export declare function createTestDatabase(modules?: readonly AnySoftureModule[]): Promise<TestDatabase>;
|
|
22
|
+
export declare function createTestDatabase(modules?: readonly AnySoftureModule[], options?: CreateTestDatabaseOptions): Promise<TestDatabase>;
|
|
14
23
|
//# sourceMappingURL=testing.d.ts.map
|
package/dist/testing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAsB,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAkC,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAG9F,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,oBAAoB,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC;CAC9B;AAMD;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,GAAE,SAAS,gBAAgB,EAAO,EAAE,OAAO,GAAE,yBAA8B,GAAG,OAAO,CAAC,YAAY,CAAC,CAIlJ"}
|
package/dist/testing.js
CHANGED
|
@@ -1,36 +1,55 @@
|
|
|
1
1
|
// Test databases for module unit tests (docs/02-module-standard.md §10): a real Postgres (PGlite)
|
|
2
2
|
// with the real migrations of the given modules. Migrating costs about two seconds, so each
|
|
3
3
|
// module set is migrated once per test worker into a template, and every call starts from a copy
|
|
4
|
-
// of the template's data folder (FIRE_TRACKER `src/db/test-db.ts`).
|
|
4
|
+
// of the template's data folder (FIRE_TRACKER `src/db/test-db.ts`). An app with its own migrations
|
|
5
|
+
// passes the same `app` hooks (and baseline) as to `migrate`, so module tables can reference app
|
|
6
|
+
// tables and a module the app's history already creates is adopted in the template.
|
|
5
7
|
import { PGlite } from "@electric-sql/pglite";
|
|
6
8
|
import { createPgliteHandle } from "./client.js";
|
|
7
9
|
import { migrate } from "./migrations/migrator.js";
|
|
8
10
|
import { describeProblem } from "./migrations/problems.js";
|
|
9
11
|
const templates = new Map();
|
|
12
|
+
const hookIds = new WeakMap();
|
|
13
|
+
let nextHookId = 1;
|
|
10
14
|
/**
|
|
11
15
|
* A fresh, migrated, in-memory database per call. List the module under test and the modules it
|
|
12
|
-
* depends on. Throws when a migration
|
|
16
|
+
* depends on. Throws when a migration (a module file or an app hook) fails: that is a bug in the
|
|
17
|
+
* module package or the app's migrations.
|
|
13
18
|
*/
|
|
14
|
-
export async function createTestDatabase(modules = []) {
|
|
15
|
-
const client = new PGlite({ loadDataDir: await getTemplate(modules) });
|
|
19
|
+
export async function createTestDatabase(modules = [], options = {}) {
|
|
20
|
+
const client = new PGlite({ loadDataDir: await getTemplate(modules, options.app ?? {}) });
|
|
16
21
|
const handle = await createPgliteHandle(client);
|
|
17
22
|
return { db: handle.db, client, close: handle.close };
|
|
18
23
|
}
|
|
19
|
-
function getTemplate(modules) {
|
|
20
|
-
const
|
|
24
|
+
function getTemplate(modules, app) {
|
|
25
|
+
const moduleKey = modules.map((module) => `${module.id}@${module.migrations?.dir.href ?? "-"}`).join("|");
|
|
26
|
+
const baselineKey = Object.entries(app.baseline ?? {}).sort(([left], [right]) => left.localeCompare(right)).map(([id, through]) => `${id}:${through}`).join(",");
|
|
27
|
+
const key = `${moduleKey}#before:${identifyHook(app.before)}#after:${identifyHook(app.after)}#baseline:${baselineKey}`;
|
|
21
28
|
let template = templates.get(key);
|
|
22
29
|
if (template === undefined) {
|
|
23
|
-
template = buildTemplate(modules);
|
|
30
|
+
template = buildTemplate(modules, app);
|
|
24
31
|
templates.set(key, template);
|
|
25
32
|
// A failed build is not cached: the next call reports the problem again.
|
|
26
33
|
template.catch(() => templates.delete(key));
|
|
27
34
|
}
|
|
28
35
|
return template;
|
|
29
36
|
}
|
|
30
|
-
|
|
37
|
+
// Hooks are functions, so the key holds an id per hook object rather than its source text.
|
|
38
|
+
function identifyHook(hook) {
|
|
39
|
+
if (hook === undefined) {
|
|
40
|
+
return "-";
|
|
41
|
+
}
|
|
42
|
+
let id = hookIds.get(hook);
|
|
43
|
+
if (id === undefined) {
|
|
44
|
+
id = nextHookId++;
|
|
45
|
+
hookIds.set(hook, id);
|
|
46
|
+
}
|
|
47
|
+
return String(id);
|
|
48
|
+
}
|
|
49
|
+
async function buildTemplate(modules, app) {
|
|
31
50
|
const handle = await createPgliteHandle(new PGlite());
|
|
32
51
|
try {
|
|
33
|
-
const result = await migrate(handle, { modules });
|
|
52
|
+
const result = await migrate(handle, { modules, app });
|
|
34
53
|
if (!result.ok) {
|
|
35
54
|
throw new Error(`createTestDatabase: migrations failed:\n${result.problems.map(describeProblem).join("\n")}`);
|
|
36
55
|
}
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,4FAA4F;AAC5F,iGAAiG;AACjG,
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softure-ai/db",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "SOFTURE AI database layer: pg/PGlite client, per-module migrator with plan and adopt, PGlite test databases.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"dist",
|
|
18
|
+
"CHANGELOG.md",
|
|
18
19
|
"src",
|
|
19
20
|
"!src/**/*.test.ts",
|
|
20
21
|
"!src/**/*.test.tsx"
|
|
@@ -47,12 +48,28 @@
|
|
|
47
48
|
"build": "tsc -p tsconfig.build.json"
|
|
48
49
|
},
|
|
49
50
|
"dependencies": {
|
|
51
|
+
"@softure-ai/core": "^0.1.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"@electric-sql/pglite": "^0.5.0",
|
|
55
|
+
"@types/pg": "^8.11.0",
|
|
56
|
+
"drizzle-orm": "^0.45.2",
|
|
57
|
+
"pg": "^8.11.0"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"@electric-sql/pglite": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"@types/pg": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"pg": {
|
|
67
|
+
"optional": true
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
50
71
|
"@electric-sql/pglite": "^0.5.8",
|
|
51
|
-
"@softure-ai/core": "^0.1.0",
|
|
52
72
|
"@types/pg": "^8.23.1",
|
|
53
73
|
"pg": "^8.23.1"
|
|
54
|
-
},
|
|
55
|
-
"peerDependencies": {
|
|
56
|
-
"drizzle-orm": "^0.45.2"
|
|
57
74
|
}
|
|
58
75
|
}
|