@telorun/sql 0.8.0 → 0.9.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 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.yaml
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
- Here is an example Telo application that defines a simple HTTP API:
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
1
  export { SqlConnectionResource, createSqlConnection, type SqlDriver, type PlaceholderStyle, } from "./sql-connection-controller.js";
2
- export { resolveSqlConnection } from "./sql-connection-ref.js";
2
+ export { isSqlConnection, resolveSqlConnection } from "./sql-connection-ref.js";
3
3
  export type { SqliteDb, SqliteStatement } from "./sqlite-driver-interface.js";
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  export { SqlConnectionResource, createSqlConnection, } from "./sql-connection-controller.js";
2
- export { resolveSqlConnection } from "./sql-connection-ref.js";
2
+ export { isSqlConnection, resolveSqlConnection } from "./sql-connection-ref.js";
@@ -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) ?? m.transaction?.getConnection();
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
  }
@@ -1,8 +1,11 @@
1
- import type { ResourceContext } from "@telorun/sdk";
1
+ import type { KindRef, ResourceContext } from "@telorun/sdk";
2
2
  import type { SqlConnectionResource } from "./sql-connection-controller.js";
3
- interface ConnectionRef {
4
- name: string;
5
- alias?: string;
6
- }
7
- export declare function resolveSqlConnection(value: SqlConnectionResource | ConnectionRef | undefined, ctx: ResourceContext): SqlConnectionResource | undefined;
8
- export {};
3
+ /** True when a value already exposes the connection contract (Phase-5 injected). */
4
+ export declare function isSqlConnection(value: unknown): value is SqlConnectionResource;
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: SqlConnectionResource | KindRef<SqlConnectionResource> | undefined, ctx: ResourceContext, describe: () => string): SqlConnectionResource | undefined;
@@ -1,24 +1,15 @@
1
- export function resolveSqlConnection(value, ctx) {
2
- if (!value) {
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
  }
@@ -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) {
@@ -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) ?? transaction?.getConnection() ?? failMissingConnection());
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");
@@ -19,7 +19,8 @@ class SqlSelectionResource {
19
19
  const having = ctx.expandValue(m.having ?? [], expandCtx);
20
20
  const limit = m.limit != null ? ctx.expandValue(m.limit, expandCtx) : undefined;
21
21
  const offset = m.offset != null ? ctx.expandValue(m.offset, expandCtx) : undefined;
22
- const connection = resolveSqlConnection(m.connection, ctx) ?? m.transaction?.getConnection();
22
+ const connection = resolveSqlConnection(m.connection, ctx, () => `Sql.Selection "${m.metadata.name}": 'connection'`) ??
23
+ m.transaction?.getConnection();
23
24
  if (!connection) {
24
25
  throw new Error("Sql.Selection: either 'connection' or 'transaction' must be set");
25
26
  }
@@ -8,8 +8,7 @@ export class SqlTransactionResource {
8
8
  this.ctx = ctx;
9
9
  }
10
10
  getConnection() {
11
- return (resolveSqlConnection(this.manifest.connection, this.ctx) ??
12
- failMissingConnection(this.manifest.metadata.name));
11
+ return (resolveSqlConnection(this.manifest.connection, this.ctx, () => `Sql.Transaction "${this.manifest.metadata.name}": 'connection'`) ?? failMissingConnection(this.manifest.metadata.name));
13
12
  }
14
13
  assertActive() {
15
14
  if (!currentTxId()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/sql",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Telo SQL module - SQL database resource kinds for Telo manifests.",
5
5
  "keywords": [
6
6
  "telo",
@@ -70,7 +70,7 @@
70
70
  "devDependencies": {
71
71
  "@types/node": "^20.0.0",
72
72
  "typescript": "^5.0.0",
73
- "@telorun/sdk": "0.34.0"
73
+ "@telorun/sdk": "0.54.0"
74
74
  },
75
75
  "peerDependencies": {
76
76
  "@telorun/sdk": "*"
package/src/index.ts CHANGED
@@ -4,5 +4,5 @@ export {
4
4
  type SqlDriver,
5
5
  type PlaceholderStyle,
6
6
  } from "./sql-connection-controller.js";
7
- export { resolveSqlConnection } from "./sql-connection-ref.js";
7
+ export { isSqlConnection, resolveSqlConnection } from "./sql-connection-ref.js";
8
8
  export type { SqliteDb, SqliteStatement } from "./sqlite-driver-interface.js";
@@ -25,7 +25,9 @@ class SqlCommandResource implements ResourceInstance {
25
25
  const m = this.manifest;
26
26
  const ctx = this.ctx;
27
27
 
28
- const connection = resolveSqlConnection(m.connection, ctx) ?? m.transaction?.getConnection();
28
+ const connection =
29
+ resolveSqlConnection(m.connection, ctx, () => `Sql.Command "${m.metadata.name}": 'connection'`) ??
30
+ m.transaction?.getConnection();
29
31
  if (!connection) {
30
32
  throw new Error("Sql: either 'connection' or 'transaction' must be set");
31
33
  }
@@ -1,41 +1,22 @@
1
- import type { ResourceContext } from "@telorun/sdk";
1
+ import type { KindRef, ResourceContext } from "@telorun/sdk";
2
2
  import type { SqlConnectionResource } from "./sql-connection-controller.js";
3
3
 
4
- interface ConnectionRef {
5
- name: string;
6
- alias?: string;
4
+ /** True when a value already exposes the connection contract (Phase-5 injected). */
5
+ export function isSqlConnection(value: unknown): value is SqlConnectionResource {
6
+ return typeof (value as SqlConnectionResource | undefined)?.execute === "function";
7
7
  }
8
8
 
9
+ /**
10
+ * Resolve a `connection` `!ref` field to a live connection. The slot is optional
11
+ * — an unset one yields `undefined` so the caller can fall back to a `transaction`
12
+ * — but a slot that IS set must resolve. `describe` names the owning resource and
13
+ * slot, so the failure points at a concrete manifest location.
14
+ */
9
15
  export function resolveSqlConnection(
10
- value: SqlConnectionResource | ConnectionRef | undefined,
16
+ value: SqlConnectionResource | KindRef<SqlConnectionResource> | undefined,
11
17
  ctx: ResourceContext,
18
+ describe: () => string,
12
19
  ): SqlConnectionResource | undefined {
13
- if (!value) {
14
- return undefined;
15
- }
16
-
17
- if (typeof (value as SqlConnectionResource).execute === "function") {
18
- return value as SqlConnectionResource;
19
- }
20
-
21
- const ref = value as ConnectionRef;
22
- if (typeof ref.name !== "string") {
23
- throw new Error("Sql: invalid connection reference");
24
- }
25
-
26
- // Cross-module reference (`!ref Alias.Connection`): a connection resolved
27
- // inside a nested library is not Phase-5-injected, so the controller receives
28
- // the raw `{name, alias}` ref and must route through the import's exported
29
- // scope rather than a bare local lookup.
30
- if (ref.alias && ref.alias !== "Self") {
31
- const instance = ctx.moduleContext.resolveImportedInstance(ref.alias, ref.name);
32
- if (typeof (instance as SqlConnectionResource | undefined)?.execute !== "function") {
33
- throw new Error(
34
- `Sql: connection reference '${ref.alias}.${ref.name}' did not resolve to an exported connection instance.`,
35
- );
36
- }
37
- return instance as unknown as SqlConnectionResource;
38
- }
39
-
40
- return ctx.moduleContext.getInstance(ref.name) as SqlConnectionResource;
20
+ if (!value) return undefined;
21
+ return ctx.resolveRef(value, isSqlConnection, describe, "std/sql#Connection");
41
22
  }
@@ -56,7 +56,11 @@ class SqlMigrationsResource implements ResourceInstance {
56
56
 
57
57
  async run(): Promise<void> {
58
58
  const conn =
59
- resolveSqlConnection(this.manifest.connection, this.ctx) ?? failMissingConnection();
59
+ resolveSqlConnection(
60
+ this.manifest.connection,
61
+ this.ctx,
62
+ () => `Sql.Migrations "${this.manifest.metadata.name}": 'connection'`,
63
+ ) ?? failMissingConnection();
60
64
 
61
65
  const migrations: Record<string, string[]> = {};
62
66
  // Legacy: standalone `Sql.Migration` resources in the same module scope.
@@ -28,7 +28,12 @@ class SqlQueryResource implements ResourceInstance {
28
28
  async invoke(input: unknown): Promise<SqlResult> {
29
29
  const m = this.manifest;
30
30
  const ctx = this.ctx;
31
- const connection = resolveConnection(m.connection, m.transaction, ctx);
31
+ const connection = resolveConnection(
32
+ m.connection,
33
+ m.transaction,
34
+ ctx,
35
+ () => `Sql.Query "${m.metadata.name}": 'connection'`,
36
+ );
32
37
  const result = await runSql(connection, m.transaction, input, ctx);
33
38
  return { rows: result.rows, rowCount: result.rows.length };
34
39
  }
@@ -38,9 +43,12 @@ function resolveConnection(
38
43
  connection: SqlConnectionResource | undefined,
39
44
  transaction: SqlTransactionResource | undefined,
40
45
  ctx: ResourceContext,
46
+ describe: () => string,
41
47
  ): SqlConnectionResource {
42
48
  return (
43
- resolveSqlConnection(connection, ctx) ?? transaction?.getConnection() ?? failMissingConnection()
49
+ resolveSqlConnection(connection, ctx, describe) ??
50
+ transaction?.getConnection() ??
51
+ failMissingConnection()
44
52
  );
45
53
  }
46
54
 
@@ -96,7 +96,9 @@ 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
  }
@@ -18,8 +18,11 @@ export class SqlTransactionResource implements ResourceInstance {
18
18
 
19
19
  getConnection(): SqlConnectionResource {
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