@nextdog/core 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Christopher Scherban
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,50 @@
1
+ import { createServer } from './server.js';
2
+ import { homedir } from 'node:os';
3
+ import { join, dirname } from 'node:path';
4
+ import { stat } from 'node:fs/promises';
5
+ import { createRequire } from 'node:module';
6
+ const DEFAULT_PORT = 6789;
7
+ const DEFAULT_DATA_DIR = join(homedir(), '.nextdog', 'data');
8
+ async function resolveUiDir() {
9
+ try {
10
+ const require = createRequire(import.meta.url);
11
+ const uiPkgPath = require.resolve('@nextdog/ui/package.json');
12
+ const uiDir = join(dirname(uiPkgPath), 'dist');
13
+ const s = await stat(uiDir);
14
+ if (s.isDirectory())
15
+ return uiDir;
16
+ }
17
+ catch {
18
+ // UI package not installed or not built
19
+ }
20
+ return undefined;
21
+ }
22
+ async function main() {
23
+ const url = process.env.NEXTDOG_URL ?? `http://localhost:${DEFAULT_PORT}`;
24
+ const parsed = new URL(url);
25
+ const port = Number(parsed.port) || DEFAULT_PORT;
26
+ const host = parsed.hostname;
27
+ const dataDir = process.env.NEXTDOG_DATA_DIR ?? DEFAULT_DATA_DIR;
28
+ const uiDir = process.env.NEXTDOG_UI_DIR ?? await resolveUiDir();
29
+ const server = await createServer({ port, host, dataDir, uiDir });
30
+ console.log(`[nextdog] sidecar running at http://${host}:${port}`);
31
+ console.log(`[nextdog] data dir: ${dataDir}`);
32
+ if (uiDir) {
33
+ console.log(`[nextdog] UI served from: ${uiDir}`);
34
+ }
35
+ else {
36
+ console.log(`[nextdog] UI not available (run pnpm build in @nextdog/ui)`);
37
+ }
38
+ process.on('SIGINT', () => {
39
+ console.log('\n[nextdog] shutting down...');
40
+ server.close(() => process.exit(0));
41
+ });
42
+ process.on('SIGTERM', () => {
43
+ server.close(() => process.exit(0));
44
+ });
45
+ }
46
+ main().catch(err => {
47
+ console.error('[nextdog] failed to start:', err);
48
+ process.exit(1);
49
+ });
50
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;AAE7D,KAAK,UAAU,YAAY;IACzB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC;QAC9D,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC;QAC/C,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,CAAC,CAAC,WAAW,EAAE;YAAE,OAAO,KAAK,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,wCAAwC;IAC1C,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,oBAAoB,YAAY,EAAE,CAAC;IAC1E,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC;IACjD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC;IAC7B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,gBAAgB,CAAC;IACjE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,MAAM,YAAY,EAAE,CAAC;IAEjE,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,OAAO,CAAC,GAAG,CAAC,uCAAuC,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,uBAAuB,OAAO,EAAE,CAAC,CAAC;IAC9C,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,CAAC,GAAG,CAAC,6BAA6B,KAAK,EAAE,CAAC,CAAC;IACpD,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,4DAA4D,CAAC,CAAC;IAC5E,CAAC;IAED,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;QAC5C,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACzB,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;IACjB,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,GAAG,CAAC,CAAC;IACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { NextDogEvent } from './types.js';
2
+ type EventType = NextDogEvent['type'] | '*';
3
+ type EventHandler = (event: NextDogEvent) => void;
4
+ export declare class EventBus {
5
+ private emitter;
6
+ on(type: EventType, handler: EventHandler): () => void;
7
+ emit(event: NextDogEvent): void;
8
+ }
9
+ export {};
10
+ //# sourceMappingURL=event-bus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../src/event-bus.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,KAAK,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC;AAC5C,KAAK,YAAY,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;AAElD,qBAAa,QAAQ;IACnB,OAAO,CAAC,OAAO,CAAsB;IAErC,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM,IAAI;IAKtD,IAAI,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;CAIhC"}
@@ -0,0 +1,13 @@
1
+ import { EventEmitter } from 'node:events';
2
+ export class EventBus {
3
+ emitter = new EventEmitter();
4
+ on(type, handler) {
5
+ this.emitter.on(type, handler);
6
+ return () => this.emitter.off(type, handler);
7
+ }
8
+ emit(event) {
9
+ this.emitter.emit(event.type, event);
10
+ this.emitter.emit('*', event);
11
+ }
12
+ }
13
+ //# sourceMappingURL=event-bus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-bus.js","sourceRoot":"","sources":["../src/event-bus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAM3C,MAAM,OAAO,QAAQ;IACX,OAAO,GAAG,IAAI,YAAY,EAAE,CAAC;IAErC,EAAE,CAAC,IAAe,EAAE,OAAqB;QACvC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC/B,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;IAED,IAAI,CAAC,KAAmB;QACtB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACrC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAChC,CAAC;CACF"}
@@ -0,0 +1,14 @@
1
+ import type { NextDogEvent } from './types.js';
2
+ export interface QueryOptions {
3
+ service?: string;
4
+ traceId?: string;
5
+ last?: number;
6
+ }
7
+ export declare class FileStore {
8
+ private dir;
9
+ constructor(dir: string);
10
+ flush(events: NextDogEvent[]): Promise<void>;
11
+ query(opts: QueryOptions): Promise<NextDogEvent[]>;
12
+ cleanup(maxAgeMs: number): Promise<void>;
13
+ }
14
+ //# sourceMappingURL=file-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAyB/C,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,SAAS;IACR,OAAO,CAAC,GAAG;gBAAH,GAAG,EAAE,MAAM;IAEzB,KAAK,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ5C,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAwBlD,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAoB/C"}
@@ -0,0 +1,74 @@
1
+ import { appendFile, readdir, readFile, unlink, mkdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ function hourlyFilename(date = new Date()) {
4
+ const y = date.getFullYear();
5
+ const m = String(date.getMonth() + 1).padStart(2, '0');
6
+ const d = String(date.getDate()).padStart(2, '0');
7
+ const h = String(date.getHours()).padStart(2, '0');
8
+ return `${y}-${m}-${d}-${h}.ndjson`;
9
+ }
10
+ function serialize(event) {
11
+ return JSON.stringify(event, (_key, value) => typeof value === 'bigint' ? value.toString() + 'n' : value);
12
+ }
13
+ function deserialize(line) {
14
+ return JSON.parse(line, (_key, value) => {
15
+ if (typeof value === 'string' && /^\d+n$/.test(value)) {
16
+ return BigInt(value.slice(0, -1));
17
+ }
18
+ return value;
19
+ });
20
+ }
21
+ export class FileStore {
22
+ dir;
23
+ constructor(dir) {
24
+ this.dir = dir;
25
+ }
26
+ async flush(events) {
27
+ if (events.length === 0)
28
+ return;
29
+ await mkdir(this.dir, { recursive: true });
30
+ const filename = hourlyFilename();
31
+ const lines = events.map(e => serialize(e)).join('\n') + '\n';
32
+ await appendFile(join(this.dir, filename), lines, 'utf-8');
33
+ }
34
+ async query(opts) {
35
+ await mkdir(this.dir, { recursive: true });
36
+ const files = (await readdir(this.dir))
37
+ .filter(f => f.endsWith('.ndjson'))
38
+ .sort();
39
+ const results = [];
40
+ for (const file of files) {
41
+ const content = await readFile(join(this.dir, file), 'utf-8');
42
+ const lines = content.trim().split('\n').filter(Boolean);
43
+ for (const line of lines) {
44
+ const event = deserialize(line);
45
+ if (opts.service && event.data.serviceName !== opts.service)
46
+ continue;
47
+ if (opts.traceId && ('traceId' in event.data) && event.data.traceId !== opts.traceId)
48
+ continue;
49
+ results.push(event);
50
+ }
51
+ }
52
+ if (opts.last)
53
+ return results.slice(-opts.last);
54
+ return results;
55
+ }
56
+ async cleanup(maxAgeMs) {
57
+ await mkdir(this.dir, { recursive: true });
58
+ const files = await readdir(this.dir);
59
+ const now = Date.now();
60
+ for (const file of files) {
61
+ if (!file.endsWith('.ndjson'))
62
+ continue;
63
+ // Parse date from filename: YYYY-MM-DD-HH.ndjson
64
+ const match = file.match(/^(\d{4})-(\d{2})-(\d{2})-(\d{2})\.ndjson$/);
65
+ if (!match)
66
+ continue;
67
+ const fileDate = new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]), Number(match[4]));
68
+ if (now - fileDate.getTime() > maxAgeMs) {
69
+ await unlink(join(this.dir, file));
70
+ }
71
+ }
72
+ }
73
+ }
74
+ //# sourceMappingURL=file-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-store.js","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export { EventBus } from './event-bus.js';
2
+ export { RingBuffer } from './ring-buffer.js';
3
+ export { FileStore } from './file-store.js';
4
+ export { SSEStream } from './sse-stream.js';
5
+ export { createServer } from './server.js';
6
+ export type { Span, LogEntry, NextDogEvent } from './types.js';
7
+ export type { QueryOptions } from './file-store.js';
8
+ export type { ServerOptions } from './server.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/D,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { EventBus } from './event-bus.js';
2
+ export { RingBuffer } from './ring-buffer.js';
3
+ export { FileStore } from './file-store.js';
4
+ export { SSEStream } from './sse-stream.js';
5
+ export { createServer } from './server.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { NextDogEvent } from './types.js';
2
+ export declare class RingBuffer {
3
+ private capacity;
4
+ private buffer;
5
+ private head;
6
+ private count;
7
+ private pending;
8
+ constructor(capacity: number);
9
+ push(event: NextDogEvent): void;
10
+ getAll(): NextDogEvent[];
11
+ getLast(n: number): NextDogEvent[];
12
+ drain(): NextDogEvent[];
13
+ }
14
+ //# sourceMappingURL=ring-buffer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ring-buffer.d.ts","sourceRoot":"","sources":["../src/ring-buffer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,qBAAa,UAAU;IAMT,OAAO,CAAC,QAAQ;IAL5B,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,OAAO,CAAsB;gBAEjB,QAAQ,EAAE,MAAM;IAIpC,IAAI,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAO/B,MAAM,IAAI,YAAY,EAAE;IAWxB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,YAAY,EAAE;IAKlC,KAAK,IAAI,YAAY,EAAE;CAKxB"}
@@ -0,0 +1,39 @@
1
+ export class RingBuffer {
2
+ capacity;
3
+ buffer;
4
+ head = 0;
5
+ count = 0;
6
+ pending = [];
7
+ constructor(capacity) {
8
+ this.capacity = capacity;
9
+ this.buffer = new Array(capacity);
10
+ }
11
+ push(event) {
12
+ this.buffer[this.head] = event;
13
+ this.head = (this.head + 1) % this.capacity;
14
+ if (this.count < this.capacity)
15
+ this.count++;
16
+ this.pending.push(event);
17
+ }
18
+ getAll() {
19
+ if (this.count === 0)
20
+ return [];
21
+ const result = [];
22
+ const start = this.count < this.capacity ? 0 : this.head;
23
+ for (let i = 0; i < this.count; i++) {
24
+ const idx = (start + i) % this.capacity;
25
+ result.push(this.buffer[idx]);
26
+ }
27
+ return result;
28
+ }
29
+ getLast(n) {
30
+ const all = this.getAll();
31
+ return all.slice(-Math.min(n, all.length));
32
+ }
33
+ drain() {
34
+ const drained = this.pending;
35
+ this.pending = [];
36
+ return drained;
37
+ }
38
+ }
39
+ //# sourceMappingURL=ring-buffer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ring-buffer.js","sourceRoot":"","sources":["../src/ring-buffer.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,UAAU;IAMD;IALZ,MAAM,CAA+B;IACrC,IAAI,GAAG,CAAC,CAAC;IACT,KAAK,GAAG,CAAC,CAAC;IACV,OAAO,GAAmB,EAAE,CAAC;IAErC,YAAoB,QAAgB;QAAhB,aAAQ,GAAR,QAAQ,CAAQ;QAClC,IAAI,CAAC,MAAM,GAAG,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED,IAAI,CAAC,KAAmB;QACtB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC5C,IAAI,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAC,KAAK,EAAE,CAAC;QAC7C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IAED,MAAM;QACJ,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAChC,MAAM,MAAM,GAAmB,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QACzD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;YACpC,MAAM,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;YACxC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAE,CAAC,CAAC;QACjC,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,CAAC,CAAS;QACf,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC1B,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7C,CAAC;IAED,KAAK;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;QAClB,OAAO,OAAO,CAAC;IACjB,CAAC;CACF"}
@@ -0,0 +1,9 @@
1
+ import { type Server } from 'node:http';
2
+ export interface ServerOptions {
3
+ port: number;
4
+ host?: string;
5
+ dataDir: string;
6
+ uiDir?: string;
7
+ }
8
+ export declare function createServer(opts: ServerOptions): Promise<Server>;
9
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,MAAM,EAA6C,MAAM,WAAW,CAAC;AASrH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA0CD,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAsKjE"}
package/dist/server.js ADDED
@@ -0,0 +1,193 @@
1
+ import { createServer as httpCreateServer } from 'node:http';
2
+ import { readFile, stat } from 'node:fs/promises';
3
+ import { extname, join } from 'node:path';
4
+ import { EventBus } from './event-bus.js';
5
+ import { RingBuffer } from './ring-buffer.js';
6
+ import { FileStore } from './file-store.js';
7
+ import { SSEStream } from './sse-stream.js';
8
+ const MIME_TYPES = {
9
+ '.html': 'text/html',
10
+ '.js': 'application/javascript',
11
+ '.css': 'text/css',
12
+ '.json': 'application/json',
13
+ '.svg': 'image/svg+xml',
14
+ '.png': 'image/png',
15
+ '.ico': 'image/x-icon',
16
+ };
17
+ function readBody(req) {
18
+ return new Promise((resolve, reject) => {
19
+ const chunks = [];
20
+ req.on('data', chunk => chunks.push(chunk));
21
+ req.on('end', () => resolve(Buffer.concat(chunks).toString()));
22
+ req.on('error', reject);
23
+ });
24
+ }
25
+ function json(res, status, data) {
26
+ const body = JSON.stringify(data, (_key, value) => typeof value === 'bigint' ? value.toString() + 'n' : value);
27
+ res.writeHead(status, {
28
+ 'Content-Type': 'application/json',
29
+ 'Access-Control-Allow-Origin': '*',
30
+ });
31
+ res.end(body);
32
+ }
33
+ function cors(res) {
34
+ res.writeHead(204, {
35
+ 'Access-Control-Allow-Origin': '*',
36
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
37
+ 'Access-Control-Allow-Headers': 'Content-Type',
38
+ 'Content-Length': '0',
39
+ });
40
+ res.end();
41
+ }
42
+ export function createServer(opts) {
43
+ const bus = new EventBus();
44
+ const ringBuffer = new RingBuffer(500);
45
+ const fileStore = new FileStore(opts.dataDir);
46
+ const sseStream = new SSEStream(ringBuffer);
47
+ const services = new Set();
48
+ // Wire EventBus subscribers
49
+ bus.on('*', (event) => {
50
+ ringBuffer.push(event);
51
+ sseStream.broadcast(event);
52
+ });
53
+ // Periodic flush to disk
54
+ let flushTimer;
55
+ const startFlushing = () => {
56
+ flushTimer = setInterval(async () => {
57
+ const events = ringBuffer.drain();
58
+ if (events.length > 0) {
59
+ await fileStore.flush(events);
60
+ }
61
+ }, 2000);
62
+ flushTimer.unref();
63
+ };
64
+ // Periodic cleanup of old files (every hour)
65
+ let cleanupTimer;
66
+ const startCleanup = () => {
67
+ const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
68
+ cleanupTimer = setInterval(() => {
69
+ fileStore.cleanup(TWENTY_FOUR_HOURS);
70
+ }, 60 * 60 * 1000);
71
+ cleanupTimer.unref();
72
+ };
73
+ const server = httpCreateServer(async (req, res) => {
74
+ const url = new URL(req.url ?? '/', `http://${req.headers.host}`);
75
+ const { pathname } = url;
76
+ if (req.method === 'OPTIONS') {
77
+ return cors(res);
78
+ }
79
+ // Health check
80
+ if (req.method === 'GET' && pathname === '/health') {
81
+ return json(res, 200, { status: 'ok', uptime: process.uptime() });
82
+ }
83
+ // Ingest spans
84
+ if (req.method === 'POST' && pathname === '/v1/spans') {
85
+ const body = JSON.parse(await readBody(req));
86
+ const spans = (body.spans ?? []).map((s) => ({
87
+ ...s,
88
+ startTimeUnixNano: BigInt(s.startTimeUnixNano),
89
+ endTimeUnixNano: BigInt(s.endTimeUnixNano),
90
+ }));
91
+ for (const span of spans) {
92
+ services.add(span.serviceName);
93
+ const event = {
94
+ type: 'span',
95
+ timestamp: Date.now(),
96
+ data: span,
97
+ };
98
+ bus.emit(event);
99
+ }
100
+ return json(res, 202, { accepted: spans.length });
101
+ }
102
+ // Ingest logs
103
+ if (req.method === 'POST' && pathname === '/v1/logs') {
104
+ const body = JSON.parse(await readBody(req));
105
+ const logs = body.logs ?? [];
106
+ for (const log of logs) {
107
+ const event = log.type === 'log'
108
+ ? log
109
+ : { type: 'log', timestamp: Date.now(), data: log };
110
+ if (event.data.serviceName)
111
+ services.add(event.data.serviceName);
112
+ bus.emit(event);
113
+ }
114
+ return json(res, 202, { accepted: logs.length });
115
+ }
116
+ // Query spans
117
+ if (req.method === 'GET' && pathname === '/api/spans') {
118
+ const service = url.searchParams.get('service') ?? undefined;
119
+ const traceId = url.searchParams.get('traceId') ?? undefined;
120
+ const last = url.searchParams.has('last')
121
+ ? Number(url.searchParams.get('last'))
122
+ : undefined;
123
+ // Serve from ring buffer for recent queries, file store for deeper
124
+ if (last && last <= 500 && !service && !traceId) {
125
+ return json(res, 200, { spans: ringBuffer.getLast(last) });
126
+ }
127
+ const results = await fileStore.query({ service, traceId, last });
128
+ return json(res, 200, { spans: results });
129
+ }
130
+ // List services
131
+ if (req.method === 'GET' && pathname === '/api/services') {
132
+ return json(res, 200, { services: [...services] });
133
+ }
134
+ // SSE live tail
135
+ if (req.method === 'GET' && pathname === '/sse') {
136
+ sseStream.addClient(res);
137
+ req.on('close', () => sseStream.removeClient(res));
138
+ return;
139
+ }
140
+ // Static file serving + SPA fallback
141
+ if (opts.uiDir) {
142
+ const filePath = join(opts.uiDir, pathname);
143
+ try {
144
+ const fileStat = await stat(filePath);
145
+ if (fileStat.isFile()) {
146
+ const ext = extname(filePath);
147
+ const contentType = MIME_TYPES[ext] ?? 'application/octet-stream';
148
+ const cacheControl = ext === '.html'
149
+ ? 'no-cache'
150
+ : 'public, max-age=31536000, immutable';
151
+ const content = await readFile(filePath);
152
+ res.writeHead(200, {
153
+ 'Content-Type': contentType,
154
+ 'Cache-Control': cacheControl,
155
+ });
156
+ return res.end(content);
157
+ }
158
+ }
159
+ catch {
160
+ // File not found — fall through
161
+ }
162
+ // SPA fallback: non-API, non-v1 routes serve index.html
163
+ if (!pathname.startsWith('/api/') && !pathname.startsWith('/v1/')) {
164
+ try {
165
+ const indexPath = join(opts.uiDir, 'index.html');
166
+ const content = await readFile(indexPath);
167
+ res.writeHead(200, {
168
+ 'Content-Type': 'text/html',
169
+ 'Cache-Control': 'no-cache',
170
+ });
171
+ return res.end(content);
172
+ }
173
+ catch {
174
+ // index.html missing — fall through to 404
175
+ }
176
+ }
177
+ }
178
+ // 404
179
+ json(res, 404, { error: 'not found' });
180
+ });
181
+ startFlushing();
182
+ startCleanup();
183
+ server.on('close', () => {
184
+ if (flushTimer)
185
+ clearInterval(flushTimer);
186
+ if (cleanupTimer)
187
+ clearInterval(cleanupTimer);
188
+ });
189
+ return new Promise(resolve => {
190
+ server.listen(opts.port, opts.host ?? '127.0.0.1', () => resolve(server));
191
+ });
192
+ }
193
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,IAAI,gBAAgB,EAA0D,MAAM,WAAW,CAAC;AACrH,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAU5C,MAAM,UAAU,GAA2B;IACzC,OAAO,EAAE,WAAW;IACpB,KAAK,EAAE,wBAAwB;IAC/B,MAAM,EAAE,UAAU;IAClB,OAAO,EAAE,kBAAkB;IAC3B,MAAM,EAAE,eAAe;IACvB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,cAAc;CACvB,CAAC;AAEF,SAAS,QAAQ,CAAC,GAAoB;IACpC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5C,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC/D,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,IAAI,CAAC,GAAmB,EAAE,MAAc,EAAE,IAAa;IAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAChD,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,CAC3D,CAAC;IACF,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE;QACpB,cAAc,EAAE,kBAAkB;QAClC,6BAA6B,EAAE,GAAG;KACnC,CAAC,CAAC;IACH,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED,SAAS,IAAI,CAAC,GAAmB;IAC/B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;QACjB,6BAA6B,EAAE,GAAG;QAClC,8BAA8B,EAAE,oBAAoB;QACpD,8BAA8B,EAAE,cAAc;QAC9C,gBAAgB,EAAE,GAAG;KACtB,CAAC,CAAC;IACH,GAAG,CAAC,GAAG,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAmB;IAC9C,MAAM,GAAG,GAAG,IAAI,QAAQ,EAAE,CAAC;IAC3B,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC9C,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC,UAAU,CAAC,CAAC;IAC5C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IAEnC,4BAA4B;IAC5B,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE;QACpB,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvB,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,yBAAyB;IACzB,IAAI,UAAsD,CAAC;IAC3D,MAAM,aAAa,GAAG,GAAG,EAAE;QACzB,UAAU,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;YAClC,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,EAAE,CAAC;YAClC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtB,MAAM,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAChC,CAAC;QACH,CAAC,EAAE,IAAI,CAAC,CAAC;QACT,UAAU,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC,CAAC;IAEF,6CAA6C;IAC7C,IAAI,YAAwD,CAAC;IAC7D,MAAM,YAAY,GAAG,GAAG,EAAE;QACxB,MAAM,iBAAiB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;QAC9C,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE;YAC9B,SAAS,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACvC,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QACnB,YAAY,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACjD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,UAAU,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;QAClE,MAAM,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC;QAEzB,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;QAED,eAAe;QACf,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YACnD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACpE,CAAC;QAED,eAAe;QACf,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;YACtD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7C,MAAM,KAAK,GAAW,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAA0B,EAAE,EAAE,CAAC,CAAC;gBAC5E,GAAG,CAAC;gBACJ,iBAAiB,EAAE,MAAM,CAAC,CAAC,CAAC,iBAA2B,CAAC;gBACxD,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC,eAAyB,CAAC;aACrD,CAAC,CAAC,CAAC;YAEJ,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;gBAC/B,MAAM,KAAK,GAAiB;oBAC1B,IAAI,EAAE,MAAM;oBACZ,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;oBACrB,IAAI,EAAE,IAAI;iBACX,CAAC;gBACF,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAClB,CAAC;YACD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QACpD,CAAC;QAED,cAAc;QACd,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,QAAQ,KAAK,UAAU,EAAE,CAAC;YACrD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;YAE7B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;gBACvB,MAAM,KAAK,GAAiB,GAAG,CAAC,IAAI,KAAK,KAAK;oBAC5C,CAAC,CAAC,GAAmB;oBACrB,CAAC,CAAC,EAAE,IAAI,EAAE,KAAc,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;gBAC/D,IAAI,KAAK,CAAC,IAAI,CAAC,WAAW;oBAAE,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;gBACjE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAClB,CAAC;YACD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QACnD,CAAC;QAED,cAAc;QACd,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,QAAQ,KAAK,YAAY,EAAE,CAAC;YACtD,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC;YAC7D,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC;YAC7D,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC;gBACvC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBACtC,CAAC,CAAC,SAAS,CAAC;YAEd,mEAAmE;YACnE,IAAI,IAAI,IAAI,IAAI,IAAI,GAAG,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;gBAChD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC7D,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YAClE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QAC5C,CAAC;QAED,gBAAgB;QAChB,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,QAAQ,KAAK,eAAe,EAAE,CAAC;YACzD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;QACrD,CAAC;QAED,gBAAgB;QAChB,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;YAChD,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACzB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC;YACnD,OAAO;QACT,CAAC;QAED,qCAAqC;QACrC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;YAC5C,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACtC,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;oBACtB,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;oBAC9B,MAAM,WAAW,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAC;oBAClE,MAAM,YAAY,GAAG,GAAG,KAAK,OAAO;wBAClC,CAAC,CAAC,UAAU;wBACZ,CAAC,CAAC,qCAAqC,CAAC;oBAC1C,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;oBACzC,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;wBACjB,cAAc,EAAE,WAAW;wBAC3B,eAAe,EAAE,YAAY;qBAC9B,CAAC,CAAC;oBACH,OAAO,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBAC1B,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,gCAAgC;YAClC,CAAC;YAED,wDAAwD;YACxD,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;gBAClE,IAAI,CAAC;oBACH,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;oBACjD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,CAAC;oBAC1C,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;wBACjB,cAAc,EAAE,WAAW;wBAC3B,eAAe,EAAE,UAAU;qBAC5B,CAAC,CAAC;oBACH,OAAO,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBAC1B,CAAC;gBAAC,MAAM,CAAC;oBACP,2CAA2C;gBAC7C,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM;QACN,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,aAAa,EAAE,CAAC;IAChB,YAAY,EAAE,CAAC;IAEf,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QACtB,IAAI,UAAU;YAAE,aAAa,CAAC,UAAU,CAAC,CAAC;QAC1C,IAAI,YAAY;YAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;QAC3B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,WAAW,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { ServerResponse } from 'node:http';
2
+ import type { NextDogEvent } from './types.js';
3
+ import type { RingBuffer } from './ring-buffer.js';
4
+ export declare class SSEStream {
5
+ private ringBuffer;
6
+ private clients;
7
+ constructor(ringBuffer: RingBuffer);
8
+ get clientCount(): number;
9
+ addClient(res: ServerResponse): void;
10
+ removeClient(res: ServerResponse): void;
11
+ broadcast(event: NextDogEvent): void;
12
+ }
13
+ //# sourceMappingURL=sse-stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-stream.d.ts","sourceRoot":"","sources":["../src/sse-stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AASnD,qBAAa,SAAS;IAGR,OAAO,CAAC,UAAU;IAF9B,OAAO,CAAC,OAAO,CAA6B;gBAExB,UAAU,EAAE,UAAU;IAE1C,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,SAAS,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI;IAiBpC,YAAY,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI;IAIvC,SAAS,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;CAMrC"}
@@ -0,0 +1,38 @@
1
+ function serializeEvent(event) {
2
+ const json = JSON.stringify(event, (_key, value) => typeof value === 'bigint' ? value.toString() + 'n' : value);
3
+ return `data: ${json}\n\n`;
4
+ }
5
+ export class SSEStream {
6
+ ringBuffer;
7
+ clients = new Set();
8
+ constructor(ringBuffer) {
9
+ this.ringBuffer = ringBuffer;
10
+ }
11
+ get clientCount() {
12
+ return this.clients.size;
13
+ }
14
+ addClient(res) {
15
+ res.writeHead(200, {
16
+ 'Content-Type': 'text/event-stream',
17
+ 'Cache-Control': 'no-cache',
18
+ 'Connection': 'keep-alive',
19
+ 'Access-Control-Allow-Origin': '*',
20
+ });
21
+ // Backfill from ring buffer
22
+ const backfill = this.ringBuffer.getLast(50);
23
+ for (const event of backfill) {
24
+ res.write(serializeEvent(event));
25
+ }
26
+ this.clients.add(res);
27
+ }
28
+ removeClient(res) {
29
+ this.clients.delete(res);
30
+ }
31
+ broadcast(event) {
32
+ const message = serializeEvent(event);
33
+ for (const client of this.clients) {
34
+ client.write(message);
35
+ }
36
+ }
37
+ }
38
+ //# sourceMappingURL=sse-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-stream.js","sourceRoot":"","sources":["../src/sse-stream.ts"],"names":[],"mappings":"AAIA,SAAS,cAAc,CAAC,KAAmB;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CACjD,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,CAC3D,CAAC;IACF,OAAO,SAAS,IAAI,MAAM,CAAC;AAC7B,CAAC;AAED,MAAM,OAAO,SAAS;IAGA;IAFZ,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE5C,YAAoB,UAAsB;QAAtB,eAAU,GAAV,UAAU,CAAY;IAAG,CAAC;IAE9C,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3B,CAAC;IAED,SAAS,CAAC,GAAmB;QAC3B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;YACjB,cAAc,EAAE,mBAAmB;YACnC,eAAe,EAAE,UAAU;YAC3B,YAAY,EAAE,YAAY;YAC1B,6BAA6B,EAAE,GAAG;SACnC,CAAC,CAAC;QAEH,4BAA4B;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC7C,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;YAC7B,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QACnC,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IAED,YAAY,CAAC,GAAmB;QAC9B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED,SAAS,CAAC,KAAmB;QAC3B,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;QACtC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,35 @@
1
+ export interface Span {
2
+ traceId: string;
3
+ spanId: string;
4
+ parentSpanId?: string;
5
+ name: string;
6
+ kind: 'SERVER' | 'CLIENT' | 'INTERNAL' | 'PRODUCER' | 'CONSUMER';
7
+ startTimeUnixNano: bigint;
8
+ endTimeUnixNano: bigint;
9
+ attributes: Record<string, string | number | boolean>;
10
+ status: {
11
+ code: 'OK' | 'ERROR' | 'UNSET';
12
+ message?: string;
13
+ };
14
+ statusCode?: number;
15
+ serviceName: string;
16
+ }
17
+ export interface LogEntry {
18
+ timestamp: number;
19
+ level: 'debug' | 'info' | 'warn' | 'error';
20
+ message: string;
21
+ attributes: Record<string, unknown>;
22
+ traceId?: string;
23
+ spanId?: string;
24
+ serviceName: string;
25
+ }
26
+ export type NextDogEvent = {
27
+ type: 'span';
28
+ timestamp: number;
29
+ data: Span;
30
+ } | {
31
+ type: 'log';
32
+ timestamp: number;
33
+ data: LogEntry;
34
+ };
35
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,IAAI;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;IACjE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IACtD,MAAM,EAAE;QAAE,IAAI,EAAE,IAAI,GAAG,OAAO,GAAG,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@nextdog/core",
3
+ "version": "0.1.0",
4
+ "description": "Zero-config dev observability sidecar for Next.js",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "nextdog": "./dist/cli.js"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "dependencies": {
18
+ "@nextdog/ui": "0.1.0"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^25.5.0",
22
+ "vitest": "^3"
23
+ },
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/cscherban/NextDog"
28
+ },
29
+ "keywords": [
30
+ "nextjs",
31
+ "observability",
32
+ "tracing",
33
+ "opentelemetry",
34
+ "devtools"
35
+ ],
36
+ "scripts": {
37
+ "build": "tsc",
38
+ "test": "vitest run",
39
+ "test:watch": "vitest"
40
+ }
41
+ }