2g 0.0.1-0.wip → 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.
Files changed (60) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE.md +22 -0
  3. package/README.md +323 -0
  4. package/api/package.json +17 -0
  5. package/bin/cli.js +19 -0
  6. package/cli/package.json +17 -0
  7. package/dist/2g-api.d.ts +52 -0
  8. package/dist/2g-api.js +129 -0
  9. package/dist/2g-api.js.map +1 -0
  10. package/dist/2g-api.mjs +121 -0
  11. package/dist/2g-api.mjs.map +1 -0
  12. package/dist/2g-cli.d.ts +4 -0
  13. package/dist/2g-cli.js +11 -0
  14. package/dist/2g-cli.js.map +1 -0
  15. package/dist/2g-cli.mjs +5 -0
  16. package/dist/2g-cli.mjs.map +1 -0
  17. package/dist/2g.d.ts +37 -0
  18. package/dist/2g.js +1184 -0
  19. package/dist/2g.js.map +1 -0
  20. package/dist/2g.mjs +1180 -0
  21. package/dist/2g.mjs.map +1 -0
  22. package/dist/chunks/cli-chunk.js +807 -0
  23. package/dist/chunks/cli-chunk.js.map +1 -0
  24. package/dist/chunks/cli-chunk.mjs +801 -0
  25. package/dist/chunks/cli-chunk.mjs.map +1 -0
  26. package/dist/chunks/index-chunk.js +176 -0
  27. package/dist/chunks/index-chunk.js.map +1 -0
  28. package/dist/chunks/index-chunk.mjs +176 -0
  29. package/dist/chunks/index-chunk.mjs.map +1 -0
  30. package/dist/chunks/pretty-chunk.js +77 -0
  31. package/dist/chunks/pretty-chunk.js.map +1 -0
  32. package/dist/chunks/pretty-chunk.mjs +79 -0
  33. package/dist/chunks/pretty-chunk.mjs.map +1 -0
  34. package/dist/chunks/sessionSockets-chunk.js +348 -0
  35. package/dist/chunks/sessionSockets-chunk.js.map +1 -0
  36. package/dist/chunks/sessionSockets-chunk.mjs +332 -0
  37. package/dist/chunks/sessionSockets-chunk.mjs.map +1 -0
  38. package/dist/chunks/tap-chunk.js +287 -0
  39. package/dist/chunks/tap-chunk.js.map +1 -0
  40. package/dist/chunks/tap-chunk.mjs +287 -0
  41. package/dist/chunks/tap-chunk.mjs.map +1 -0
  42. package/dist/chunks/types-chunk.d.ts +127 -0
  43. package/package.json +96 -11
  44. package/.editorconfig +0 -9
  45. package/bsconfig.json +0 -25
  46. package/build_script.re +0 -12
  47. package/example/Example.re +0 -56
  48. package/include/glad/glad.c +0 -11881
  49. package/include/glad/glad.h +0 -19876
  50. package/include/glad/khrplatform.h +0 -290
  51. package/include/platform/package.json +0 -12
  52. package/include/platform/postinstall.js +0 -45
  53. package/include/platform/src/g2platform.ml +0 -3
  54. package/include/platform/src/g2platform.re +0 -3
  55. package/include/sokol/sokol.c +0 -2
  56. package/include/sokol/sokol.h +0 -11
  57. package/include/sokol/sokol_app.h +0 -7074
  58. package/include/sokol/sokol_gfx.h +0 -10557
  59. package/src/g2.c +0 -237
  60. package/src/g2.re +0 -25
