@leftium/gg 0.0.47 → 0.0.49

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/README.md CHANGED
@@ -6,7 +6,8 @@
6
6
  - Each namespace gets a unique color for easier visual parsing.
7
7
  - Simple syntax with wildcards to filter/hide debug output at runtime.
8
8
  - Millisecond diff (timestamps) for each namespace.
9
- - Can be inserted into the middle of expressions (returns the value of the first argument).
9
+ - Chainable API: `.ns()`, `.warn()`, `.error()`, `.info()`, `.trace()`, `.table()`.
10
+ - Can be inserted into the middle of expressions (use `.v` to get the passthrough value).
10
11
  - Can output a link that opens the source file in your editor (like VS Code).
11
12
  - Simple to disable (turn all loggs into NOP's for production).
12
13
  - Diagnostics/hints in dev console & terminal to help install and configure correctly.
@@ -28,8 +29,12 @@ npm add @leftium/gg
28
29
 
29
30
  gg('Hello world');
30
31
 
31
- // Log expressions (returns first argument)
32
- const result = gg(someFunction());
32
+ // Log with modifiers
33
+ gg('Connection timeout').warn();
34
+ gg('User authenticated', user).info();
35
+
36
+ // Passthrough with .v (returns the first argument)
37
+ const result = gg(someFunction()).v;
33
38
 
34
39
  // Multiple arguments
35
40
  gg('User:', user, 'Status:', status);
@@ -57,11 +62,10 @@ export default defineConfig({
57
62
 
58
63
  - **Call-sites plugin** -- rewrites `gg()` calls with source file/line/col metadata
59
64
  - **Open-in-editor plugin** -- adds dev server middleware for click-to-open
60
- - **Automatic `es2022` target** -- required for top-level await
61
65
 
62
66
  ### 3. Add the debug console (optional, recommended)
63
67
 
64
- An in-browser debug console (powered by Eruda) with a dedicated GG tab for filtering and inspecting logs especially useful on mobile.
68
+ An in-browser debug console (powered by Eruda) with a dedicated GG tab for filtering and inspecting logs -- especially useful on mobile.
65
69
 
66
70
  ```svelte
67
71
  <!-- src/routes/+layout.svelte -->
@@ -76,6 +80,173 @@ An in-browser debug console (powered by Eruda) with a dedicated GG tab for filte
76
80
  In development, the debug console appears automatically.
77
81
  In production, add `?gg` to the URL or use a 5-tap gesture to activate.
78
82
 
83
+ ## Chaining API
84
+
85
+ `gg()` returns a `GgChain<T>` with composable modifiers. Chain any combination, in any order. The log auto-flushes on the next microtask, or use `.v` to flush immediately and get the passthrough value.
86
+
87
+ ```javascript
88
+ import { gg } from '@leftium/gg';
89
+
90
+ // Basic logging (auto-flushes on microtask)
91
+ gg('hello');
92
+ gg('multiple', 'args', { data: 42 });
93
+
94
+ // Passthrough with .v (flushes immediately, returns first arg)
95
+ const result = gg(computeValue()).v;
96
+ const user = gg(await fetchUser()).ns('api').v;
97
+
98
+ // Log levels
99
+ gg('System ready').info(); // blue indicator
100
+ gg('Deprecated API call').warn(); // yellow indicator
101
+ gg('Connection failed').error(); // red indicator + stack trace
102
+
103
+ // Custom namespace
104
+ gg('Processing request').ns('api:handler');
105
+
106
+ // Stack trace
107
+ gg('Debug checkpoint').trace();
108
+
109
+ // Table formatting (also emits native console.table)
110
+ gg(arrayOfObjects).table();
111
+ gg(data).table(['name', 'age']); // filter columns
112
+
113
+ // Combine modifiers freely
114
+ gg('Slow query', { ms: 3200 }).ns('db').warn();
115
+ const rows = gg(queryResult).ns('db:query').table().v;
116
+ ```
117
+
118
+ ### `.v` -- Passthrough
119
+
120
+ Use `.v` at the end of any chain to flush the log immediately and return the first argument. This lets you insert `gg()` into the middle of expressions:
121
+
122
+ ```javascript
123
+ // Without .v: logs on microtask, returns GgChain (not the value!)
124
+ gg(someValue);
125
+
126
+ // With .v: logs immediately, returns someValue
127
+ const x = gg(someValue).v;
128
+ const y = gg(compute()).ns('math').warn().v;
129
+
130
+ // Insert into expressions
131
+ processData(gg(inputData).v);
132
+ return gg(result).ns('output').v;
133
+ ```
134
+
135
+ ### `.ns(label)` -- Custom Namespace
136
+
137
+ Override the auto-generated namespace (file@function) with a custom label. Useful for grouping related logs across files:
138
+
139
+ ```javascript
140
+ gg('Request received').ns('api:incoming');
141
+ gg('Response sent').ns('api:outgoing');
142
+ gg('Cache hit').ns('api:cache');
143
+ ```
144
+
145
+ Namespace labels support **template variables** that resolve from plugin-provided metadata:
146
+
147
+ | Variable | Description | Example |
148
+ | -------- | ----------------------------- | --------------------------------- |
149
+ | `$NS` | Full auto-generated callpoint | `routes/+page.svelte@handleClick` |
150
+ | `$FN` | Enclosing function name | `handleClick` |
151
+ | `$FILE` | Source file path | `routes/+page.svelte` |
152
+ | `$LINE` | Line number | `42` |
153
+ | `$COL` | Column number | `3` |
154
+
155
+ ```javascript
156
+ gg('debug info').ns('ERROR:$NS'); // → ERROR:routes/+page.svelte@handleClick
157
+ gg('validation').ns('$FILE:validate'); // → routes/+page.svelte:validate
158
+ gg('step 1').ns('TRACE:$FN'); // → TRACE:handleClick
159
+ gg('context').ns('$NS:debug'); // → routes/+page.svelte@handleClick:debug
160
+ ```
161
+
162
+ Without the Vite plugin, `$NS` falls back to a runtime word-tuple (e.g. `calm-fox`). `$FN`, `$FILE`, `$LINE`, and `$COL` require the plugin.
163
+
164
+ ### `.info()` / `.warn()` / `.error()` -- Log Levels
165
+
166
+ ```javascript
167
+ gg('Server started on port 3000').info(); // blue badge
168
+ gg('Rate limit approaching').warn(); // yellow badge
169
+ gg('Unhandled exception').error(); // red badge + captures stack
170
+
171
+ // .error() with an Error object uses its .stack
172
+ try {
173
+ riskyOperation();
174
+ } catch (err) {
175
+ gg(err).error();
176
+ }
177
+ ```
178
+
179
+ ### `.trace()` -- Stack Trace
180
+
181
+ Captures a full stack trace (cleaned of internal gg frames) alongside the log entry:
182
+
183
+ ```javascript
184
+ gg('How did we get here?').trace();
185
+ ```
186
+
187
+ ### `.table(columns?)` -- Table Formatting
188
+
189
+ Formats the first argument as a table. Also emits a native `console.table()` call. Optionally filter columns:
190
+
191
+ ```javascript
192
+ gg([
193
+ { name: 'Alice', age: 30, role: 'admin' },
194
+ { name: 'Bob', age: 25, role: 'user' }
195
+ ]).table();
196
+
197
+ // Filter columns
198
+ gg(users).table(['name', 'role']);
199
+
200
+ // Works with objects-of-objects and arrays of primitives too
201
+ gg({ us: { pop: '331M' }, uk: { pop: '67M' } }).table();
202
+ gg(['apple', 'banana', 'cherry']).table();
203
+ ```
204
+
205
+ ## Timers
206
+
207
+ Measure elapsed time with `gg.time()`, `gg.timeLog()`, and `gg.timeEnd()`:
208
+
209
+ ```javascript
210
+ import { gg } from '@leftium/gg';
211
+
212
+ gg.time('fetch');
213
+
214
+ // ... some work ...
215
+ gg.timeLog('fetch', 'headers received'); // logs elapsed without stopping
216
+
217
+ // ... more work ...
218
+ gg.timeEnd('fetch'); // logs elapsed and stops timer
219
+ ```
220
+
221
+ ### Timer Namespaces
222
+
223
+ `gg.time()` returns a `GgTimerChain` that supports `.ns()` for grouping. The namespace is inherited by subsequent `timeLog` and `timeEnd` calls for the same label:
224
+
225
+ ```javascript
226
+ gg.time('fetch').ns('api-pipeline');
227
+
228
+ gg.timeLog('fetch', 'step 1 done'); // logged under 'api-pipeline'
229
+ gg.timeEnd('fetch'); // logged under 'api-pipeline'
230
+
231
+ // Template variables work too
232
+ gg.time('db-query').ns('$FN:timers');
233
+ ```
234
+
235
+ ## `gg.here()` -- Open in Editor
236
+
237
+ Returns call-site metadata for rendering "open in editor" links. Replaces the old no-arg `gg()` introspection.
238
+
239
+ ```svelte
240
+ <script>
241
+ import { gg } from '@leftium/gg';
242
+ </script>
243
+
244
+ <!-- Pass to a link component -->
245
+ <OpenInEditorLink gg={gg.here()} />
246
+ ```
247
+
248
+ Returns `{ fileName, functionName, url }` where `url` points to the dev server's open-in-editor endpoint.
249
+
79
250
  ## GgConsole Options
80
251
 
81
252
  ```svelte
@@ -226,6 +397,40 @@ initGgEruda();
226
397
  gg('works in any framework');
227
398
  ```
228
399
 
400
+ ## API Reference
401
+
402
+ ### `gg(value, ...args)` -- Returns `GgChain<T>`
403
+
404
+ | Method / Property | Description |
405
+ | ----------------- | --------------------------------------------- |
406
+ | `.v` | Flush log immediately, return first argument |
407
+ | `.ns(label)` | Set custom namespace (supports template vars) |
408
+ | `.info()` | Set log level to info |
409
+ | `.warn()` | Set log level to warn |
410
+ | `.error()` | Set log level to error (captures stack) |
411
+ | `.trace()` | Attach full stack trace |
412
+ | `.table(cols?)` | Format as table, optional column filter |
413
+
414
+ ### `gg.time(label?)` -- Returns `GgTimerChain`
415
+
416
+ | Method | Description |
417
+ | ------------ | ----------------------------------------- |
418
+ | `.ns(label)` | Set namespace for timer group (inherited) |
419
+
420
+ ### `gg.timeLog(label?, ...args)` -- Log elapsed without stopping
421
+
422
+ ### `gg.timeEnd(label?)` -- Log elapsed and stop timer
423
+
424
+ ### `gg.here()` -- Returns `{ fileName, functionName, url }`
425
+
426
+ ### Control
427
+
428
+ | Method | Description |
429
+ | ------------------- | ----------------------------------------- |
430
+ | `gg.enable(ns)` | Enable debug output for namespace pattern |
431
+ | `gg.disable()` | Disable all debug output |
432
+ | `gg.clearPersist()` | Clear `gg-enabled` from localStorage |
433
+
229
434
  ## Technical Details
230
435
 
231
436
  ### Internal Debug Implementation
@@ -248,6 +453,20 @@ Features implemented internally (~290 lines of TypeScript):
248
453
 
249
454
  This approach eliminates the need for vendoring, patching, and bundling third-party code, resulting in better type safety and simpler maintenance.
250
455
 
456
+ ### Microtask Auto-Flush
457
+
458
+ When you call `gg(value)`, the log is **deferred to the next microtask**. This means chain modifiers (`.ns()`, `.warn()`, etc.) can be added synchronously after the call. If you need the value immediately (passthrough), use `.v` which forces an immediate flush.
459
+
460
+ ```javascript
461
+ // These two are equivalent:
462
+ gg('hello').warn(); // .warn() runs sync, log flushes on microtask
463
+ gg('hello').warn().v; // .v forces immediate flush + returns 'hello'
464
+
465
+ // Auto-flush means order doesn't matter for modifiers:
466
+ gg('x').ns('foo').warn(); // same as:
467
+ gg('x').warn().ns('foo'); // (both set ns and level before flush)
468
+ ```
469
+
251
470
  ## Inspirations
252
471
 
253
472
  ### debug
@@ -90,16 +90,19 @@ export declare function findEnclosingFunctionFromScopes(pos: number, scopes: Fun
90
90
  */
91
91
  export declare function escapeForString(s: string): string;
92
92
  /**
93
- * Transform gg() and gg.ns() calls in source code to gg._ns({ns, file, line, col, src}, ...) calls.
93
+ * Transform gg() calls in source code to inject call-site metadata.
94
94
  *
95
95
  * Handles:
96
96
  * - bare gg(expr) → gg._ns({ns, file, line, col, src: 'expr'}, expr)
97
- * - gg.ns('label', expr) → gg._ns({ns, file, line, col, src: 'expr'}, expr)
98
- * - label supports template variables: $NS, $FN, $FILE, $LINE, $COL
99
- * - plain label (no variables) is used as-is (no auto @fn append)
100
- * - gg.enable, gg.disable, gg.clearPersist, gg._onLog, gg._ns → left untouched
97
+ * - gg.here() → gg._here({ns, file, line, col})
98
+ * - gg.time/timeLog/timeEnd gg._time/_timeLog/_timeEnd with metadata
99
+ * - gg.enable, gg.disable, gg.clearPersist, gg._ns, gg._onLog left untouched
101
100
  * - gg inside strings and comments → left untouched
102
101
  *
102
+ * Chain methods (.ns(), .warn(), .error(), etc.) are NOT rewritten —
103
+ * they run at runtime and resolve template variables from the metadata
104
+ * that the plugin baked into the gg._ns() options object.
105
+ *
103
106
  * For .svelte files, `svelteInfo` (from `collectCodeRanges()`) determines which
104
107
  * positions contain JS code and provides AST-based function scope detection.
105
108
  * Script ranges use `{...}` object literal syntax; template ranges use `gg._o()`
@@ -47,13 +47,7 @@ export default function ggCallSitesPlugin(options = {}) {
47
47
  return null;
48
48
  // Quick bail: no gg calls in this file
49
49
  if (!code.includes('gg(') &&
50
- !code.includes('gg.ns(') &&
51
- !code.includes('gg.info(') &&
52
- !code.includes('gg.warn(') &&
53
- !code.includes('gg.error(') &&
54
- !code.includes('gg.table(') &&
55
- !code.includes('gg.trace(') &&
56
- !code.includes('gg.assert(') &&
50
+ !code.includes('gg.here(') &&
57
51
  !code.includes('gg.time(') &&
58
52
  !code.includes('gg.timeLog(') &&
59
53
  !code.includes('gg.timeEnd('))
@@ -590,16 +584,19 @@ export function escapeForString(s) {
590
584
  return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n').replace(/\r/g, '\\r');
591
585
  }
592
586
  /**
593
- * Transform gg() and gg.ns() calls in source code to gg._ns({ns, file, line, col, src}, ...) calls.
587
+ * Transform gg() calls in source code to inject call-site metadata.
594
588
  *
595
589
  * Handles:
596
590
  * - bare gg(expr) → gg._ns({ns, file, line, col, src: 'expr'}, expr)
597
- * - gg.ns('label', expr) → gg._ns({ns, file, line, col, src: 'expr'}, expr)
598
- * - label supports template variables: $NS, $FN, $FILE, $LINE, $COL
599
- * - plain label (no variables) is used as-is (no auto @fn append)
600
- * - gg.enable, gg.disable, gg.clearPersist, gg._onLog, gg._ns → left untouched
591
+ * - gg.here() → gg._here({ns, file, line, col})
592
+ * - gg.time/timeLog/timeEnd gg._time/_timeLog/_timeEnd with metadata
593
+ * - gg.enable, gg.disable, gg.clearPersist, gg._ns, gg._onLog left untouched
601
594
  * - gg inside strings and comments → left untouched
602
595
  *
596
+ * Chain methods (.ns(), .warn(), .error(), etc.) are NOT rewritten —
597
+ * they run at runtime and resolve template variables from the metadata
598
+ * that the plugin baked into the gg._ns() options object.
599
+ *
603
600
  * For .svelte files, `svelteInfo` (from `collectCodeRanges()`) determines which
604
601
  * positions contain JS code and provides AST-based function scope detection.
605
602
  * Script ranges use `{...}` object literal syntax; template ranges use `gg._o()`
@@ -720,7 +717,7 @@ export function transformGgCalls(code, shortPath, filePath, svelteInfo, jsFuncti
720
717
  continue;
721
718
  }
722
719
  }
723
- // Look for 'gg' pattern — could be gg( or gg.ns(
720
+ // Look for 'gg' pattern — could be gg( or gg.here( or gg.time(
724
721
  if (code[i] === 'g' && code[i + 1] === 'g') {
725
722
  // In .svelte files, skip gg outside code ranges (prose text, etc.)
726
723
  const range = rangeAt(i);
@@ -734,83 +731,22 @@ export function transformGgCalls(code, shortPath, filePath, svelteInfo, jsFuncti
734
731
  i++;
735
732
  continue;
736
733
  }
737
- // Case 1: gg.ns('label', ...) → gg._ns({ns: 'label', file, line, col, src}, ...)
738
- if (code.slice(i + 2, i + 6) === '.ns(') {
734
+ // Case 1: gg.here() → gg._here({ns, file, line, col})
735
+ if (code.slice(i + 2, i + 9) === '.here()') {
739
736
  const { line, col } = getLineCol(code, i);
740
737
  const fnName = getFunctionName(i, range);
741
- const openParenPos = i + 5; // position of '(' in 'gg.ns('
742
- // Find matching closing paren for the entire gg.ns(...) call
743
- const closeParenPos = findMatchingParen(code, openParenPos);
744
- if (closeParenPos === -1) {
745
- i += 6;
746
- continue;
747
- }
748
- // Extract the first argument (the namespace string)
749
- // Look for the string literal after 'gg.ns('
750
- let afterNsParen = i + 6; // position after 'gg.ns('
751
- while (afterNsParen < code.length && /\s/.test(code[afterNsParen]))
752
- afterNsParen++;
753
- const quoteChar = code[afterNsParen];
754
- if (quoteChar === "'" || quoteChar === '"') {
755
- // Find the closing quote
756
- let j = afterNsParen + 1;
757
- while (j < code.length && code[j] !== quoteChar) {
758
- if (code[j] === '\\')
759
- j++; // skip escaped chars
760
- j++;
761
- }
762
- // j now points to closing quote
763
- const nsLabelRaw = code.slice(afterNsParen + 1, j);
764
- // Build callpoint: substitute $NS/$FN/$FILE/$LINE/$COL template variables.
765
- // The auto-generated callpoint (file@fn) is what bare gg() would produce.
766
- const autoCallpoint = `${shortPath}${fnName ? `@${fnName}` : ''}`;
767
- const callpoint = escapeForString(nsLabelRaw
768
- .replace(/\$NS/g, autoCallpoint)
769
- .replace(/\$FN/g, fnName)
770
- .replace(/\$FILE/g, shortPath)
771
- .replace(/\$LINE/g, String(line))
772
- .replace(/\$COL/g, String(col)));
773
- // Check if there are more args after the string
774
- const afterClosingQuote = j + 1;
775
- let k = afterClosingQuote;
776
- while (k < code.length && /\s/.test(code[k]))
777
- k++;
778
- if (code[k] === ')') {
779
- // gg.ns('label') → gg._ns(opts)
780
- result.push(code.slice(lastIndex, i));
781
- result.push(`gg._ns(${buildOptions(range, callpoint, line, col)})`);
782
- lastIndex = k + 1;
783
- i = k + 1;
784
- }
785
- else if (code[k] === ',') {
786
- // gg.ns('label', args...) → gg._ns(opts, args...)
787
- let argsStart = k + 1;
788
- while (argsStart < closeParenPos && /\s/.test(code[argsStart]))
789
- argsStart++;
790
- const argsSrc = code.slice(argsStart, closeParenPos).trim();
791
- const escapedSrc = escapeForString(argsSrc);
792
- result.push(code.slice(lastIndex, i));
793
- result.push(`gg._ns(${buildOptions(range, callpoint, line, col, escapedSrc)}, `);
794
- lastIndex = k + 1; // skip past the comma, keep args as-is
795
- i = k + 1;
796
- }
797
- else {
798
- // Unexpected — leave untouched
799
- i += 6;
800
- continue;
801
- }
802
- modified = true;
803
- continue;
804
- }
805
- // Non-string first arg to gg.ns — skip (can't extract ns at build time)
806
- i += 6;
738
+ const callpoint = `${shortPath}${fnName ? `@${fnName}` : ''}`;
739
+ const escapedNs = escapeForString(callpoint);
740
+ result.push(code.slice(lastIndex, i));
741
+ result.push(`gg._here(${buildOptions(range, escapedNs, line, col)})`);
742
+ lastIndex = i + 9; // skip past 'gg.here()'
743
+ i = lastIndex;
744
+ modified = true;
807
745
  continue;
808
746
  }
809
- // Case 1b: gg.info/warn/error/table/trace/assert → gg._info/_warn/_error/_table/_trace/_assert
810
- // These methods are rewritten like bare gg() but with their internal variant.
811
- const dotMethodMatch = code
812
- .slice(i + 2)
813
- .match(/^\.(info|warn|error|table|trace|assert|time|timeLog|timeEnd)\(/);
747
+ // Case 2: gg.time/timeLog/timeEnd → gg._time/_timeLog/_timeEnd
748
+ // Timer methods are rewritten to inject call-site metadata.
749
+ const dotMethodMatch = code.slice(i + 2).match(/^\.(time|timeLog|timeEnd)\(/);
814
750
  if (dotMethodMatch) {
815
751
  const methodName = dotMethodMatch[1];
816
752
  const internalName = `_${methodName}`;
@@ -828,13 +764,13 @@ export function transformGgCalls(code, shortPath, filePath, svelteInfo, jsFuncti
828
764
  const argsText = code.slice(openParenPos + 1, closeParenPos).trim();
829
765
  result.push(code.slice(lastIndex, i));
830
766
  if (argsText === '') {
831
- // gg.warn() → gg._warn(opts)
767
+ // gg.time() → gg._time(opts)
832
768
  result.push(`gg.${internalName}(${buildOptions(range, escapedNs, line, col)})`);
833
769
  lastIndex = closeParenPos + 1;
834
770
  i = closeParenPos + 1;
835
771
  }
836
772
  else {
837
- // gg.warn(expr) → gg._warn(opts, expr)
773
+ // gg.time('label') → gg._time(opts, 'label')
838
774
  const escapedSrc = escapeForString(argsText);
839
775
  result.push(`gg.${internalName}(${buildOptions(range, escapedNs, line, col, escapedSrc)}, `);
840
776
  lastIndex = openParenPos + 1; // keep original args
@@ -843,12 +779,12 @@ export function transformGgCalls(code, shortPath, filePath, svelteInfo, jsFuncti
843
779
  modified = true;
844
780
  continue;
845
781
  }
846
- // Skip other gg.* calls (gg.enable, gg.disable, gg._ns, gg._onLog, gg.time, etc.)
782
+ // Skip other gg.* calls (gg.enable, gg.disable, gg._ns, gg._onLog, etc.)
847
783
  if (code[i + 2] === '.') {
848
784
  i += 3;
849
785
  continue;
850
786
  }
851
- // Case 2: bare gg(...) → gg._ns({ns, file, line, col, src}, ...)
787
+ // Case 3: bare gg(...) → gg._ns({ns, file, line, col, src}, ...)
852
788
  if (code[i + 2] === '(') {
853
789
  const { line, col } = getLineCol(code, i);
854
790
  const fnName = getFunctionName(i, range);
package/dist/gg.d.ts CHANGED
@@ -21,16 +21,91 @@ interface CapturedEntry {
21
21
  };
22
22
  }
23
23
  type OnLogCallback = (entry: CapturedEntry) => void;
24
- export declare function gg(): {
25
- fileName: string;
26
- functionName: string;
27
- url: string;
28
- };
29
- export declare function gg<T>(arg: T, ...args: unknown[]): T;
30
- export declare namespace gg {
31
- var disable: () => string;
32
- var enable: (ns: string) => void;
33
- var clearPersist: () => void;
24
+ /**
25
+ * Log a value and return a chainable wrapper.
26
+ *
27
+ * Chain modifiers to configure the log entry:
28
+ * - `.ns('label')` — set a custom namespace
29
+ * - `.warn()` / `.error()` / `.info()` — set log level
30
+ * - `.trace()` — include stack trace
31
+ * - `.table()` format as ASCII table
32
+ * - `.v` flush immediately and return the passthrough value
33
+ *
34
+ * Without `.v`, the log auto-flushes on the next microtask.
35
+ *
36
+ * @example
37
+ * gg(value) // log with auto namespace
38
+ * gg(value).ns('label').warn() // log with namespace + warn level
39
+ * const x = gg(value).v // passthrough
40
+ * const x = gg(value).ns('foo').v // passthrough with namespace
41
+ */
42
+ export declare function gg<T>(arg: T, ...args: unknown[]): GgChain<T>;
43
+ /** Internal options for the core log function */
44
+ interface LogOptions {
45
+ ns: string;
46
+ file?: string;
47
+ line?: number;
48
+ col?: number;
49
+ src?: string;
50
+ level?: LogLevel;
51
+ stack?: string;
52
+ tableData?: {
53
+ keys: string[];
54
+ rows: Array<Record<string, unknown>>;
55
+ };
56
+ }
57
+ /**
58
+ * Chainable wrapper returned by gg(). Collects modifiers (.ns(), .warn(), etc.)
59
+ * and auto-flushes the log on the next microtask. Use `.v` to flush immediately
60
+ * and get the passthrough value.
61
+ *
62
+ * @example
63
+ * gg(value) // logs on microtask
64
+ * gg(value).ns('label').warn() // logs with namespace + warn level
65
+ * const x = gg(value).v // logs immediately, returns value
66
+ * const x = gg(value).ns('foo').v // logs with namespace, returns value
67
+ */
68
+ export declare class GgChain<T> {
69
+ #private;
70
+ constructor(value: T, args: unknown[], options: LogOptions, disabled?: boolean);
71
+ /** Set a custom namespace for this log entry.
72
+ *
73
+ * Supports template variables (resolved from plugin-provided metadata):
74
+ * $NS - auto-generated callpoint (file@fn with plugin, word-tuple without)
75
+ * $FN - enclosing function name (extracted from $NS)
76
+ * $FILE - short file path (extracted from $NS)
77
+ * $LINE - line number
78
+ * $COL - column number
79
+ */
80
+ ns(label: string): GgChain<T>;
81
+ /** Set log level to info (blue indicator). */
82
+ info(): GgChain<T>;
83
+ /** Set log level to warn (yellow indicator). */
84
+ warn(): GgChain<T>;
85
+ /** Set log level to error (red indicator, captures stack trace). */
86
+ error(): GgChain<T>;
87
+ /** Include a full stack trace with this log entry. */
88
+ trace(): GgChain<T>;
89
+ /** Format the log output as an ASCII table. */
90
+ table(columns?: string[]): GgChain<T>;
91
+ /** Flush the log immediately and return the passthrough value. */
92
+ get v(): T;
93
+ }
94
+ /**
95
+ * Chainable wrapper returned by gg.time(). Only supports .ns() for setting
96
+ * the namespace for the entire timer group (inherited by timeLog/timeEnd).
97
+ *
98
+ * @example
99
+ * gg.time('fetch').ns('api-pipeline')
100
+ * gg.time('fetch').ns('$FN:timers') // template vars work too
101
+ */
102
+ export declare class GgTimerChain {
103
+ #private;
104
+ constructor(label: string, options: LogOptions);
105
+ /** Set a custom namespace for this timer group.
106
+ * Supports the same template variables as GgChain.ns().
107
+ */
108
+ ns(label: string): GgTimerChain;
34
109
  }
35
110
  /**
36
111
  * ANSI Color Helpers for gg()
@@ -134,7 +209,6 @@ export declare function underline(): ChainableColorFn;
134
209
  export declare function dim(): ChainableColorFn;
135
210
  export declare namespace gg {
136
211
  let _onLog: OnLogCallback | null;
137
- let ns: (nsLabel: string, ...args: unknown[]) => unknown;
138
212
  let _ns: (options: {
139
213
  ns: string;
140
214
  file?: string;
@@ -143,7 +217,7 @@ export declare namespace gg {
143
217
  src?: string;
144
218
  level?: LogLevel;
145
219
  stack?: string;
146
- }, ...args: unknown[]) => unknown;
220
+ }, ...args: unknown[]) => GgChain<unknown>;
147
221
  let _o: (ns: string, file?: string, line?: number, col?: number, src?: string) => {
148
222
  ns: string;
149
223
  file?: string;
@@ -151,64 +225,34 @@ export declare namespace gg {
151
225
  col?: number;
152
226
  src?: string;
153
227
  };
154
- let info: (...args: unknown[]) => unknown;
155
- let warn: (...args: unknown[]) => unknown;
156
- let error: (...args: unknown[]) => unknown;
157
- let assert: (condition: unknown, ...args: unknown[]) => unknown;
158
- let table: (data: unknown, columns?: string[]) => unknown;
159
- let time: (label?: string) => void;
160
- let timeLog: (label?: string, ...args: unknown[]) => void;
161
- let timeEnd: (label?: string) => void;
162
- let trace: (...args: unknown[]) => unknown;
163
- let _info: (options: {
164
- ns: string;
165
- file?: string;
166
- line?: number;
167
- col?: number;
168
- src?: string;
169
- }, ...args: unknown[]) => unknown;
170
- let _warn: (options: {
171
- ns: string;
172
- file?: string;
173
- line?: number;
174
- col?: number;
175
- src?: string;
176
- }, ...args: unknown[]) => unknown;
177
- let _error: (options: {
178
- ns: string;
179
- file?: string;
180
- line?: number;
181
- col?: number;
182
- src?: string;
183
- }, ...args: unknown[]) => unknown;
184
- let _assert: (options: {
185
- ns: string;
186
- file?: string;
187
- line?: number;
188
- col?: number;
189
- src?: string;
190
- }, condition: unknown, ...args: unknown[]) => unknown;
191
- let _table: (options: {
192
- ns: string;
193
- file?: string;
194
- line?: number;
195
- col?: number;
196
- src?: string;
197
- }, data: unknown, columns?: string[]) => unknown;
198
- let _trace: (options: {
228
+ let here: () => {
229
+ fileName: string;
230
+ functionName: string;
231
+ url: string;
232
+ };
233
+ let _here: (options: {
199
234
  ns: string;
200
235
  file?: string;
201
236
  line?: number;
202
237
  col?: number;
203
- src?: string;
204
- }, ...args: unknown[]) => unknown;
238
+ }) => {
239
+ fileName: string;
240
+ functionName: string;
241
+ url: string;
242
+ };
243
+ let enable: (ns: string) => void;
244
+ let disable: () => string;
245
+ let clearPersist: () => void;
246
+ let time: (label?: string) => GgTimerChain;
247
+ let timeLog: (label?: string, ...args: unknown[]) => void;
248
+ let timeEnd: (label?: string) => void;
205
249
  let _time: (options: {
206
250
  ns: string;
207
251
  file?: string;
208
252
  line?: number;
209
253
  col?: number;
210
254
  src?: string;
211
- }, label?: string) => void;
255
+ }, label?: string) => GgTimerChain;
212
256
  let _timeLog: (options: {
213
257
  ns: string;
214
258
  file?: string;