@idlebox/common 1.5.13 → 1.5.15

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 (43) hide show
  1. package/lib/autoindex.d.ts +5 -3
  2. package/lib/autoindex.d.ts.map +1 -1
  3. package/lib/autoindex.js +70 -66
  4. package/lib/autoindex.js.map +1 -1
  5. package/lib/debugging/inspect.d.ts +2 -0
  6. package/lib/debugging/inspect.d.ts.map +1 -1
  7. package/lib/debugging/inspect.js +20 -8
  8. package/lib/debugging/inspect.js.map +1 -1
  9. package/lib/error/pretty.nodejs.d.ts +0 -29
  10. package/lib/error/pretty.nodejs.d.ts.map +1 -1
  11. package/lib/error/pretty.nodejs.js +157 -237
  12. package/lib/error/pretty.nodejs.js.map +1 -1
  13. package/lib/error/stack-parser.v8.d.ts +31 -0
  14. package/lib/error/stack-parser.v8.d.ts.map +1 -0
  15. package/lib/error/stack-parser.v8.js +106 -0
  16. package/lib/error/stack-parser.v8.js.map +1 -0
  17. package/lib/lifecycle/dispose/bridges/function.d.ts.map +1 -1
  18. package/lib/lifecycle/dispose/bridges/function.js +5 -2
  19. package/lib/lifecycle/dispose/bridges/function.js.map +1 -1
  20. package/lib/lifecycle/dispose/bridges/streams.d.ts.map +1 -1
  21. package/lib/lifecycle/dispose/bridges/streams.js +9 -4
  22. package/lib/lifecycle/dispose/bridges/streams.js.map +1 -1
  23. package/lib/lifecycle/dispose/disposable.d.ts.map +1 -1
  24. package/lib/lifecycle/dispose/disposable.js +11 -1
  25. package/lib/lifecycle/dispose/disposable.js.map +1 -1
  26. package/lib/lifecycle/event/event.d.ts +2 -0
  27. package/lib/lifecycle/event/event.d.ts.map +1 -1
  28. package/lib/lifecycle/event/event.js +15 -1
  29. package/lib/lifecycle/event/event.js.map +1 -1
  30. package/lib/schedule/interval.d.ts +2 -0
  31. package/lib/schedule/interval.d.ts.map +1 -1
  32. package/lib/schedule/interval.js +11 -0
  33. package/lib/schedule/interval.js.map +1 -1
  34. package/package.json +7 -7
  35. package/src/autoindex.ts +94 -90
  36. package/src/debugging/inspect.ts +24 -4
  37. package/src/error/pretty.nodejs.ts +214 -337
  38. package/src/error/stack-parser.v8.ts +157 -0
  39. package/src/lifecycle/dispose/bridges/function.ts +11 -5
  40. package/src/lifecycle/dispose/bridges/streams.ts +47 -36
  41. package/src/lifecycle/dispose/disposable.ts +14 -1
  42. package/src/lifecycle/event/event.ts +18 -1
  43. package/src/schedule/interval.ts +12 -0
