@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.
Files changed (35) hide show
  1. package/README.md +2 -141
  2. package/dist/index.d.ts +3 -3
  3. package/dist/index.js +3 -2
  4. package/dist/sql-command-controller.d.ts +2 -2
  5. package/dist/sql-command-controller.js +2 -1
  6. package/dist/sql-connection-base.d.ts +29 -0
  7. package/dist/{sql-connection-controller.js → sql-connection-base.js} +18 -36
  8. package/dist/sql-connection-ref.d.ts +11 -8
  9. package/dist/sql-connection-ref.js +13 -22
  10. package/dist/sql-connection.d.ts +45 -0
  11. package/dist/sql-connection.js +4 -0
  12. package/dist/sql-migrations-controller.d.ts +2 -2
  13. package/dist/sql-migrations-controller.js +6 -1
  14. package/dist/sql-query-controller.d.ts +2 -2
  15. package/dist/sql-query-controller.js +5 -3
  16. package/dist/sql-run.d.ts +2 -2
  17. package/dist/sql-selection-controller.d.ts +2 -2
  18. package/dist/sql-selection-controller.js +24 -28
  19. package/dist/sql-transaction-controller.d.ts +3 -3
  20. package/dist/sql-transaction-controller.js +1 -2
  21. package/package.json +2 -7
  22. package/src/index.ts +6 -6
  23. package/src/sql-command-controller.ts +5 -3
  24. package/src/{sql-connection-controller.ts → sql-connection-base.ts} +22 -55
  25. package/src/sql-connection-ref.ts +16 -35
  26. package/src/sql-connection.ts +71 -0
  27. package/src/sql-migrations-controller.ts +15 -3
  28. package/src/sql-query-controller.ts +14 -6
  29. package/src/sql-run.ts +2 -2
  30. package/src/sql-selection-controller.ts +30 -33
  31. package/src/sql-transaction-controller.ts +8 -5
  32. package/dist/sql-connection-controller.d.ts +0 -43
  33. package/dist/sqlite-driver-interface.d.ts +0 -14
  34. package/dist/sqlite-driver-interface.js +0 -1
  35. package/src/sqlite-driver-interface.ts +0 -15
@@ -1,5 +1,5 @@
1
1
  import type { ResourceContext, ResourceInstance } from "@telorun/sdk";
2
- import type { SqlConnectionResource } from "./sql-connection-controller.js";
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?: SqlConnectionResource;
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 = resolveSqlConnection(m.connection, ctx) ?? m.transaction?.getConnection();
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.driver);
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
- driver: Driver,
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", driver, addParam);
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", driver, addParam);
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
- driver: Driver,
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, driver, addParam);
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
- driver: Driver,
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, driver, addParam);
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", driver, addParam);
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", driver, addParam);
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, driver, addParam);
221
+ return buildCondition(node, dialect, addParam);
220
222
  }
221
223
  return null;
222
224
  }
223
225
 
224
- function buildCondition(c: Condition, driver: Driver, addParam: (v: unknown) => string): string {
225
- const col = quoteIdent(c.column);
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
- if (driver === "postgres") {
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 ? quoteIdent(c.ref) : addParam(c.value);
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 { SqlConnectionResource } from "./sql-connection-controller.js";
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: SqlConnectionResource;
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(): SqlConnectionResource {
19
+ getConnection(): SqlConnection {
20
20
  return (
21
- resolveSqlConnection(this.manifest.connection, this.ctx) ??
22
- failMissingConnection(this.manifest.metadata.name)
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
- }