@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 +2 -149
- package/dist/collect-controller.d.ts +29 -0
- package/dist/collect-controller.js +33 -0
- package/package.json +7 -2
- package/src/collect-controller.ts +55 -0
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,154 +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.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.
|
|
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.
|
|
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
|
+
}
|