@ingram-tech/nk-db 0.8.0 → 0.9.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/errors.d.ts +14 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +27 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/pglite/dev.js +0 -0
- package/dist/pool.d.ts +15 -0
- package/dist/pool.d.ts.map +1 -1
- package/dist/pool.js +16 -1
- package/dist/pool.js.map +1 -1
- package/dist/queries.d.ts +42 -4
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +44 -15
- package/dist/queries.js.map +1 -1
- package/package.json +2 -2
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** SQLSTATE `23505` — `unique_violation`. */
|
|
2
|
+
export declare const PG_UNIQUE_VIOLATION = "23505";
|
|
3
|
+
/**
|
|
4
|
+
* True when `error`, or anything in its `.cause` chain, is a Postgres error
|
|
5
|
+
* whose SQLSTATE equals `code`.
|
|
6
|
+
*
|
|
7
|
+
* Prefer `INSERT … ON CONFLICT` over catch-and-branch where you can (it avoids
|
|
8
|
+
* the pooled-connection-destroy footgun — see `docs/db-package.md`), but use
|
|
9
|
+
* this for the cases that genuinely must inspect the failure.
|
|
10
|
+
*/
|
|
11
|
+
export declare const isPgError: (error: unknown, code: string) => boolean;
|
|
12
|
+
/** Convenience for the most common check: a `23505` unique violation. */
|
|
13
|
+
export declare const isUniqueViolation: (error: unknown) => boolean;
|
|
14
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAKA,6CAA6C;AAC7C,eAAO,MAAM,mBAAmB,UAAU,CAAC;AAE3C;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,GAAI,OAAO,OAAO,EAAE,MAAM,MAAM,KAAG,OASxD,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,OACb,CAAC"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Inspect Postgres driver errors by SQLSTATE without hand-rolling the `.cause`
|
|
2
|
+
// chain walk at every call site. `pg` puts the SQLSTATE on `error.code`, but
|
|
3
|
+
// wrappers (Drizzle, our own helpers) often re-throw with the original error
|
|
4
|
+
// nested under `.cause`, so a flat `err.code === …` check silently misses it.
|
|
5
|
+
/** SQLSTATE `23505` — `unique_violation`. */
|
|
6
|
+
export const PG_UNIQUE_VIOLATION = "23505";
|
|
7
|
+
/**
|
|
8
|
+
* True when `error`, or anything in its `.cause` chain, is a Postgres error
|
|
9
|
+
* whose SQLSTATE equals `code`.
|
|
10
|
+
*
|
|
11
|
+
* Prefer `INSERT … ON CONFLICT` over catch-and-branch where you can (it avoids
|
|
12
|
+
* the pooled-connection-destroy footgun — see `docs/db-package.md`), but use
|
|
13
|
+
* this for the cases that genuinely must inspect the failure.
|
|
14
|
+
*/
|
|
15
|
+
export const isPgError = (error, code) => {
|
|
16
|
+
let current = error;
|
|
17
|
+
while (current !== null && current !== undefined) {
|
|
18
|
+
if (typeof current === "object" && "code" in current && current.code === code) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
current = current instanceof Error ? current.cause : undefined;
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
};
|
|
25
|
+
/** Convenience for the most common check: a `23505` unique violation. */
|
|
26
|
+
export const isUniqueViolation = (error) => isPgError(error, PG_UNIQUE_VIOLATION);
|
|
27
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,6EAA6E;AAC7E,6EAA6E;AAC7E,8EAA8E;AAE9E,6CAA6C;AAC7C,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC;AAE3C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAAc,EAAE,IAAY,EAAW,EAAE;IAClE,IAAI,OAAO,GAAY,KAAK,CAAC;IAC7B,OAAO,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAClD,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,MAAM,IAAI,OAAO,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YAC/E,OAAO,IAAI,CAAC;QACb,CAAC;QACD,OAAO,GAAG,OAAO,YAAY,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAChE,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC,CAAC;AAEF,yEAAyE;AACzE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAc,EAAW,EAAE,CAC5D,SAAS,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export { pgNumericToNumber, pgTimestampToIso } from "./coerce.js";
|
|
2
2
|
export { createDb, withRlsTransaction } from "./drizzle.js";
|
|
3
|
+
export { isPgError, isUniqueViolation, PG_UNIQUE_VIOLATION } from "./errors.js";
|
|
3
4
|
export { type DbEnv, dbEnv, getDatabaseUrl, isConfigured } from "./keys.js";
|
|
4
5
|
export { type CreatePoolConfig, createPool } from "./pool.js";
|
|
5
|
-
export { createQueries, type PoolQueries, type Queries } from "./queries.js";
|
|
6
|
+
export { createQueries, parseMaybeRow, parseOneRow, parseRows, type PoolQueries, type Queries, type RowsResult, } from "./queries.js";
|
|
6
7
|
export { RLS_CLAIMS_SETTING, RLS_DEFAULT_ROLE, type ResolvedRlsConfig, type RlsClaims, type RlsOptions, resolveRlsConfig, rlsPreamble, } from "./rls.js";
|
|
7
8
|
export { configureTimestampsAsStrings } from "./types.js";
|
|
8
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,EAAE,KAAK,gBAAgB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,EAAE,KAAK,gBAAgB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EACN,aAAa,EACb,aAAa,EACb,WAAW,EACX,SAAS,EACT,KAAK,WAAW,EAChB,KAAK,OAAO,EACZ,KAAK,UAAU,GACf,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,kBAAkB,EAClB,gBAAgB,EAChB,KAAK,iBAAiB,EACtB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,gBAAgB,EAChB,WAAW,GACX,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
// production bundle.
|
|
4
4
|
export { pgNumericToNumber, pgTimestampToIso } from "./coerce.js";
|
|
5
5
|
export { createDb, withRlsTransaction } from "./drizzle.js";
|
|
6
|
+
export { isPgError, isUniqueViolation, PG_UNIQUE_VIOLATION } from "./errors.js";
|
|
6
7
|
export { dbEnv, getDatabaseUrl, isConfigured } from "./keys.js";
|
|
7
8
|
export { createPool } from "./pool.js";
|
|
8
|
-
export { createQueries } from "./queries.js";
|
|
9
|
+
export { createQueries, parseMaybeRow, parseOneRow, parseRows, } from "./queries.js";
|
|
9
10
|
export { RLS_CLAIMS_SETTING, RLS_DEFAULT_ROLE, resolveRlsConfig, rlsPreamble, } from "./rls.js";
|
|
10
11
|
export { configureTimestampsAsStrings } from "./types.js";
|
|
11
12
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,8EAA8E;AAC9E,qBAAqB;AAErB,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAc,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,EAAyB,UAAU,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,8EAA8E;AAC9E,qBAAqB;AAErB,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EAAc,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,EAAyB,UAAU,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EACN,aAAa,EACb,aAAa,EACb,WAAW,EACX,SAAS,GAIT,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,kBAAkB,EAClB,gBAAgB,EAIhB,gBAAgB,EAChB,WAAW,GACX,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/pglite/dev.js
CHANGED
|
File without changes
|
package/dist/pool.d.ts
CHANGED
|
@@ -7,6 +7,21 @@ export interface CreatePoolConfig {
|
|
|
7
7
|
/** Pool size cap. Defaults to env DATABASE_POOL_MAX, or 1 for a local socket. */
|
|
8
8
|
max?: number;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Un-escape a PEM CA cert that arrived with literal `\n` instead of real
|
|
12
|
+
* newlines. A multiline secret survives intact in the app's runtime env (e.g.
|
|
13
|
+
* Vercel hands `process.env.DATABASE_CA_CERT` back with real newlines), but
|
|
14
|
+
* `vercel env pull` — and most `.env` serializers — collapse it to a single
|
|
15
|
+
* quoted line with `\n` escapes. A caller that then sources that file (the
|
|
16
|
+
* `nk-pg-migrate` runner, a CI job) gets the literal-`\n` form, which OpenSSL
|
|
17
|
+
* rejects with "self-signed certificate in certificate chain" even though the
|
|
18
|
+
* deployed app verifies the very same cert fine. Normalise here, the one place
|
|
19
|
+
* the cert reaches `pg`, so every caller gets verify-full regardless of how the
|
|
20
|
+
* env was loaded. A correctly-newlined PEM contains no literal `\n`, so this is
|
|
21
|
+
* a no-op there — idempotent and safe (base64 + the BEGIN/END lines never
|
|
22
|
+
* contain a backslash).
|
|
23
|
+
*/
|
|
24
|
+
export declare const normalizeCaCert: (caCert: string | undefined) => string | undefined;
|
|
10
25
|
/**
|
|
11
26
|
* The one shared `pg.Pool`. Reuse this for everything — app queries (via
|
|
12
27
|
* `createQueries` / Drizzle) AND Better Auth's adapter — so there's exactly one
|
package/dist/pool.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,IAAI,CAAC;AAG3C,MAAM,WAAW,gBAAgB;IAChC,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAKD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,UAAU,GAAI,SAAQ,gBAAqB,KAAG,IA6B1D,CAAC"}
|
|
1
|
+
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,IAAI,CAAC;AAG3C,MAAM,WAAW,gBAAgB;IAChC,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAKD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,GAAG,SAAS,KAAG,MAAM,GAAG,SACN,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,UAAU,GAAI,SAAQ,gBAAqB,KAAG,IA6B1D,CAAC"}
|
package/dist/pool.js
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { Pool } from "pg";
|
|
2
2
|
import { dbEnv } from "./keys.js";
|
|
3
3
|
const isLocal = (connectionString) => connectionString.includes("127.0.0.1") || connectionString.includes("localhost");
|
|
4
|
+
/**
|
|
5
|
+
* Un-escape a PEM CA cert that arrived with literal `\n` instead of real
|
|
6
|
+
* newlines. A multiline secret survives intact in the app's runtime env (e.g.
|
|
7
|
+
* Vercel hands `process.env.DATABASE_CA_CERT` back with real newlines), but
|
|
8
|
+
* `vercel env pull` — and most `.env` serializers — collapse it to a single
|
|
9
|
+
* quoted line with `\n` escapes. A caller that then sources that file (the
|
|
10
|
+
* `nk-pg-migrate` runner, a CI job) gets the literal-`\n` form, which OpenSSL
|
|
11
|
+
* rejects with "self-signed certificate in certificate chain" even though the
|
|
12
|
+
* deployed app verifies the very same cert fine. Normalise here, the one place
|
|
13
|
+
* the cert reaches `pg`, so every caller gets verify-full regardless of how the
|
|
14
|
+
* env was loaded. A correctly-newlined PEM contains no literal `\n`, so this is
|
|
15
|
+
* a no-op there — idempotent and safe (base64 + the BEGIN/END lines never
|
|
16
|
+
* contain a backslash).
|
|
17
|
+
*/
|
|
18
|
+
export const normalizeCaCert = (caCert) => caCert?.includes("\\n") ? caCert.replace(/\\n/g, "\n") : caCert;
|
|
4
19
|
/**
|
|
5
20
|
* The one shared `pg.Pool`. Reuse this for everything — app queries (via
|
|
6
21
|
* `createQueries` / Drizzle) AND Better Auth's adapter — so there's exactly one
|
|
@@ -29,7 +44,7 @@ export const createPool = (config = {}) => {
|
|
|
29
44
|
if (!connectionString) {
|
|
30
45
|
throw new Error("@ingram-tech/nk-db: createPool needs a connection string.");
|
|
31
46
|
}
|
|
32
|
-
const caCert = config.caCert ?? env?.caCert;
|
|
47
|
+
const caCert = normalizeCaCert(config.caCert ?? env?.caCert);
|
|
33
48
|
const local = isLocal(connectionString);
|
|
34
49
|
const max = config.max ?? env?.poolMax ?? (local ? 1 : undefined);
|
|
35
50
|
const base = max === undefined ? {} : { max };
|
package/dist/pool.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pool.js","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,IAAI,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAWlC,MAAM,OAAO,GAAG,CAAC,gBAAwB,EAAW,EAAE,CACrD,gBAAgB,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,gBAAgB,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAElF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,SAA2B,EAAE,EAAQ,EAAE;IACjE,MAAM,GAAG,GAAG,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;IAC1D,MAAM,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,IAAI,GAAG,EAAE,gBAAgB,CAAC;IAC1E,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAC;IAC9E,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,GAAG,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"pool.js","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,IAAI,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAWlC,MAAM,OAAO,GAAG,CAAC,gBAAwB,EAAW,EAAE,CACrD,gBAAgB,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,gBAAgB,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAElF;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAA0B,EAAsB,EAAE,CACjF,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,SAA2B,EAAE,EAAQ,EAAE;IACjE,MAAM,GAAG,GAAG,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;IAC1D,MAAM,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,IAAI,GAAG,EAAE,gBAAgB,CAAC;IAC1E,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAC;IAC9E,CAAC;IACD,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,MAAM,IAAI,GAAG,EAAE,MAAM,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,GAAG,EAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAElE,MAAM,IAAI,GAAe,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;IAE1D,IAAI,MAAM,EAAE,CAAC;QACZ,OAAO,IAAI,IAAI,CAAC;YACf,GAAG,IAAI;YACP,gBAAgB;YAChB,GAAG,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE;SAC7C,CAAC,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,EAAE,CAAC;QACX,OAAO,IAAI,IAAI,CAAC,EAAE,GAAG,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAChD,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACtC,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACnC,OAAO,IAAI,IAAI,CAAC;QACf,GAAG,IAAI;QACP,gBAAgB,EAAE,GAAG,CAAC,QAAQ,EAAE;QAChC,GAAG,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE;KAClC,CAAC,CAAC;AACJ,CAAC,CAAC"}
|
package/dist/queries.d.ts
CHANGED
|
@@ -1,15 +1,53 @@
|
|
|
1
1
|
import type { Pool, QueryResultRow } from "pg";
|
|
2
|
+
import { z } from "zod";
|
|
2
3
|
import { type RlsClaims, type RlsOptions } from "./rls.js";
|
|
4
|
+
/**
|
|
5
|
+
* A raw result shaped like pg's `QueryResult` or Drizzle's `tx.execute()` output
|
|
6
|
+
* — just the `rows`. Consumed by {@link parseRows} / {@link parseMaybeRow} /
|
|
7
|
+
* {@link parseOneRow}, which validate that array against a Zod schema.
|
|
8
|
+
*/
|
|
9
|
+
export interface RowsResult {
|
|
10
|
+
rows: readonly unknown[];
|
|
11
|
+
}
|
|
3
12
|
export interface Queries {
|
|
4
|
-
/**
|
|
5
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Run a query and return all rows. Pass a Zod row `schema` as the third
|
|
15
|
+
* argument to validate (and coerce) every row instead of trusting a `<T>`
|
|
16
|
+
* cast — the schema is also where `numeric`/timestamp coercion belongs
|
|
17
|
+
* (`z.coerce.number()`, an ISO transform), per the no-`as`-on-external-input
|
|
18
|
+
* rule.
|
|
19
|
+
*/
|
|
20
|
+
query: {
|
|
21
|
+
<T extends QueryResultRow = QueryResultRow>(text: string, params?: unknown[]): Promise<T[]>;
|
|
22
|
+
<S extends z.ZodType>(text: string, params: unknown[] | undefined, schema: S): Promise<z.output<S>[]>;
|
|
23
|
+
};
|
|
6
24
|
/** Run a query expected to return at most one row; `null` when none. */
|
|
7
|
-
maybeOne:
|
|
25
|
+
maybeOne: {
|
|
26
|
+
<T extends QueryResultRow = QueryResultRow>(text: string, params?: unknown[]): Promise<T | null>;
|
|
27
|
+
<S extends z.ZodType>(text: string, params: unknown[] | undefined, schema: S): Promise<z.output<S> | null>;
|
|
28
|
+
};
|
|
8
29
|
/** Run a query that must return exactly one row; throws otherwise. */
|
|
9
|
-
one:
|
|
30
|
+
one: {
|
|
31
|
+
<T extends QueryResultRow = QueryResultRow>(text: string, params?: unknown[]): Promise<T>;
|
|
32
|
+
<S extends z.ZodType>(text: string, params: unknown[] | undefined, schema: S): Promise<z.output<S>>;
|
|
33
|
+
};
|
|
10
34
|
/** Run a write and return the affected row count. */
|
|
11
35
|
execute: (text: string, params?: unknown[]) => Promise<number>;
|
|
12
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Validate the `rows` of a raw result (a pg `QueryResult` or the output of
|
|
39
|
+
* Drizzle's `tx.execute()`) against a Zod row schema, returning the parsed rows.
|
|
40
|
+
*
|
|
41
|
+
* This is the result-side counterpart to {@link Queries.query}'s `schema` arg,
|
|
42
|
+
* for the Drizzle RLS path: inside `withRlsTransaction`, `tx.execute()` hands
|
|
43
|
+
* back an untyped `{ rows }`, and this gives it the same validated/coerced
|
|
44
|
+
* result the pool helpers have. The schema doubles as the coercion layer.
|
|
45
|
+
*/
|
|
46
|
+
export declare const parseRows: <S extends z.ZodType>(result: RowsResult, schema: S) => z.output<S>[];
|
|
47
|
+
/** Like {@link parseRows} but for at most one row; `null` when none. */
|
|
48
|
+
export declare const parseMaybeRow: <S extends z.ZodType>(result: RowsResult, schema: S) => z.output<S> | null;
|
|
49
|
+
/** Like {@link parseRows} but requires exactly one row; throws otherwise. */
|
|
50
|
+
export declare const parseOneRow: <S extends z.ZodType>(result: RowsResult, schema: S) => z.output<S>;
|
|
13
51
|
export interface PoolQueries extends Queries {
|
|
14
52
|
/**
|
|
15
53
|
* Run `fn` inside a single transaction on a dedicated client (`begin` /
|
package/dist/queries.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAc,cAAc,EAAE,MAAM,IAAI,CAAC;AAC3D,OAAO,EACN,KAAK,SAAS,EACd,KAAK,UAAU,EAGf,MAAM,UAAU,CAAC;AAKlB,MAAM,WAAW,OAAO;
|
|
1
|
+
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"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"}
|
package/dist/queries.js
CHANGED
|
@@ -1,29 +1,58 @@
|
|
|
1
1
|
import { resolveRlsConfig, rlsPreamble, } from "./rls.js";
|
|
2
2
|
const bind = (executor) => {
|
|
3
|
-
|
|
3
|
+
async function query(text, params = [], schema) {
|
|
4
4
|
const result = await executor.query(text, params);
|
|
5
|
-
return result.rows;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
return schema ? result.rows.map((row) => schema.parse(row)) : result.rows;
|
|
6
|
+
}
|
|
7
|
+
async function maybeOne(text, params = [], schema) {
|
|
8
|
+
const result = await executor.query(text, params);
|
|
9
|
+
const first = result.rows[0];
|
|
10
|
+
if (first === undefined)
|
|
11
|
+
return null;
|
|
12
|
+
return schema ? schema.parse(first) : first;
|
|
13
|
+
}
|
|
14
|
+
async function one(text, params = [], schema) {
|
|
15
|
+
const result = await executor.query(text, params);
|
|
16
|
+
if (result.rows.length === 0) {
|
|
15
17
|
throw new Error("Expected exactly one row, got none");
|
|
16
|
-
if (rows.length > 1) {
|
|
17
|
-
throw new Error(`Expected exactly one row, got ${rows.length}`);
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
if (result.rows.length > 1) {
|
|
20
|
+
throw new Error(`Expected exactly one row, got ${result.rows.length}`);
|
|
21
|
+
}
|
|
22
|
+
const row = result.rows[0];
|
|
23
|
+
return schema ? schema.parse(row) : row;
|
|
24
|
+
}
|
|
21
25
|
const execute = async (text, params = []) => {
|
|
22
26
|
const result = await executor.query(text, params);
|
|
23
27
|
return result.rowCount ?? 0;
|
|
24
28
|
};
|
|
25
29
|
return { query, maybeOne, one, execute };
|
|
26
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* Validate the `rows` of a raw result (a pg `QueryResult` or the output of
|
|
33
|
+
* Drizzle's `tx.execute()`) against a Zod row schema, returning the parsed rows.
|
|
34
|
+
*
|
|
35
|
+
* This is the result-side counterpart to {@link Queries.query}'s `schema` arg,
|
|
36
|
+
* for the Drizzle RLS path: inside `withRlsTransaction`, `tx.execute()` hands
|
|
37
|
+
* back an untyped `{ rows }`, and this gives it the same validated/coerced
|
|
38
|
+
* result the pool helpers have. The schema doubles as the coercion layer.
|
|
39
|
+
*/
|
|
40
|
+
export const parseRows = (result, schema) => result.rows.map((row) => schema.parse(row));
|
|
41
|
+
/** Like {@link parseRows} but for at most one row; `null` when none. */
|
|
42
|
+
export const parseMaybeRow = (result, schema) => {
|
|
43
|
+
const first = result.rows[0];
|
|
44
|
+
return first === undefined ? null : schema.parse(first);
|
|
45
|
+
};
|
|
46
|
+
/** Like {@link parseRows} but requires exactly one row; throws otherwise. */
|
|
47
|
+
export const parseOneRow = (result, schema) => {
|
|
48
|
+
if (result.rows.length === 0) {
|
|
49
|
+
throw new Error("Expected exactly one row, got none");
|
|
50
|
+
}
|
|
51
|
+
if (result.rows.length > 1) {
|
|
52
|
+
throw new Error(`Expected exactly one row, got ${result.rows.length}`);
|
|
53
|
+
}
|
|
54
|
+
return schema.parse(result.rows[0]);
|
|
55
|
+
};
|
|
27
56
|
/**
|
|
28
57
|
* Bind the raw-SQL helpers (`query` / `one` / `maybeOne` / `execute` / `withTx`)
|
|
29
58
|
* to a pool. Signatures match what apps hand-rolled, so adopting
|
package/dist/queries.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ingram-tech/nk-db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "The Ingram Postgres data layer: one TLS-aware pg pool, raw-SQL helpers, Drizzle wiring, and a PGlite (no-Docker) dev/test harness for Next.js sites.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@electric-sql/pglite": "^0.5.0",
|
|
68
68
|
"@electric-sql/pglite-socket": "^0.2.4",
|
|
69
|
-
"@ingram-tech/nk-dev": "
|
|
69
|
+
"@ingram-tech/nk-dev": "0.2.3",
|
|
70
70
|
"@types/node": "^25.0.0",
|
|
71
71
|
"@types/pg": "^8.11.0",
|
|
72
72
|
"drizzle-orm": "^0.45.0",
|