@di-framework/bindings 6.0.7 → 6.0.8
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/README.md +49 -0
- package/dist/postgres/adapter.d.ts +69 -0
- package/dist/postgres/adapter.js +77 -0
- package/dist/postgres/atomic-batch.d.ts +23 -0
- package/dist/postgres/atomic-batch.js +104 -0
- package/dist/postgres/bind-params.d.ts +17 -0
- package/dist/postgres/bind-params.js +44 -0
- package/dist/postgres/index.d.ts +3 -0
- package/dist/postgres/index.js +2 -0
- package/package.json +14 -1
package/README.md
CHANGED
|
@@ -46,6 +46,55 @@ connection and the selected database; multiple independently credentialed Postgr
|
|
|
46
46
|
bindings are not supported by this QuickJS path yet. Guests initialize before the
|
|
47
47
|
application, including bindings constructed at module startup.
|
|
48
48
|
|
|
49
|
+
## Transaction semantics
|
|
50
|
+
|
|
51
|
+
`wasmcloud:postgres@0.2.0` has `query` and `queryBatch`, and no transaction handle. Each call goes
|
|
52
|
+
to whichever pooled connection is free. A named import (`user-database-query`) is served from a
|
|
53
|
+
per-credential pool with no connection affinity, so a `BEGIN` in one call does not cover the next
|
|
54
|
+
statement. A `COMMIT` can land on a connection that never saw the `BEGIN`, and that connection
|
|
55
|
+
stays `idle in transaction`.
|
|
56
|
+
|
|
57
|
+
Guests that use `@di-framework/repo` open the binding with `openPostgresDatabase` from
|
|
58
|
+
`@di-framework/bindings/postgres`. The handle is an `AutocommitDatabase`. Every statement is its
|
|
59
|
+
own transaction, and `transaction(fn)` runs `fn` on that same handle. The adapter never sends
|
|
60
|
+
`BEGIN`, `COMMIT`, or `ROLLBACK`. `FOR UPDATE` and advisory locks last only for the statement
|
|
61
|
+
that carries them.
|
|
62
|
+
|
|
63
|
+
Keep an invariant in one statement:
|
|
64
|
+
|
|
65
|
+
- a conditional `UPDATE … RETURNING`
|
|
66
|
+
- a data-modifying CTE
|
|
67
|
+
- `INSERT … SELECT … WHERE NOT EXISTS`
|
|
68
|
+
|
|
69
|
+
`run` sets `changes` from the rows returned. The provider does not report a command tag, so a
|
|
70
|
+
count is a `RETURNING` list. A `changes` value of `0` means no rows came back, which is what an
|
|
71
|
+
`UPDATE` or `DELETE` without `RETURNING` reports even when Postgres modified rows.
|
|
72
|
+
|
|
73
|
+
`exec` sends its script as one `queryBatch`. The host runs that string on one pooled client, and
|
|
74
|
+
Postgres treats a multi-statement simple query as one implicit transaction.
|
|
75
|
+
|
|
76
|
+
`atomicBatch` is for a write-only sequence that must commit or roll back together and that does
|
|
77
|
+
not read between statements. It renders each parameter as a literal (quotes doubled, a negative
|
|
78
|
+
number parenthesized so it cannot form a `--` comment) and submits that script as one
|
|
79
|
+
`queryBatch`. The script has no `BEGIN` or `COMMIT`. Postgres runs a multi-statement simple
|
|
80
|
+
query as one implicit transaction and rolls it back on error, returning the pooled client idle.
|
|
81
|
+
wasmCloud v2.8.0 runs that string on one pooled client:
|
|
82
|
+
`crates/wash-runtime/src/plugin/wasmcloud_postgres/async_p3.rs` checks out a single client in
|
|
83
|
+
`query_batch` and calls `client.batch_execute` (`batch_with_client`, line 191).
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { openPostgresDatabase } from '@di-framework/bindings/postgres';
|
|
87
|
+
|
|
88
|
+
const db = openPostgresDatabase(database);
|
|
89
|
+
await db.atomicBatch([
|
|
90
|
+
{ sql: 'INSERT INTO t (name, n) VALUES (?, ?)', params: ["o'brien", -1] },
|
|
91
|
+
]);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`@di-framework/repo` is an optional peer of this package. The main `@di-framework/bindings` entry
|
|
95
|
+
leaves it unloaded. Import `@di-framework/bindings/postgres` from a guest that depends on
|
|
96
|
+
`@di-framework/repo`.
|
|
97
|
+
|
|
49
98
|
Secret material is referenced, never inlined:
|
|
50
99
|
|
|
51
100
|
```ts
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { SqlDatabase } from '@di-framework/repo';
|
|
2
|
+
import type { PostgresGuest } from '../bindings/postgres.js';
|
|
3
|
+
import { type AtomicStatement } from './atomic-batch.js';
|
|
4
|
+
/**
|
|
5
|
+
* `SqlDatabase` over `wasmcloud:postgres@0.2.0` where every statement is its own transaction.
|
|
6
|
+
*
|
|
7
|
+
* The binding has no connection or transaction handle, and a named import has no connection
|
|
8
|
+
* affinity: each `query` and `queryBatch` may run on any pooled connection. A `BEGIN` sent on
|
|
9
|
+
* one call does not cover the next. This handle never sends `BEGIN`, `COMMIT`, or `ROLLBACK`.
|
|
10
|
+
* `transaction(fn)` runs `fn` on this same handle. `FOR UPDATE` and advisory locks last only
|
|
11
|
+
* for their statement.
|
|
12
|
+
*
|
|
13
|
+
* Keep an invariant in one statement:
|
|
14
|
+
*
|
|
15
|
+
* - conditional `UPDATE … RETURNING`
|
|
16
|
+
* - a data-modifying CTE
|
|
17
|
+
* - `INSERT … SELECT … WHERE NOT EXISTS`
|
|
18
|
+
*
|
|
19
|
+
* `run` reports `changes` as the number of rows returned. The provider does not report a
|
|
20
|
+
* command tag, so a count comes from `RETURNING`.
|
|
21
|
+
*
|
|
22
|
+
* A multi-statement `exec` script is still one `queryBatch`. The host runs that string with
|
|
23
|
+
* `batch_execute` on a single pooled client, so Postgres treats it as one implicit transaction.
|
|
24
|
+
*/
|
|
25
|
+
export interface AutocommitDatabase extends SqlDatabase {
|
|
26
|
+
/**
|
|
27
|
+
* Writes `statements` as one transaction and returns no rows.
|
|
28
|
+
*
|
|
29
|
+
* Parameters are rendered as SQL literals and the text is submitted as a single
|
|
30
|
+
* `queryBatch` string. The string has no `BEGIN`, `COMMIT`, or `ROLLBACK`.
|
|
31
|
+
* Postgres treats that multi-statement simple query as one implicit transaction
|
|
32
|
+
* and rolls it back on error, so the pooled client returns idle. A quote is
|
|
33
|
+
* doubled. A negative number is parenthesized so it cannot form a `--` comment.
|
|
34
|
+
* Do not use this when a later statement must read an earlier one.
|
|
35
|
+
*
|
|
36
|
+
* The whole string runs on one pooled client. wasmCloud v2.8.0 (commit
|
|
37
|
+
* `5c4ec4a3d008b3f401d9e763515f434deebc9936`, the wash revision this platform
|
|
38
|
+
* pins) implements `query_batch` in
|
|
39
|
+
* `crates/wash-runtime/src/plugin/wasmcloud_postgres/async_p3.rs`. The default
|
|
40
|
+
* import checks out one client (`query_batch`, line 331) and `batch_with_client`
|
|
41
|
+
* (line 190) calls `client.batch_execute` with that string (line 191). The named
|
|
42
|
+
* import does the same (`query_batch`, line 407; `id.client()`, line 412;
|
|
43
|
+
* `batch_with_client`, line 416). `crates/provider-sqldb-postgres` is not in that
|
|
44
|
+
* tag. The invocation-lease patch does not split this string, so it still
|
|
45
|
+
* reaches one `batch_execute`.
|
|
46
|
+
*/
|
|
47
|
+
atomicBatch(statements: readonly AtomicStatement[]): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Opens `binding` as an {@link AutocommitDatabase}.
|
|
51
|
+
*
|
|
52
|
+
* `wasmcloud:postgres@0.2.0` has no connection or transaction handle: each `query` and
|
|
53
|
+
* `query-batch` call may run on any pooled connection of the host, so a `BEGIN` sent in one call
|
|
54
|
+
* does not cover the next. The guest therefore never opens a transaction. Every statement
|
|
55
|
+
* commits on its own, `transaction(fn)` runs `fn` on this same handle, and `FOR UPDATE` and
|
|
56
|
+
* advisory locks last only for their statement. Keep invariants in one statement (conditional
|
|
57
|
+
* `UPDATE … RETURNING`, data-modifying CTEs, `INSERT … SELECT … WHERE NOT EXISTS`). A
|
|
58
|
+
* multi-statement `exec` script still runs as one `query-batch`, which Postgres executes as one
|
|
59
|
+
* implicit transaction on one connection.
|
|
60
|
+
*
|
|
61
|
+
* `run`, `query`, and `first` call `query`. `exec` calls `queryBatch`. Failures keep a
|
|
62
|
+
* five-digit SQLSTATE on `error.code`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function openPostgresDatabase(binding: PostgresGuest): AutocommitDatabase;
|
|
65
|
+
/**
|
|
66
|
+
* {@link AutocommitDatabase.atomicBatch} for a `PostgresGuest` that is not wrapped
|
|
67
|
+
* as a `SqlDatabase`.
|
|
68
|
+
*/
|
|
69
|
+
export declare function atomicBatch(binding: PostgresGuest, statements: readonly AtomicStatement[]): Promise<void>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { assertBatch, postgresError, readRows } from '@di-framework/repo/postgres';
|
|
2
|
+
import { renderAtomicBatch } from './atomic-batch.js';
|
|
3
|
+
import { bindParams } from './bind-params.js';
|
|
4
|
+
/**
|
|
5
|
+
* Opens `binding` as an {@link AutocommitDatabase}.
|
|
6
|
+
*
|
|
7
|
+
* `wasmcloud:postgres@0.2.0` has no connection or transaction handle: each `query` and
|
|
8
|
+
* `query-batch` call may run on any pooled connection of the host, so a `BEGIN` sent in one call
|
|
9
|
+
* does not cover the next. The guest therefore never opens a transaction. Every statement
|
|
10
|
+
* commits on its own, `transaction(fn)` runs `fn` on this same handle, and `FOR UPDATE` and
|
|
11
|
+
* advisory locks last only for their statement. Keep invariants in one statement (conditional
|
|
12
|
+
* `UPDATE … RETURNING`, data-modifying CTEs, `INSERT … SELECT … WHERE NOT EXISTS`). A
|
|
13
|
+
* multi-statement `exec` script still runs as one `query-batch`, which Postgres executes as one
|
|
14
|
+
* implicit transaction on one connection.
|
|
15
|
+
*
|
|
16
|
+
* `run`, `query`, and `first` call `query`. `exec` calls `queryBatch`. Failures keep a
|
|
17
|
+
* five-digit SQLSTATE on `error.code`.
|
|
18
|
+
*/
|
|
19
|
+
export function openPostgresDatabase(binding) {
|
|
20
|
+
const handle = {
|
|
21
|
+
async run(sql, params = []) {
|
|
22
|
+
const rows = await statements(binding, sql, params);
|
|
23
|
+
return { changes: rows.length };
|
|
24
|
+
},
|
|
25
|
+
async query(sql, params = []) {
|
|
26
|
+
return (await statements(binding, sql, params));
|
|
27
|
+
},
|
|
28
|
+
async first(sql, params = []) {
|
|
29
|
+
const rows = await statements(binding, sql, params);
|
|
30
|
+
return (rows[0] ?? null);
|
|
31
|
+
},
|
|
32
|
+
async exec(sql) {
|
|
33
|
+
await submitBatch(binding, sql);
|
|
34
|
+
},
|
|
35
|
+
transaction(fn) {
|
|
36
|
+
return fn(handle);
|
|
37
|
+
},
|
|
38
|
+
atomicBatch(statements) {
|
|
39
|
+
return submitAtomicBatch(binding, statements);
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
return handle;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* {@link AutocommitDatabase.atomicBatch} for a `PostgresGuest` that is not wrapped
|
|
46
|
+
* as a `SqlDatabase`.
|
|
47
|
+
*/
|
|
48
|
+
export async function atomicBatch(binding, statements) {
|
|
49
|
+
await submitAtomicBatch(binding, statements);
|
|
50
|
+
}
|
|
51
|
+
async function submitAtomicBatch(binding, statements) {
|
|
52
|
+
await submitBatch(binding, renderAtomicBatch(statements));
|
|
53
|
+
}
|
|
54
|
+
async function statements(binding, sql, params) {
|
|
55
|
+
const bound = bindParams(sql, params);
|
|
56
|
+
try {
|
|
57
|
+
return await readRows(await binding.query(bound.text, bound.params));
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
throw coded(error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function submitBatch(binding, sql) {
|
|
64
|
+
try {
|
|
65
|
+
assertBatch(await binding.queryBatch(sql));
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
throw coded(error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function coded(error) {
|
|
72
|
+
const wrapped = (error instanceof Error && error.message.startsWith('PostgreSQL') ? error : postgresError(error));
|
|
73
|
+
const code = /PostgreSQL (\d{5})/.exec(wrapped.message)?.[1];
|
|
74
|
+
if (code !== undefined)
|
|
75
|
+
wrapped.code = code;
|
|
76
|
+
return wrapped;
|
|
77
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One write statement inside {@link renderAtomicBatch}.
|
|
3
|
+
* `?` is a value placeholder. `queryBatch` takes no parameters, so each placeholder
|
|
4
|
+
* is rendered as a SQL literal before the batch is submitted.
|
|
5
|
+
*/
|
|
6
|
+
export type AtomicStatement = {
|
|
7
|
+
sql: string;
|
|
8
|
+
params?: readonly unknown[];
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Renders `statements` as one multi-statement simple query.
|
|
12
|
+
*
|
|
13
|
+
* There is no `BEGIN` or `COMMIT`. Postgres runs the string as one implicit
|
|
14
|
+
* transaction and rolls it back on error, returning the pooled client idle.
|
|
15
|
+
* An explicit `BEGIN` whose `COMMIT` is skipped would leave that client
|
|
16
|
+
* `idle in transaction (aborted)`.
|
|
17
|
+
*
|
|
18
|
+
* Literals use standard SQL quotes (`standard_conforming_strings`): a quote is
|
|
19
|
+
* doubled, and a backslash is not an escape. A negative number is parenthesized
|
|
20
|
+
* so `x-?` with `-1` becomes `x-(-1)` and cannot form a `--` comment. Strings
|
|
21
|
+
* that match a UUID are quoted too; the hex pattern cannot close the quote.
|
|
22
|
+
*/
|
|
23
|
+
export declare function renderAtomicBatch(statements: readonly AtomicStatement[]): string;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders `statements` as one multi-statement simple query.
|
|
3
|
+
*
|
|
4
|
+
* There is no `BEGIN` or `COMMIT`. Postgres runs the string as one implicit
|
|
5
|
+
* transaction and rolls it back on error, returning the pooled client idle.
|
|
6
|
+
* An explicit `BEGIN` whose `COMMIT` is skipped would leave that client
|
|
7
|
+
* `idle in transaction (aborted)`.
|
|
8
|
+
*
|
|
9
|
+
* Literals use standard SQL quotes (`standard_conforming_strings`): a quote is
|
|
10
|
+
* doubled, and a backslash is not an escape. A negative number is parenthesized
|
|
11
|
+
* so `x-?` with `-1` becomes `x-(-1)` and cannot form a `--` comment. Strings
|
|
12
|
+
* that match a UUID are quoted too; the hex pattern cannot close the quote.
|
|
13
|
+
*/
|
|
14
|
+
export function renderAtomicBatch(statements) {
|
|
15
|
+
if (statements.length === 0)
|
|
16
|
+
throw new Error('atomicBatch requires at least one statement');
|
|
17
|
+
const rendered = statements.map((statement) => renderStatement(statement.sql, statement.params ?? []));
|
|
18
|
+
// No BEGIN/COMMIT: a multi-statement simple query is one implicit transaction
|
|
19
|
+
// that Postgres rolls back on error without leaving the connection aborted.
|
|
20
|
+
return `${rendered.join(';\n')};`;
|
|
21
|
+
}
|
|
22
|
+
function renderStatement(sql, params) {
|
|
23
|
+
const body = sql
|
|
24
|
+
.trim()
|
|
25
|
+
.replace(/;+\s*$/u, '')
|
|
26
|
+
.trim();
|
|
27
|
+
if (body.length === 0)
|
|
28
|
+
throw new Error('atomicBatch statement is empty');
|
|
29
|
+
let index = 0;
|
|
30
|
+
const rendered = body.replaceAll('?', () => {
|
|
31
|
+
const value = params[index++];
|
|
32
|
+
return sqlLiteral(value);
|
|
33
|
+
});
|
|
34
|
+
if (index !== params.length) {
|
|
35
|
+
throw new Error(`SQL placeholder count ${index} does not match ${params.length} parameters`);
|
|
36
|
+
}
|
|
37
|
+
return rendered;
|
|
38
|
+
}
|
|
39
|
+
function sqlLiteral(value) {
|
|
40
|
+
if (value == null)
|
|
41
|
+
return 'NULL';
|
|
42
|
+
if (typeof value === 'boolean')
|
|
43
|
+
return value ? 'true' : 'false';
|
|
44
|
+
if (typeof value === 'string')
|
|
45
|
+
return quote(value);
|
|
46
|
+
if (typeof value === 'bigint')
|
|
47
|
+
return integerLiteral(value);
|
|
48
|
+
if (typeof value === 'number')
|
|
49
|
+
return numberLiteral(value);
|
|
50
|
+
if (value instanceof Date)
|
|
51
|
+
return timestampLiteral(value);
|
|
52
|
+
if (value instanceof Uint8Array)
|
|
53
|
+
return byteaLiteral(value);
|
|
54
|
+
if (typeof value === 'object')
|
|
55
|
+
return jsonLiteral(value);
|
|
56
|
+
return unrenderable();
|
|
57
|
+
}
|
|
58
|
+
function quote(value) {
|
|
59
|
+
if (value.includes('\0'))
|
|
60
|
+
throw new Error('PostgreSQL text parameter contains a null byte');
|
|
61
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
62
|
+
}
|
|
63
|
+
function integerLiteral(value) {
|
|
64
|
+
return value < 0n ? `(${value})` : `${value}`;
|
|
65
|
+
}
|
|
66
|
+
function numberLiteral(value) {
|
|
67
|
+
if (!Number.isFinite(value))
|
|
68
|
+
throw new Error('PostgreSQL parameter is not a finite number');
|
|
69
|
+
if (Number.isInteger(value)) {
|
|
70
|
+
if (!Number.isSafeInteger(value)) {
|
|
71
|
+
throw new Error('PostgreSQL int8 parameter is not an exact integer');
|
|
72
|
+
}
|
|
73
|
+
return integerLiteral(BigInt(value));
|
|
74
|
+
}
|
|
75
|
+
const text = String(value);
|
|
76
|
+
if (text.startsWith('-'))
|
|
77
|
+
return `(${text})`;
|
|
78
|
+
return text;
|
|
79
|
+
}
|
|
80
|
+
function timestampLiteral(value) {
|
|
81
|
+
if (Number.isNaN(value.getTime())) {
|
|
82
|
+
throw new Error('PostgreSQL timestamp-tz parameter has the wrong type');
|
|
83
|
+
}
|
|
84
|
+
return quote(value.toISOString());
|
|
85
|
+
}
|
|
86
|
+
function byteaLiteral(value) {
|
|
87
|
+
const hex = [...value].map((byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
88
|
+
return `decode('${hex}', 'hex')`;
|
|
89
|
+
}
|
|
90
|
+
function jsonLiteral(value) {
|
|
91
|
+
let json;
|
|
92
|
+
try {
|
|
93
|
+
json = JSON.stringify(value);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return unrenderable();
|
|
97
|
+
}
|
|
98
|
+
if (json === undefined)
|
|
99
|
+
return unrenderable();
|
|
100
|
+
return quote(json);
|
|
101
|
+
}
|
|
102
|
+
function unrenderable() {
|
|
103
|
+
throw new Error('PostgreSQL parameter cannot be rendered as a literal');
|
|
104
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type PgParameter } from '@di-framework/repo/postgres';
|
|
2
|
+
/**
|
|
3
|
+
* Rewrites `@di-framework/repo` `?` placeholders for the wasmCloud Postgres binding.
|
|
4
|
+
*
|
|
5
|
+
* The host binds every parameter in binary as the type Postgres infers for its slot, and the
|
|
6
|
+
* guest never sees that type. A UUID string may fill a `uuid` column or a `varchar` one (the
|
|
7
|
+
* Spring Authorization Server tables), and its 16 binary bytes are invalid UTF-8 in a
|
|
8
|
+
* `varchar`. An integer may fill an `int4` slot or an `int8` one (`LIMIT`), and the widths
|
|
9
|
+
* differ. So UUID strings go inline as untyped literals and safe integers as numeric
|
|
10
|
+
* constants, which Postgres resolves from context. Neither can carry SQL: the UUID pattern
|
|
11
|
+
* admits only hex digits and dashes, and a negative integer is parenthesized so it cannot
|
|
12
|
+
* form a `--` comment.
|
|
13
|
+
*/
|
|
14
|
+
export declare function bindParams(sql: string, params?: readonly unknown[]): {
|
|
15
|
+
text: string;
|
|
16
|
+
params: PgParameter[];
|
|
17
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { pgValue } from '@di-framework/repo/postgres';
|
|
2
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3
|
+
/**
|
|
4
|
+
* Rewrites `@di-framework/repo` `?` placeholders for the wasmCloud Postgres binding.
|
|
5
|
+
*
|
|
6
|
+
* The host binds every parameter in binary as the type Postgres infers for its slot, and the
|
|
7
|
+
* guest never sees that type. A UUID string may fill a `uuid` column or a `varchar` one (the
|
|
8
|
+
* Spring Authorization Server tables), and its 16 binary bytes are invalid UTF-8 in a
|
|
9
|
+
* `varchar`. An integer may fill an `int4` slot or an `int8` one (`LIMIT`), and the widths
|
|
10
|
+
* differ. So UUID strings go inline as untyped literals and safe integers as numeric
|
|
11
|
+
* constants, which Postgres resolves from context. Neither can carry SQL: the UUID pattern
|
|
12
|
+
* admits only hex digits and dashes, and a negative integer is parenthesized so it cannot
|
|
13
|
+
* form a `--` comment.
|
|
14
|
+
*/
|
|
15
|
+
export function bindParams(sql, params = []) {
|
|
16
|
+
if (params.length === 0)
|
|
17
|
+
return { text: sql, params: [] };
|
|
18
|
+
const bound = [];
|
|
19
|
+
let index = 0;
|
|
20
|
+
const text = sql.replaceAll('?', () => {
|
|
21
|
+
const value = params[index++];
|
|
22
|
+
const literal = inlineLiteral(value);
|
|
23
|
+
if (literal !== undefined)
|
|
24
|
+
return literal;
|
|
25
|
+
bound.push(pgValue(value));
|
|
26
|
+
return `$${bound.length}`;
|
|
27
|
+
});
|
|
28
|
+
if (index !== params.length) {
|
|
29
|
+
throw new Error(`SQL placeholder count ${index} does not match ${params.length} parameters`);
|
|
30
|
+
}
|
|
31
|
+
return { text, params: bound };
|
|
32
|
+
}
|
|
33
|
+
function inlineLiteral(value) {
|
|
34
|
+
if (typeof value === 'string')
|
|
35
|
+
return UUID.test(value) ? `'${value}'` : undefined;
|
|
36
|
+
const integer = typeof value === 'bigint'
|
|
37
|
+
? value
|
|
38
|
+
: Number.isSafeInteger(value)
|
|
39
|
+
? BigInt(value)
|
|
40
|
+
: undefined;
|
|
41
|
+
if (integer === undefined)
|
|
42
|
+
return undefined;
|
|
43
|
+
return integer < 0n ? `(${integer})` : `${integer}`;
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@di-framework/bindings",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.8",
|
|
4
4
|
"description": "Native platform WIT service bindings for DI Framework applications.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -36,12 +36,24 @@
|
|
|
36
36
|
"import": "./dist/index.js",
|
|
37
37
|
"default": "./dist/index.js"
|
|
38
38
|
},
|
|
39
|
+
"./postgres": {
|
|
40
|
+
"types": "./dist/postgres/index.d.ts",
|
|
41
|
+
"bun": "./dist/postgres/index.js",
|
|
42
|
+
"import": "./dist/postgres/index.js",
|
|
43
|
+
"default": "./dist/postgres/index.js"
|
|
44
|
+
},
|
|
39
45
|
"./catalog.json": "./catalog.json"
|
|
40
46
|
},
|
|
41
47
|
"peerDependencies": {
|
|
42
48
|
"@di-framework/core": "^5",
|
|
49
|
+
"@di-framework/repo": "^6.0.4",
|
|
43
50
|
"typescript": "^5"
|
|
44
51
|
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"@di-framework/repo": {
|
|
54
|
+
"optional": true
|
|
55
|
+
}
|
|
56
|
+
},
|
|
45
57
|
"files": [
|
|
46
58
|
"dist",
|
|
47
59
|
"catalog.json",
|
|
@@ -49,6 +61,7 @@
|
|
|
49
61
|
],
|
|
50
62
|
"devDependencies": {
|
|
51
63
|
"@di-framework/core": "^5",
|
|
64
|
+
"@di-framework/repo": "^6.0.4",
|
|
52
65
|
"@types/bun": "latest"
|
|
53
66
|
}
|
|
54
67
|
}
|