@telorun/stream 0.2.0 → 0.3.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,154 +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.6.1
59
- Sql: std/sql@0.5.1
60
- targets:
61
- - Migrations
62
- - 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:
76
- kind: Sql.Connection
77
- name: Db
78
- ---
79
- kind: Sql.Migration
80
- metadata:
81
- name: Migration_20260413_182154_CreateFeedback
82
- version: 20260413_182154_CreateFeedback
83
- sql: |
84
- CREATE TABLE IF NOT EXISTS feedback (
85
- id INTEGER PRIMARY KEY AUTOINCREMENT,
86
- text TEXT NOT NULL,
87
- source TEXT,
88
- score INTEGER NOT NULL DEFAULT 0,
89
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
90
- )
91
- ---
92
- kind: Http.Server
93
- metadata:
94
- name: Server
95
- baseUrl: http://localhost:8844
96
- port: 8844
97
- logger: true
98
- openapi:
99
- info:
100
- title: Feedback API
101
- version: 1.0.0
102
- mounts:
103
- - path: /v1
104
- type: Http.Api.FeedbackRoutes
105
- ---
106
- kind: Http.Api
107
- metadata:
108
- name: FeedbackRoutes
109
- routes:
110
- # POST /v1/feedback — insert a new entry, score derived from body length heuristic
111
- - request:
112
- path: /feedback
113
- method: POST
114
- schema:
115
- body:
116
- type: object
117
- properties:
118
- text:
119
- type: string
120
- minLength: 1
121
- source:
122
- type: string
123
- required: [ text ]
124
- handler:
125
- kind: Sql.Exec
126
- connection:
127
- kind: Sql.Connection
128
- name: Db
129
- inputs:
130
- sql: "INSERT INTO feedback (text, source, score) VALUES (?, ?, ?)"
131
- bindings:
132
- - "${{ request.body.text }}"
133
- - "${{ request.body.source }}"
134
- - "${{ size(request.body.text) }}"
135
- response:
136
- - status: 201
137
- headers:
138
- Content-Type: application/json
139
- body:
140
- ok: true
141
- message: Feedback received
142
-
143
- # GET /v1/feedback — list all entries, newest first
144
- - request:
145
- path: /feedback
146
- method: GET
147
- handler:
148
- kind: Sql.Select
149
- connection:
150
- kind: Sql.Connection
151
- name: Db
152
- from: feedback
153
- columns: [ id, text, source, score, created_at ]
154
- orderBy:
155
- - { column: created_at, direction: desc }
156
- response:
157
- - status: 200
158
- headers:
159
- Content-Type: application/json
160
- body: "${{ result.rows }}"
161
-
162
- # GET /v1/feedback/{id} — fetch a single entry
163
- - request:
164
- path: /feedback/{id}
165
- method: GET
166
- schema:
167
- params:
168
- type: object
169
- properties:
170
- id:
171
- type: integer
172
- required: [ id ]
173
- handler:
174
- kind: Sql.Select
175
- connection:
176
- kind: Sql.Connection
177
- name: Db
178
- from: feedback
179
- columns: [ id, text, source, score, created_at ]
180
- where:
181
- - { column: id, op: "=", value: "${{ request.params.id }}" }
182
- response:
183
- - status: 200
184
- when: "size(result.rows) > 0"
185
- headers:
186
- Content-Type: application/json
187
- body: "${{ result.rows[0] }}"
188
- - status: 404
189
- headers:
190
- Content-Type: application/json
191
- body:
192
- ok: false
193
- message: Not found
194
- ```
47
+ See [examples/](./examples/) for a list of working applications.
195
48
 
196
49
  ## Status
197
50
 
@@ -0,0 +1,29 @@
1
+ import type { ControllerContext, ResourceContext, ResourceInstance } from "@telorun/sdk";
2
+ interface CollectResource {
3
+ metadata: {
4
+ name: string;
5
+ module?: string;
6
+ };
7
+ }
8
+ interface CollectInputs {
9
+ input: AsyncIterable<unknown>;
10
+ }
11
+ interface CollectOutputs {
12
+ items: unknown[];
13
+ }
14
+ /**
15
+ * Terminal stream sink — the inverse of Stream.Of. Consumes `input` fully and
16
+ * returns every item as `items`, in order. Draining drives the producer's side
17
+ * effects (so it "runs" an upstream agent / pipeline); the result materializes the
18
+ * finite stream for inspection, assertion, or aggregation in CEL. Value-agnostic and
19
+ * buffered — bounded by the stream's length.
20
+ */
21
+ declare class StreamCollect implements ResourceInstance<CollectInputs, CollectOutputs> {
22
+ private readonly resource;
23
+ constructor(resource: CollectResource);
24
+ invoke(inputs: CollectInputs): Promise<CollectOutputs>;
25
+ snapshot(): Record<string, unknown>;
26
+ }
27
+ export declare function register(_ctx: ControllerContext): void;
28
+ export declare function create(resource: CollectResource, _ctx: ResourceContext): Promise<StreamCollect>;
29
+ export {};
@@ -0,0 +1,33 @@
1
+ import { InvokeError } from "@telorun/sdk";
2
+ /**
3
+ * Terminal stream sink — the inverse of Stream.Of. Consumes `input` fully and
4
+ * returns every item as `items`, in order. Draining drives the producer's side
5
+ * effects (so it "runs" an upstream agent / pipeline); the result materializes the
6
+ * finite stream for inspection, assertion, or aggregation in CEL. Value-agnostic and
7
+ * buffered — bounded by the stream's length.
8
+ */
9
+ class StreamCollect {
10
+ resource;
11
+ constructor(resource) {
12
+ this.resource = resource;
13
+ }
14
+ async invoke(inputs) {
15
+ const name = this.resource.metadata.name;
16
+ const input = inputs?.input;
17
+ if (!input ||
18
+ typeof input[Symbol.asyncIterator] !== "function") {
19
+ throw new InvokeError("ERR_INVALID_INPUT", `Stream.Collect "${name}": 'input' must be an AsyncIterable.`);
20
+ }
21
+ const items = [];
22
+ for await (const item of input)
23
+ items.push(item);
24
+ return { items };
25
+ }
26
+ snapshot() {
27
+ return {};
28
+ }
29
+ }
30
+ export function register(_ctx) { }
31
+ export async function create(resource, _ctx) {
32
+ return new StreamCollect(resource);
33
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telorun/stream",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Generic stream substrate — value-agnostic stream construction (Stream.Of).",
5
5
  "keywords": [
6
6
  "telo",
@@ -32,6 +32,11 @@
32
32
  "types": "./dist/of-controller.d.ts",
33
33
  "bun": "./src/of-controller.ts",
34
34
  "import": "./dist/of-controller.js"
35
+ },
36
+ "./collect": {
37
+ "types": "./dist/collect-controller.d.ts",
38
+ "bun": "./src/collect-controller.ts",
39
+ "import": "./dist/collect-controller.js"
35
40
  }
36
41
  },
37
42
  "files": [
@@ -41,7 +46,7 @@
41
46
  "devDependencies": {
42
47
  "@types/node": "^20.0.0",
43
48
  "typescript": "^5.0.0",
44
- "@telorun/sdk": "0.18.0"
49
+ "@telorun/sdk": "0.38.0"
45
50
  },
46
51
  "peerDependencies": {
47
52
  "@telorun/sdk": "*"
@@ -0,0 +1,55 @@
1
+ import type { ControllerContext, ResourceContext, ResourceInstance } from "@telorun/sdk";
2
+ import { InvokeError } from "@telorun/sdk";
3
+
4
+ interface CollectResource {
5
+ metadata: { name: string; module?: string };
6
+ }
7
+
8
+ interface CollectInputs {
9
+ input: AsyncIterable<unknown>;
10
+ }
11
+
12
+ interface CollectOutputs {
13
+ items: unknown[];
14
+ }
15
+
16
+ /**
17
+ * Terminal stream sink — the inverse of Stream.Of. Consumes `input` fully and
18
+ * returns every item as `items`, in order. Draining drives the producer's side
19
+ * effects (so it "runs" an upstream agent / pipeline); the result materializes the
20
+ * finite stream for inspection, assertion, or aggregation in CEL. Value-agnostic and
21
+ * buffered — bounded by the stream's length.
22
+ */
23
+ class StreamCollect implements ResourceInstance<CollectInputs, CollectOutputs> {
24
+ constructor(private readonly resource: CollectResource) {}
25
+
26
+ async invoke(inputs: CollectInputs): Promise<CollectOutputs> {
27
+ const name = this.resource.metadata.name;
28
+ const input = inputs?.input;
29
+ if (
30
+ !input ||
31
+ typeof (input as { [Symbol.asyncIterator]?: unknown })[Symbol.asyncIterator] !== "function"
32
+ ) {
33
+ throw new InvokeError(
34
+ "ERR_INVALID_INPUT",
35
+ `Stream.Collect "${name}": 'input' must be an AsyncIterable.`,
36
+ );
37
+ }
38
+ const items: unknown[] = [];
39
+ for await (const item of input) items.push(item);
40
+ return { items };
41
+ }
42
+
43
+ snapshot(): Record<string, unknown> {
44
+ return {};
45
+ }
46
+ }
47
+
48
+ export function register(_ctx: ControllerContext): void {}
49
+
50
+ export async function create(
51
+ resource: CollectResource,
52
+ _ctx: ResourceContext,
53
+ ): Promise<StreamCollect> {
54
+ return new StreamCollect(resource);
55
+ }