@telorun/sql 0.8.0 → 0.10.0
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 +2 -141
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -2
- package/dist/sql-command-controller.d.ts +2 -2
- package/dist/sql-command-controller.js +2 -1
- package/dist/sql-connection-base.d.ts +29 -0
- package/dist/{sql-connection-controller.js → sql-connection-base.js} +18 -36
- package/dist/sql-connection-ref.d.ts +11 -8
- package/dist/sql-connection-ref.js +13 -22
- package/dist/sql-connection.d.ts +45 -0
- package/dist/sql-connection.js +4 -0
- package/dist/sql-migrations-controller.d.ts +2 -2
- package/dist/sql-migrations-controller.js +6 -1
- package/dist/sql-query-controller.d.ts +2 -2
- package/dist/sql-query-controller.js +5 -3
- package/dist/sql-run.d.ts +2 -2
- package/dist/sql-selection-controller.d.ts +2 -2
- package/dist/sql-selection-controller.js +24 -28
- package/dist/sql-transaction-controller.d.ts +3 -3
- package/dist/sql-transaction-controller.js +1 -2
- package/package.json +2 -7
- package/src/index.ts +6 -6
- package/src/sql-command-controller.ts +5 -3
- package/src/{sql-connection-controller.ts → sql-connection-base.ts} +22 -55
- package/src/sql-connection-ref.ts +16 -35
- package/src/sql-connection.ts +71 -0
- package/src/sql-migrations-controller.ts +15 -3
- package/src/sql-query-controller.ts +14 -6
- package/src/sql-run.ts +2 -2
- package/src/sql-selection-controller.ts +30 -33
- package/src/sql-transaction-controller.ts +8 -5
- package/dist/sql-connection-controller.d.ts +0 -43
- package/dist/sqlite-driver-interface.d.ts +0 -14
- package/dist/sqlite-driver-interface.js +0 -1
- package/src/sqlite-driver-interface.ts +0 -15
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ResourceContext, ResourceInstance } from "@telorun/sdk";
|
|
2
|
-
import type {
|
|
2
|
+
import type { SqlConnection, SqlDialect } from "./sql-connection.js";
|
|
3
3
|
import { resolveSqlConnection } from "./sql-connection-ref.js";
|
|
4
4
|
import type { SqlResult } from "./sql-query-controller.js";
|
|
5
5
|
import type { SqlTransactionResource } from "./sql-transaction-controller.js";
|
|
@@ -59,7 +59,7 @@ interface OrderByItem {
|
|
|
59
59
|
|
|
60
60
|
interface SelectManifest {
|
|
61
61
|
metadata: { name: string; module: string };
|
|
62
|
-
connection?:
|
|
62
|
+
connection?: SqlConnection;
|
|
63
63
|
transaction?: SqlTransactionResource;
|
|
64
64
|
from: string;
|
|
65
65
|
columns?: ColumnDef[];
|
|
@@ -96,12 +96,14 @@ class SqlSelectionResource implements ResourceInstance {
|
|
|
96
96
|
const limit = m.limit != null ? ctx.expandValue(m.limit, expandCtx) : undefined;
|
|
97
97
|
const offset = m.offset != null ? ctx.expandValue(m.offset, expandCtx) : undefined;
|
|
98
98
|
|
|
99
|
-
const connection =
|
|
99
|
+
const connection =
|
|
100
|
+
resolveSqlConnection(m.connection, ctx, () => `Sql.Selection "${m.metadata.name}": 'connection'`) ??
|
|
101
|
+
m.transaction?.getConnection();
|
|
100
102
|
if (!connection) {
|
|
101
103
|
throw new Error("Sql.Selection: either 'connection' or 'transaction' must be set");
|
|
102
104
|
}
|
|
103
105
|
|
|
104
|
-
const { sql, params } = buildSelect(m, where, having, limit, offset, connection.
|
|
106
|
+
const { sql, params } = buildSelect(m, where, having, limit, offset, connection.dialect);
|
|
105
107
|
const result = await connection.execute<Record<string, unknown>>(sql, params, m.transaction);
|
|
106
108
|
return { rows: result.rows, rowCount: result.rows.length };
|
|
107
109
|
}
|
|
@@ -109,21 +111,20 @@ class SqlSelectionResource implements ResourceInstance {
|
|
|
109
111
|
|
|
110
112
|
// ── SQL building ──────────────────────────────────────────────────────────────
|
|
111
113
|
|
|
112
|
-
type Driver = "postgres" | "sqlite";
|
|
113
|
-
|
|
114
114
|
function buildSelect(
|
|
115
115
|
m: SelectManifest,
|
|
116
116
|
where: WhereNode[],
|
|
117
117
|
having: WhereNode[],
|
|
118
118
|
limit: unknown,
|
|
119
119
|
offset: unknown,
|
|
120
|
-
|
|
120
|
+
dialect: SqlDialect,
|
|
121
121
|
): { sql: string; params: unknown[] } {
|
|
122
122
|
const params: unknown[] = [];
|
|
123
123
|
const addParam = (value: unknown): string => {
|
|
124
124
|
params.push(value);
|
|
125
|
-
return `$${params.length}
|
|
125
|
+
return dialect.placeholderStyle === "numbered" ? `$${params.length}` : "?";
|
|
126
126
|
};
|
|
127
|
+
const quoteIdent = (name: string): string => dialect.quoteIdentifier(name);
|
|
127
128
|
|
|
128
129
|
const parts: string[] = [];
|
|
129
130
|
|
|
@@ -134,14 +135,14 @@ function buildSelect(
|
|
|
134
135
|
} else if (m.distinctOn && m.distinctOn.length > 0) {
|
|
135
136
|
selectClause += ` DISTINCT ON (${m.distinctOn.map(quoteIdent).join(", ")})`;
|
|
136
137
|
}
|
|
137
|
-
const colList = m.columns && m.columns.length > 0 ? buildColumns(m.columns) : "*";
|
|
138
|
+
const colList = m.columns && m.columns.length > 0 ? buildColumns(m.columns, dialect) : "*";
|
|
138
139
|
parts.push(`${selectClause} ${colList}`);
|
|
139
140
|
|
|
140
141
|
// FROM
|
|
141
142
|
parts.push(`FROM ${quoteIdent(m.from)}`);
|
|
142
143
|
|
|
143
144
|
// WHERE
|
|
144
|
-
const whereStr = buildClauses(where, "AND",
|
|
145
|
+
const whereStr = buildClauses(where, "AND", dialect, addParam);
|
|
145
146
|
if (whereStr) parts.push(`WHERE ${whereStr}`);
|
|
146
147
|
|
|
147
148
|
// GROUP BY
|
|
@@ -150,7 +151,7 @@ function buildSelect(
|
|
|
150
151
|
}
|
|
151
152
|
|
|
152
153
|
// HAVING
|
|
153
|
-
const havingStr = buildClauses(having, "AND",
|
|
154
|
+
const havingStr = buildClauses(having, "AND", dialect, addParam);
|
|
154
155
|
if (havingStr) parts.push(`HAVING ${havingStr}`);
|
|
155
156
|
|
|
156
157
|
// ORDER BY
|
|
@@ -168,7 +169,8 @@ function buildSelect(
|
|
|
168
169
|
return { sql: parts.join("\n"), params };
|
|
169
170
|
}
|
|
170
171
|
|
|
171
|
-
function buildColumns(columns: ColumnDef[]): string {
|
|
172
|
+
function buildColumns(columns: ColumnDef[], dialect: SqlDialect): string {
|
|
173
|
+
const quoteIdent = (name: string): string => dialect.quoteIdentifier(name);
|
|
172
174
|
return columns
|
|
173
175
|
.map((c) => {
|
|
174
176
|
if (typeof c === "string") return quoteIdent(c);
|
|
@@ -181,13 +183,13 @@ function buildColumns(columns: ColumnDef[]): string {
|
|
|
181
183
|
function buildClauses(
|
|
182
184
|
clauses: WhereNode[],
|
|
183
185
|
join: "AND" | "OR",
|
|
184
|
-
|
|
186
|
+
dialect: SqlDialect,
|
|
185
187
|
addParam: (v: unknown) => string,
|
|
186
188
|
): string | null {
|
|
187
189
|
const parts: string[] = [];
|
|
188
190
|
for (const clause of clauses) {
|
|
189
191
|
if (clause.when === false) continue;
|
|
190
|
-
const built = buildClause(clause,
|
|
192
|
+
const built = buildClause(clause, dialect, addParam);
|
|
191
193
|
if (built !== null) parts.push(built);
|
|
192
194
|
}
|
|
193
195
|
if (parts.length === 0) return null;
|
|
@@ -197,46 +199,45 @@ function buildClauses(
|
|
|
197
199
|
|
|
198
200
|
function buildClause(
|
|
199
201
|
node: WhereNode,
|
|
200
|
-
|
|
202
|
+
dialect: SqlDialect,
|
|
201
203
|
addParam: (v: unknown) => string,
|
|
202
204
|
): string | null {
|
|
203
205
|
if ("not" in node) {
|
|
204
|
-
const inner = buildClause(node.not,
|
|
206
|
+
const inner = buildClause(node.not, dialect, addParam);
|
|
205
207
|
return inner ? `NOT (${inner})` : null;
|
|
206
208
|
}
|
|
207
209
|
if ("or" in node) {
|
|
208
|
-
const inner = buildClauses(node.or, "OR",
|
|
210
|
+
const inner = buildClauses(node.or, "OR", dialect, addParam);
|
|
209
211
|
return inner ? `(${inner})` : null;
|
|
210
212
|
}
|
|
211
213
|
if ("and" in node) {
|
|
212
|
-
const inner = buildClauses(node.and, "AND",
|
|
214
|
+
const inner = buildClauses(node.and, "AND", dialect, addParam);
|
|
213
215
|
return inner ? `(${inner})` : null;
|
|
214
216
|
}
|
|
215
217
|
if ("sql" in node) {
|
|
216
218
|
return renumberFragment(node.sql, node.bindings ?? [], addParam);
|
|
217
219
|
}
|
|
218
220
|
if ("column" in node) {
|
|
219
|
-
return buildCondition(node,
|
|
221
|
+
return buildCondition(node, dialect, addParam);
|
|
220
222
|
}
|
|
221
223
|
return null;
|
|
222
224
|
}
|
|
223
225
|
|
|
224
|
-
function buildCondition(
|
|
225
|
-
|
|
226
|
+
function buildCondition(
|
|
227
|
+
c: Condition,
|
|
228
|
+
dialect: SqlDialect,
|
|
229
|
+
addParam: (v: unknown) => string,
|
|
230
|
+
): string {
|
|
231
|
+
const col = dialect.quoteIdentifier(c.column);
|
|
226
232
|
switch (c.op) {
|
|
227
233
|
case "is_null":
|
|
228
234
|
return `${col} IS NULL`;
|
|
229
235
|
case "is_not_null":
|
|
230
236
|
return `${col} IS NOT NULL`;
|
|
231
|
-
case "in":
|
|
232
|
-
|
|
233
|
-
return `${col} = ANY(${addParam(c.value)})`;
|
|
234
|
-
}
|
|
235
|
-
const placeholders = (c.value as unknown[]).map((v) => addParam(v)).join(", ");
|
|
236
|
-
return `${col} IN (${placeholders})`;
|
|
237
|
-
}
|
|
237
|
+
case "in":
|
|
238
|
+
return dialect.renderIn(col, c.value as unknown[], addParam);
|
|
238
239
|
default: {
|
|
239
|
-
const rhs = c.ref !== undefined ?
|
|
240
|
+
const rhs = c.ref !== undefined ? dialect.quoteIdentifier(c.ref) : addParam(c.value);
|
|
240
241
|
return `${col} ${opToSql(c.op)} ${rhs}`;
|
|
241
242
|
}
|
|
242
243
|
}
|
|
@@ -250,10 +251,6 @@ function renumberFragment(
|
|
|
250
251
|
return sql.replace(/\$(\d+)/g, (_, idx) => addParam(bindings[Number(idx) - 1]));
|
|
251
252
|
}
|
|
252
253
|
|
|
253
|
-
function quoteIdent(name: string): string {
|
|
254
|
-
return `"${name.replace(/"/g, '""')}"`;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
254
|
function opToSql(op: Op): string {
|
|
258
255
|
const map: Record<string, string> = {
|
|
259
256
|
eq: "=",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { Invocable, ResourceContext, ResourceInstance } from "@telorun/sdk";
|
|
2
|
-
import type {
|
|
2
|
+
import type { SqlConnection } from "./sql-connection.js";
|
|
3
3
|
import { resolveSqlConnection } from "./sql-connection-ref.js";
|
|
4
4
|
import { currentTxId } from "./transaction-store.js";
|
|
5
5
|
|
|
6
6
|
interface SqlTransactionManifest {
|
|
7
7
|
metadata: { name: string; module: string };
|
|
8
|
-
connection:
|
|
8
|
+
connection: SqlConnection;
|
|
9
9
|
steps: Invocable;
|
|
10
10
|
inputs?: Record<string, unknown>;
|
|
11
11
|
}
|
|
@@ -16,10 +16,13 @@ export class SqlTransactionResource implements ResourceInstance {
|
|
|
16
16
|
private readonly ctx: ResourceContext,
|
|
17
17
|
) {}
|
|
18
18
|
|
|
19
|
-
getConnection():
|
|
19
|
+
getConnection(): SqlConnection {
|
|
20
20
|
return (
|
|
21
|
-
resolveSqlConnection(
|
|
22
|
-
|
|
21
|
+
resolveSqlConnection(
|
|
22
|
+
this.manifest.connection,
|
|
23
|
+
this.ctx,
|
|
24
|
+
() => `Sql.Transaction "${this.manifest.metadata.name}": 'connection'`,
|
|
25
|
+
) ?? failMissingConnection(this.manifest.metadata.name)
|
|
23
26
|
);
|
|
24
27
|
}
|
|
25
28
|
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { ResourceInstance } from "@telorun/sdk";
|
|
2
|
-
import { Kysely, type QueryResult } from "kysely";
|
|
3
|
-
import type { SqlTransactionResource } from "./sql-transaction-controller.js";
|
|
4
|
-
import type { SqliteDb } from "./sqlite-driver-interface.js";
|
|
5
|
-
export type SqlDriver = "postgres" | "sqlite";
|
|
6
|
-
/** Native bind-placeholder syntax per driver: SQLite binds anonymous `?`,
|
|
7
|
-
* PostgreSQL binds numbered `$1`, `$2`, … */
|
|
8
|
-
export type PlaceholderStyle = "qmark" | "numbered";
|
|
9
|
-
/**
|
|
10
|
-
* Driver-agnostic SQL connection. The kysely instance (and, for SQLite, the
|
|
11
|
-
* underlying database handle used by `executeScript`) is built by the driver
|
|
12
|
-
* backend (`sql-postgres`, `sql-sqlite`) and handed in via
|
|
13
|
-
* {@link createSqlConnection}. Everything here — execution, transactions,
|
|
14
|
-
* placeholder style, row-count normalization — is transport-neutral.
|
|
15
|
-
*/
|
|
16
|
-
export declare class SqlConnectionResource implements ResourceInstance {
|
|
17
|
-
readonly driver: SqlDriver;
|
|
18
|
-
private readonly db;
|
|
19
|
-
private readonly sqlite?;
|
|
20
|
-
constructor(driver: SqlDriver, db: Kysely<any>, sqlite?: SqliteDb);
|
|
21
|
-
init(): Promise<void>;
|
|
22
|
-
teardown(): Promise<void>;
|
|
23
|
-
transaction<T>(cb: () => Promise<T>): Promise<T>;
|
|
24
|
-
execute<T>(sql: string, params?: unknown[], transaction?: SqlTransactionResource): Promise<QueryResult<T>>;
|
|
25
|
-
get placeholderStyle(): PlaceholderStyle;
|
|
26
|
-
/** Assemble SQL from literal fragments by interleaving driver-native
|
|
27
|
-
* placeholders, then bind `values` positionally. `fragments.length` must
|
|
28
|
-
* equal `values.length + 1`. */
|
|
29
|
-
executeTemplate<T>(fragments: string[], values: unknown[], transaction?: SqlTransactionResource): Promise<QueryResult<T>>;
|
|
30
|
-
private placeholder;
|
|
31
|
-
executeScript(sql: string): Promise<void>;
|
|
32
|
-
toRowCount(result: QueryResult<unknown>): number;
|
|
33
|
-
get kysely(): Kysely<any>;
|
|
34
|
-
snapshot(): Record<string, unknown>;
|
|
35
|
-
private resolveExecutor;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Build a connection from a driver-constructed kysely instance. Driver backends
|
|
39
|
-
* (`sql-postgres`, `sql-sqlite`) own dialect construction and call this; the
|
|
40
|
-
* `sqlite` handle is required only for SQLite (its `executeScript` runs through
|
|
41
|
-
* the native handle).
|
|
42
|
-
*/
|
|
43
|
-
export declare function createSqlConnection(driver: SqlDriver, db: Kysely<any>, sqlite?: SqliteDb): SqlConnectionResource;
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export interface SqliteStatement {
|
|
2
|
-
readonly reader: boolean;
|
|
3
|
-
all(params: ReadonlyArray<unknown>): unknown[];
|
|
4
|
-
run(params: ReadonlyArray<unknown>): {
|
|
5
|
-
changes: number | bigint;
|
|
6
|
-
lastInsertRowid: number | bigint;
|
|
7
|
-
};
|
|
8
|
-
iterate(params: ReadonlyArray<unknown>): IterableIterator<unknown>;
|
|
9
|
-
}
|
|
10
|
-
export interface SqliteDb {
|
|
11
|
-
prepare(sql: string): SqliteStatement;
|
|
12
|
-
exec(sql: string): void;
|
|
13
|
-
close(): void;
|
|
14
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export interface SqliteStatement {
|
|
2
|
-
readonly reader: boolean;
|
|
3
|
-
all(params: ReadonlyArray<unknown>): unknown[];
|
|
4
|
-
run(params: ReadonlyArray<unknown>): {
|
|
5
|
-
changes: number | bigint;
|
|
6
|
-
lastInsertRowid: number | bigint;
|
|
7
|
-
};
|
|
8
|
-
iterate(params: ReadonlyArray<unknown>): IterableIterator<unknown>;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface SqliteDb {
|
|
12
|
-
prepare(sql: string): SqliteStatement;
|
|
13
|
-
exec(sql: string): void;
|
|
14
|
-
close(): void;
|
|
15
|
-
}
|