@tangle-network/tcloud 0.1.3 → 0.2.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.
@@ -0,0 +1,110 @@
1
+ import { a as TCloudConfig, T as TCloudClient } from './client-CcuHG7_w.cjs';
2
+
3
+ /**
4
+ * Instance — programmatic harness for spinning up a local Tangle dev environment.
5
+ *
6
+ * Wraps `cargo tangle harness up` as a child process and exposes health
7
+ * checks, log streaming, and a pre-configured {@link TCloudClient} pointed
8
+ * at the local stack.
9
+ *
10
+ * NODE-ONLY. This module uses `child_process` and `fs` and must not be
11
+ * imported in browser or edge runtime bundles. Always import from
12
+ * `@tangle-network/tcloud/instance`, never the package root.
13
+ *
14
+ * ## Example
15
+ *
16
+ * ```ts
17
+ * import { Instance } from '@tangle-network/tcloud/instance'
18
+ *
19
+ * const instance = await Instance.start({
20
+ * config: './harness.dev.toml',
21
+ * only: ['llm'],
22
+ * })
23
+ *
24
+ * const client = instance.client({ model: 'llama-3.1-8b' })
25
+ * const res = await client.chat({
26
+ * messages: [{ role: 'user', content: 'hello' }],
27
+ * })
28
+ * console.log(res.choices[0].message.content)
29
+ *
30
+ * await instance.stop()
31
+ * ```
32
+ */
33
+
34
+ interface InstanceOptions {
35
+ /** Path to harness config TOML. Defaults to ./harness.toml or ~/.tangle/harness.toml */
36
+ config?: string;
37
+ /** Only start these blueprints (subset) */
38
+ only?: string[];
39
+ /** Working directory for `cargo tangle harness` (default: current) */
40
+ cwd?: string;
41
+ /** Stream anvil logs (default false) */
42
+ includeAnvilLogs?: boolean;
43
+ /** Suppress stdout passthrough (default false) */
44
+ quiet?: boolean;
45
+ /** Startup timeout in ms — how long to wait for "Harness up" marker (default 300_000) */
46
+ timeoutMs?: number;
47
+ /** Router URL the instance exposes. Defaults to http://localhost:3000 */
48
+ routerUrl?: string;
49
+ /** Override the `cargo tangle` binary path. Defaults to resolving `cargo-tangle` from PATH */
50
+ cargoBinary?: string;
51
+ }
52
+ interface InstanceConfig {
53
+ /** URL of the router the instance is serving on */
54
+ routerUrl: string;
55
+ /** Names of blueprints that were started */
56
+ blueprints: string[];
57
+ }
58
+ /**
59
+ * Append a line to a bounded log buffer, evicting the oldest entry once the
60
+ * buffer exceeds `maxLines`. Exported for unit testing.
61
+ */
62
+ declare function appendLogLine(buffer: string[], line: string, maxLines?: number): void;
63
+ /**
64
+ * A running Tangle dev environment. Hold one per test file or dev session.
65
+ */
66
+ declare class Instance {
67
+ private child;
68
+ private _config;
69
+ private _stopped;
70
+ private logBuffer;
71
+ private readonly maxLogLines;
72
+ private constructor();
73
+ /**
74
+ * Start a new harness instance. Resolves once `cargo tangle harness up`
75
+ * prints its "Harness up" marker, indicating all blueprints are healthy.
76
+ *
77
+ * Throws on timeout, process exit before ready, or missing `cargo-tangle`.
78
+ */
79
+ static start(options?: InstanceOptions): Promise<Instance>;
80
+ /** URL of the router serving this instance */
81
+ get routerUrl(): string;
82
+ /** Names of blueprints that were started */
83
+ get blueprints(): string[];
84
+ /**
85
+ * Create a pre-configured {@link TCloudClient} pointed at this instance's router.
86
+ * Merges any passed config with the instance's routerUrl (instance wins).
87
+ */
88
+ client(config?: Partial<TCloudConfig>): TCloudClient;
89
+ /** Return the last N log lines from the harness */
90
+ logs(lines?: number): string[];
91
+ /** Whether the harness process is still running */
92
+ get isRunning(): boolean;
93
+ /**
94
+ * Stop the harness. Sends SIGTERM, waits up to 10s for clean shutdown,
95
+ * then SIGKILL.
96
+ */
97
+ stop(timeoutMs?: number): Promise<void>;
98
+ }
99
+ /**
100
+ * Write a temporary harness.toml with the given blueprints, for inline test configs.
101
+ * Returns the path to the created file. The caller is responsible for cleanup.
102
+ */
103
+ declare function writeTempHarnessConfig(blueprints: Array<{
104
+ name: string;
105
+ path: string;
106
+ port?: number;
107
+ env?: Record<string, string>;
108
+ }>): string;
109
+
110
+ export { Instance, type InstanceConfig, type InstanceOptions, appendLogLine, writeTempHarnessConfig };
@@ -0,0 +1,110 @@
1
+ import { a as TCloudConfig, T as TCloudClient } from './client-CcuHG7_w.js';
2
+
3
+ /**
4
+ * Instance — programmatic harness for spinning up a local Tangle dev environment.
5
+ *
6
+ * Wraps `cargo tangle harness up` as a child process and exposes health
7
+ * checks, log streaming, and a pre-configured {@link TCloudClient} pointed
8
+ * at the local stack.
9
+ *
10
+ * NODE-ONLY. This module uses `child_process` and `fs` and must not be
11
+ * imported in browser or edge runtime bundles. Always import from
12
+ * `@tangle-network/tcloud/instance`, never the package root.
13
+ *
14
+ * ## Example
15
+ *
16
+ * ```ts
17
+ * import { Instance } from '@tangle-network/tcloud/instance'
18
+ *
19
+ * const instance = await Instance.start({
20
+ * config: './harness.dev.toml',
21
+ * only: ['llm'],
22
+ * })
23
+ *
24
+ * const client = instance.client({ model: 'llama-3.1-8b' })
25
+ * const res = await client.chat({
26
+ * messages: [{ role: 'user', content: 'hello' }],
27
+ * })
28
+ * console.log(res.choices[0].message.content)
29
+ *
30
+ * await instance.stop()
31
+ * ```
32
+ */
33
+
34
+ interface InstanceOptions {
35
+ /** Path to harness config TOML. Defaults to ./harness.toml or ~/.tangle/harness.toml */
36
+ config?: string;
37
+ /** Only start these blueprints (subset) */
38
+ only?: string[];
39
+ /** Working directory for `cargo tangle harness` (default: current) */
40
+ cwd?: string;
41
+ /** Stream anvil logs (default false) */
42
+ includeAnvilLogs?: boolean;
43
+ /** Suppress stdout passthrough (default false) */
44
+ quiet?: boolean;
45
+ /** Startup timeout in ms — how long to wait for "Harness up" marker (default 300_000) */
46
+ timeoutMs?: number;
47
+ /** Router URL the instance exposes. Defaults to http://localhost:3000 */
48
+ routerUrl?: string;
49
+ /** Override the `cargo tangle` binary path. Defaults to resolving `cargo-tangle` from PATH */
50
+ cargoBinary?: string;
51
+ }
52
+ interface InstanceConfig {
53
+ /** URL of the router the instance is serving on */
54
+ routerUrl: string;
55
+ /** Names of blueprints that were started */
56
+ blueprints: string[];
57
+ }
58
+ /**
59
+ * Append a line to a bounded log buffer, evicting the oldest entry once the
60
+ * buffer exceeds `maxLines`. Exported for unit testing.
61
+ */
62
+ declare function appendLogLine(buffer: string[], line: string, maxLines?: number): void;
63
+ /**
64
+ * A running Tangle dev environment. Hold one per test file or dev session.
65
+ */
66
+ declare class Instance {
67
+ private child;
68
+ private _config;
69
+ private _stopped;
70
+ private logBuffer;
71
+ private readonly maxLogLines;
72
+ private constructor();
73
+ /**
74
+ * Start a new harness instance. Resolves once `cargo tangle harness up`
75
+ * prints its "Harness up" marker, indicating all blueprints are healthy.
76
+ *
77
+ * Throws on timeout, process exit before ready, or missing `cargo-tangle`.
78
+ */
79
+ static start(options?: InstanceOptions): Promise<Instance>;
80
+ /** URL of the router serving this instance */
81
+ get routerUrl(): string;
82
+ /** Names of blueprints that were started */
83
+ get blueprints(): string[];
84
+ /**
85
+ * Create a pre-configured {@link TCloudClient} pointed at this instance's router.
86
+ * Merges any passed config with the instance's routerUrl (instance wins).
87
+ */
88
+ client(config?: Partial<TCloudConfig>): TCloudClient;
89
+ /** Return the last N log lines from the harness */
90
+ logs(lines?: number): string[];
91
+ /** Whether the harness process is still running */
92
+ get isRunning(): boolean;
93
+ /**
94
+ * Stop the harness. Sends SIGTERM, waits up to 10s for clean shutdown,
95
+ * then SIGKILL.
96
+ */
97
+ stop(timeoutMs?: number): Promise<void>;
98
+ }
99
+ /**
100
+ * Write a temporary harness.toml with the given blueprints, for inline test configs.
101
+ * Returns the path to the created file. The caller is responsible for cleanup.
102
+ */
103
+ declare function writeTempHarnessConfig(blueprints: Array<{
104
+ name: string;
105
+ path: string;
106
+ port?: number;
107
+ env?: Record<string, string>;
108
+ }>): string;
109
+
110
+ export { Instance, type InstanceConfig, type InstanceOptions, appendLogLine, writeTempHarnessConfig };
@@ -0,0 +1,229 @@
1
+ import {
2
+ TCloudClient
3
+ } from "./chunk-HL4CXKET.js";
4
+
5
+ // src/instance.ts
6
+ import { spawn } from "child_process";
7
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
8
+ import { join } from "path";
9
+ import { tmpdir } from "os";
10
+ import { randomUUID } from "crypto";
11
+ var MAX_LOG_LINES = 1e4;
12
+ function appendLogLine(buffer, line, maxLines = MAX_LOG_LINES) {
13
+ buffer.push(line);
14
+ if (buffer.length > maxLines) {
15
+ buffer.shift();
16
+ }
17
+ }
18
+ var Instance = class _Instance {
19
+ child;
20
+ _config;
21
+ _stopped = false;
22
+ logBuffer;
23
+ maxLogLines = MAX_LOG_LINES;
24
+ constructor(child, config, logBuffer) {
25
+ this.child = child;
26
+ this._config = config;
27
+ this.logBuffer = logBuffer;
28
+ }
29
+ /**
30
+ * Start a new harness instance. Resolves once `cargo tangle harness up`
31
+ * prints its "Harness up" marker, indicating all blueprints are healthy.
32
+ *
33
+ * Throws on timeout, process exit before ready, or missing `cargo-tangle`.
34
+ */
35
+ static async start(options = {}) {
36
+ const cargoBinary = options.cargoBinary ?? "cargo-tangle";
37
+ const timeoutMs = options.timeoutMs ?? 3e5;
38
+ const routerUrl = options.routerUrl ?? "http://localhost:3000";
39
+ const args = ["tangle", "harness", "up"];
40
+ if (options.config) {
41
+ args.push("--config", options.config);
42
+ }
43
+ if (options.only && options.only.length > 0) {
44
+ args.push("--only", options.only.join(","));
45
+ }
46
+ if (options.includeAnvilLogs) {
47
+ args.push("--include-anvil-logs");
48
+ }
49
+ const child = spawn(cargoBinary, args, {
50
+ cwd: options.cwd ?? process.cwd(),
51
+ env: process.env,
52
+ stdio: ["ignore", "pipe", "pipe"]
53
+ });
54
+ const logBuffer = [];
55
+ const pushLog = (line) => {
56
+ appendLogLine(logBuffer, line, MAX_LOG_LINES);
57
+ if (!options.quiet) {
58
+ process.stdout.write(line + "\n");
59
+ }
60
+ };
61
+ let stdoutTail = "";
62
+ let stderrTail = "";
63
+ child.stdout?.on("data", (chunk) => {
64
+ stdoutTail += chunk.toString();
65
+ const lines = stdoutTail.split("\n");
66
+ stdoutTail = lines.pop() ?? "";
67
+ for (const line of lines) pushLog(line);
68
+ });
69
+ child.stderr?.on("data", (chunk) => {
70
+ stderrTail += chunk.toString();
71
+ const lines = stderrTail.split("\n");
72
+ stderrTail = lines.pop() ?? "";
73
+ for (const line of lines) pushLog(line);
74
+ });
75
+ const blueprintNames = [];
76
+ try {
77
+ await new Promise((resolve, reject) => {
78
+ let settled = false;
79
+ let timer;
80
+ let pollInterval;
81
+ const cleanup = () => {
82
+ if (settled) return;
83
+ settled = true;
84
+ if (pollInterval !== void 0) clearInterval(pollInterval);
85
+ if (timer !== void 0) clearTimeout(timer);
86
+ child.removeListener("exit", onExit);
87
+ child.removeListener("error", onError);
88
+ };
89
+ const onExit = (code) => {
90
+ if (settled) return;
91
+ cleanup();
92
+ reject(
93
+ new Error(
94
+ `cargo tangle harness exited with code ${code} before becoming ready. Last 20 lines:
95
+ ${logBuffer.slice(-20).join("\n")}`
96
+ )
97
+ );
98
+ };
99
+ const onError = (err) => {
100
+ if (settled) return;
101
+ cleanup();
102
+ reject(new Error(`Failed to spawn ${cargoBinary}: ${err.message}`));
103
+ };
104
+ timer = setTimeout(() => {
105
+ if (settled) return;
106
+ cleanup();
107
+ reject(
108
+ new Error(
109
+ `Timed out after ${timeoutMs}ms waiting for harness to start. Last 20 lines:
110
+ ${logBuffer.slice(-20).join("\n")}`
111
+ )
112
+ );
113
+ }, timeoutMs);
114
+ child.once("exit", onExit);
115
+ child.once("error", onError);
116
+ pollInterval = setInterval(() => {
117
+ for (const line of logBuffer) {
118
+ const match = line.match(/Harness up\.\s+(\d+)\s+blueprint/i);
119
+ if (match) {
120
+ if (settled) return;
121
+ cleanup();
122
+ resolve();
123
+ return;
124
+ }
125
+ const bpMatch = line.match(/Starting blueprint-manager for '([^']+)'/);
126
+ if (bpMatch) {
127
+ blueprintNames.push(bpMatch[1]);
128
+ }
129
+ }
130
+ }, 100);
131
+ });
132
+ } catch (err) {
133
+ if (!child.killed) {
134
+ child.kill("SIGTERM");
135
+ }
136
+ throw err;
137
+ }
138
+ return new _Instance(
139
+ child,
140
+ { routerUrl, blueprints: blueprintNames },
141
+ logBuffer
142
+ );
143
+ }
144
+ /** URL of the router serving this instance */
145
+ get routerUrl() {
146
+ return this._config.routerUrl;
147
+ }
148
+ /** Names of blueprints that were started */
149
+ get blueprints() {
150
+ return [...this._config.blueprints];
151
+ }
152
+ /**
153
+ * Create a pre-configured {@link TCloudClient} pointed at this instance's router.
154
+ * Merges any passed config with the instance's routerUrl (instance wins).
155
+ */
156
+ client(config = {}) {
157
+ return new TCloudClient({
158
+ ...config,
159
+ baseURL: this._config.routerUrl
160
+ });
161
+ }
162
+ /** Return the last N log lines from the harness */
163
+ logs(lines = 100) {
164
+ return this.logBuffer.slice(-lines);
165
+ }
166
+ /** Whether the harness process is still running */
167
+ get isRunning() {
168
+ return !this._stopped && !this.child.killed && this.child.exitCode === null;
169
+ }
170
+ /**
171
+ * Stop the harness. Sends SIGTERM, waits up to 10s for clean shutdown,
172
+ * then SIGKILL.
173
+ */
174
+ async stop(timeoutMs = 1e4) {
175
+ if (this._stopped) return;
176
+ this._stopped = true;
177
+ if (this.child.exitCode !== null) {
178
+ return;
179
+ }
180
+ this.child.kill("SIGTERM");
181
+ await new Promise((resolve) => {
182
+ const timer = setTimeout(() => {
183
+ if (this.child.exitCode === null) {
184
+ this.child.kill("SIGKILL");
185
+ }
186
+ resolve();
187
+ }, timeoutMs);
188
+ this.child.once("exit", () => {
189
+ clearTimeout(timer);
190
+ resolve();
191
+ });
192
+ });
193
+ }
194
+ };
195
+ function writeTempHarnessConfig(blueprints) {
196
+ const dir = join(
197
+ tmpdir(),
198
+ `tangle-harness-${process.pid}-${Date.now()}-${randomUUID()}`
199
+ );
200
+ if (!existsSync(dir)) {
201
+ mkdirSync(dir, { recursive: true });
202
+ }
203
+ const file = join(dir, "harness.toml");
204
+ const lines = [];
205
+ lines.push("[chain]");
206
+ lines.push("anvil = true");
207
+ lines.push("");
208
+ for (const bp of blueprints) {
209
+ lines.push("[[blueprint]]");
210
+ lines.push(`name = "${bp.name}"`);
211
+ lines.push(`path = "${bp.path}"`);
212
+ if (bp.port !== void 0) {
213
+ lines.push(`port = ${bp.port}`);
214
+ }
215
+ if (bp.env) {
216
+ for (const [k, v] of Object.entries(bp.env)) {
217
+ lines.push(`env.${k} = "${v.replace(/"/g, '\\"')}"`);
218
+ }
219
+ }
220
+ lines.push("");
221
+ }
222
+ writeFileSync(file, lines.join("\n"));
223
+ return file;
224
+ }
225
+ export {
226
+ Instance,
227
+ appendLogLine,
228
+ writeTempHarnessConfig
229
+ };