@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 +224 -5
- package/dist/gg-call-sites-plugin.d.ts +8 -5
- package/dist/gg-call-sites-plugin.js +26 -90
- package/dist/gg.d.ts +104 -60
- package/dist/gg.js +246 -279
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/package.json +1 -2
package/dist/gg.js
CHANGED
|
@@ -41,18 +41,11 @@ if (isCloudflareWorker()) {
|
|
|
41
41
|
}
|
|
42
42
|
// Lazy-load Node.js modules to avoid top-level await (Safari compatibility).
|
|
43
43
|
// The imports start immediately but don't block module evaluation.
|
|
44
|
-
let dotenvModule = null;
|
|
45
44
|
let httpModule = null;
|
|
46
45
|
function loadServerModules() {
|
|
47
46
|
if (isCloudflareWorker() || BROWSER)
|
|
48
47
|
return Promise.resolve();
|
|
49
48
|
return (async () => {
|
|
50
|
-
try {
|
|
51
|
-
dotenvModule = await import('dotenv');
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
// dotenv not available — optional dependency
|
|
55
|
-
}
|
|
56
49
|
try {
|
|
57
50
|
httpModule = await import('http');
|
|
58
51
|
}
|
|
@@ -221,7 +214,8 @@ function openInEditorUrl(fileName, line, col) {
|
|
|
221
214
|
}
|
|
222
215
|
export function gg(...args) {
|
|
223
216
|
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
224
|
-
|
|
217
|
+
// Return a no-op chain that skips logging
|
|
218
|
+
return new GgChain(args[0], args, { ns: '' }, true);
|
|
225
219
|
}
|
|
226
220
|
// Without the call-sites plugin, use cheap stack hash → deterministic word tuple.
|
|
227
221
|
// When the plugin IS installed, all gg() calls are rewritten to gg._ns() at build time,
|
|
@@ -229,49 +223,166 @@ export function gg(...args) {
|
|
|
229
223
|
// Same call site always produces the same word pair (e.g. "calm-fox").
|
|
230
224
|
// depth=2: skip "Error" header [0] and gg() frame [1]
|
|
231
225
|
const callpoint = resolveCallpoint(2);
|
|
232
|
-
return
|
|
226
|
+
return new GgChain(args[0], args, { ns: callpoint });
|
|
233
227
|
}
|
|
234
228
|
/**
|
|
235
|
-
* gg.
|
|
229
|
+
* gg.here() - Return call-site info for open-in-editor.
|
|
236
230
|
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
* metadata, see gg._ns().
|
|
240
|
-
*
|
|
241
|
-
* The label supports template variables (substituted by the vite plugin
|
|
242
|
-
* at build time, or at runtime for $NS):
|
|
243
|
-
* $NS - auto-generated callpoint (file@fn with plugin, word-tuple without)
|
|
244
|
-
* $FN - enclosing function name (plugin only, empty without)
|
|
245
|
-
* $FILE - short file path (plugin only, empty without)
|
|
246
|
-
* $LINE - line number (plugin only, empty without)
|
|
247
|
-
* $COL - column number (plugin only, empty without)
|
|
248
|
-
*
|
|
249
|
-
* @param nsLabel - The namespace label (appears as gg:<nsLabel> in output)
|
|
250
|
-
* @param args - Same arguments as gg()
|
|
251
|
-
* @returns Same as gg() - the first arg, or call-site info if no args
|
|
231
|
+
* Replaces the old no-arg gg() overload. Returns an object with the
|
|
232
|
+
* file name, function name, and URL for opening the source in an editor.
|
|
252
233
|
*
|
|
253
234
|
* @example
|
|
254
|
-
* gg.
|
|
255
|
-
* gg.ns("ERROR:$NS", msg) // → gg:ERROR:routes/+page.svelte@handleClick (with plugin)
|
|
256
|
-
* // → gg:ERROR:calm-fox (without plugin)
|
|
257
|
-
* gg.ns("$NS:validation", fieldName) // → gg:routes/+page.svelte@handleClick:validation
|
|
235
|
+
* <OpenInEditorLink gg={gg.here()} />
|
|
258
236
|
*/
|
|
259
|
-
gg.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
// depth=3: skip "Error" [0], resolveCallpoint [1], gg.ns [2] → caller [3]
|
|
263
|
-
if (nsLabel.includes('$NS')) {
|
|
264
|
-
const callpoint = resolveCallpoint(3);
|
|
265
|
-
nsLabel = nsLabel.replace(/\$NS/g, callpoint);
|
|
237
|
+
gg.here = function () {
|
|
238
|
+
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
239
|
+
return { fileName: '', functionName: '', url: '' };
|
|
266
240
|
}
|
|
267
|
-
|
|
241
|
+
const callpoint = resolveCallpoint(3);
|
|
242
|
+
const namespace = `gg:${callpoint}`;
|
|
243
|
+
// Log the call-site info
|
|
244
|
+
const ggLogFunction = namespaceToLogFunction.get(namespace) ||
|
|
245
|
+
namespaceToLogFunction.set(namespace, createGgDebugger(namespace)).get(namespace);
|
|
246
|
+
ggLogFunction(` 📝 ${callpoint}`);
|
|
247
|
+
return {
|
|
248
|
+
fileName: callpoint,
|
|
249
|
+
functionName: callpoint.includes('@') ? callpoint.split('@').pop() || '' : '',
|
|
250
|
+
url: ''
|
|
251
|
+
};
|
|
268
252
|
};
|
|
253
|
+
/**
|
|
254
|
+
* Resolve template variables in a namespace label using metadata from the plugin.
|
|
255
|
+
*
|
|
256
|
+
* The Vite plugin bakes the auto-generated callpoint into options.ns at build time
|
|
257
|
+
* (e.g. "routes/+page.svelte@handleClick"). This function extracts components from
|
|
258
|
+
* that callpoint and substitutes template variables:
|
|
259
|
+
*
|
|
260
|
+
* $NS - the full auto-generated callpoint (or runtime word-tuple fallback)
|
|
261
|
+
* $FN - the function name portion (after @)
|
|
262
|
+
* $FILE - the file path portion (before @)
|
|
263
|
+
* $LINE - the line number
|
|
264
|
+
* $COL - the column number
|
|
265
|
+
*/
|
|
266
|
+
function resolveNsTemplateVars(label, options) {
|
|
267
|
+
if (!label.includes('$'))
|
|
268
|
+
return label;
|
|
269
|
+
const ns = options.ns || '';
|
|
270
|
+
// $NS: use the full auto-generated callpoint. If no plugin, fall back to runtime stack hash.
|
|
271
|
+
if (label.includes('$NS')) {
|
|
272
|
+
const callpoint = ns || resolveCallpoint(4);
|
|
273
|
+
label = label.replace(/\$NS/g, callpoint);
|
|
274
|
+
}
|
|
275
|
+
// $FN: extract function name from "file@fn" format
|
|
276
|
+
if (label.includes('$FN')) {
|
|
277
|
+
const fn = ns.includes('@') ? ns.split('@').pop() || '' : '';
|
|
278
|
+
label = label.replace(/\$FN/g, fn);
|
|
279
|
+
}
|
|
280
|
+
// $FILE: extract file path from "file@fn" format
|
|
281
|
+
if (label.includes('$FILE')) {
|
|
282
|
+
const file = ns.includes('@') ? ns.split('@')[0] : ns;
|
|
283
|
+
label = label.replace(/\$FILE/g, file);
|
|
284
|
+
}
|
|
285
|
+
// $LINE / $COL: from plugin metadata
|
|
286
|
+
if (label.includes('$LINE')) {
|
|
287
|
+
label = label.replace(/\$LINE/g, String(options.line ?? ''));
|
|
288
|
+
}
|
|
289
|
+
if (label.includes('$COL')) {
|
|
290
|
+
label = label.replace(/\$COL/g, String(options.col ?? ''));
|
|
291
|
+
}
|
|
292
|
+
return label;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Chainable wrapper returned by gg(). Collects modifiers (.ns(), .warn(), etc.)
|
|
296
|
+
* and auto-flushes the log on the next microtask. Use `.v` to flush immediately
|
|
297
|
+
* and get the passthrough value.
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* gg(value) // logs on microtask
|
|
301
|
+
* gg(value).ns('label').warn() // logs with namespace + warn level
|
|
302
|
+
* const x = gg(value).v // logs immediately, returns value
|
|
303
|
+
* const x = gg(value).ns('foo').v // logs with namespace, returns value
|
|
304
|
+
*/
|
|
305
|
+
export class GgChain {
|
|
306
|
+
#value;
|
|
307
|
+
#args;
|
|
308
|
+
#options;
|
|
309
|
+
#flushed = false;
|
|
310
|
+
#disabled;
|
|
311
|
+
constructor(value, args, options, disabled = false) {
|
|
312
|
+
this.#value = value;
|
|
313
|
+
this.#args = args;
|
|
314
|
+
this.#options = options;
|
|
315
|
+
this.#disabled = disabled;
|
|
316
|
+
if (!disabled) {
|
|
317
|
+
// Auto-flush on microtask if not flushed synchronously by .v or another trigger
|
|
318
|
+
queueMicrotask(() => this.#flush());
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/** Set a custom namespace for this log entry.
|
|
322
|
+
*
|
|
323
|
+
* Supports template variables (resolved from plugin-provided metadata):
|
|
324
|
+
* $NS - auto-generated callpoint (file@fn with plugin, word-tuple without)
|
|
325
|
+
* $FN - enclosing function name (extracted from $NS)
|
|
326
|
+
* $FILE - short file path (extracted from $NS)
|
|
327
|
+
* $LINE - line number
|
|
328
|
+
* $COL - column number
|
|
329
|
+
*/
|
|
330
|
+
ns(label) {
|
|
331
|
+
this.#options.ns = resolveNsTemplateVars(label, this.#options);
|
|
332
|
+
return this;
|
|
333
|
+
}
|
|
334
|
+
/** Set log level to info (blue indicator). */
|
|
335
|
+
info() {
|
|
336
|
+
this.#options.level = 'info';
|
|
337
|
+
return this;
|
|
338
|
+
}
|
|
339
|
+
/** Set log level to warn (yellow indicator). */
|
|
340
|
+
warn() {
|
|
341
|
+
this.#options.level = 'warn';
|
|
342
|
+
return this;
|
|
343
|
+
}
|
|
344
|
+
/** Set log level to error (red indicator, captures stack trace). */
|
|
345
|
+
error() {
|
|
346
|
+
this.#options.level = 'error';
|
|
347
|
+
this.#options.stack = getErrorStack(this.#args[0], 3);
|
|
348
|
+
return this;
|
|
349
|
+
}
|
|
350
|
+
/** Include a full stack trace with this log entry. */
|
|
351
|
+
trace() {
|
|
352
|
+
this.#options.stack = captureStack(3);
|
|
353
|
+
return this;
|
|
354
|
+
}
|
|
355
|
+
/** Format the log output as an ASCII table. */
|
|
356
|
+
table(columns) {
|
|
357
|
+
const { keys, rows } = formatTable(this.#args[0], columns);
|
|
358
|
+
this.#options.tableData = { keys, rows };
|
|
359
|
+
// Override args to show '(table)' label, matching original gg.table() behavior
|
|
360
|
+
this.#args = ['(table)'];
|
|
361
|
+
// Also emit native console.table
|
|
362
|
+
if (columns) {
|
|
363
|
+
console.table(this.#value, columns);
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
console.table(this.#value);
|
|
367
|
+
}
|
|
368
|
+
return this;
|
|
369
|
+
}
|
|
370
|
+
/** Flush the log immediately and return the passthrough value. */
|
|
371
|
+
get v() {
|
|
372
|
+
this.#flush();
|
|
373
|
+
return this.#value;
|
|
374
|
+
}
|
|
375
|
+
#flush() {
|
|
376
|
+
if (this.#flushed)
|
|
377
|
+
return;
|
|
378
|
+
this.#flushed = true;
|
|
379
|
+
ggLog(this.#options, ...this.#args);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
269
382
|
/**
|
|
270
383
|
* Core logging function shared by all gg methods.
|
|
271
384
|
*
|
|
272
|
-
*
|
|
273
|
-
* funnel through this function. It handles namespace resolution,
|
|
274
|
-
* debug output, capture hook, and passthrough return.
|
|
385
|
+
* Handles namespace resolution, debug output, capture hook, and return value.
|
|
275
386
|
*/
|
|
276
387
|
function ggLog(options, ...args) {
|
|
277
388
|
const { ns: nsLabel, file, line, col, src, level, stack, tableData } = options;
|
|
@@ -286,24 +397,7 @@ function ggLog(options, ...args) {
|
|
|
286
397
|
namespaceToLogFunction.set(namespace, createGgDebugger(namespace)).get(namespace);
|
|
287
398
|
// Prepare args for logging (console output is value-only; src is carried
|
|
288
399
|
// on CapturedEntry for the Eruda UI to display on hover)
|
|
289
|
-
|
|
290
|
-
let returnValue;
|
|
291
|
-
if (!args.length) {
|
|
292
|
-
// No arguments: return call-site info for open-in-editor
|
|
293
|
-
const fileName = file ? file.replace(srcRootRegex, '') : nsLabel;
|
|
294
|
-
const functionName = nsLabel.includes('@') ? nsLabel.split('@').pop() || '' : '';
|
|
295
|
-
const url = file ? openInEditorUrl(file, line, col) : '';
|
|
296
|
-
logArgs = [` 📝 ${nsLabel}`];
|
|
297
|
-
returnValue = { fileName, functionName, url };
|
|
298
|
-
}
|
|
299
|
-
else if (args.length === 1) {
|
|
300
|
-
logArgs = [args[0]];
|
|
301
|
-
returnValue = args[0];
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
304
|
-
logArgs = [args[0], ...args.slice(1)];
|
|
305
|
-
returnValue = args[0];
|
|
306
|
-
}
|
|
400
|
+
const logArgs = args.length === 0 ? ['(no args)'] : [...args];
|
|
307
401
|
// Add level prefix emoji for info/warn/error
|
|
308
402
|
if (level === 'info') {
|
|
309
403
|
logArgs[0] = `ℹ️ ${logArgs[0]}`;
|
|
@@ -351,22 +445,39 @@ function ggLog(options, ...args) {
|
|
|
351
445
|
else {
|
|
352
446
|
earlyLogBuffer.push(entry);
|
|
353
447
|
}
|
|
354
|
-
return returnValue;
|
|
355
448
|
}
|
|
356
449
|
/**
|
|
357
450
|
* gg._ns() - Internal: log with namespace and source file metadata.
|
|
358
451
|
*
|
|
359
|
-
* Called by the ggCallSitesPlugin Vite plugin, which rewrites
|
|
360
|
-
* calls
|
|
361
|
-
*
|
|
452
|
+
* Called by the ggCallSitesPlugin Vite plugin, which rewrites bare gg()
|
|
453
|
+
* calls to gg._ns({ns, file, line, col, src}, ...) at build time.
|
|
454
|
+
* This gives each call site a unique namespace plus the source
|
|
362
455
|
* location for open-in-editor support.
|
|
363
456
|
*
|
|
364
|
-
*
|
|
365
|
-
* @param args - Same arguments as gg()
|
|
366
|
-
* @returns Same as gg() - the first arg, or call-site info if no args
|
|
457
|
+
* Returns a GgChain for chaining modifiers (.ns(), .warn(), etc.)
|
|
367
458
|
*/
|
|
368
459
|
gg._ns = function (options, ...args) {
|
|
369
|
-
|
|
460
|
+
const disabled = !ggConfig.enabled || isCloudflareWorker();
|
|
461
|
+
return new GgChain(args[0], args, options, disabled);
|
|
462
|
+
};
|
|
463
|
+
/**
|
|
464
|
+
* gg._here() - Internal: call-site info with source metadata from Vite plugin.
|
|
465
|
+
*
|
|
466
|
+
* Called by the ggCallSitesPlugin when it rewrites gg.here() calls.
|
|
467
|
+
*/
|
|
468
|
+
gg._here = function (options) {
|
|
469
|
+
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
470
|
+
return { fileName: '', functionName: '', url: '' };
|
|
471
|
+
}
|
|
472
|
+
const { ns: nsLabel, file, line, col } = options;
|
|
473
|
+
const namespace = `gg:${nsLabel}`;
|
|
474
|
+
const ggLogFunction = namespaceToLogFunction.get(namespace) ||
|
|
475
|
+
namespaceToLogFunction.set(namespace, createGgDebugger(namespace)).get(namespace);
|
|
476
|
+
ggLogFunction(` 📝 ${nsLabel}`);
|
|
477
|
+
const fileName = file ? file.replace(srcRootRegex, '') : nsLabel;
|
|
478
|
+
const functionName = nsLabel.includes('@') ? nsLabel.split('@').pop() || '' : '';
|
|
479
|
+
const url = file ? openInEditorUrl(file, line, col) : '';
|
|
480
|
+
return { fileName, functionName, url };
|
|
370
481
|
};
|
|
371
482
|
/**
|
|
372
483
|
* gg._o() - Internal: build options object for gg._ns() without object literal syntax.
|
|
@@ -423,181 +534,66 @@ function getErrorStack(firstArg, skipFrames) {
|
|
|
423
534
|
}
|
|
424
535
|
return captureStack(skipFrames);
|
|
425
536
|
}
|
|
537
|
+
// Timer storage for gg.time / gg.timeEnd / gg.timeLog
|
|
538
|
+
// Maps timer label → { start: number, ns?: string, options?: LogOptions }
|
|
539
|
+
const timers = new Map();
|
|
426
540
|
/**
|
|
427
|
-
* gg.
|
|
428
|
-
*
|
|
429
|
-
* Passthrough: returns the first argument.
|
|
430
|
-
* In Eruda, entries are styled with a blue/info indicator.
|
|
431
|
-
*
|
|
432
|
-
* @example
|
|
433
|
-
* gg.info('System startup complete');
|
|
434
|
-
* const config = gg.info(loadedConfig, 'loaded config');
|
|
435
|
-
*/
|
|
436
|
-
gg.info = function (...args) {
|
|
437
|
-
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
438
|
-
return args.length ? args[0] : undefined;
|
|
439
|
-
}
|
|
440
|
-
const callpoint = resolveCallpoint(3);
|
|
441
|
-
return ggLog({ ns: callpoint, level: 'info' }, ...args);
|
|
442
|
-
};
|
|
443
|
-
/**
|
|
444
|
-
* gg._info() - Internal: info with call-site metadata from Vite plugin.
|
|
445
|
-
*/
|
|
446
|
-
gg._info = function (options, ...args) {
|
|
447
|
-
return ggLog({ ...options, level: 'info' }, ...args);
|
|
448
|
-
};
|
|
449
|
-
/**
|
|
450
|
-
* gg.warn() - Log at warning level.
|
|
451
|
-
*
|
|
452
|
-
* Passthrough: returns the first argument.
|
|
453
|
-
* In Eruda, entries are styled with a yellow/warning indicator.
|
|
454
|
-
*
|
|
455
|
-
* @example
|
|
456
|
-
* gg.warn('deprecated API used');
|
|
457
|
-
* const result = gg.warn(computeValue(), 'might be slow');
|
|
458
|
-
*/
|
|
459
|
-
gg.warn = function (...args) {
|
|
460
|
-
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
461
|
-
return args.length ? args[0] : undefined;
|
|
462
|
-
}
|
|
463
|
-
const callpoint = resolveCallpoint(3);
|
|
464
|
-
return ggLog({ ns: callpoint, level: 'warn' }, ...args);
|
|
465
|
-
};
|
|
466
|
-
/**
|
|
467
|
-
* gg._warn() - Internal: warn with call-site metadata from Vite plugin.
|
|
468
|
-
*/
|
|
469
|
-
gg._warn = function (options, ...args) {
|
|
470
|
-
return ggLog({ ...options, level: 'warn' }, ...args);
|
|
471
|
-
};
|
|
472
|
-
/**
|
|
473
|
-
* gg.error() - Log at error level.
|
|
474
|
-
*
|
|
475
|
-
* Passthrough: returns the first argument.
|
|
476
|
-
* Captures a stack trace silently — visible in Eruda via a collapsible toggle.
|
|
477
|
-
* If the first argument is an Error object, its .stack is used instead.
|
|
478
|
-
*
|
|
479
|
-
* @example
|
|
480
|
-
* gg.error('connection failed');
|
|
481
|
-
* gg.error(new Error('timeout'));
|
|
482
|
-
* const val = gg.error(response, 'unexpected status');
|
|
483
|
-
*/
|
|
484
|
-
gg.error = function (...args) {
|
|
485
|
-
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
486
|
-
return args.length ? args[0] : undefined;
|
|
487
|
-
}
|
|
488
|
-
const callpoint = resolveCallpoint(3);
|
|
489
|
-
const stack = getErrorStack(args[0], 4);
|
|
490
|
-
return ggLog({ ns: callpoint, level: 'error', stack }, ...args);
|
|
491
|
-
};
|
|
492
|
-
/**
|
|
493
|
-
* gg._error() - Internal: error with call-site metadata from Vite plugin.
|
|
494
|
-
*/
|
|
495
|
-
gg._error = function (options, ...args) {
|
|
496
|
-
const stack = getErrorStack(args[0], 3);
|
|
497
|
-
return ggLog({ ...options, level: 'error', stack }, ...args);
|
|
498
|
-
};
|
|
499
|
-
/**
|
|
500
|
-
* gg.assert() - Log only if condition is false.
|
|
501
|
-
*
|
|
502
|
-
* Like console.assert: if the first argument is falsy, logs the remaining
|
|
503
|
-
* arguments at error level. If the condition is truthy, does nothing.
|
|
504
|
-
* Passthrough: always returns the condition value.
|
|
541
|
+
* Chainable wrapper returned by gg.time(). Only supports .ns() for setting
|
|
542
|
+
* the namespace for the entire timer group (inherited by timeLog/timeEnd).
|
|
505
543
|
*
|
|
506
544
|
* @example
|
|
507
|
-
* gg.
|
|
508
|
-
* gg.
|
|
509
|
-
*/
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
if (!ggConfig.enabled || isCloudflareWorker())
|
|
527
|
-
return condition;
|
|
528
|
-
const stack = captureStack(3);
|
|
529
|
-
const assertArgs = args.length > 0 ? args : ['Assertion failed'];
|
|
530
|
-
ggLog({ ...options, level: 'error', stack }, ...assertArgs);
|
|
545
|
+
* gg.time('fetch').ns('api-pipeline')
|
|
546
|
+
* gg.time('fetch').ns('$FN:timers') // template vars work too
|
|
547
|
+
*/
|
|
548
|
+
export class GgTimerChain {
|
|
549
|
+
#label;
|
|
550
|
+
#options;
|
|
551
|
+
constructor(label, options) {
|
|
552
|
+
this.#label = label;
|
|
553
|
+
this.#options = options;
|
|
554
|
+
}
|
|
555
|
+
/** Set a custom namespace for this timer group.
|
|
556
|
+
* Supports the same template variables as GgChain.ns().
|
|
557
|
+
*/
|
|
558
|
+
ns(label) {
|
|
559
|
+
const resolved = resolveNsTemplateVars(label, this.#options);
|
|
560
|
+
const timer = timers.get(this.#label);
|
|
561
|
+
if (timer)
|
|
562
|
+
timer.ns = resolved;
|
|
563
|
+
return this;
|
|
531
564
|
}
|
|
532
|
-
|
|
533
|
-
};
|
|
565
|
+
}
|
|
534
566
|
/**
|
|
535
|
-
* gg.
|
|
567
|
+
* gg.time() - Start a named timer. Returns a GgTimerChain for optional .ns() chaining.
|
|
536
568
|
*
|
|
537
|
-
*
|
|
538
|
-
* Passthrough: returns the data argument.
|
|
569
|
+
* @param label - Timer label (default: 'default')
|
|
539
570
|
*
|
|
540
571
|
* @example
|
|
541
|
-
* gg.
|
|
542
|
-
* gg.
|
|
543
|
-
|
|
544
|
-
gg.table = function (data, columns) {
|
|
545
|
-
if (!ggConfig.enabled || isCloudflareWorker())
|
|
546
|
-
return data;
|
|
547
|
-
const callpoint = resolveCallpoint(3);
|
|
548
|
-
const { keys, rows } = formatTable(data, columns);
|
|
549
|
-
ggLog({ ns: callpoint, tableData: { keys, rows } }, '(table)');
|
|
550
|
-
// Also emit a native console.table for proper rendering in browser/Node consoles
|
|
551
|
-
if (columns) {
|
|
552
|
-
console.table(data, columns);
|
|
553
|
-
}
|
|
554
|
-
else {
|
|
555
|
-
console.table(data);
|
|
556
|
-
}
|
|
557
|
-
return data;
|
|
558
|
-
};
|
|
559
|
-
/**
|
|
560
|
-
* gg._table() - Internal: table with call-site metadata from Vite plugin.
|
|
561
|
-
*/
|
|
562
|
-
gg._table = function (options, data, columns) {
|
|
563
|
-
if (!ggConfig.enabled || isCloudflareWorker())
|
|
564
|
-
return data;
|
|
565
|
-
const { keys, rows } = formatTable(data, columns);
|
|
566
|
-
ggLog({ ...options, tableData: { keys, rows } }, '(table)');
|
|
567
|
-
if (columns) {
|
|
568
|
-
console.table(data, columns);
|
|
569
|
-
}
|
|
570
|
-
else {
|
|
571
|
-
console.table(data);
|
|
572
|
-
}
|
|
573
|
-
return data;
|
|
574
|
-
};
|
|
575
|
-
// Timer storage for gg.time / gg.timeEnd / gg.timeLog
|
|
576
|
-
const timers = new Map();
|
|
577
|
-
/**
|
|
578
|
-
* gg.time() - Start a named timer.
|
|
579
|
-
*
|
|
580
|
-
* @example
|
|
581
|
-
* gg.time('fetch');
|
|
582
|
-
* const data = await fetchData();
|
|
583
|
-
* gg.timeEnd('fetch'); // logs "+123ms fetch: 456ms"
|
|
572
|
+
* gg.time('fetch') // basic timer
|
|
573
|
+
* gg.time('fetch').ns('api-pipeline') // with namespace (inherited by timeLog/timeEnd)
|
|
574
|
+
* gg.time('fetch').ns('$FN:timers') // with template variable (plugin)
|
|
584
575
|
*/
|
|
585
576
|
gg.time = function (label = 'default') {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
577
|
+
const options = { ns: resolveCallpoint(3) };
|
|
578
|
+
if (ggConfig.enabled && !isCloudflareWorker()) {
|
|
579
|
+
timers.set(label, { start: performance.now(), options });
|
|
580
|
+
}
|
|
581
|
+
return new GgTimerChain(label, options);
|
|
589
582
|
};
|
|
590
583
|
/** gg._time() - Internal: time with call-site metadata from Vite plugin. */
|
|
591
|
-
gg._time = function (
|
|
592
|
-
if (
|
|
593
|
-
|
|
594
|
-
|
|
584
|
+
gg._time = function (options, label = 'default') {
|
|
585
|
+
if (ggConfig.enabled && !isCloudflareWorker()) {
|
|
586
|
+
timers.set(label, { start: performance.now(), options });
|
|
587
|
+
}
|
|
588
|
+
return new GgTimerChain(label, options);
|
|
595
589
|
};
|
|
596
590
|
/**
|
|
597
591
|
* gg.timeLog() - Log the current elapsed time without stopping the timer.
|
|
598
592
|
*
|
|
593
|
+
* Inherits the namespace set by gg.time().ns() for this timer label.
|
|
594
|
+
*
|
|
599
595
|
* @example
|
|
600
|
-
* gg.time('process');
|
|
596
|
+
* gg.time('process').ns('my-namespace');
|
|
601
597
|
* // ... step 1 ...
|
|
602
598
|
* gg.timeLog('process', 'step 1 done');
|
|
603
599
|
* // ... step 2 ...
|
|
@@ -606,92 +602,66 @@ gg._time = function (_options, label = 'default') {
|
|
|
606
602
|
gg.timeLog = function (label = 'default', ...args) {
|
|
607
603
|
if (!ggConfig.enabled || isCloudflareWorker())
|
|
608
604
|
return;
|
|
609
|
-
const
|
|
610
|
-
if (
|
|
605
|
+
const timer = timers.get(label);
|
|
606
|
+
if (timer === undefined) {
|
|
611
607
|
const callpoint = resolveCallpoint(3);
|
|
612
608
|
ggLog({ ns: callpoint, level: 'warn' }, `Timer '${label}' does not exist`);
|
|
613
609
|
return;
|
|
614
610
|
}
|
|
615
|
-
const elapsed = performance.now() - start;
|
|
616
|
-
const
|
|
617
|
-
ggLog({ ns
|
|
611
|
+
const elapsed = performance.now() - timer.start;
|
|
612
|
+
const ns = timer.ns ?? timer.options?.ns ?? resolveCallpoint(3);
|
|
613
|
+
ggLog({ ...timer.options, ns }, `${label}: ${formatElapsed(elapsed)}`, ...args);
|
|
618
614
|
};
|
|
619
615
|
/** gg._timeLog() - Internal: timeLog with call-site metadata from Vite plugin. */
|
|
620
616
|
gg._timeLog = function (options, label = 'default', ...args) {
|
|
621
617
|
if (!ggConfig.enabled || isCloudflareWorker())
|
|
622
618
|
return;
|
|
623
|
-
const
|
|
624
|
-
if (
|
|
619
|
+
const timer = timers.get(label);
|
|
620
|
+
if (timer === undefined) {
|
|
625
621
|
ggLog({ ...options, level: 'warn' }, `Timer '${label}' does not exist`);
|
|
626
622
|
return;
|
|
627
623
|
}
|
|
628
|
-
const elapsed = performance.now() - start;
|
|
629
|
-
|
|
624
|
+
const elapsed = performance.now() - timer.start;
|
|
625
|
+
const ns = timer.ns ?? timer.options?.ns ?? options.ns;
|
|
626
|
+
ggLog({ ...options, ns }, `${label}: ${formatElapsed(elapsed)}`, ...args);
|
|
630
627
|
};
|
|
631
628
|
/**
|
|
632
629
|
* gg.timeEnd() - Stop a named timer and log the elapsed time.
|
|
633
630
|
*
|
|
631
|
+
* Inherits the namespace set by gg.time().ns() for this timer label.
|
|
632
|
+
*
|
|
634
633
|
* @example
|
|
635
|
-
* gg.time('fetch');
|
|
634
|
+
* gg.time('fetch').ns('api-pipeline');
|
|
636
635
|
* const data = await fetchData();
|
|
637
|
-
* gg.timeEnd('fetch'); // logs
|
|
636
|
+
* gg.timeEnd('fetch'); // logs under 'api-pipeline' namespace
|
|
638
637
|
*/
|
|
639
638
|
gg.timeEnd = function (label = 'default') {
|
|
640
639
|
if (!ggConfig.enabled || isCloudflareWorker())
|
|
641
640
|
return;
|
|
642
|
-
const
|
|
643
|
-
if (
|
|
641
|
+
const timer = timers.get(label);
|
|
642
|
+
if (timer === undefined) {
|
|
644
643
|
const callpoint = resolveCallpoint(3);
|
|
645
644
|
ggLog({ ns: callpoint, level: 'warn' }, `Timer '${label}' does not exist`);
|
|
646
645
|
return;
|
|
647
646
|
}
|
|
648
|
-
const elapsed = performance.now() - start;
|
|
647
|
+
const elapsed = performance.now() - timer.start;
|
|
649
648
|
timers.delete(label);
|
|
650
|
-
const
|
|
651
|
-
ggLog({ ns
|
|
649
|
+
const ns = timer.ns ?? timer.options?.ns ?? resolveCallpoint(3);
|
|
650
|
+
ggLog({ ...timer.options, ns }, `${label}: ${formatElapsed(elapsed)}`);
|
|
652
651
|
};
|
|
653
652
|
/** gg._timeEnd() - Internal: timeEnd with call-site metadata from Vite plugin. */
|
|
654
653
|
gg._timeEnd = function (options, label = 'default') {
|
|
655
654
|
if (!ggConfig.enabled || isCloudflareWorker())
|
|
656
655
|
return;
|
|
657
|
-
const
|
|
658
|
-
if (
|
|
656
|
+
const timer = timers.get(label);
|
|
657
|
+
if (timer === undefined) {
|
|
659
658
|
ggLog({ ...options, level: 'warn' }, `Timer '${label}' does not exist`);
|
|
660
659
|
return;
|
|
661
660
|
}
|
|
662
|
-
const elapsed = performance.now() - start;
|
|
661
|
+
const elapsed = performance.now() - timer.start;
|
|
663
662
|
timers.delete(label);
|
|
664
|
-
|
|
665
|
-
};
|
|
666
|
-
/**
|
|
667
|
-
* gg.trace() - Log with a stack trace.
|
|
668
|
-
*
|
|
669
|
-
* Like console.trace: logs the arguments plus a full stack trace.
|
|
670
|
-
* Passthrough: returns the first argument.
|
|
671
|
-
*
|
|
672
|
-
* @example
|
|
673
|
-
* gg.trace('how did we get here?');
|
|
674
|
-
* const val = gg.trace(result, 'call path');
|
|
675
|
-
*/
|
|
676
|
-
gg.trace = function (...args) {
|
|
677
|
-
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
678
|
-
return args.length ? args[0] : undefined;
|
|
679
|
-
}
|
|
680
|
-
const callpoint = resolveCallpoint(3);
|
|
681
|
-
const stack = captureStack(4);
|
|
682
|
-
const traceArgs = args.length > 0 ? args : ['Trace'];
|
|
683
|
-
return ggLog({ ns: callpoint, stack }, ...traceArgs);
|
|
684
|
-
};
|
|
685
|
-
/**
|
|
686
|
-
* gg._trace() - Internal: trace with call-site metadata from Vite plugin.
|
|
687
|
-
*/
|
|
688
|
-
gg._trace = function (options, ...args) {
|
|
689
|
-
if (!ggConfig.enabled || isCloudflareWorker()) {
|
|
690
|
-
return args.length ? args[0] : undefined;
|
|
691
|
-
}
|
|
692
|
-
const stack = captureStack(3);
|
|
693
|
-
const traceArgs = args.length > 0 ? args : ['Trace'];
|
|
694
|
-
return ggLog({ ...options, stack }, ...traceArgs);
|
|
663
|
+
const ns = timer.ns ?? timer.options?.ns ?? options.ns;
|
|
664
|
+
ggLog({ ...options, ns }, `${label}: ${formatElapsed(elapsed)}`);
|
|
695
665
|
};
|
|
696
666
|
/**
|
|
697
667
|
* Format elapsed time with appropriate precision.
|
|
@@ -986,7 +956,7 @@ export async function runGgDiagnostics() {
|
|
|
986
956
|
if (!ggConfig.showHints || isCloudflareWorker() || diagnosticsRan)
|
|
987
957
|
return;
|
|
988
958
|
diagnosticsRan = true;
|
|
989
|
-
// Ensure server modules
|
|
959
|
+
// Ensure server modules and debug factory are loaded before diagnostics
|
|
990
960
|
await serverModulesReady;
|
|
991
961
|
await debugReady;
|
|
992
962
|
// Create test debugger for server-side enabled check
|
|
@@ -1018,10 +988,7 @@ export async function runGgDiagnostics() {
|
|
|
1018
988
|
message(`${checkbox(ggConfig.enabled)} gg enabled: ${ggConfig.enabled}${enableHint}`);
|
|
1019
989
|
if (!BROWSER) {
|
|
1020
990
|
// Server-side: check DEBUG env var (the only output path on the server)
|
|
1021
|
-
const hint = makeHint(!ggLogTest.enabled, ' (Try `DEBUG=gg:* npm run dev`)');
|
|
1022
|
-
if (dotenvModule) {
|
|
1023
|
-
dotenvModule.config();
|
|
1024
|
-
}
|
|
991
|
+
const hint = makeHint(!ggLogTest.enabled, ' (Try `DEBUG=gg:* npm run dev` or use --env-file=.env)');
|
|
1025
992
|
message(`${checkbox(ggLogTest.enabled)} DEBUG env variable: ${process?.env?.DEBUG}${hint}`);
|
|
1026
993
|
}
|
|
1027
994
|
// Optional plugin diagnostics
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { gg, fg, bg, bold, italic, underline, dim } from './gg.js';
|
|
1
|
+
import { gg, GgChain, GgTimerChain, fg, bg, bold, italic, underline, dim } from './gg.js';
|
|
2
2
|
import openInEditorPlugin from './open-in-editor.js';
|
|
3
3
|
import ggCallSitesPlugin from './gg-call-sites-plugin.js';
|
|
4
4
|
export { default as GgConsole } from './GgConsole.svelte';
|
|
5
|
-
export { gg, fg, bg, bold, italic, underline, dim, openInEditorPlugin, ggCallSitesPlugin };
|
|
5
|
+
export { gg, GgChain, GgTimerChain, fg, bg, bold, italic, underline, dim, openInEditorPlugin, ggCallSitesPlugin };
|