@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
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Built to be language-agnostic and infinitely extensible.
|
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
25
|
# Reconcile your manifest into a running backend
|
|
26
|
-
$ telo ./examples/hello-api
|
|
26
|
+
$ telo ./examples/hello-api
|
|
27
27
|
|
|
28
28
|
{"level":30,"time":1771610393008,"pid":1310178,"hostname":"dev","msg":"Server listening at http://127.0.0.1:8844"}
|
|
29
29
|
```
|
|
@@ -44,146 +44,7 @@ $ telo ./examples/hello-api.yaml
|
|
|
44
44
|
|
|
45
45
|
## Example manifest
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
```yaml
|
|
50
|
-
kind: Telo.Application
|
|
51
|
-
metadata:
|
|
52
|
-
name: feedback
|
|
53
|
-
version: 1.0.0
|
|
54
|
-
description: |
|
|
55
|
-
A complete feedback collection REST API — no code, pure YAML.
|
|
56
|
-
Persists entries to SQLite and serves them over HTTP.
|
|
57
|
-
imports:
|
|
58
|
-
Http: std/http-server@0.12.0
|
|
59
|
-
Sql: std/sql@0.9.2
|
|
60
|
-
targets:
|
|
61
|
-
- !ref Migrations
|
|
62
|
-
- !ref Server
|
|
63
|
-
---
|
|
64
|
-
# SQLite database — swap driver/host/database for PostgreSQL with zero YAML changes
|
|
65
|
-
kind: Sql.Connection
|
|
66
|
-
metadata:
|
|
67
|
-
name: Db
|
|
68
|
-
driver: sqlite
|
|
69
|
-
file: ./tmp/feedback.db
|
|
70
|
-
---
|
|
71
|
-
# Migrations: applied automatically before the server starts
|
|
72
|
-
kind: Sql.Migrations
|
|
73
|
-
metadata:
|
|
74
|
-
name: Migrations
|
|
75
|
-
connection: !ref Db
|
|
76
|
-
---
|
|
77
|
-
kind: Sql.Migration
|
|
78
|
-
metadata:
|
|
79
|
-
name: Migration_20260413_182154_CreateFeedback
|
|
80
|
-
version: 20260413_182154_CreateFeedback
|
|
81
|
-
sql: |
|
|
82
|
-
CREATE TABLE IF NOT EXISTS feedback (
|
|
83
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
84
|
-
text TEXT NOT NULL,
|
|
85
|
-
source TEXT,
|
|
86
|
-
score INTEGER NOT NULL DEFAULT 0,
|
|
87
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
88
|
-
)
|
|
89
|
-
---
|
|
90
|
-
kind: Http.Server
|
|
91
|
-
metadata:
|
|
92
|
-
name: Server
|
|
93
|
-
baseUrl: http://localhost:8844
|
|
94
|
-
port: 8844
|
|
95
|
-
logger: true
|
|
96
|
-
openapi:
|
|
97
|
-
info:
|
|
98
|
-
title: Feedback API
|
|
99
|
-
version: 1.0.0
|
|
100
|
-
mounts:
|
|
101
|
-
- path: /v1
|
|
102
|
-
mount: !ref FeedbackRoutes
|
|
103
|
-
---
|
|
104
|
-
kind: Http.Api
|
|
105
|
-
metadata:
|
|
106
|
-
name: FeedbackRoutes
|
|
107
|
-
routes:
|
|
108
|
-
# POST /v1/feedback — insert a new entry, score derived from body length heuristic
|
|
109
|
-
- request:
|
|
110
|
-
path: /feedback
|
|
111
|
-
method: POST
|
|
112
|
-
schema:
|
|
113
|
-
body:
|
|
114
|
-
type: object
|
|
115
|
-
properties:
|
|
116
|
-
text:
|
|
117
|
-
type: string
|
|
118
|
-
minLength: 1
|
|
119
|
-
source:
|
|
120
|
-
type: string
|
|
121
|
-
required: [ text ]
|
|
122
|
-
handler:
|
|
123
|
-
kind: Sql.Exec
|
|
124
|
-
connection: !ref Db
|
|
125
|
-
inputs:
|
|
126
|
-
sql: "INSERT INTO feedback (text, source, score) VALUES (?, ?, ?)"
|
|
127
|
-
bindings:
|
|
128
|
-
- "${{ request.body.text }}"
|
|
129
|
-
- "${{ request.body.source }}"
|
|
130
|
-
- "${{ size(request.body.text) }}"
|
|
131
|
-
response:
|
|
132
|
-
- status: 201
|
|
133
|
-
headers:
|
|
134
|
-
Content-Type: application/json
|
|
135
|
-
body:
|
|
136
|
-
ok: true
|
|
137
|
-
message: Feedback received
|
|
138
|
-
|
|
139
|
-
# GET /v1/feedback — list all entries, newest first
|
|
140
|
-
- request:
|
|
141
|
-
path: /feedback
|
|
142
|
-
method: GET
|
|
143
|
-
handler:
|
|
144
|
-
kind: Sql.Select
|
|
145
|
-
connection: !ref Db
|
|
146
|
-
from: feedback
|
|
147
|
-
columns: [ id, text, source, score, created_at ]
|
|
148
|
-
orderBy:
|
|
149
|
-
- { column: created_at, direction: desc }
|
|
150
|
-
response:
|
|
151
|
-
- status: 200
|
|
152
|
-
headers:
|
|
153
|
-
Content-Type: application/json
|
|
154
|
-
body: "${{ result.rows }}"
|
|
155
|
-
|
|
156
|
-
# GET /v1/feedback/{id} — fetch a single entry
|
|
157
|
-
- request:
|
|
158
|
-
path: /feedback/{id}
|
|
159
|
-
method: GET
|
|
160
|
-
schema:
|
|
161
|
-
params:
|
|
162
|
-
type: object
|
|
163
|
-
properties:
|
|
164
|
-
id:
|
|
165
|
-
type: integer
|
|
166
|
-
required: [ id ]
|
|
167
|
-
handler:
|
|
168
|
-
kind: Sql.Select
|
|
169
|
-
connection: !ref Db
|
|
170
|
-
from: feedback
|
|
171
|
-
columns: [ id, text, source, score, created_at ]
|
|
172
|
-
where:
|
|
173
|
-
- { column: id, op: "=", value: "${{ request.params.id }}" }
|
|
174
|
-
response:
|
|
175
|
-
- status: 200
|
|
176
|
-
when: "size(result.rows) > 0"
|
|
177
|
-
headers:
|
|
178
|
-
Content-Type: application/json
|
|
179
|
-
body: "${{ result.rows[0] }}"
|
|
180
|
-
- status: 404
|
|
181
|
-
headers:
|
|
182
|
-
Content-Type: application/json
|
|
183
|
-
body:
|
|
184
|
-
ok: false
|
|
185
|
-
message: Not found
|
|
186
|
-
```
|
|
47
|
+
See [examples/](./examples/) for a list of working applications.
|
|
187
48
|
|
|
188
49
|
## Status
|
|
189
50
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export
|
|
1
|
+
export { SqlConnectionBase } from "./sql-connection-base.js";
|
|
2
|
+
export { quoteAnsiIdentifier, type PlaceholderStyle, type SqlConnection, type SqlDialect, } from "./sql-connection.js";
|
|
3
|
+
export { isSqlConnection, resolveSqlConnection } from "./sql-connection-ref.js";
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
1
|
+
export { SqlConnectionBase } from "./sql-connection-base.js";
|
|
2
|
+
export { quoteAnsiIdentifier, } from "./sql-connection.js";
|
|
3
|
+
export { isSqlConnection, resolveSqlConnection } from "./sql-connection-ref.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ResourceContext, ResourceInstance } from "@telorun/sdk";
|
|
2
|
-
import type {
|
|
2
|
+
import type { SqlConnection } from "./sql-connection.js";
|
|
3
3
|
import type { SqlResult } from "./sql-query-controller.js";
|
|
4
4
|
import type { SqlTransactionResource } from "./sql-transaction-controller.js";
|
|
5
5
|
interface SqlCommandManifest {
|
|
@@ -7,7 +7,7 @@ interface SqlCommandManifest {
|
|
|
7
7
|
name: string;
|
|
8
8
|
module: string;
|
|
9
9
|
};
|
|
10
|
-
connection?:
|
|
10
|
+
connection?: SqlConnection;
|
|
11
11
|
transaction?: SqlTransactionResource;
|
|
12
12
|
inputs: {
|
|
13
13
|
sql: string;
|
|
@@ -10,7 +10,8 @@ class SqlCommandResource {
|
|
|
10
10
|
async invoke(input) {
|
|
11
11
|
const m = this.manifest;
|
|
12
12
|
const ctx = this.ctx;
|
|
13
|
-
const connection = resolveSqlConnection(m.connection, ctx)
|
|
13
|
+
const connection = resolveSqlConnection(m.connection, ctx, () => `Sql.Command "${m.metadata.name}": 'connection'`) ??
|
|
14
|
+
m.transaction?.getConnection();
|
|
14
15
|
if (!connection) {
|
|
15
16
|
throw new Error("Sql: either 'connection' or 'transaction' must be set");
|
|
16
17
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Kysely, type QueryResult } from "kysely";
|
|
2
|
+
import type { SqlConnection, SqlDialect } from "./sql-connection.js";
|
|
3
|
+
import type { SqlTransactionResource } from "./sql-transaction-controller.js";
|
|
4
|
+
/**
|
|
5
|
+
* The dialect-neutral half of a connection: statement execution, transaction
|
|
6
|
+
* scoping, template binding and row-count normalization over a kysely instance.
|
|
7
|
+
*
|
|
8
|
+
* Backends extend it, supply their {@link SqlDialect}, and override only what is
|
|
9
|
+
* genuinely theirs — `teardown` for resources kysely does not own, `executeScript`
|
|
10
|
+
* where the driver has a native multi-statement path.
|
|
11
|
+
*/
|
|
12
|
+
export declare abstract class SqlConnectionBase implements SqlConnection {
|
|
13
|
+
readonly dialect: SqlDialect;
|
|
14
|
+
protected readonly db: Kysely<any>;
|
|
15
|
+
constructor(db: Kysely<any>, dialect: SqlDialect);
|
|
16
|
+
get kysely(): Kysely<any>;
|
|
17
|
+
init(): Promise<void>;
|
|
18
|
+
teardown(): Promise<void>;
|
|
19
|
+
transaction<T>(cb: () => Promise<T>): Promise<T>;
|
|
20
|
+
execute<T>(sql: string, params?: unknown[], transaction?: SqlTransactionResource): Promise<QueryResult<T>>;
|
|
21
|
+
executeTemplate<T>(fragments: string[], values: unknown[], transaction?: SqlTransactionResource): Promise<QueryResult<T>>;
|
|
22
|
+
/** Hand the whole script to the driver as one statement. Backends whose driver
|
|
23
|
+
* needs a dedicated multi-statement entry point override this. */
|
|
24
|
+
executeScript(sql: string): Promise<void>;
|
|
25
|
+
toRowCount(result: QueryResult<unknown>): number;
|
|
26
|
+
snapshot(): Record<string, unknown>;
|
|
27
|
+
private placeholder;
|
|
28
|
+
private resolveExecutor;
|
|
29
|
+
}
|
|
@@ -2,20 +2,22 @@ import { randomUUID } from "crypto";
|
|
|
2
2
|
import { CompiledQuery } from "kysely";
|
|
3
3
|
import { currentTxId, deleteTx, getTx, setTx, txStorage } from "./transaction-store.js";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* {@link
|
|
9
|
-
*
|
|
5
|
+
* The dialect-neutral half of a connection: statement execution, transaction
|
|
6
|
+
* scoping, template binding and row-count normalization over a kysely instance.
|
|
7
|
+
*
|
|
8
|
+
* Backends extend it, supply their {@link SqlDialect}, and override only what is
|
|
9
|
+
* genuinely theirs — `teardown` for resources kysely does not own, `executeScript`
|
|
10
|
+
* where the driver has a native multi-statement path.
|
|
10
11
|
*/
|
|
11
|
-
export class
|
|
12
|
-
|
|
12
|
+
export class SqlConnectionBase {
|
|
13
|
+
dialect;
|
|
13
14
|
db;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
this.driver = driver;
|
|
15
|
+
constructor(db, dialect) {
|
|
16
|
+
this.dialect = dialect;
|
|
17
17
|
this.db = db;
|
|
18
|
-
|
|
18
|
+
}
|
|
19
|
+
get kysely() {
|
|
20
|
+
return this.db;
|
|
19
21
|
}
|
|
20
22
|
async init() {
|
|
21
23
|
await this.db.connection().execute(async () => {
|
|
@@ -41,12 +43,6 @@ export class SqlConnectionResource {
|
|
|
41
43
|
const executor = this.resolveExecutor(transaction);
|
|
42
44
|
return executor.executeQuery(CompiledQuery.raw(sql, params));
|
|
43
45
|
}
|
|
44
|
-
get placeholderStyle() {
|
|
45
|
-
return this.driver === "postgres" ? "numbered" : "qmark";
|
|
46
|
-
}
|
|
47
|
-
/** Assemble SQL from literal fragments by interleaving driver-native
|
|
48
|
-
* placeholders, then bind `values` positionally. `fragments.length` must
|
|
49
|
-
* equal `values.length + 1`. */
|
|
50
46
|
async executeTemplate(fragments, values, transaction) {
|
|
51
47
|
let sql = fragments[0] ?? "";
|
|
52
48
|
for (let i = 1; i < fragments.length; i++) {
|
|
@@ -54,14 +50,9 @@ export class SqlConnectionResource {
|
|
|
54
50
|
}
|
|
55
51
|
return this.execute(sql, values, transaction);
|
|
56
52
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
53
|
+
/** Hand the whole script to the driver as one statement. Backends whose driver
|
|
54
|
+
* needs a dedicated multi-statement entry point override this. */
|
|
60
55
|
async executeScript(sql) {
|
|
61
|
-
if (this.driver === "sqlite") {
|
|
62
|
-
this.sqlite?.exec(sql);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
56
|
await this.execute(sql);
|
|
66
57
|
}
|
|
67
58
|
toRowCount(result) {
|
|
@@ -70,12 +61,12 @@ export class SqlConnectionResource {
|
|
|
70
61
|
}
|
|
71
62
|
return result.rows.length;
|
|
72
63
|
}
|
|
73
|
-
get kysely() {
|
|
74
|
-
return this.db;
|
|
75
|
-
}
|
|
76
64
|
snapshot() {
|
|
77
65
|
return {};
|
|
78
66
|
}
|
|
67
|
+
placeholder(index) {
|
|
68
|
+
return this.dialect.placeholderStyle === "numbered" ? `$${index}` : "?";
|
|
69
|
+
}
|
|
79
70
|
resolveExecutor(transaction) {
|
|
80
71
|
if (transaction) {
|
|
81
72
|
transaction.assertActive();
|
|
@@ -90,12 +81,3 @@ export class SqlConnectionResource {
|
|
|
90
81
|
return this.db;
|
|
91
82
|
}
|
|
92
83
|
}
|
|
93
|
-
/**
|
|
94
|
-
* Build a connection from a driver-constructed kysely instance. Driver backends
|
|
95
|
-
* (`sql-postgres`, `sql-sqlite`) own dialect construction and call this; the
|
|
96
|
-
* `sqlite` handle is required only for SQLite (its `executeScript` runs through
|
|
97
|
-
* the native handle).
|
|
98
|
-
*/
|
|
99
|
-
export function createSqlConnection(driver, db, sqlite) {
|
|
100
|
-
return new SqlConnectionResource(driver, db, sqlite);
|
|
101
|
-
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import type { ResourceContext } from "@telorun/sdk";
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import type { KindRef, ResourceContext } from "@telorun/sdk";
|
|
2
|
+
import type { SqlConnection } from "./sql-connection.js";
|
|
3
|
+
/** True when a value already exposes the connection contract (Phase-5 injected). */
|
|
4
|
+
export declare function isSqlConnection(value: unknown): value is SqlConnection;
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a `connection` `!ref` field to a live connection. The slot is optional
|
|
7
|
+
* — an unset one yields `undefined` so the caller can fall back to a `transaction`
|
|
8
|
+
* — but a slot that IS set must resolve. `describe` names the owning resource and
|
|
9
|
+
* slot, so the failure points at a concrete manifest location.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveSqlConnection(value: SqlConnection | KindRef<SqlConnection> | undefined, ctx: ResourceContext, describe: () => string): SqlConnection | undefined;
|
|
@@ -1,24 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/** True when a value already exposes the connection contract (Phase-5 injected). */
|
|
2
|
+
export function isSqlConnection(value) {
|
|
3
|
+
return typeof value?.execute === "function";
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a `connection` `!ref` field to a live connection. The slot is optional
|
|
7
|
+
* — an unset one yields `undefined` so the caller can fall back to a `transaction`
|
|
8
|
+
* — but a slot that IS set must resolve. `describe` names the owning resource and
|
|
9
|
+
* slot, so the failure points at a concrete manifest location.
|
|
10
|
+
*/
|
|
11
|
+
export function resolveSqlConnection(value, ctx, describe) {
|
|
12
|
+
if (!value)
|
|
3
13
|
return undefined;
|
|
4
|
-
|
|
5
|
-
if (typeof value.execute === "function") {
|
|
6
|
-
return value;
|
|
7
|
-
}
|
|
8
|
-
const ref = value;
|
|
9
|
-
if (typeof ref.name !== "string") {
|
|
10
|
-
throw new Error("Sql: invalid connection reference");
|
|
11
|
-
}
|
|
12
|
-
// Cross-module reference (`!ref Alias.Connection`): a connection resolved
|
|
13
|
-
// inside a nested library is not Phase-5-injected, so the controller receives
|
|
14
|
-
// the raw `{name, alias}` ref and must route through the import's exported
|
|
15
|
-
// scope rather than a bare local lookup.
|
|
16
|
-
if (ref.alias && ref.alias !== "Self") {
|
|
17
|
-
const instance = ctx.moduleContext.resolveImportedInstance(ref.alias, ref.name);
|
|
18
|
-
if (typeof instance?.execute !== "function") {
|
|
19
|
-
throw new Error(`Sql: connection reference '${ref.alias}.${ref.name}' did not resolve to an exported connection instance.`);
|
|
20
|
-
}
|
|
21
|
-
return instance;
|
|
22
|
-
}
|
|
23
|
-
return ctx.moduleContext.getInstance(ref.name);
|
|
14
|
+
return ctx.resolveRef(value, isSqlConnection, describe, "std/sql#Connection");
|
|
24
15
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ResourceInstance } from "@telorun/sdk";
|
|
2
|
+
import type { Kysely, QueryResult } from "kysely";
|
|
3
|
+
import type { SqlTransactionResource } from "./sql-transaction-controller.js";
|
|
4
|
+
/** Native bind-placeholder syntax: SQLite binds anonymous `?`, PostgreSQL binds
|
|
5
|
+
* numbered `$1`, `$2`, … */
|
|
6
|
+
export type PlaceholderStyle = "qmark" | "numbered";
|
|
7
|
+
/**
|
|
8
|
+
* Renders the SQL constructs that differ between database dialects. A backend
|
|
9
|
+
* supplies one; the `Sql.*` operations build statements through it and never
|
|
10
|
+
* branch on which database sits behind the connection.
|
|
11
|
+
*/
|
|
12
|
+
export interface SqlDialect {
|
|
13
|
+
readonly placeholderStyle: PlaceholderStyle;
|
|
14
|
+
/** Quote an identifier — table, column, alias. */
|
|
15
|
+
quoteIdentifier(name: string): string;
|
|
16
|
+
/** Render set membership. Dialects disagree: PostgreSQL binds the whole array
|
|
17
|
+
* to a single placeholder, SQLite binds one per element. `column` arrives
|
|
18
|
+
* already quoted. */
|
|
19
|
+
renderIn(column: string, values: unknown[], addParam: (value: unknown) => string): string;
|
|
20
|
+
}
|
|
21
|
+
/** ANSI identifier quoting (`"name"`), for the dialects that follow the standard. */
|
|
22
|
+
export declare function quoteAnsiIdentifier(name: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* The contract every SQL backend satisfies and every `Sql.*` operation programs
|
|
25
|
+
* against. Backends (`sql-postgres`, `sql-sqlite`) own their own implementation
|
|
26
|
+
* — usually by extending {@link SqlConnectionBase} — so nothing in this module
|
|
27
|
+
* knows which databases exist.
|
|
28
|
+
*/
|
|
29
|
+
export interface SqlConnection extends ResourceInstance {
|
|
30
|
+
readonly dialect: SqlDialect;
|
|
31
|
+
/** The underlying kysely instance, when the backend is built on one —
|
|
32
|
+
* {@link SqlConnectionBase} always provides it. Optional because the contract
|
|
33
|
+
* must stay implementable by a driver kysely does not support; a consumer
|
|
34
|
+
* that needs it (`Sql.Migrations`) checks and fails with a clear message. */
|
|
35
|
+
readonly kysely?: Kysely<any>;
|
|
36
|
+
execute<T>(sql: string, params?: unknown[], transaction?: SqlTransactionResource): Promise<QueryResult<T>>;
|
|
37
|
+
/** Assemble SQL from literal fragments by interleaving dialect-native
|
|
38
|
+
* placeholders, then bind `values` positionally. */
|
|
39
|
+
executeTemplate<T>(fragments: string[], values: unknown[], transaction?: SqlTransactionResource): Promise<QueryResult<T>>;
|
|
40
|
+
/** Run a multi-statement script. */
|
|
41
|
+
executeScript(sql: string): Promise<void>;
|
|
42
|
+
transaction<T>(cb: () => Promise<T>): Promise<T>;
|
|
43
|
+
/** Rows affected by a write, normalized across drivers. */
|
|
44
|
+
toRowCount(result: QueryResult<unknown>): number;
|
|
45
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ResourceContext, ResourceInstance } from "@telorun/sdk";
|
|
2
|
-
import type {
|
|
2
|
+
import type { SqlConnection } from "./sql-connection.js";
|
|
3
3
|
interface MigrationEntry {
|
|
4
4
|
statement?: string;
|
|
5
5
|
statements?: string[];
|
|
@@ -9,7 +9,7 @@ interface SqlMigrationsManifest {
|
|
|
9
9
|
name: string;
|
|
10
10
|
module: string;
|
|
11
11
|
};
|
|
12
|
-
connection:
|
|
12
|
+
connection: SqlConnection;
|
|
13
13
|
migrations?: Record<string, MigrationEntry>;
|
|
14
14
|
}
|
|
15
15
|
declare class SqlMigrationsResource implements ResourceInstance {
|
|
@@ -32,7 +32,7 @@ class SqlMigrationsResource {
|
|
|
32
32
|
this.ctx = ctx;
|
|
33
33
|
}
|
|
34
34
|
async run() {
|
|
35
|
-
const conn = resolveSqlConnection(this.manifest.connection, this.ctx) ?? failMissingConnection();
|
|
35
|
+
const conn = resolveSqlConnection(this.manifest.connection, this.ctx, () => `Sql.Migrations "${this.manifest.metadata.name}": 'connection'`) ?? failMissingConnection();
|
|
36
36
|
const migrations = {};
|
|
37
37
|
// Legacy: standalone `Sql.Migration` resources in the same module scope.
|
|
38
38
|
for (const [, { resource }] of this.ctx.moduleContext.resourceInstances) {
|
|
@@ -50,6 +50,11 @@ class SqlMigrationsResource {
|
|
|
50
50
|
}
|
|
51
51
|
migrations[name] = statements;
|
|
52
52
|
}
|
|
53
|
+
if (!conn.kysely) {
|
|
54
|
+
throw new Error(`Sql.Migrations '${this.manifest.metadata.name}': the referenced connection is not ` +
|
|
55
|
+
`built on kysely, which this kind's migration runner requires. Use a backend that ` +
|
|
56
|
+
`extends SqlConnectionBase, or run the statements through Sql.Command.`);
|
|
57
|
+
}
|
|
53
58
|
const migrator = new Migrator({
|
|
54
59
|
db: conn.kysely,
|
|
55
60
|
provider: new TeloMigrationProvider(migrations),
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { ResourceContext, ResourceInstance } from "@telorun/sdk";
|
|
2
|
-
import type {
|
|
2
|
+
import type { SqlConnection } from "./sql-connection.js";
|
|
3
3
|
import type { SqlTransactionResource } from "./sql-transaction-controller.js";
|
|
4
4
|
interface SqlQueryManifest {
|
|
5
5
|
metadata: {
|
|
6
6
|
name: string;
|
|
7
7
|
module: string;
|
|
8
8
|
};
|
|
9
|
-
connection?:
|
|
9
|
+
connection?: SqlConnection;
|
|
10
10
|
transaction?: SqlTransactionResource;
|
|
11
11
|
inputs: {
|
|
12
12
|
sql: string;
|
|
@@ -10,13 +10,15 @@ class SqlQueryResource {
|
|
|
10
10
|
async invoke(input) {
|
|
11
11
|
const m = this.manifest;
|
|
12
12
|
const ctx = this.ctx;
|
|
13
|
-
const connection = resolveConnection(m.connection, m.transaction, ctx);
|
|
13
|
+
const connection = resolveConnection(m.connection, m.transaction, ctx, () => `Sql.Query "${m.metadata.name}": 'connection'`);
|
|
14
14
|
const result = await runSql(connection, m.transaction, input, ctx);
|
|
15
15
|
return { rows: result.rows, rowCount: result.rows.length };
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
function resolveConnection(connection, transaction, ctx) {
|
|
19
|
-
return (resolveSqlConnection(connection, ctx
|
|
18
|
+
function resolveConnection(connection, transaction, ctx, describe) {
|
|
19
|
+
return (resolveSqlConnection(connection, ctx, describe) ??
|
|
20
|
+
transaction?.getConnection() ??
|
|
21
|
+
failMissingConnection());
|
|
20
22
|
}
|
|
21
23
|
function failMissingConnection() {
|
|
22
24
|
throw new Error("Sql: either 'connection' or 'transaction' must be set");
|
package/dist/sql-run.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ResourceContext } from "@telorun/sdk";
|
|
2
2
|
import type { QueryResult } from "kysely";
|
|
3
|
-
import type {
|
|
3
|
+
import type { SqlConnection } from "./sql-connection.js";
|
|
4
4
|
import type { SqlTransactionResource } from "./sql-transaction-controller.js";
|
|
5
5
|
/** Execute the `sql` input of a Query/Exec resource against `connection`.
|
|
6
6
|
*
|
|
@@ -13,4 +13,4 @@ import type { SqlTransactionResource } from "./sql-transaction-controller.js";
|
|
|
13
13
|
*
|
|
14
14
|
* Mixing a `!sql` template with `bindings` is rejected. A plain string `sql`
|
|
15
15
|
* with neither is executed verbatim. */
|
|
16
|
-
export declare function runSql(connection:
|
|
16
|
+
export declare function runSql(connection: SqlConnection, transaction: SqlTransactionResource | undefined, input: unknown, ctx: ResourceContext): Promise<QueryResult<Record<string, unknown>>>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ResourceContext, ResourceInstance } from "@telorun/sdk";
|
|
2
|
-
import type {
|
|
2
|
+
import type { SqlConnection } from "./sql-connection.js";
|
|
3
3
|
import type { SqlResult } from "./sql-query-controller.js";
|
|
4
4
|
import type { SqlTransactionResource } from "./sql-transaction-controller.js";
|
|
5
5
|
type ColumnDef = string | {
|
|
@@ -44,7 +44,7 @@ interface SelectManifest {
|
|
|
44
44
|
name: string;
|
|
45
45
|
module: string;
|
|
46
46
|
};
|
|
47
|
-
connection?:
|
|
47
|
+
connection?: SqlConnection;
|
|
48
48
|
transaction?: SqlTransactionResource;
|
|
49
49
|
from: string;
|
|
50
50
|
columns?: ColumnDef[];
|