@loghead/core 0.1.18 → 0.1.20

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/loggerhead.db DELETED
Binary file
package/loghead.db DELETED
Binary file
package/src/api/server.ts DELETED
@@ -1,154 +0,0 @@
1
- import express from "express";
2
- import cors from "cors";
3
- import { DbService } from "../services/db";
4
- import { AuthService } from "../services/auth";
5
- import chalk from "chalk";
6
-
7
- const auth = new AuthService();
8
-
9
- export async function startApiServer(db: DbService) {
10
- const app = express();
11
- const port = process.env.PORT || 4567;
12
-
13
- app.use(cors());
14
- app.use(express.json());
15
-
16
- await auth.initialize();
17
-
18
- console.log(chalk.bold.green(`\nšŸ’» MCP server running on:`));
19
- console.log(chalk.green(`http://localhost:${port}`));
20
-
21
- app.post("/api/ingest", async (req, res) => {
22
- try {
23
- const authHeader = req.headers.authorization;
24
- if (!authHeader || !authHeader.startsWith("Bearer ")) {
25
- return res.status(401).send("Unauthorized: Missing token");
26
- }
27
- const token = authHeader.split(" ")[1];
28
- const payload = await auth.verifyToken(token);
29
- if (!payload || !payload.streamId) {
30
- return res.status(401).send("Unauthorized: Invalid token");
31
- }
32
-
33
- const { streamId, logs } = req.body;
34
-
35
- if (streamId !== payload.streamId) {
36
- return res.status(403).send("Forbidden: Token does not match streamId");
37
- }
38
-
39
- if (!logs) {
40
- return res.status(400).send("Missing logs");
41
- }
42
-
43
- const logEntries = Array.isArray(logs) ? logs : [logs];
44
-
45
- for (const log of logEntries) {
46
- let content = "";
47
- let metadata = {};
48
-
49
- if (typeof log === "string") {
50
- content = log;
51
- } else if (typeof log === "object") {
52
- content = log.content || JSON.stringify(log);
53
- metadata = log.metadata || {};
54
- }
55
-
56
- if (content) {
57
- await db.addLog(streamId, content, metadata);
58
- }
59
- }
60
-
61
- res.json({ success: true, count: logEntries.length });
62
- } catch (e) {
63
- console.error("Ingest error:", e);
64
- res.status(500).json({ error: String(e) });
65
- }
66
- });
67
-
68
- app.get("/api/projects", (req, res) => {
69
- const projects = db.listProjects();
70
- res.json(projects);
71
- });
72
-
73
- app.post("/api/projects", (req, res) => {
74
- const { name } = req.body;
75
- if (!name) return res.status(400).json({ error: "Name required" });
76
- const project = db.createProject(name);
77
- res.json(project);
78
- });
79
-
80
- app.delete("/api/projects/:id", (req, res) => {
81
- const { id } = req.params;
82
- db.deleteProject(id);
83
- res.json({ success: true });
84
- });
85
-
86
- app.get("/api/streams", (req, res) => {
87
- const projectId = req.query.projectId as string;
88
- if (projectId) {
89
- const streams = db.listStreams(projectId);
90
- res.json(streams);
91
- } else {
92
- res.status(400).send("Missing projectId");
93
- }
94
- });
95
-
96
- app.delete("/api/streams/:id", (req, res) => {
97
- const { id } = req.params;
98
- db.deleteStream(id);
99
- res.json({ success: true });
100
- });
101
-
102
- app.post("/api/streams", (req, res) => {
103
- // Deprecated or just listing? The previous code had this returning listStreams for POST?
104
- // I'll remove it or keep it if CLI uses it?
105
- // CLI uses db directly.
106
- // MCP uses GET /api/streams
107
- // I'll replace this with the actual CREATE logic to be RESTful, or keep /create
108
- const projectId = req.body.projectId;
109
- if (projectId) {
110
- const streams = db.listStreams(projectId);
111
- res.json(streams);
112
- } else {
113
- res.status(400).send("Missing projectId");
114
- }
115
- });
116
-
117
- app.post("/api/streams/create", async (req, res) => {
118
- const body = req.body;
119
- const stream = await db.createStream(body.projectId, body.type, body.name, body.config || {});
120
- res.json(stream);
121
- });
122
-
123
- app.get("/api/logs", async (req, res) => {
124
- const streamId = req.query.streamId as string;
125
- if (!streamId) {
126
- return res.status(400).send("Missing streamId");
127
- }
128
-
129
- let page = parseInt((req.query.page as string) || "1");
130
- if (page < 1) page = 1;
131
-
132
- let pageSize = parseInt((req.query.pageSize as string) || "100");
133
- let limit = req.query.limit ? parseInt(req.query.limit as string) : pageSize;
134
-
135
- // Enforce max limit
136
- if (limit > 1000) limit = 1000;
137
-
138
- const offset = (page - 1) * limit;
139
-
140
- const query = req.query.q as string;
141
-
142
- let logs;
143
- if (query) {
144
- logs = await db.searchLogs(streamId, query, limit);
145
- } else {
146
- logs = db.getRecentLogs(streamId, limit, offset);
147
- }
148
- res.json(logs);
149
- });
150
-
151
- app.listen(port, () => {
152
- // listening
153
- });
154
- }
package/src/cli_main.ts DELETED
@@ -1,83 +0,0 @@
1
- #!/usr/bin/env node
2
- import yargs from "yargs";
3
- import { hideBin } from "yargs/helpers";
4
- import { DbService } from "./services/db";
5
- import { startApiServer } from "./api/server";
6
- import { migrate } from "./db/migrate";
7
- // import { ensureInfrastructure } from "./utils/startup"; // Might need adjustment
8
- import { startTui } from "./ui/main";
9
- import { AuthService } from "./services/auth";
10
- import chalk from "chalk";
11
-
12
- const db = new DbService();
13
- const auth = new AuthService();
14
-
15
- async function main() {
16
- const argv = await yargs(hideBin(process.argv))
17
- .command(["start", "$0"], "Start API Server", {}, async () => {
18
- console.log("Ensuring database is initialized...");
19
- await migrate(false); // Run migrations silently
20
-
21
- const token = await auth.getOrCreateMcpToken();
22
-
23
- // Start API Server (this sets up express listen)
24
- await startApiServer(db);
25
-
26
- // Start TUI (this will clear screen and take over)
27
- await startTui(db, token);
28
- process.exit(0);
29
- })
30
- .command("projects <cmd> [name]", "Manage projects", (yargs) => {
31
- yargs
32
- .command("list", "List projects", {}, () => {
33
- const projects = db.listProjects();
34
- console.table(projects);
35
- })
36
- .command("add <name>", "Add project", {}, (argv) => {
37
- const p = db.createProject(argv.name as string);
38
- console.log(`Project created: ${p.id}`);
39
- })
40
- .command("delete <id>", "Delete project", {}, (argv) => {
41
- db.deleteProject(argv.id as string);
42
- console.log(`Project deleted: ${argv.id}`);
43
- });
44
- })
45
- .command("streams <cmd> [type] [name]", "Manage streams", (yargs) => {
46
- yargs
47
- .command("list", "List streams", {
48
- project: { type: "string", demandOption: true }
49
- }, (argv) => {
50
- const streams = db.listStreams(argv.project);
51
- console.table(streams);
52
- })
53
- .command("add <type> <name>", "Add stream", {
54
- project: { type: "string", demandOption: true },
55
- container: { type: "string" }
56
- }, async (argv) => {
57
- const config: Record<string, unknown> = {};
58
- if (argv.type === "docker" && argv.container) {
59
- config.container = argv.container;
60
- }
61
- const s = await db.createStream(argv.project, argv.type as string, argv.name as string, config);
62
- console.log(`Stream created: ${s.id}`);
63
- console.log(`Token: ${s.token}`);
64
- })
65
- .command("token <streamId>", "Get token for stream", {}, async (argv) => {
66
- const token = await auth.createStreamToken(argv.streamId as string);
67
- console.log(`Token: ${token}`);
68
- })
69
- .command("delete <id>", "Delete stream", {}, (argv) => {
70
- db.deleteStream(argv.id as string);
71
- console.log(`Stream deleted: ${argv.id}`);
72
- });
73
- })
74
- .demandCommand(1)
75
- .strict()
76
- .help()
77
- .parse();
78
- }
79
-
80
- main().catch(err => {
81
- console.error(err);
82
- process.exit(1);
83
- });
package/src/db/client.ts DELETED
@@ -1,20 +0,0 @@
1
- import Database from "better-sqlite3";
2
- import * as sqliteVec from "sqlite-vec";
3
- import path from "path";
4
- import dotenv from "dotenv";
5
-
6
- dotenv.config();
7
-
8
- const dbPath = process.env.LOGHEAD_DB_PATH || "loghead.db";
9
- console.log(`[DB] Using database at: ${path.resolve(dbPath)}`);
10
-
11
- const db = new Database(dbPath);
12
-
13
- // Load sqlite-vec extension
14
- try {
15
- sqliteVec.load(db);
16
- } catch (e) {
17
- console.error("Failed to load sqlite-vec extension:", e);
18
- }
19
-
20
- export { db };
package/src/db/migrate.ts DELETED
@@ -1,67 +0,0 @@
1
- import { db } from "./client";
2
-
3
- export function migrate(verbose = true) {
4
- if (verbose) console.log("Running migrations...");
5
-
6
- // Enable foreign keys
7
- db.exec("PRAGMA foreign_keys = ON;");
8
-
9
- // System Config table (for secrets, etc.)
10
- db.exec(`
11
- CREATE TABLE IF NOT EXISTS system_config (
12
- key TEXT PRIMARY KEY,
13
- value TEXT NOT NULL
14
- );
15
- `);
16
-
17
- // Projects table
18
- db.exec(`
19
- CREATE TABLE IF NOT EXISTS projects (
20
- id TEXT PRIMARY KEY,
21
- name TEXT NOT NULL,
22
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
23
- );
24
- `);
25
-
26
- // Data Streams table
27
- db.exec(`
28
- CREATE TABLE IF NOT EXISTS data_streams (
29
- id TEXT PRIMARY KEY,
30
- project_id TEXT,
31
- type TEXT NOT NULL,
32
- name TEXT NOT NULL,
33
- config TEXT DEFAULT '{}',
34
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
35
- FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
36
- );
37
- `);
38
-
39
- // Logs table
40
- db.exec(`
41
- CREATE TABLE IF NOT EXISTS logs (
42
- id TEXT PRIMARY KEY,
43
- stream_id TEXT,
44
- content TEXT NOT NULL,
45
- timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
46
- metadata TEXT DEFAULT '{}',
47
- FOREIGN KEY(stream_id) REFERENCES data_streams(id) ON DELETE CASCADE
48
- );
49
- `);
50
-
51
- // Vector table (using sqlite-vec)
52
- // Assuming 1024 dimensions for qwen3-embedding:0.6b (check actual dim)
53
- // qwen2.5-0.5b is 1536?
54
- // qwen-embedding-0.6b might be 384 or 1024?
55
- // Let's assume 1024 as per previous code.
56
- try {
57
- db.exec(`
58
- CREATE VIRTUAL TABLE IF NOT EXISTS vec_logs USING vec0(
59
- embedding float[1024]
60
- );
61
- `);
62
- } catch (e) {
63
- console.warn("Failed to create virtual vector table. Is sqlite-vec loaded?", e);
64
- }
65
-
66
- if (verbose) console.log("Migrations complete.");
67
- }
@@ -1,74 +0,0 @@
1
- import jwt from "jsonwebtoken";
2
- import { db } from "../db/client";
3
- import { randomBytes } from "crypto";
4
-
5
- // Helper type for DB access
6
- // deno-lint-ignore no-explicit-any
7
- type DbAny = any;
8
-
9
- export class AuthService {
10
- private secretKey: string | null = null;
11
-
12
- async initialize() {
13
- if (this.secretKey) return;
14
-
15
- // Try to load secret from DB
16
- const row = (db.prepare("SELECT value FROM system_config WHERE key = 'jwt_secret'") as unknown as DbAny).get();
17
-
18
- let rawSecret = row?.value;
19
-
20
- if (!rawSecret) {
21
- // Generate new secret
22
- rawSecret = randomBytes(64).toString('hex');
23
- (db.prepare("INSERT INTO system_config (key, value) VALUES ('jwt_secret', ?)") as unknown as DbAny).run(rawSecret);
24
- }
25
-
26
- this.secretKey = rawSecret;
27
- }
28
-
29
- async getOrCreateMcpToken(): Promise<string> {
30
- await this.initialize();
31
- if (!this.secretKey) throw new Error("Auth not initialized");
32
-
33
- // Check if token exists in DB
34
- const row = (db.prepare("SELECT value FROM system_config WHERE key = 'mcp_token'") as unknown as DbAny).get();
35
- if (row?.value) {
36
- return row.value;
37
- }
38
-
39
- // Create new token
40
- // A system token that has access to everything (conceptually)
41
- const token = jwt.sign({ sub: "system:mcp", iss: "loghead", role: "admin" }, this.secretKey, { algorithm: "HS512" });
42
-
43
- (db.prepare("INSERT INTO system_config (key, value) VALUES ('mcp_token', ?)") as unknown as DbAny).run(token);
44
-
45
- return token;
46
- }
47
-
48
- async createStreamToken(streamId: string): Promise<string> {
49
- await this.initialize();
50
- if (!this.secretKey) throw new Error("Auth not initialized");
51
-
52
- const token = jwt.sign({ sub: streamId, iss: "loghead" }, this.secretKey, { algorithm: "HS512" });
53
- return token;
54
- }
55
-
56
- async verifyToken(token: string): Promise<{ streamId: string } | null> {
57
- await this.initialize();
58
- if (!this.secretKey) throw new Error("Auth not initialized");
59
-
60
- try {
61
- // console.log(`[Auth] Verifying token with secret: ${this.secretKey.substring(0, 10)}...`);
62
- const payload = jwt.verify(token, this.secretKey, { issuer: "loghead", algorithms: ["HS512"] }) as jwt.JwtPayload;
63
- if (!payload.sub) return null;
64
- return { streamId: payload.sub };
65
- } catch (e) {
66
- console.error("Token verification failed:", e);
67
- if (e instanceof Error && e.message === "invalid signature") {
68
- console.error("[Auth] Secret key mismatch. Ensure the server is using the same database (and secret) as when the token was generated.");
69
- console.error(`[Auth] Current secret starts with: ${this.secretKey?.substring(0, 8)}...`);
70
- }
71
- return null;
72
- }
73
- }
74
- }
@@ -1,173 +0,0 @@
1
- import { db } from "../db/client";
2
- import { OllamaService } from "./ollama";
3
- import { AuthService } from "./auth";
4
- import { Project, Stream, Log, SearchResult } from "../types";
5
- import { randomUUID } from "crypto";
6
-
7
- const ollama = new OllamaService();
8
- const auth = new AuthService();
9
-
10
- // Helper to cast DB results to avoid no-explicit-any
11
- // deno-lint-ignore no-explicit-any
12
- type DbAny = any;
13
-
14
- export class DbService {
15
- createProject(name: string): Project {
16
- const id = randomUUID();
17
- (db.prepare("INSERT INTO projects (id, name) VALUES (?, ?)") as unknown as DbAny).run(id, name);
18
- return this.getProject(id);
19
- }
20
-
21
- getProject(id: string): Project {
22
- return (db.prepare("SELECT * FROM projects WHERE id = ?") as unknown as DbAny).get(id);
23
- }
24
-
25
- deleteProject(id: string): boolean {
26
- (db.prepare("DELETE FROM projects WHERE id = ?") as unknown as DbAny).run(id);
27
- return true;
28
- }
29
-
30
- listProjects(): Project[] {
31
- try {
32
- const projects = (db.prepare("SELECT * FROM projects ORDER BY created_at DESC") as unknown as DbAny).all();
33
- return projects.map((p: Project) => {
34
- const streams = (db.prepare("SELECT * FROM data_streams WHERE project_id = ?") as unknown as DbAny).all(p.id);
35
- return { ...p, streams };
36
- });
37
- } catch (e) {
38
- console.error("Error in listProjects:", e);
39
- throw e;
40
- }
41
- }
42
-
43
- async createStream(projectId: string, type: string, name: string, config: Record<string, unknown> = {}): Promise<Stream & { token: string }> {
44
- const id = randomUUID();
45
- (db.prepare("INSERT INTO data_streams (id, project_id, type, name, config) VALUES (?, ?, ?, ?, ?)") as unknown as DbAny).run(
46
- id, projectId, type, name, JSON.stringify(config)
47
- );
48
-
49
- const token = await auth.createStreamToken(id);
50
- const stream = this.getStream(id);
51
-
52
- return { ...stream, token };
53
- }
54
-
55
- getStream(id: string): Stream {
56
- const stream = (db.prepare("SELECT * FROM data_streams WHERE id = ?") as unknown as DbAny).get(id);
57
- if (stream && typeof stream.config === "string") {
58
- try { stream.config = JSON.parse(stream.config); } catch { /* ignore */ }
59
- }
60
- return stream;
61
- }
62
-
63
- deleteStream(id: string): boolean {
64
- (db.prepare("DELETE FROM data_streams WHERE id = ?") as unknown as DbAny).run(id);
65
- return true;
66
- }
67
-
68
- listStreams(projectId: string): Stream[] {
69
- const streams = (db.prepare("SELECT * FROM data_streams WHERE project_id = ? ORDER BY created_at DESC") as unknown as DbAny).all(projectId);
70
- return streams.map((s: Stream) => {
71
- if (typeof s.config === "string") try { s.config = JSON.parse(s.config); } catch { /* ignore */ }
72
- return s;
73
- });
74
- }
75
-
76
- async addLog(streamId: string, content: string, metadata: Record<string, unknown> = {}): Promise<{ id: string }> {
77
- // Generate embedding
78
- let embedding: number[] | null = null;
79
- try {
80
- embedding = await ollama.generateEmbedding(content);
81
- } catch (_e) {
82
- // console.warn("Embedding failed", _e);
83
- }
84
-
85
- const id = randomUUID();
86
- const metadataStr = JSON.stringify(metadata);
87
-
88
- // Manual Transaction
89
- const insertTx = db.transaction(() => {
90
- // 1. Insert into logs
91
- const info = (db.prepare("INSERT INTO logs (id, stream_id, content, metadata) VALUES (?, ?, ?, ?)") as unknown as DbAny).run(
92
- id, streamId, content, metadataStr
93
- );
94
-
95
- // 2. Get rowid directly
96
- const rowid = info.lastInsertRowid;
97
-
98
- // 3. Insert into vec_logs if embedding exists
99
- if (embedding && embedding.length > 0) {
100
- if (embedding.length !== 1024) {
101
- console.warn(`[Warning] Embedding dimension mismatch. Expected 1024, got ${embedding.length}. Skipping vector index.`);
102
- return;
103
- }
104
- const vectorJson = JSON.stringify(embedding);
105
- // Explicitly cast rowid to BigInt to ensure better-sqlite3 binds it as an INTEGER
106
- (db.prepare("INSERT INTO vec_logs(rowid, embedding) VALUES (?, ?)") as unknown as DbAny).run(BigInt(rowid), vectorJson);
107
- }
108
- });
109
-
110
- try {
111
- insertTx();
112
- } catch (e) {
113
- throw e;
114
- }
115
-
116
- return { id };
117
- }
118
-
119
- async searchLogs(streamId: string, query: string, limit = 10): Promise<SearchResult[]> {
120
- const embedding = await ollama.generateEmbedding(query);
121
- if (!embedding) return [];
122
-
123
- const vectorJson = JSON.stringify(embedding);
124
-
125
- // KNN Search
126
- const rows = (db.prepare(`
127
- SELECT l.content, l.timestamp, l.metadata, v.distance
128
- FROM vec_logs v
129
- JOIN logs l ON l.rowid = v.rowid
130
- WHERE v.embedding MATCH ? AND k = ? AND l.stream_id = ?
131
- ORDER BY v.distance
132
- `) as unknown as DbAny).all(vectorJson, limit, streamId);
133
-
134
- return rows.map((row: { content: string; timestamp: string; metadata: string; distance: number }) => {
135
- let meta: Record<string, unknown> | undefined;
136
- try { meta = JSON.parse(row.metadata); } catch { /* ignore */ }
137
- return {
138
- content: row.content,
139
- timestamp: row.timestamp,
140
- similarity: 1 - row.distance, // Rough approx
141
- metadata: (meta && Object.keys(meta).length > 0) ? meta : undefined
142
- };
143
- });
144
- }
145
-
146
- getRecentLogs(streamId: string, limit = 50, offset = 0): Log[] {
147
- const rows = (db.prepare(`
148
- SELECT content, timestamp, metadata FROM logs
149
- WHERE stream_id = ?
150
- ORDER BY timestamp DESC
151
- LIMIT ? OFFSET ?
152
- `) as unknown as DbAny).all(streamId, limit, offset);
153
-
154
- return rows.map((row: Log) => {
155
- let meta = row.metadata;
156
- if (typeof meta === "string") {
157
- try { meta = JSON.parse(meta); } catch { /* ignore */ }
158
- }
159
- return {
160
- id: row.id, // Ensure id is included if needed, or update Log type
161
- stream_id: streamId,
162
- content: row.content,
163
- timestamp: row.timestamp,
164
- metadata: (typeof meta === "object" && meta && Object.keys(meta).length > 0) ? meta : {}
165
- } as Log;
166
- });
167
- }
168
-
169
- close() {
170
- db.close();
171
- }
172
- }
173
-
@@ -1,39 +0,0 @@
1
- import { Ollama } from "ollama";
2
-
3
- export class OllamaService {
4
- private client: Ollama;
5
- private model: string;
6
-
7
- constructor(host = "http://localhost:11434", model = "qwen3-embedding:0.6b") {
8
- this.client = new Ollama({ host });
9
- this.model = model;
10
- }
11
-
12
- async generateEmbedding(prompt: string): Promise<number[]> {
13
- try {
14
- const response = await this.client.embeddings({
15
- model: this.model,
16
- prompt: prompt,
17
- });
18
- return response.embedding;
19
- } catch (error) {
20
- console.error("Failed to generate embedding:", error);
21
- throw error;
22
- }
23
- }
24
-
25
- async ensureModel() {
26
- try {
27
- const list = await this.client.list();
28
- const exists = list.models.some((m) => m.name.includes(this.model));
29
-
30
- if (!exists) {
31
- console.log(`Model ${this.model} not found. Pulling...`);
32
- await this.client.pull({ model: this.model });
33
- console.log("Model pulled.");
34
- }
35
- } catch (e) {
36
- console.warn("Could not check/pull ollama model:", e);
37
- }
38
- }
39
- }
@@ -1,63 +0,0 @@
1
- import { assertEquals, assertExists } from "@std/assert";
2
- import { migrate } from "../db/migrate.ts";
3
-
4
- // Set up environment to use in-memory DB before importing client
5
- Deno.env.set("LOGHEAD_DB_PATH", ":memory:");
6
-
7
- // Dynamic import to ensure env var is set before module loads
8
- // We trigger the side-effect of client.ts (creating DB connection)
9
- await import("../db/client.ts");
10
- const { DbService } = await import("../services/db.ts");
11
-
12
- Deno.test("DbService - Project Management", async (t) => {
13
- // @ts-ignore: Dynamic import returns any
14
- const service = new DbService();
15
-
16
- // Initialize DB schema
17
- migrate(false);
18
-
19
- await t.step("createProject creates a project", () => {
20
- const project = service.createProject("Test Project");
21
- assertExists(project.id);
22
- assertEquals(project.name, "Test Project");
23
- });
24
-
25
- await t.step("listProjects returns projects", () => {
26
- const projects = service.listProjects();
27
- assertExists(projects.find(p => p.name === "Test Project"));
28
- });
29
-
30
- await t.step("deleteProject deletes a project", () => {
31
- const projects = service.listProjects();
32
- const project = projects.find(p => p.name === "Test Project");
33
- if (project) {
34
- service.deleteProject(project.id);
35
-
36
- const remaining = service.listProjects();
37
- assertEquals(remaining.find(p => p.name === "Test Project"), undefined);
38
- }
39
- });
40
- });
41
-
42
- Deno.test("DbService - Stream Management", async (t) => {
43
- // @ts-ignore: Dynamic import returns any
44
- const service = new DbService();
45
-
46
- // Create a fresh project for this test suite
47
- const project = service.createProject("Stream Project");
48
-
49
- await t.step("createStream creates a stream", () => {
50
- const stream = service.createStream(project.id, "terminal", "My Stream", { foo: "bar" });
51
- assertExists(stream.id);
52
- assertEquals(stream.name, "My Stream");
53
- assertEquals(stream.type, "terminal");
54
- // @ts-ignore: Property access
55
- assertEquals(stream.config, { foo: "bar" });
56
- });
57
-
58
- await t.step("listStreams returns streams", () => {
59
- const streams = service.listStreams(project.id);
60
- assertEquals(streams.length, 1);
61
- assertEquals(streams[0].name, "My Stream");
62
- });
63
- });