trickle-observe 0.2.105 → 0.2.106
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/dist/transport.js +67 -1
- package/package.json +1 -1
- package/src/transport.ts +67 -1
package/dist/transport.js
CHANGED
|
@@ -54,6 +54,13 @@ let localFilePath = '';
|
|
|
54
54
|
let queue = [];
|
|
55
55
|
let flushTimer = null;
|
|
56
56
|
let isFlushing = false;
|
|
57
|
+
// Cloud streaming: when TRICKLE_CLOUD_URL + TRICKLE_CLOUD_TOKEN are set,
|
|
58
|
+
// observations are also streamed to the cloud backend in real-time.
|
|
59
|
+
let cloudUrl = process.env.TRICKLE_CLOUD_URL || '';
|
|
60
|
+
let cloudToken = process.env.TRICKLE_CLOUD_TOKEN || '';
|
|
61
|
+
let cloudProject = '';
|
|
62
|
+
let cloudBuffer = [];
|
|
63
|
+
let cloudFlushTimer = null;
|
|
57
64
|
/**
|
|
58
65
|
* Configure the transport layer with global options.
|
|
59
66
|
*/
|
|
@@ -63,6 +70,34 @@ function configure(opts) {
|
|
|
63
70
|
maxBatchSize = opts.maxBatchSize || DEFAULT_MAX_BATCH_SIZE;
|
|
64
71
|
enabled = opts.enabled !== false;
|
|
65
72
|
debug = opts.debug === true;
|
|
73
|
+
// Load cloud config from ~/.trickle/cloud.json if env vars not set
|
|
74
|
+
if (!cloudUrl || !cloudToken) {
|
|
75
|
+
try {
|
|
76
|
+
const configPath = pathMod.join(process.env.HOME || '~', '.trickle', 'cloud.json');
|
|
77
|
+
if (fs.existsSync(configPath)) {
|
|
78
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
79
|
+
if (!cloudUrl && config.url)
|
|
80
|
+
cloudUrl = config.url;
|
|
81
|
+
if (!cloudToken && config.token)
|
|
82
|
+
cloudToken = config.token;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch { }
|
|
86
|
+
}
|
|
87
|
+
cloudProject = process.env.TRICKLE_CLOUD_PROJECT || pathMod.basename(process.cwd());
|
|
88
|
+
// Start cloud streaming if configured
|
|
89
|
+
if (cloudUrl && cloudToken && !cloudFlushTimer) {
|
|
90
|
+
cloudFlushTimer = setInterval(() => {
|
|
91
|
+
flushCloud().catch(() => { });
|
|
92
|
+
}, 5000); // Flush to cloud every 5 seconds
|
|
93
|
+
if (cloudFlushTimer.unref)
|
|
94
|
+
cloudFlushTimer.unref();
|
|
95
|
+
if (debug) {
|
|
96
|
+
console.log(`[trickle] Cloud streaming enabled → ${cloudUrl}`);
|
|
97
|
+
}
|
|
98
|
+
// Flush cloud buffer on exit
|
|
99
|
+
process.on('beforeExit', () => { flushCloud().catch(() => { }); });
|
|
100
|
+
}
|
|
66
101
|
// Check for local/file-based mode
|
|
67
102
|
if (process.env.TRICKLE_LOCAL === '1') {
|
|
68
103
|
localMode = true;
|
|
@@ -93,7 +128,12 @@ function enqueue(payload) {
|
|
|
93
128
|
// Local file mode: append directly to JSONL file
|
|
94
129
|
if (localMode && localFilePath) {
|
|
95
130
|
try {
|
|
96
|
-
|
|
131
|
+
const line = JSON.stringify(payload) + '\n';
|
|
132
|
+
fs.appendFileSync(localFilePath, line);
|
|
133
|
+
// Also buffer for cloud if configured
|
|
134
|
+
if (cloudUrl && cloudToken) {
|
|
135
|
+
cloudBuffer.push(line);
|
|
136
|
+
}
|
|
97
137
|
}
|
|
98
138
|
catch {
|
|
99
139
|
// Never crash user's app
|
|
@@ -200,6 +240,32 @@ function stopTimer() {
|
|
|
200
240
|
function sleep(ms) {
|
|
201
241
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
202
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Flush buffered observations to the cloud backend.
|
|
245
|
+
*/
|
|
246
|
+
async function flushCloud() {
|
|
247
|
+
if (cloudBuffer.length === 0 || !cloudUrl || !cloudToken)
|
|
248
|
+
return;
|
|
249
|
+
const lines = cloudBuffer.splice(0);
|
|
250
|
+
try {
|
|
251
|
+
await fetch(`${cloudUrl}/api/v1/ingest`, {
|
|
252
|
+
method: 'POST',
|
|
253
|
+
headers: {
|
|
254
|
+
'Content-Type': 'application/json',
|
|
255
|
+
'Authorization': `Bearer ${cloudToken}`,
|
|
256
|
+
},
|
|
257
|
+
body: JSON.stringify({
|
|
258
|
+
project: cloudProject,
|
|
259
|
+
file: 'observations.jsonl',
|
|
260
|
+
lines: lines.join(''),
|
|
261
|
+
}),
|
|
262
|
+
signal: AbortSignal.timeout(10000),
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
// Silent — never crash user's app. Data is still saved locally.
|
|
267
|
+
}
|
|
268
|
+
}
|
|
203
269
|
function silentError() {
|
|
204
270
|
// Intentionally empty — never crash user's app
|
|
205
271
|
}
|
package/package.json
CHANGED
package/src/transport.ts
CHANGED
|
@@ -19,6 +19,14 @@ let queue: IngestPayload[] = [];
|
|
|
19
19
|
let flushTimer: ReturnType<typeof setInterval> | null = null;
|
|
20
20
|
let isFlushing = false;
|
|
21
21
|
|
|
22
|
+
// Cloud streaming: when TRICKLE_CLOUD_URL + TRICKLE_CLOUD_TOKEN are set,
|
|
23
|
+
// observations are also streamed to the cloud backend in real-time.
|
|
24
|
+
let cloudUrl = process.env.TRICKLE_CLOUD_URL || '';
|
|
25
|
+
let cloudToken = process.env.TRICKLE_CLOUD_TOKEN || '';
|
|
26
|
+
let cloudProject = '';
|
|
27
|
+
let cloudBuffer: string[] = [];
|
|
28
|
+
let cloudFlushTimer: ReturnType<typeof setInterval> | null = null;
|
|
29
|
+
|
|
22
30
|
/**
|
|
23
31
|
* Configure the transport layer with global options.
|
|
24
32
|
*/
|
|
@@ -29,6 +37,33 @@ export function configure(opts: GlobalOpts): void {
|
|
|
29
37
|
enabled = opts.enabled !== false;
|
|
30
38
|
debug = opts.debug === true;
|
|
31
39
|
|
|
40
|
+
// Load cloud config from ~/.trickle/cloud.json if env vars not set
|
|
41
|
+
if (!cloudUrl || !cloudToken) {
|
|
42
|
+
try {
|
|
43
|
+
const configPath = pathMod.join(process.env.HOME || '~', '.trickle', 'cloud.json');
|
|
44
|
+
if (fs.existsSync(configPath)) {
|
|
45
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
46
|
+
if (!cloudUrl && config.url) cloudUrl = config.url;
|
|
47
|
+
if (!cloudToken && config.token) cloudToken = config.token;
|
|
48
|
+
}
|
|
49
|
+
} catch {}
|
|
50
|
+
}
|
|
51
|
+
cloudProject = process.env.TRICKLE_CLOUD_PROJECT || pathMod.basename(process.cwd());
|
|
52
|
+
|
|
53
|
+
// Start cloud streaming if configured
|
|
54
|
+
if (cloudUrl && cloudToken && !cloudFlushTimer) {
|
|
55
|
+
cloudFlushTimer = setInterval(() => {
|
|
56
|
+
flushCloud().catch(() => {});
|
|
57
|
+
}, 5000); // Flush to cloud every 5 seconds
|
|
58
|
+
if (cloudFlushTimer.unref) cloudFlushTimer.unref();
|
|
59
|
+
if (debug) {
|
|
60
|
+
console.log(`[trickle] Cloud streaming enabled → ${cloudUrl}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Flush cloud buffer on exit
|
|
64
|
+
process.on('beforeExit', () => { flushCloud().catch(() => {}); });
|
|
65
|
+
}
|
|
66
|
+
|
|
32
67
|
// Check for local/file-based mode
|
|
33
68
|
if (process.env.TRICKLE_LOCAL === '1') {
|
|
34
69
|
localMode = true;
|
|
@@ -58,7 +93,12 @@ export function enqueue(payload: IngestPayload): void {
|
|
|
58
93
|
// Local file mode: append directly to JSONL file
|
|
59
94
|
if (localMode && localFilePath) {
|
|
60
95
|
try {
|
|
61
|
-
|
|
96
|
+
const line = JSON.stringify(payload) + '\n';
|
|
97
|
+
fs.appendFileSync(localFilePath, line);
|
|
98
|
+
// Also buffer for cloud if configured
|
|
99
|
+
if (cloudUrl && cloudToken) {
|
|
100
|
+
cloudBuffer.push(line);
|
|
101
|
+
}
|
|
62
102
|
} catch {
|
|
63
103
|
// Never crash user's app
|
|
64
104
|
}
|
|
@@ -175,6 +215,32 @@ function sleep(ms: number): Promise<void> {
|
|
|
175
215
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
176
216
|
}
|
|
177
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Flush buffered observations to the cloud backend.
|
|
220
|
+
*/
|
|
221
|
+
async function flushCloud(): Promise<void> {
|
|
222
|
+
if (cloudBuffer.length === 0 || !cloudUrl || !cloudToken) return;
|
|
223
|
+
|
|
224
|
+
const lines = cloudBuffer.splice(0);
|
|
225
|
+
try {
|
|
226
|
+
await fetch(`${cloudUrl}/api/v1/ingest`, {
|
|
227
|
+
method: 'POST',
|
|
228
|
+
headers: {
|
|
229
|
+
'Content-Type': 'application/json',
|
|
230
|
+
'Authorization': `Bearer ${cloudToken}`,
|
|
231
|
+
},
|
|
232
|
+
body: JSON.stringify({
|
|
233
|
+
project: cloudProject,
|
|
234
|
+
file: 'observations.jsonl',
|
|
235
|
+
lines: lines.join(''),
|
|
236
|
+
}),
|
|
237
|
+
signal: AbortSignal.timeout(10000),
|
|
238
|
+
});
|
|
239
|
+
} catch {
|
|
240
|
+
// Silent — never crash user's app. Data is still saved locally.
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
178
244
|
function silentError(): void {
|
|
179
245
|
// Intentionally empty — never crash user's app
|
|
180
246
|
}
|