@@ -0,0 +1,157 @@
1
+ import { isAbsolute } from '../path/isAbsolute.js';
2
+
3
+ const padding = /^(?<padding> {4})at /.source;
4
+ const func_call = /(?<func_name>(?:(?:async|new) )?[^/\\\s]+) (?:\[as (?<func_alias>[^\]]+)] )?/.source;
5
+ // xxxx.yyyyy [as eval]
6
+ const line_column = /(?::(?<line>\d+))?(?::(?<column>\d+))?/.source;
7
+ const locationEsm = /(?<schema>node:|file:\/\/|https?:\/\/)?(?<path2>[^:]+)/.source;
8
+ // node:internal/modules/cjs/loader.js:883:14
9
+ const locationCjs = /(?<path1>(?:\/|[a-zA-Z]:)[^:]+)/.source;
10
+ // /data/to/file.js
11
+ const location = `(?:${locationCjs}|${locationEsm})${line_column}`;
12
+
13
+ const regNormal = new RegExp(`${padding}${func_call}\\(${location}\\)$`);
14
+ type TypeMatchNormal = 'padding' | TypeMatchNoFile | TypeMatchFileOnly;
15
+
16
+ const regNoFile = new RegExp(`${padding}${func_call}$`);
17
+ type TypeMatchNoFile = 'padding' | 'func_name' | 'func_alias';
18
+
19
+ const regFileOnly = new RegExp(`${padding}${location}$`);
20
+ type TypeMatchFileOnly = 'padding' | 'schema' | 'path1' | 'path2' | 'line' | 'column';
21
+
22
+ const regEvalItem = new RegExp(`\\(eval at ${func_call}`, 'g');
23
+ const eval_source = /, (?<eval_func>[\S]+):(?<eval_line>\d+):(?<eval_column>\d+)/.source;
24
+ const regEval = new RegExp(`${padding}${func_call}.*?\\(${location}\\)+${eval_source}`);
25
+ type TypeMatchEval = 'padding' | TypeMatchNoFile | TypeMatchFileOnly | 'eval_func' | 'eval_line' | 'eval_column';
26
+
27
+ const regInvalid = new RegExp(`${padding}(?<content>.+) \\(${location}\\)$`);
28
+ type TypeMatchInvalid = TypeMatchFileOnly | 'content';
29
+
30
+ export function parseStackString(stack: string) {
31
+ return stack.split('\n').map(parseStackLine);
32
+ }
33
+
34
+ interface IFunction {
35
+ name: string;
36
+ alias?: string;
37
+ }
38
+ interface IFileLocation {
39
+ path: string;
40
+ schema: string; // '' | 'node:' | 'file:' | 'http:' | 'https:';
41
+ line: number;
42
+ column: number;
43
+ isAbsolute: boolean;
44
+ }
45
+ interface IEvalDef {
46
+ eval_func: string;
47
+ eval_line: number;
48
+ eval_column: number;
49
+ funcs: string[];
50
+ }
51
+
52
+ export interface IStructreStackLine {
53
+ invalid?: boolean;
54
+ special?: boolean;
55
+ toString(): string;
56
+ padding?: string;
57
+ func?: IFunction;
58
+ location?: IFileLocation;
59
+ eval?: IEvalDef;
60
+ _matches?: RegExp;
61
+ }
62
+
63
+ function matchLine<T extends string>(line: string, reg: RegExp): null | Record<T, string> {
64
+ const m = reg.exec(line);
65
+ if (!m) {
66
+ return null;
67
+ }
68
+ return m.groups as any;
69
+ }
70
+ const endingSlashes = /\/+$/;
71
+ function addLoc(ret: IStructreStackLine, m: Record<TypeMatchFileOnly, string>) {
72
+ const path = m.path1 || m.path2;
73
+ ret.location = {
74
+ schema: m.schema?.replace(endingSlashes, '') ?? '',
75
+ path: path,
76
+ line: Number.parseInt(m.line, 10),
77
+ column: Number.parseInt(m.column, 10),
78
+ isAbsolute: isAbsolute(path),
79
+ };
80
+ }
81
+
82
+ function addFunc(ret: IStructreStackLine, m: Record<TypeMatchNoFile, string>) {
83
+ ret.func = {
84
+ name: m.func_name,
85
+ alias: m.func_alias,
86
+ };
87
+ }
88
+
89
+ export function parseStackLine(line: string): IStructreStackLine {
90
+ const __raw = line;
91
+ const ret: IStructreStackLine = {
92
+ invalid: false,
93
+ toString() {
94
+ return __raw;
95
+ },
96
+ };
97
+ Object.assign(ret, { __raw });
98
+
99
+ const mNormal = matchLine<TypeMatchNormal>(line, regNormal);
100
+ if (mNormal) {
101
+ ret._matches = regNormal;
102
+ ret.padding = mNormal.padding;
103
+ addFunc(ret, mNormal);
104
+ addLoc(ret, mNormal);
105
+ return ret;
106
+ }
107
+
108
+ const mFile = matchLine<TypeMatchFileOnly>(line, regFileOnly);
109
+ if (mFile) {
110
+ ret._matches = regFileOnly;
111
+ ret.padding = mFile.padding;
112
+ addLoc(ret, mFile);
113
+ return ret;
114
+ }
115
+
116
+ const mNoFile = matchLine<TypeMatchNoFile>(line, regNoFile);
117
+ if (mNoFile) {
118
+ ret._matches = regNoFile;
119
+ ret.padding = mNoFile.padding;
120
+ addFunc(ret, mNoFile);
121
+ return ret;
122
+ }
123
+
124
+ const mEval = matchLine<TypeMatchEval>(line.replaceAll(regEvalItem, ''), regEval);
125
+ if (mEval) {
126
+ ret._matches = regEval;
127
+ ret.padding = mEval.padding;
128
+ addFunc(ret, mEval);
129
+ addLoc(ret, mEval);
130
+
131
+ ret.eval = {
132
+ eval_column: Number.parseInt(mEval.eval_column, 10),
133
+ eval_func: mEval.eval_func,
134
+ eval_line: Number.parseInt(mEval.eval_line, 10),
135
+ funcs: [],
136
+ };
137
+ for (const item of line.matchAll(regEvalItem)) {
138
+ // biome-ignore lint/style/noNonNullAssertion: 有匹配必然有 groups
139
+ ret.eval.funcs.push(item.groups!['func_name']);
140
+ }
141
+ ret.eval.funcs.push(mEval.eval_func);
142
+
143
+ return ret;
144
+ }
145
+
146
+ const mInv = matchLine<TypeMatchInvalid>(line, regInvalid);
147
+ if (mInv) {
148
+ const path = mInv.path1 || mInv.path2;
149
+ if (path.endsWith(mInv.content)) {
150
+ addLoc(ret, mInv);
151
+ return ret;
152
+ }
153
+ }
154
+
155
+ ret.invalid = true;
156
+ return ret;
157
+ }
@@ -1,3 +1,4 @@
1
+ import { defineInspectMethod } from '../../../debugging/inspect.js';
1
2
  import { functionName, nameObject } from '../../../debugging/object-with-name.js';