package/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # 2g
2
+
3
+ ## 0.1.0
4
+
5
+ Initial Release.
package/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) Phil Pluckthun,
4
+ Copyright (c) 650 Industries, Inc. (aka Expo),
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,323 @@
1
+ <div align="center">
2
+ <h2>2g</h2>
3
+ <strong>Low-overhead structured event logs for agents and developers</strong>
4
+ <br />
5
+ <br />
6
+ </div>
7
+
8
+ `2g` is a structured event logger for command-line tools. It writes JSONL logs
9
+ that can be replayed, tailed, or exported to trace formats.
10
+
11
+ In short, `2g`,
12
+
13
+ - keeps logging cheap enough to leave enabled
14
+ - writes bounded session logs to the system temporary directory
15
+ - forwards worker and child-process events into the same session
16
+ - lets tools and agents tap into running commands
17
+ - derives typed event payloads through TypeScript declaration merging
18
+
19
+ ## Implementation
20
+
21
+ The goal of `2g` is to replace ad-hoc `debug` logging with structured events
22
+ that are always recorded, without instrumented code paths paying for it. A disabled logging
23
+ call is a single property check, and an enabled call serializes straight into a batched
24
+ file stream — there is no interchange format, no transport negotiation, and no
25
+ per-event allocation beyond the payload the caller already builds.
26
+
27
+ This has been built with the explicit purpose in mind to:
28
+
29
+ - Instrument CLI processes that spawn workers and child processes, unifying their events
30
+ into one session log
31
+ - Keep a bounded, self-cleaning record of recent sessions that tooling and agents can
32
+ discover and replay after the fact
33
+ - Tap into live processes without those processes knowing or slowing down
34
+
35
+ As such, sessions write newline-framed JSONL through a single batched fd writer, rotate
36
+ across fixed-size segments, and broadcast to live subscribers over a local socket. Child
37
+ processes and workers inherit the session over an IPC socket published through the
38
+ environment, so the whole process tree agrees on one destination.
39
+
40
+ The package ships two entrypoints so consumers don't load the producer graph and vice
41
+ versa:
42
+
43
+ - `2g` — the producer surface instrumented processes import
44
+ - `2g/api` — the consumption surface for tooling that reads sessions
45
+
46
+ ## Quick start
47
+
48
+ Install logging once when a CLI command starts, then create loggers in any package:
49
+
50
+ ```ts
51
+ import { installEventLogger, events } from '2g';
52
+
53
+ installEventLogger({ command: 'expo start -p web', version: '1.0.0' });
54
+
55
+ const log = events('metro');
56
+
57
+ log('ready', { port: 8081 });
58
+
59
+ const end = log.span('bundle', { platform: 'ios' });
60
+ end('bundle', { cached: false });
61
+ ```
62
+
63
+ Tap into the session from another terminal:
64
+
65
+ ```sh
66
+ 2g tap "expo start" --filter metro:* --tail
67
+ ```
68
+
69
+ ## Behavior
70
+
71
+ ### Activation
72
+
73
+ `installEventLogger` resolves its destination in precedence order:
74
+
75
+ 1. An explicit `LOG_EVENTS` target — an fd number or a file path, from the environment or
76
+ passed directly
77
+ 2. A parent 2g process, inherited through the environment; events forward into the
78
+ parent's session over IPC
79
+ 3. A session in the system temporary directory, when an options object is passed
80
+ 4. Otherwise logging stays inactive and all calls are no-ops
81
+
82
+ Calling `installEventLogger()` with no arguments activates logging only when `LOG_EVENTS`
83
+ or a parent process is present — the right idiom for child processes, workers, and
84
+ libraries. Passing `session: false` keeps this env-only activation while still providing
85
+ options. There is no uninstall: logging is installed once per process and torn down on
86
+ exit.
87
+
88
+ When `LOG_EVENTS` targets stdout, `console` is transparently redirected to stderr so
89
+ event output stays machine-readable.
90
+
91
+ ### Sessions
92
+
93
+ Sessions live under `event-log-<uid>` in the system temporary directory. Each
94
+ session directory holds rotating JSONL segments (`0.jsonl` is current), a `meta.json`
95
+ describing the process, and local sockets for live subscribers and child IPC. With the
96
+ defaults of 3 segments × 512 KiB, a session retains roughly the last 12k events.
97
+
98
+ Stale sessions are cleaned opportunistically: exited sessions are removed after 7 days,
99
+ and only the newest 100 exited sessions are kept. An unwritable temporary directory
100
+ disables session capture without affecting the host process.
101
+
102
+ ### Debug events
103
+
104
+ `events.debug(category)` creates a logger for chatty, debug-level events. Debug events
105
+ carry `_l: 1` on the wire. Session output drops them at the emit site — keeping the
106
+ rotation budget for normal history — unless `LOG_DEBUG` is set or `installEventLogger`
107
+ is passed `debug: true`; explicit `LOG_EVENTS` targets record them. `tap` and `export`
108
+ skip debug events unless `--debug` (CLI) or `debug: true` (API) is passed.
109
+
110
+ `LOG_DEBUG` also prints matching events to stderr in a readable format while structured
111
+ logging continues unchanged:
112
+
113
+ ```sh
114
+ LOG_DEBUG=metro:* expo start
115
+ LOG_DEBUG=* expo export
116
+ ```
117
+
118
+ ### Deferred payload helpers
119
+
120
+ `log.path(absolutePath)` logs paths relative to the log target, and `log.error(error)`
121
+ serializes an error to `{ name, message, code, stack, cause }`. Both return
122
+ `Serialized<T>` wrappers — `{ toJSON(): T }` — that only do their work when an event is
123
+ written, so disabled loggers skip `path.relative` calls and `error.stack`
124
+ materialization entirely. Payloads accept `Serialized<T>` wherever their declared types
125
+ expect `T`, so `EventRegistry` declarations keep using the wire shapes.
126
+
127
+ ### Typed events
128
+
129
+ Extend `EventRegistry` to type event payloads; keys are `category:kind` and payloads
130
+ merge across packages through declaration merging:
131
+
132
+ ```ts
133
+ declare module '2g' {
134
+ interface EventRegistry {
135
+ 'metro:ready': { port: number };
136
+ 'metro:bundle': { platform?: string; cached?: boolean };
137
+ }
138
+ }
139
+ ```
140
+
141
+ Unknown event names are uncallable, reserved wire fields (`_e`, `_t`, `_d`, `_l`, `_w`)
142
+ are rejected in payload types, and payloads with no required keys become optional
143
+ arguments. `2g typegen` merges the registry declarations of a whole project into
144
+ one schema.
145
+
146
+ ## CLI
147
+
148
+ Use the CLI to find sessions, replay logs, or export traces:
149
+
150
+ ```sh
151
+ 2g ps --json
152
+ 2g tap "expo start" --filter metro:* --tail
153
+ 2g export "expo start" --format chrome-trace -o trace.json
154
+ 2g export "expo start" --format opentelemetry -o otel.json
155
+ 2g typegen --project tsconfig.json --format dts
156
+ 2g clean --json
157
+ ```
158
+
159
+ Selectors match a session by PID, command, session directory, or working directory.
160
+ `tap` and `export` replay retained history first. `--tail` appends live events; an event
161
+ written in the instant `--tail` attaches may appear twice at the seam.
162
+
163
+ Open Chrome traces in [ui.perfetto.dev](https://ui.perfetto.dev) or via Chrome DevTools →
164
+ Performance → "Load profile" (renders as a plain flame chart); `chrome://tracing` is
165
+ deprecated. For a near-live view of a running command, tail the export:
166
+
167
+ ```sh
168
+ 2g export "expo start" --tail -o trace.json
169
+ ```
170
+
171
+ ## Testing
172
+
173
+ Capture a subprocess's events in integration tests by handing it a pipe as its
174
+ `LOG_EVENTS` target:
175
+
176
+ ```ts
177
+ import { spawn } from 'node:child_process';
178
+ import { captureEvents } from '2g/api';
179
+
180
+ const capture = captureEvents({ filter: 'metro:*' });
181
+ const child = spawn(
182
+ 'expo',
183
+ ['export'],
184
+ capture.spawnOptions({ env: process.env })
185
+ );
186
+
187
+ const events = await capture.attach(child).collect();
188
+ ```
189
+
190
+ `spawnOptions` appends a pipe to `stdio` and points `LOG_EVENTS` at it; `attach`
191
+ consumes the pipe and parses events, and can also be iterated with `for await` for live
192
+ consumption. Iteration ends when the pipe closes, so every event the child wrote is
193
+ received.
194
+
195
+ > [!IMPORTANT]
196
+ > The child's events are flushed on natural exit. Child code that calls `process.exit()`
197
+ > should `await flushEventLogger()` first or trailing events may be lost.
198
+
199
+ ## API Reference
200
+
201
+ ### `2g`
202
+
203
+ The producer entry that instrumented processes import.
204
+
205
+ #### `events(category: string) => EventLogger`
206
+
207
+ Returns a typed logger for `category`. The logger is callable directly and carries
208
+ helpers:
209
+
210
+ - `log(event, data?)`: writes a `category:event` line with the payload
211
+ - `log.span(event, data?)`: starts a span; returns an `end(event, data?)` function that
212
+ writes a single event carrying the duration as `_d`
213
+ - `log.path(target)`: returns a `Serialized<string>` of the path relative to the log
214
+ target
215
+ - `log.error(error)`: returns a `Serialized<SerializedError>` of
216
+ `{ name, message, code, stack, cause }`, with cause chains serialized recursively
217
+ - `log.category`: the category string
218
+
219
+ Span kinds ending in `:started`, `:done`, or `:failed` are naming conventions; trace
220
+ exporters strip the suffix from displayed names.
221
+
222
+ #### `events.debug(category: string) => EventLogger`
223
+
224
+ Identical to `events(category)`, but events are debug-level (`_l: 1`) and subject to
225
+ debug gating.
226
+
227
+ #### `installEventLogger(target?: string | number | InstallEventLoggerOptions) => void`
228
+
229
+ - Accepts an explicit target (fd number or file path) or an options object
230
+
231
+ Installs logging for the process following the activation precedence above. All options
232
+ are optional:
233
+
234
+ | Option | Default | Description |
235
+ | ---------------- | ------- | ----------------------------------------------------------------------------------------------------------------- |
236
+ | `command` | argv | Command line recorded in session metadata |
237
+ | `version` | — | Tool version recorded in session metadata and the init event |
238
+ | `maxSegments` | `3` | Rotated JSONL segments kept per session |
239
+ | `maxSegmentSize` | 512 KiB | Segment size that triggers rotation |
240
+ | `session` | `true` | Allow the session fall-through; `false` keeps env-only activation |
241
+ | `debug` | varies | Record debug events; defaults to `true` on explicit targets and to `LOG_DEBUG` presence on session and IPC output |
242
+
243
+ #### `flushEventLogger() => Promise<void>`
244
+
245
+ Drains buffered events to the active destination. Resolves immediately when logging is
246
+ inactive.
247
+
248
+ #### `getEventLoggerInfo() => EventLoggerInfo | null`
249
+
250
+ Returns the active destination — `{ destination, isUserVisibleOutput }` plus a `file`,
251
+ `fd`, or `sessionDir` — or `null` when logging is inactive. `isUserVisibleOutput` is
252
+ `true` when events write to stdout or stderr.
253
+
254
+ #### `interface Serialized<T>`
255
+
256
+ ```ts
257
+ interface Serialized<T> {
258
+ toJSON(): T;
259
+ }
260
+ ```
261
+
262
+ A deferred value that serializes to `T` when an event is written. Payload types accept
263
+ `Serialized<T>` wherever `T` is declared, so user code can defer its own expensive
264
+ values the same way.
265
+
266
+ ### `2g/api`
267
+
268
+ The consumption entry for tooling that reads sessions.
269
+
270
+ #### `list(options?: ListSessionsOptions) => Promise<ListedSession[]>`
271
+
272
+ - **Parameters**
273
+ - `selector?`: matches by PID, command, session directory, or working directory
274
+
275
+ Returns known sessions, newest first. Each `ListedSession` carries `pid`, `command`,
276
+ `cwd`, `startedAt`, `alive`, and `sessionDir`.
277
+
278
+ #### `resolveSession(selector?: string) => Promise<ListedSession>`
279
+
280
+ Resolves a selector to exactly one session, preferring the only alive match. Throws when
281
+ nothing matches or the selector is ambiguous.
282
+
283
+ #### `tap(sessionDir: string, options?: TapOptions) => AsyncIterable<ParsedEvent>`
284
+
285
+ Replays a session's retained history and optionally follows live events.
286
+
287
+ | Option | Default | Description |
288
+ | ------------- | ------- | --------------------------------------------------------------- |
289
+ | `since` | — | Replay from a duration (`'5m'`), Unix time, ISO date, or `Date` |
290
+ | `follow` | `false` | Follow live events after replaying history |
291
+ | `filter` | — | `category[:kind]` patterns; `*` wildcards, comma-separated |
292
+ | `spans` | `false` | Only yield events carrying a span duration |
293
+ | `debug` | `false` | Include debug-level events |
294
+ | `signal` | — | `AbortSignal` that stops following |
295
+ | `timeout` | — | Stop following after an absolute duration in milliseconds |
296
+ | `idleTimeout` | — | Stop following after this many milliseconds without a new event |
297
+
298
+ #### `captureEvents(options?: CaptureOptions) => EventCapture`
299
+
300
+ - **Parameters**
301
+ - `filter?` and `debug?`: as in `TapOptions`
302
+
303
+ Returns an `EventCapture` for receiving a subprocess's events over an inherited pipe:
304
+
305
+ - `capture.spawnOptions(options?)`: returns spawn options with a `'pipe'` slot appended
306
+ to `stdio` and `LOG_EVENTS` pointing at it, preserving the caller's `env` and `stdio`
307
+ - `capture.attach(child)`: consumes the child's pipe; the capture is an
308
+ `AsyncIterable<ParsedEvent>` that ends when the pipe closes
309
+ - `capture.collect()`: iterates to the end and returns all events
310
+
311
+ #### `interface ParsedEvent`
312
+
313
+ The wire format of a parsed JSONL event; all other properties are payload fields.
314
+
315
+ ```ts
316
+ interface ParsedEvent {
317
+ _e: string; // "category:kind"
318
+ _t: number; // wall-clock timestamp in milliseconds
319
+ _d?: number; // span duration in milliseconds
320
+ _l?: number; // level; 1 marks debug events
321
+ _w?: string; // originating worker or child process
322
+ }
323
+ ```
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "2g-api",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "main": "../dist/2g-api.js",
6
+ "module": "../dist/2g-api.mjs",
7
+ "types": "../dist/2g-api.d.ts",
8
+ "source": "../src/api.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "../dist/2g-api.d.ts",
12
+ "import": "../dist/2g-api.mjs",
13
+ "require": "../dist/2g-api.js",
14
+ "source": "../src/api.ts"
15
+ }
16
+ }
17
+ }
package/bin/cli.js ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+
3
+ (async function main() {
4
+ let cli;
5
+ try {
6
+ cli = await import('../dist/2g-cli.mjs');
7
+ } catch {
8
+ cli = require('../dist/2g-cli.js');
9
+ }
10
+
11
+ try {
12
+ process.exitCode = await cli.main(process.argv.slice(2));
13
+ } catch (error) {
14
+ process.stderr.write(
15
+ `${error instanceof Error ? error.message : String(error)}\n`
16
+ );
17
+ process.exitCode = 1;
18
+ }
19
+ })();
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "2g-cli",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "main": "../dist/2g-cli.js",
6
+ "module": "../dist/2g-cli.mjs",
7
+ "types": "../dist/2g-cli.d.ts",
8
+ "source": "../src/cli.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "../dist/2g-cli.d.ts",
12
+ "import": "../dist/2g-cli.mjs",
13
+ "require": "../dist/2g-cli.js",
14
+ "source": "../src/cli.ts"
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,52 @@
1
+ import { S as SessionMeta, P as ParsedEvent } from './chunks/types-chunk.js';
2
+ import { StdioOptions, ChildProcess } from 'node:child_process';
3
+
4
+ interface EventFilterOptions {
5
+ since?: number | string | Date;
6
+ filter?: string | string[];
7
+ spans?: boolean;
8
+ debug?: boolean;
9
+ }
10
+
11
+ interface ListedSession {
12
+ id: string;
13
+ pid: number;
14
+ formatVersion: number;
15
+ alive: boolean;
16
+ startedAt: number;
17
+ command: string;
18
+ cwd: string;
19
+ version?: string;
20
+ origin?: SessionMeta['origin'];
21
+ sessionDir: string;
22
+ }
23
+ interface ListSessionsOptions {
24
+ selector?: string;
25
+ }
26
+ interface TapOptions extends EventFilterOptions {
27
+ follow?: boolean;
28
+ signal?: AbortSignal;
29
+ timeout?: number;
30
+ idleTimeout?: number;
31
+ }
32
+ declare function listSessions(options?: ListSessionsOptions): Promise<ListedSession[]>;
33
+ declare function resolveSession(selector?: string): Promise<ListedSession>;
34
+ declare function tap(sessionDir: string, options?: TapOptions): AsyncIterable<ParsedEvent>;
35
+
36
+ type CaptureOptions = Pick<TapOptions, 'debug' | 'filter'>;
37
+ interface CaptureSpawnOptions {
38
+ env?: NodeJS.ProcessEnv;
39
+ stdio?: StdioOptions;
40
+ }
41
+ declare class EventCapture implements AsyncIterable<ParsedEvent> {
42
+ #private;
43
+ constructor(options?: CaptureOptions);
44
+ spawnOptions<T extends CaptureSpawnOptions>(options?: T): T & Required<CaptureSpawnOptions>;
45
+ attach(child: ChildProcess): this;
46
+ collect(): Promise<ParsedEvent[]>;
47
+ [Symbol.asyncIterator](): AsyncIterator<ParsedEvent>;
48
+ }
49
+ declare function captureEvents(options?: CaptureOptions): EventCapture;
50
+
51
+ export { EventCapture, ParsedEvent, captureEvents, listSessions as list, resolveSession, tap };
52
+ export type { CaptureOptions, CaptureSpawnOptions, ListSessionsOptions, ListedSession, TapOptions };
package/dist/2g-api.js ADDED
@@ -0,0 +1,129 @@
1
+ Object.defineProperty(exports, "__esModule", {
2
+ value: !0
3
+ });
4
+
5
+ var t = require("./chunks/tap-chunk.js");
6
+
7
+ var e = require("./chunks/sessionSockets-chunk.js");
8
+
9
+ class EventCapture {
10
+ #t;
11
+ #e;
12
+ #s;
13
+ #i=!1;
14
+ #n=!1;
15
+ #r=[];
16
+ #o=[];
17
+ constructor(t = {}) {
18
+ this.#t = t;
19
+ this.#e = e.compileEventFilter(t.filter);
20
+ }
21
+ spawnOptions(t) {
22
+ const s = Array.isArray(t?.stdio) ? [ ...t.stdio ] : null != t?.stdio ? [ t.stdio, t.stdio, t.stdio ] : [];
23
+ while (s.length < 3) {
24
+ s.push("inherit");
25
+ }
26
+ this.#s = s.length;
27
+ s.push("pipe");
28
+ return {
29
+ ...t,
30
+ stdio: s,
31
+ env: {
32
+ ...t?.env ?? process.env,
33
+ [e.LOG_EVENTS_ENV]: `${this.#s}`
34
+ }
35
+ };
36
+ }
37
+ attach(t) {
38
+ if (null == this.#s) {
39
+ throw new Error("captureEvents: spawnOptions() must create the pipe before attach()");
40
+ }
41
+ if (this.#i) {
42
+ throw new Error("captureEvents: a child is already attached");
43
+ }
44
+ const e = t.stdio[this.#s];
45
+ if (!e || "function" != typeof e.setEncoding) {
46
+ throw new Error(`captureEvents: child stdio[${this.#s}] is not a readable pipe`);
47
+ }
48
+ this.#i = !0;
49
+ let s = "";
50
+ e.setEncoding("utf8");
51
+ e.on("data", t => {
52
+ const e = s ? s + t : t;
53
+ let i = 0;
54
+ let n;
55
+ while (-1 !== (n = e.indexOf("\n", i))) {
56
+ const t = e.slice(i, n);
57
+ i = n + 1;
58
+ if (t) {
59
+ this.#h(t);
60
+ }
61
+ }
62
+ s = i ? e.slice(i) : e;
63
+ });
64
+ const finish = () => {
65
+ if (this.#n) {
66
+ return;
67
+ }
68
+ this.#n = !0;
69
+ if (s) {
70
+ this.#h(s);
71
+ }
72
+ this.#a(null);
73
+ };
74
+ e.on("end", finish);
75
+ e.on("close", finish);
76
+ e.on("error", finish);
77
+ return this;
78
+ }
79
+ async collect() {
80
+ const t = [];
81
+ for await (const e of this) {
82
+ t.push(e);
83
+ }
84
+ return t;
85
+ }
86
+ async* [Symbol.asyncIterator]() {
87
+ let t;
88
+ while (t = await this.#c()) {
89
+ yield t;
90
+ }
91
+ }
92
+ #h(t) {
93
+ const s = e.parseEventLine(t, this.#t, this.#e);
94
+ if (s) {
95
+ this.#a(s);
96
+ }
97
+ }
98
+ #a(t) {
99
+ const e = this.#o.shift();
100
+ if (e) {
101
+ e(t);
102
+ } else if (t) {
103
+ this.#r.push(t);
104
+ }
105
+ }
106
+ #c() {
107
+ if (this.#r.length) {
108
+ return Promise.resolve(this.#r.shift());
109
+ }
110
+ if (this.#n) {
111
+ return Promise.resolve(null);
112
+ }
113
+ if (!this.#i) {
114
+ throw new Error("captureEvents: attach() a child before iterating");
115
+ }
116
+ return new Promise(t => this.#o.push(t));
117
+ }
118
+ }
119
+
120
+ exports.list = t.listSessions;
121
+
122
+ exports.resolveSession = t.resolveSession;
123
+
124
+ exports.tap = t.tap;
125
+
126
+ exports.captureEvents = function captureEvents(t) {
127
+ return new EventCapture(t);
128
+ };
129
+ //# sourceMappingURL=2g-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"2g-api.js","sources":["../src/capture.ts"],"sourcesContent":["import type { ChildProcess, StdioOptions } from 'node:child_process';\nimport type { Readable } from 'node:stream';\n\nimport { LOG_EVENTS_ENV } from './constants';\nimport type { TapOptions } from './tap';\nimport type { ParsedEvent } from './types';\nimport { compileEventFilter, parseEventLine } from './utils/eventFilter';\n\nexport type CaptureOptions = Pick<TapOptions, 'debug' | 'filter'>;\n\nexport interface CaptureSpawnOptions {\n env?: NodeJS.ProcessEnv;\n stdio?: StdioOptions;\n}\n\nexport class EventCapture implements AsyncIterable<ParsedEvent> {\n #options: CaptureOptions;\n #eventFilter: RegExp | null;\n #index: number | undefined;\n #attached = false;\n #done = false;\n #events: ParsedEvent[] = [];\n #waiters: Array<(event: ParsedEvent | null) => void> = [];\n\n constructor(options: CaptureOptions = {}) {\n this.#options = options;\n this.#eventFilter = compileEventFilter(options.filter);\n }\n\n spawnOptions<T extends CaptureSpawnOptions>(\n options?: T\n ): T & Required<CaptureSpawnOptions> {\n const stdio: Extract<StdioOptions, readonly unknown[]> = Array.isArray(\n options?.stdio\n )\n ? [...options.stdio]\n : options?.stdio != null\n ? [options.stdio, options.stdio, options.stdio]\n : [];\n while (stdio.length < 3) stdio.push('inherit');\n this.#index = stdio.length;\n stdio.push('pipe');\n return {\n ...(options as T),\n stdio,\n env: {\n ...(options?.env ?? process.env),\n [LOG_EVENTS_ENV]: `${this.#index}`,\n },\n };\n }\n\n attach(child: ChildProcess): this {\n if (this.#index == null)\n throw new Error(\n 'captureEvents: spawnOptions() must create the pipe before attach()'\n );\n if (this.#attached)\n throw new Error('captureEvents: a child is already attached');\n const stream = child.stdio[this.#index] as Readable | null | undefined;\n if (!stream || typeof stream.setEncoding !== 'function')\n throw new Error(\n `captureEvents: child stdio[${this.#index}] is not a readable pipe`\n );\n this.#attached = true;\n\n let pending = '';\n stream.setEncoding('utf8');\n stream.on('data', (chunk: string) => {\n const data = pending ? pending + chunk : chunk;\n let start = 0;\n let end: number;\n while ((end = data.indexOf('\\n', start)) !== -1) {\n const line = data.slice(start, end);\n start = end + 1;\n if (line) this.#ingest(line);\n }\n pending = start ? data.slice(start) : data;\n });\n\n const finish = () => {\n if (this.#done) return;\n this.#done = true;\n if (pending) this.#ingest(pending);\n this.#notify(null);\n };\n stream.on('end', finish);\n stream.on('close', finish);\n stream.on('error', finish);\n return this;\n }\n\n async collect(): Promise<ParsedEvent[]> {\n const events: ParsedEvent[] = [];\n for await (const event of this) events.push(event);\n return events;\n }\n\n async *[Symbol.asyncIterator](): AsyncIterator<ParsedEvent> {\n let event: ParsedEvent | null;\n while ((event = await this.#next())) yield event;\n }\n\n #ingest(line: string) {\n const event = parseEventLine(line, this.#options, this.#eventFilter);\n if (event) this.#notify(event);\n }\n\n #notify(event: ParsedEvent | null) {\n const waiter = this.#waiters.shift();\n if (waiter) waiter(event);\n else if (event) this.#events.push(event);\n }\n\n #next(): Promise<ParsedEvent | null> {\n if (this.#events.length) return Promise.resolve(this.#events.shift()!);\n if (this.#done) return Promise.resolve(null);\n if (!this.#attached)\n throw new Error('captureEvents: attach() a child before iterating');\n return new Promise(resolve => this.#waiters.push(resolve));\n }\n}\n\nexport function captureEvents(options?: CaptureOptions): EventCapture {\n return new EventCapture(options);\n}\n"],"names":["EventCapture","options","eventFilter","index","attached","done","events","waiters","constructor","this","compileEventFilter","filter","spawnOptions","stdio","Array","isArray","length","push","env","process","LOG_EVENTS_ENV","attach","child","Error","stream","setEncoding","pending","on","chunk","data","start","end","indexOf","line","slice","ingest","finish","notify","collect","event","Symbol","asyncIterator","next","parseEventLine","waiter","shift","Promise","resolve","captureEvents"],"mappings":";;;;;;;;AAeO,MAAMA;EACXC;EACAC;EACAC;EACAC,IAAY;EACZC,IAAQ;EACRC,GAAyB;EACzBC,GAAuD;EAEvDC,WAAAA,CAAYP,IAA0B;IACpCQ,MAAKR,IAAWA;IAChBQ,MAAKP,IAAeQ,qBAAmBT,EAAQU;AACjD;EAEAC,YAAAA,CACEX;IAEA,MAAMY,IAAmDC,MAAMC,QAC7Dd,GAASY,SAEP,KAAIZ,EAAQY,UACM,QAAlBZ,GAASY,QACP,EAACZ,EAAQY,OAAOZ,EAAQY,OAAOZ,EAAQY,UACvC;IACN,OAAOA,EAAMG,SAAS;MAAGH,EAAMI,KAAK;;IACpCR,MAAKN,IAASU,EAAMG;IACpBH,EAAMI,KAAK;IACX,OAAO;SACDhB;MACJY;MACAK,KAAK;WACCjB,GAASiB,OAAOC,QAAQD;QAC5B,CAACE,mBAAiB,GAAGX,MAAKN;;;AAGhC;EAEAkB,MAAAA,CAAOC;IACL,IAAmB,QAAfb,MAAKN;MACP,MAAM,IAAIoB,MACR;;IAEJ,IAAId,MAAKL;MACP,MAAM,IAAImB,MAAM;;IAClB,MAAMC,IAASF,EAAMT,MAAMJ,MAAKN;IAChC,KAAKqB,KAAwC,qBAAvBA,EAAOC;MAC3B,MAAM,IAAIF,MACR,8BAA8Bd,MAAKN;;IAEvCM,MAAKL,KAAY;IAEjB,IAAIsB,IAAU;IACdF,EAAOC,YAAY;IACnBD,EAAOG,GAAG,QAASC;MACjB,MAAMC,IAAOH,IAAUA,IAAUE,IAAQA;MACzC,IAAIE,IAAQ;MACZ,IAAIC;MACJ,eAAQA,IAAMF,EAAKG,QAAQ,MAAMF,KAAgB;QAC/C,MAAMG,IAAOJ,EAAKK,MAAMJ,GAAOC;QAC/BD,IAAQC,IAAM;QACd,IAAIE;UAAMxB,MAAK0B,EAAQF;;AACzB;MACAP,IAAUI,IAAQD,EAAKK,MAAMJ,KAASD;;IAGxC,MAAMO,SAASA;MACb,IAAI3B,MAAKJ;QAAO;;MAChBI,MAAKJ,KAAQ;MACb,IAAIqB;QAASjB,MAAK0B,EAAQT;;MAC1BjB,MAAK4B,EAAQ;;IAEfb,EAAOG,GAAG,OAAOS;IACjBZ,EAAOG,GAAG,SAASS;IACnBZ,EAAOG,GAAG,SAASS;IACnB,OAAO3B;AACT;EAEA,aAAM6B;IACJ,MAAMhC,IAAwB;IAC9B,WAAW,MAAMiC,KAAS9B;MAAMH,EAAOW,KAAKsB;;IAC5C,OAAOjC;AACT;EAEA,QAAQkC,OAAOC;IACb,IAAIF;IACJ,OAAQA,UAAc9B,MAAKiC;YAAgBH;;AAC7C;EAEA,EAAAJ,CAAQF;IACN,MAAMM,IAAQI,EAAAA,eAAeV,GAAMxB,MAAKR,GAAUQ,MAAKP;IACvD,IAAIqC;MAAO9B,MAAK4B,EAAQE;;AAC1B;EAEA,EAAAF,CAAQE;IACN,MAAMK,IAASnC,MAAKF,EAASsC;IAC7B,IAAID;MAAQA,EAAOL;WACd,IAAIA;MAAO9B,MAAKH,EAAQW,KAAKsB;;AACpC;EAEA,EAAAG;IACE,IAAIjC,MAAKH,EAAQU;MAAQ,OAAO8B,QAAQC,QAAQtC,MAAKH,EAAQuC;;IAC7D,IAAIpC,MAAKJ;MAAO,OAAOyC,QAAQC,QAAQ;;IACvC,KAAKtC,MAAKL;MACR,MAAM,IAAImB,MAAM;;IAClB,OAAO,IAAIuB,QAAQC,KAAWtC,MAAKF,EAASU,KAAK8B;AACnD;;;;;;;;;wBAGK,SAASC,cAAc/C;EAC5B,OAAO,IAAID,aAAaC;AAC1B"}