2
3
  import { dispose_name } from '../debug.js';
3
4
  import type { IAsyncDisposable, IDisposable } from '../disposable.js';
@@ -7,12 +8,17 @@ import type { IAsyncDisposable, IDisposable } from '../disposable.js';
7
8
  * @public
8
9
  */
9
10
  export function functionToDisposable<RT>(fn: () => RT): RT extends Promise<any> ? IAsyncDisposable : IDisposable {
10
- return {
11
- get displayName() {
12
- return `disposeFn(${functionName(fn)})`;
11
+ return defineInspectMethod(
12
+ {
13
+ get displayName() {
14
+ return `disposeFn(${functionName(fn)})`;
15
+ },
16
+ dispose: fn,
17
+ } as any,
18
+ (_depth, options) => {
19
+ return options.stylize(`[FunctionDisposable ${functionName(fn)}]`, 'special');
13
20
  },
14
- dispose: fn,
15
- } as any;
21
+ );
16
22
  }
17
23
 
18
24
  /**
@@ -1,3 +1,4 @@
1
+ import { defineInspectMethod } from '../../../debugging/inspect.js';
1
2
  import { objectName } from '../../../debugging/object-with-name.js';
2
3
  import type { IAsyncDisposable } from '../disposable.js';
3
4
 
@@ -13,26 +14,31 @@ type ClosableAsync = {
13
14
  export function closableToDisposable<T extends ClosableAsync>(closable: T): IAsyncDisposable {
14
15
  const promised = closable.close.length === 0;
15
16
 
16
- return {
17
- get displayName() {
18
- return `closable(${objectName(closable) || 'unknown'})`;
19
- },
20
- dispose(): Promise<void> {
21
- if (promised) {
22
- return Promise.resolve(closable.close()).then(() => undefined);
23
- } else {
24
- return new Promise<void>((resolve, reject) => {
25
- closable.close((error) => {
26
- if (error) {
27
- reject(error);
28
- } else {
29
- resolve();
30
- }
17
+ return defineInspectMethod(
18
+ {
19
+ get displayName() {
20
+ return `closable(${objectName(closable) || 'unknown'})`;
21
+ },
22
+ dispose(): Promise<void> {
23
+ if (promised) {
24
+ return Promise.resolve(closable.close()).then(() => undefined);
25
+ } else {
26
+ return new Promise<void>((resolve, reject) => {
27
+ closable.close((error) => {
28
+ if (error) {
29
+ reject(error);
30
+ } else {
31
+ resolve();
32
+ }
33
+ });
31
34
  });
32
- });
33
- }
35
+ }
36
+ },
37
+ },
38
+ (_depth, options) => {
39
+ return options.stylize(`[ClosableDisposable ${objectName(closable) || 'unknown'}]`, 'special');
34
40
  },
35
- };
41
+ );
36
42
  }
37
43
 
38
44
  type EndableAsync = {
@@ -47,24 +53,29 @@ type EndableAsync = {
47
53
  export function endableToDisposable<T extends EndableAsync>(endable: T): IAsyncDisposable {
48
54
  const promised = endable.end.length === 0;
49
55
 
50
- return {
51
- get displayName() {
52
- return `endable(${objectName(endable) || 'unknown'})`;
53
- },
54
- dispose(): Promise<void> {
55
- if (promised) {
56
- return Promise.resolve(endable.end()).then(() => undefined);
57
- } else {
58
- return new Promise<void>((resolve, reject) => {
59
- return endable.end((error) => {
60
- if (error) {
61
- reject(error);
62
- } else {
63
- resolve();
64
- }
56
+ return defineInspectMethod(
57
+ {
58
+ get displayName() {
59
+ return `endable(${objectName(endable) || 'unknown'})`;
60
+ },
61
+ dispose(): Promise<void> {
62
+ if (promised) {
63
+ return Promise.resolve(endable.end()).then(() => undefined);
64
+ } else {
65
+ return new Promise<void>((resolve, reject) => {
66
+ return endable.end((error) => {
67
+ if (error) {
68
+ reject(error);
69
+ } else {
70
+ resolve();
71
+ }
72
+ });
65
73
  });
66
- });
67
- }
74
+ }
75
+ },
76
+ },
77
+ (_depth, options) => {
78
+ return options.stylize(`[EndableDisposable ${objectName(endable) || 'unknown'}]`, 'special');
68
79
  },
69
- };
80
+ );
70
81
  }
@@ -1,3 +1,4 @@
1
+ import { defineInspectMethod } from '../../debugging/inspect.js';
1
2
  import type { MaybeNamed } from '../../debugging/object-with-name.js';
2
3
  import { createStackTraceHolder, type StackTraceHolder } from '../../error/stack-trace.js';
3
4
  import { Emitter } from '../event/event.js';
@@ -57,13 +58,25 @@ export abstract class AbstractEnhancedDisposable<Async extends boolean> implemen
57
58
  this.displayName = displayName;
58
59
  }
59
60
 
60
- this._logger = _debug_dispose.extend(this.displayName || 'disposable');
61
+ this._logger = defineInspectMethod(_debug_dispose.extend(this.displayName || 'disposable'), () => {
62
+ return `[Function debug]`;
63
+ });
61
64
 
62
65
  this._onPostDispose.handle(() => {
63
66
  this._onPostDispose.dispose();
64
67
  });
65
68
  this._disposables.push(this._onBeforeDispose);
66
69
  this._disposables.push(this._onDisposeError);
70
+
71
+ if (
72
+ this.constructor.name === 'EnhancedAsyncDisposable' ||
73
+ this.constructor.name === 'UnorderedAsyncDisposable' ||
74
+ this.constructor.name === 'EnhancedDisposable'
75
+ ) {
76
+ defineInspectMethod(this, (_depth: number, options: any) => {
77
+ return options.stylize(`[${this.displayName}]`, 'special');
78
+ });
79
+ }
67
80
  }
68
81
 
69
82
  /**
@@ -1,4 +1,5 @@
1
1
  import { Exit } from '@idlebox/errors';
2
+ import { defineInspectMethod, inspectSymbol } from '../../debugging/inspect.js';
2
3
  import { nameObject, objectName } from '../../debugging/object-with-name.js';
3
4
  import { createStackTraceHolder } from '../../error/stack-trace.js';
4
5
  import { functionToDisposable } from '../dispose/bridges/function.js';
@@ -6,6 +7,8 @@ import type { IDisposable } from '../dispose/disposable.js';
6
7
  import { DisposedError } from '../dispose/disposedError.js';
7
8
  import type { EventHandler, EventRegister, IEventEmitter } from './type.js';
8
9
 
10
+ const anonymousName = 'AnonymousEmitter';
11
+
9
12
  /**
10
13
  * @public
11
14
  */
@@ -14,7 +17,7 @@ export class Emitter<T = unknown> implements IEventEmitter<T> {
14
17
  private executing = false;
15
18
  private _something_change_during_call = false;
16
19
 
17
- constructor(public readonly displayName: string = 'AnonymousEmitter') {
20
+ constructor(public readonly displayName: string = anonymousName) {
18
21
  this.handle = Object.defineProperties(this.handle.bind(this), {
19
22
  once: {
20
23
  get: () => this.once.bind(this),
@@ -26,6 +29,9 @@ export class Emitter<T = unknown> implements IEventEmitter<T> {
26
29
  get: () => this._disposed,
27
30
  },
28
31
  });
32
+ defineInspectMethod(this.handle, (_depth, options) => {
33
+ return options.stylize(`[EmitterRegister ${this.displayName}]`, 'special');
34
+ });
29
35
  }
30
36
 
31
37
  public listenerCount() {
@@ -181,6 +187,17 @@ export class Emitter<T = unknown> implements IEventEmitter<T> {
181
187
 
182
188
  readonly [Symbol.dispose] = this.dispose;
183
189
 
190
+ [inspectSymbol](_depth: number, options: any) {
191
+ let r = `${options.stylize(this.constructor.name, 'name')} {`;
192
+ if (this.displayName !== anonymousName) {
193
+ r += ` ${options.stylize(this.displayName, 'string')},`;
194
+ }
195
+ r += ` listeners: ${options.stylize(this.listenerCount(), 'number')}`;
196
+
197
+ r += ' }';
198
+ return r;
199
+ }
200
+
184
201
  private requireNotExecuting() {
185
202
  if (this.executing) {
186
203
  throw new Error('conflict state, emitter is firing');
@@ -1,3 +1,4 @@
1
+ import { inspectSymbol } from '../debugging/inspect.js';
1
2
  import { objectName } from '../debugging/object-with-name.js';
2
3
  import type { IDisposable } from '../lifecycle/dispose/disposable.js';
3
4
  import { EnhancedDisposable } from '../lifecycle/dispose/sync-disposable.js';
@@ -61,4 +62,15 @@ export class Interval extends EnhancedDisposable {
61
62
  if (this.timer) this.timer.dispose();
62
63
  return super.dispose();
63
64
  }
65
+
66
+ [inspectSymbol](depth: number, options: any, inspect: any) {
67
+ if (depth < 0) {
68
+ return options.stylize(`[Interval ${this.ms}ms]`, 'special');
69
+ }
70
+ let r = `${options.stylize(this.constructor.name, 'name')} ${options.stylize(`${this.ms}`, 'number')}ms`;
71
+ const padding = ' '.repeat(2);
72
+ const inner = inspect(this._emitter, options, inspect).replace(/\n/g, `\n${padding}`);
73
+ r += ` {\n${padding}onTick: ${inner}\n}`;
74
+ return r;
75
+ }
64
76
  }