@travetto/runtime 7.0.0-rc.1 → 7.0.0-rc.2
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 +11 -11
- package/package.json +3 -3
- package/src/binary.ts +14 -14
- package/src/console.ts +15 -15
- package/src/context.ts +16 -16
- package/src/env.ts +30 -30
- package/src/error.ts +15 -15
- package/src/exec.ts +23 -23
- package/src/queue.ts +2 -2
- package/src/resources.ts +3 -3
- package/src/shutdown.ts +4 -4
- package/src/time.ts +11 -11
- package/src/types.ts +6 -12
- package/src/util.ts +49 -49
- package/src/watch.ts +6 -6
- package/support/transformer/metadata.ts +4 -4
- package/support/transformer.concrete-type.ts +4 -4
- package/support/transformer.console-log.ts +5 -5
- package/support/transformer.debug-method.ts +1 -1
- package/support/transformer.function-metadata.ts +2 -2
- package/support/transformer.rewrite-path-import.ts +11 -9
package/README.md
CHANGED
|
@@ -51,11 +51,11 @@ class $Runtime {
|
|
|
51
51
|
/** Main source path */
|
|
52
52
|
get mainSourcePath(): string;
|
|
53
53
|
/** Produce a workspace relative path */
|
|
54
|
-
workspaceRelative(...
|
|
54
|
+
workspaceRelative(...parts: string[]): string;
|
|
55
55
|
/** Strip off the workspace path from a file */
|
|
56
56
|
stripWorkspacePath(full: string): string;
|
|
57
57
|
/** Produce a workspace path for tooling, with '@' being replaced by node_module/name folder */
|
|
58
|
-
toolPath(...
|
|
58
|
+
toolPath(...parts: string[]): string;
|
|
59
59
|
/** Resolve single module path */
|
|
60
60
|
modulePath(modulePath: string, overrides?: Record<string, string>): string;
|
|
61
61
|
/** Resolve resource paths */
|
|
@@ -145,9 +145,9 @@ export class EnvProp<T> {
|
|
|
145
145
|
/** Remove value */
|
|
146
146
|
clear(): void;
|
|
147
147
|
/** Export value */
|
|
148
|
-
export(
|
|
148
|
+
export(value?: T | undefined | null): Record<string, string>;
|
|
149
149
|
/** Read value as string */
|
|
150
|
-
get
|
|
150
|
+
get value(): string | undefined;
|
|
151
151
|
/** Read value as list */
|
|
152
152
|
get list(): string[] | undefined;
|
|
153
153
|
/** Read value as object */
|
|
@@ -204,8 +204,8 @@ export function work() {
|
|
|
204
204
|
|
|
205
205
|
try {
|
|
206
206
|
1 / 0;
|
|
207
|
-
} catch (
|
|
208
|
-
console.error('Divide by zero', { error
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error('Divide by zero', { error });
|
|
209
209
|
}
|
|
210
210
|
console.debug('End Work');
|
|
211
211
|
}
|
|
@@ -225,12 +225,12 @@ function work() {
|
|
|
225
225
|
try {
|
|
226
226
|
1 / 0;
|
|
227
227
|
}
|
|
228
|
-
catch (
|
|
229
|
-
Δconsole.log({ level: "error", import: mod_1, line: 7, scope: "work", args: ['Divide by zero', { error
|
|
228
|
+
catch (error) {
|
|
229
|
+
Δconsole.log({ level: "error", import: mod_1, line: 7, scope: "work", args: ['Divide by zero', { error }] });
|
|
230
230
|
}
|
|
231
231
|
Δconsole.log({ level: "debug", import: mod_1, line: 9, scope: "work", args: ['End Work'] });
|
|
232
232
|
}
|
|
233
|
-
Δfunction.registerFunction(work, mod_1, { hash:
|
|
233
|
+
Δfunction.registerFunction(work, mod_1, { hash: 159357293, lines: [1, 10, 2] });
|
|
234
234
|
```
|
|
235
235
|
|
|
236
236
|
#### Filtering Debug
|
|
@@ -271,7 +271,7 @@ Common utilities used throughout the framework. Currently [Util](https://github.
|
|
|
271
271
|
|
|
272
272
|
**Code: Sample makeTemplate Usage**
|
|
273
273
|
```typescript
|
|
274
|
-
const tpl = makeTemplate((name: 'age'|'name',
|
|
274
|
+
const tpl = makeTemplate((name: 'age'|'name', value) => `**${name}: ${value}**`);
|
|
275
275
|
tpl`{{age:20}} {{name: 'bob'}}</>;
|
|
276
276
|
// produces
|
|
277
277
|
'**age: 20** **name: bob**'
|
|
@@ -287,7 +287,7 @@ export class TimeUtil {
|
|
|
287
287
|
* Test to see if a string is valid for relative time
|
|
288
288
|
* @param val
|
|
289
289
|
*/
|
|
290
|
-
static isTimeSpan(
|
|
290
|
+
static isTimeSpan(value: string): value is TimeSpan;
|
|
291
291
|
/**
|
|
292
292
|
* Returns time units convert to ms
|
|
293
293
|
* @param amount Number of units to extend
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/runtime",
|
|
3
|
-
"version": "7.0.0-rc.
|
|
3
|
+
"version": "7.0.0-rc.2",
|
|
4
4
|
"description": "Runtime for travetto applications.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"console-manager",
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"directory": "module/runtime"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@travetto/manifest": "^7.0.0-rc.
|
|
28
|
+
"@travetto/manifest": "^7.0.0-rc.1",
|
|
29
29
|
"@types/debug": "^4.1.12",
|
|
30
30
|
"debug": "^4.4.3"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@travetto/transformer": "^7.0.0-rc.
|
|
33
|
+
"@travetto/transformer": "^7.0.0-rc.2"
|
|
34
34
|
},
|
|
35
35
|
"peerDependenciesMeta": {
|
|
36
36
|
"@travetto/transformer": {
|
package/src/binary.ts
CHANGED
|
@@ -24,27 +24,27 @@ export class BinaryUtil {
|
|
|
24
24
|
/** Is ReadableStream */
|
|
25
25
|
static isReadableStream = hasFunction<ReadableStream>('pipeTo');
|
|
26
26
|
/** Is Async Iterable */
|
|
27
|
-
static isAsyncIterable = (
|
|
28
|
-
!!
|
|
27
|
+
static isAsyncIterable = (value: unknown): value is AsyncIterable<unknown> =>
|
|
28
|
+
!!value && (typeof value === 'object' || typeof value === 'function') && Symbol.asyncIterator in value;
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
* Is
|
|
31
|
+
* Is value a binary type
|
|
32
32
|
*/
|
|
33
|
-
static isBinaryType(
|
|
34
|
-
return
|
|
35
|
-
this.isArrayBuffer(
|
|
33
|
+
static isBinaryType(value: unknown): boolean {
|
|
34
|
+
return value instanceof Blob || Buffer.isBuffer(value) || this.isReadable(value) ||
|
|
35
|
+
this.isArrayBuffer(value) || this.isReadableStream(value) || this.isAsyncIterable(value);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
* Generate a proper sha512 hash from
|
|
40
|
-
* @param
|
|
41
|
-
* @param
|
|
39
|
+
* Generate a proper sha512 hash from an input value
|
|
40
|
+
* @param input The seed value to build the hash from
|
|
41
|
+
* @param length The optional length of the hash to generate
|
|
42
42
|
*/
|
|
43
|
-
static hash(
|
|
43
|
+
static hash(input: string, length: number = -1): string {
|
|
44
44
|
const hash = crypto.createHash('sha512');
|
|
45
|
-
hash.update(
|
|
45
|
+
hash.update(input);
|
|
46
46
|
const digest = hash.digest('hex');
|
|
47
|
-
return
|
|
47
|
+
return length > 0 ? digest.substring(0, length) : digest;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
@@ -88,7 +88,7 @@ export class BinaryUtil {
|
|
|
88
88
|
static readableBlob(input: () => (Readable | Promise<Readable>), metadata: BlobMeta = {}): Blob | File {
|
|
89
89
|
const go = (): Readable => {
|
|
90
90
|
const stream = new PassThrough();
|
|
91
|
-
Promise.resolve(input()).then(
|
|
91
|
+
Promise.resolve(input()).then(readable => readable.pipe(stream), (error) => stream.destroy(error));
|
|
92
92
|
return stream;
|
|
93
93
|
};
|
|
94
94
|
|
|
@@ -102,7 +102,7 @@ export class BinaryUtil {
|
|
|
102
102
|
stream: { value: () => ReadableStream.from(go()) },
|
|
103
103
|
arrayBuffer: { value: () => toArrayBuffer(go()) },
|
|
104
104
|
text: { value: () => toText(go()) },
|
|
105
|
-
bytes: { value: () => toArrayBuffer(go()).then(
|
|
105
|
+
bytes: { value: () => toArrayBuffer(go()).then(buffer => new Uint8Array(buffer)) },
|
|
106
106
|
[BlobMetaSymbol]: { value: metadata }
|
|
107
107
|
});
|
|
108
108
|
}
|
package/src/console.ts
CHANGED
|
@@ -27,10 +27,10 @@ export interface ConsoleEvent {
|
|
|
27
27
|
* @concrete
|
|
28
28
|
*/
|
|
29
29
|
export interface ConsoleListener {
|
|
30
|
-
log(
|
|
30
|
+
log(event: ConsoleEvent): void;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const
|
|
33
|
+
const DEBUG_HANDLE = { formatArgs: debug.formatArgs, log: debug.log };
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* Provides a general abstraction against the console.* methods to allow for easier capture and redirection.
|
|
@@ -50,7 +50,7 @@ class $ConsoleManager implements ConsoleListener {
|
|
|
50
50
|
/**
|
|
51
51
|
* List of logging filters
|
|
52
52
|
*/
|
|
53
|
-
#filters: Partial<Record<ConsoleEvent['level'], (
|
|
53
|
+
#filters: Partial<Record<ConsoleEvent['level'], (event: ConsoleEvent) => boolean>> = {};
|
|
54
54
|
|
|
55
55
|
constructor(listener: ConsoleListener) {
|
|
56
56
|
this.set(listener);
|
|
@@ -66,8 +66,8 @@ class $ConsoleManager implements ConsoleListener {
|
|
|
66
66
|
filter(level: ConsoleEvent['level'], filter?: boolean | ((ctx: ConsoleEvent) => boolean)): void {
|
|
67
67
|
if (filter !== undefined) {
|
|
68
68
|
if (typeof filter === 'boolean') {
|
|
69
|
-
const
|
|
70
|
-
filter = (): boolean =>
|
|
69
|
+
const filterValue = filter;
|
|
70
|
+
filter = (): boolean => filterValue;
|
|
71
71
|
}
|
|
72
72
|
this.#filters[level] = filter;
|
|
73
73
|
} else {
|
|
@@ -90,8 +90,8 @@ class $ConsoleManager implements ConsoleListener {
|
|
|
90
90
|
args: [util.format(...args)], line: 0, timestamp: new Date()
|
|
91
91
|
});
|
|
92
92
|
} else {
|
|
93
|
-
debug.formatArgs =
|
|
94
|
-
debug.log =
|
|
93
|
+
debug.formatArgs = DEBUG_HANDLE.formatArgs;
|
|
94
|
+
debug.log = DEBUG_HANDLE.log;
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -111,18 +111,18 @@ class $ConsoleManager implements ConsoleListener {
|
|
|
111
111
|
/**
|
|
112
112
|
* Handle direct call in lieu of the console.* commands
|
|
113
113
|
*/
|
|
114
|
-
log(
|
|
115
|
-
const
|
|
116
|
-
...
|
|
114
|
+
log(event: ConsoleEvent & { import?: [string, string] }): void {
|
|
115
|
+
const result = {
|
|
116
|
+
...event,
|
|
117
117
|
timestamp: new Date(),
|
|
118
|
-
module:
|
|
119
|
-
modulePath:
|
|
118
|
+
module: event.module ?? event.import?.[0],
|
|
119
|
+
modulePath: event.modulePath ?? event.import?.[1]
|
|
120
120
|
};
|
|
121
121
|
|
|
122
|
-
if (this.#filters[
|
|
122
|
+
if (this.#filters[result.level] && !this.#filters[result.level]!(result)) {
|
|
123
123
|
return; // Do nothing
|
|
124
124
|
} else {
|
|
125
|
-
return this.#listener.log(
|
|
125
|
+
return this.#listener.log(result);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -141,5 +141,5 @@ class $ConsoleManager implements ConsoleListener {
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
export const ConsoleManager = new $ConsoleManager({ log(
|
|
144
|
+
export const ConsoleManager = new $ConsoleManager({ log(event): void { console; } });
|
|
145
145
|
export const log = ConsoleManager.log.bind(ConsoleManager);
|
package/src/context.ts
CHANGED
|
@@ -28,7 +28,7 @@ class $Runtime {
|
|
|
28
28
|
|
|
29
29
|
/** Get env name, with support for the default env */
|
|
30
30
|
get env(): string | undefined {
|
|
31
|
-
return Env.TRV_ENV.
|
|
31
|
+
return Env.TRV_ENV.value || (!this.production ? this.#idx.manifest.workspace.defaultEnv : undefined);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/** Are we in development mode */
|
|
@@ -43,8 +43,8 @@ class $Runtime {
|
|
|
43
43
|
|
|
44
44
|
/** Get debug value */
|
|
45
45
|
get debug(): false | string {
|
|
46
|
-
const
|
|
47
|
-
return (!
|
|
46
|
+
const value = Env.DEBUG.value ?? '';
|
|
47
|
+
return (!value && this.production) || Env.DEBUG.isFalse ? false : value;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/** Manifest main */
|
|
@@ -68,8 +68,8 @@ class $Runtime {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/** Produce a workspace relative path */
|
|
71
|
-
workspaceRelative(...
|
|
72
|
-
return path.resolve(this.workspace.path, ...
|
|
71
|
+
workspaceRelative(...parts: string[]): string {
|
|
72
|
+
return path.resolve(this.workspace.path, ...parts);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/** Strip off the workspace path from a file */
|
|
@@ -78,23 +78,23 @@ class $Runtime {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/** Produce a workspace path for tooling, with '@' being replaced by node_module/name folder */
|
|
81
|
-
toolPath(...
|
|
82
|
-
|
|
83
|
-
return path.resolve(this.workspace.path, this.#idx.manifest.build.toolFolder, ...
|
|
81
|
+
toolPath(...parts: string[]): string {
|
|
82
|
+
parts = parts.flatMap(part => part === '@' ? ['node_modules', this.#idx.manifest.main.name] : [part]);
|
|
83
|
+
return path.resolve(this.workspace.path, this.#idx.manifest.build.toolFolder, ...parts);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
/** Resolve single module path */
|
|
87
87
|
modulePath(modulePath: string, overrides?: Record<string, string>): string {
|
|
88
88
|
const combined = { ...this.#resourceOverrides, ...overrides };
|
|
89
89
|
const [base, sub] = (combined[modulePath] ?? modulePath)
|
|
90
|
-
.replace(/^([^#]*)(#|$)/g, (_,
|
|
90
|
+
.replace(/^([^#]*)(#|$)/g, (_, module, relativePath) => `${this.#moduleAliases[module] ?? module}${relativePath}`)
|
|
91
91
|
.split('#');
|
|
92
92
|
return path.resolve(this.#idx.getModule(base)?.sourcePath ?? base, sub ?? '.');
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/** Resolve resource paths */
|
|
96
96
|
resourcePaths(paths: string[] = []): string[] {
|
|
97
|
-
return [...new Set([...paths, ...Env.TRV_RESOURCES.list ?? [], '@#resources', '@@#resources'].map(
|
|
97
|
+
return [...new Set([...paths, ...Env.TRV_RESOURCES.list ?? [], '@#resources', '@@#resources'].map(module => this.modulePath(module)))];
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
/** Get source for function */
|
|
@@ -121,7 +121,7 @@ class $Runtime {
|
|
|
121
121
|
return fs.readFile(imp, 'utf8').then(JSON.parse);
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
if (!ManifestModuleUtil.
|
|
124
|
+
if (!ManifestModuleUtil.SOURCE_EXT_REGEX.test(imp)) {
|
|
125
125
|
if (imp.startsWith('@')) {
|
|
126
126
|
if (/[/].*?[/]/.test(imp)) {
|
|
127
127
|
imp = `${imp}.ts`;
|
|
@@ -132,16 +132,16 @@ class $Runtime {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
imp = ManifestModuleUtil.withOutputExtension(imp);
|
|
135
|
-
const
|
|
136
|
-
if (
|
|
135
|
+
const imported = await import(imp);
|
|
136
|
+
if (imported?.default?.default) {
|
|
137
137
|
// Unpack default.default, typescript does this in a way that requires recreating the whole object
|
|
138
|
-
const def =
|
|
138
|
+
const def = imported?.default?.default;
|
|
139
139
|
return Object.defineProperties(castTo({}), {
|
|
140
|
-
...Object.getOwnPropertyDescriptors(
|
|
140
|
+
...Object.getOwnPropertyDescriptors(imported),
|
|
141
141
|
default: { get: () => def, configurable: false }
|
|
142
142
|
});
|
|
143
143
|
}
|
|
144
|
-
return
|
|
144
|
+
return imported;
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
|
package/src/env.ts
CHANGED
|
@@ -9,12 +9,12 @@ export class EnvProp<T> {
|
|
|
9
9
|
readonly key: string;
|
|
10
10
|
constructor(key: string) { this.key = key; }
|
|
11
11
|
|
|
12
|
-
/** Set value according to
|
|
13
|
-
set(
|
|
14
|
-
if (
|
|
12
|
+
/** Set value according to type */
|
|
13
|
+
set(value: T | undefined | null): void {
|
|
14
|
+
if (value === undefined || value === null) {
|
|
15
15
|
delete process.env[this.key];
|
|
16
16
|
} else {
|
|
17
|
-
process.env[this.key] = Array.isArray(
|
|
17
|
+
process.env[this.key] = Array.isArray(value) ? `${value.join(',')}` : `${value}`;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -24,36 +24,36 @@ export class EnvProp<T> {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/** Export value */
|
|
27
|
-
export(
|
|
27
|
+
export(value?: T | undefined | null): Record<string, string> {
|
|
28
28
|
let out: string;
|
|
29
29
|
if (arguments.length === 0) { // If nothing passed in
|
|
30
|
-
out = `${this.
|
|
31
|
-
} else if (
|
|
30
|
+
out = `${this.value}`;
|
|
31
|
+
} else if (value === undefined || value === null) {
|
|
32
32
|
out = '';
|
|
33
|
-
} else if (Array.isArray(
|
|
34
|
-
out =
|
|
35
|
-
} else if (typeof
|
|
36
|
-
out = Object.entries(
|
|
33
|
+
} else if (Array.isArray(value)) {
|
|
34
|
+
out = value.join(',');
|
|
35
|
+
} else if (typeof value === 'object') {
|
|
36
|
+
out = Object.entries(value).map(([key, keyValue]) => `${key}=${keyValue}`).join(',');
|
|
37
37
|
} else {
|
|
38
|
-
out = `${
|
|
38
|
+
out = `${value}`;
|
|
39
39
|
}
|
|
40
40
|
return { [this.key]: out };
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/** Read value as string */
|
|
44
|
-
get
|
|
44
|
+
get value(): string | undefined { return process.env[this.key] || undefined; }
|
|
45
45
|
|
|
46
46
|
/** Read value as list */
|
|
47
47
|
get list(): string[] | undefined {
|
|
48
|
-
const
|
|
49
|
-
return (
|
|
50
|
-
undefined :
|
|
48
|
+
const value = this.value;
|
|
49
|
+
return (value === undefined || value === '') ?
|
|
50
|
+
undefined : value.split(/[, ]+/g).map(item => item.trim()).filter(item => !!item);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/** Read value as object */
|
|
54
54
|
get object(): Record<string, string> | undefined {
|
|
55
55
|
const items = this.list;
|
|
56
|
-
return items ? Object.fromEntries(items.map(
|
|
56
|
+
return items ? Object.fromEntries(items.map(item => item.split(/[:=]/g))) : undefined;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/** Add values to list */
|
|
@@ -63,35 +63,35 @@ export class EnvProp<T> {
|
|
|
63
63
|
|
|
64
64
|
/** Read value as int */
|
|
65
65
|
get int(): number | undefined {
|
|
66
|
-
const
|
|
67
|
-
return Number.isNaN(
|
|
66
|
+
const parsed = parseInt(this.value ?? '', 10);
|
|
67
|
+
return Number.isNaN(parsed) ? undefined : parsed;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/** Read value as boolean */
|
|
71
71
|
get bool(): boolean | undefined {
|
|
72
|
-
const
|
|
73
|
-
return (
|
|
72
|
+
const value = this.value;
|
|
73
|
+
return (value === undefined || value === '') ? undefined : IS_TRUE.test(value);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/** Determine if the underlying value is truthy */
|
|
77
77
|
get isTrue(): boolean {
|
|
78
|
-
return IS_TRUE.test(this.
|
|
78
|
+
return IS_TRUE.test(this.value ?? '');
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/** Determine if the underlying value is falsy */
|
|
82
82
|
get isFalse(): boolean {
|
|
83
|
-
return IS_FALSE.test(this.
|
|
83
|
+
return IS_FALSE.test(this.value ?? '');
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
/** Determine if the underlying value is set */
|
|
87
87
|
get isSet(): boolean {
|
|
88
|
-
const
|
|
89
|
-
return
|
|
88
|
+
const value = this.value;
|
|
89
|
+
return value !== undefined && value !== '';
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
type AllType = {
|
|
94
|
-
[K in keyof EnvData]: Pick<EnvProp<EnvData[K]>, 'key' | 'export' | '
|
|
94
|
+
[K in keyof EnvData]: Pick<EnvProp<EnvData[K]>, 'key' | 'export' | 'value' | 'set' | 'clear' | 'isSet' |
|
|
95
95
|
(EnvData[K] extends unknown[] ? 'list' | 'add' : never) |
|
|
96
96
|
(Extract<EnvData[K], object> extends never ? never : 'object') |
|
|
97
97
|
(Extract<EnvData[K], number> extends never ? never : 'int') |
|
|
@@ -101,10 +101,10 @@ type AllType = {
|
|
|
101
101
|
|
|
102
102
|
function delegate<T extends object>(base: T): AllType & T {
|
|
103
103
|
return new Proxy(castTo(base), {
|
|
104
|
-
get(target,
|
|
105
|
-
return typeof
|
|
106
|
-
(
|
|
107
|
-
target[castKey<typeof target>(
|
|
104
|
+
get(target, property): unknown {
|
|
105
|
+
return typeof property !== 'string' ? undefined :
|
|
106
|
+
(property in base ? base[castKey(property)] :
|
|
107
|
+
target[castKey<typeof target>(property)] ??= castTo(new EnvProp(property))
|
|
108
108
|
);
|
|
109
109
|
}
|
|
110
110
|
});
|
package/src/error.ts
CHANGED
|
@@ -28,14 +28,14 @@ export class AppError<T = Record<string, unknown> | undefined> extends Error {
|
|
|
28
28
|
static defaultCategory?: ErrorCategory;
|
|
29
29
|
|
|
30
30
|
/** Convert from JSON object */
|
|
31
|
-
static fromJSON(
|
|
32
|
-
if (!!
|
|
33
|
-
('message' in
|
|
34
|
-
('category' in
|
|
35
|
-
('type' in
|
|
36
|
-
('at' in
|
|
31
|
+
static fromJSON(error: unknown): AppError | undefined {
|
|
32
|
+
if (!!error && typeof error === 'object' &&
|
|
33
|
+
('message' in error && typeof error.message === 'string') &&
|
|
34
|
+
('category' in error && typeof error.category === 'string') &&
|
|
35
|
+
('type' in error && typeof error.type === 'string') &&
|
|
36
|
+
('at' in error && typeof error.at === 'string')
|
|
37
37
|
) {
|
|
38
|
-
return new AppError(
|
|
38
|
+
return new AppError(error.message, castTo<AppErrorOptions<Record<string, unknown>>>(error));
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -50,21 +50,21 @@ export class AppError<T = Record<string, unknown> | undefined> extends Error {
|
|
|
50
50
|
* @param message The error message
|
|
51
51
|
*/
|
|
52
52
|
constructor(
|
|
53
|
-
...[message,
|
|
53
|
+
...[message, options]:
|
|
54
54
|
T extends undefined ? ([string] | [string, AppErrorOptions<T>]) : [string, AppErrorOptions<T>]
|
|
55
55
|
) {
|
|
56
|
-
super(message,
|
|
57
|
-
this.type =
|
|
58
|
-
this.details =
|
|
59
|
-
this.category =
|
|
60
|
-
this.at = new Date(
|
|
56
|
+
super(message, options?.cause ? { cause: options.cause } : undefined);
|
|
57
|
+
this.type = options?.type ?? this.constructor.name;
|
|
58
|
+
this.details = options?.details!;
|
|
59
|
+
this.category = options?.category ?? castTo<typeof AppError>(this.constructor).defaultCategory ?? 'general';
|
|
60
|
+
this.at = new Date(options?.at ?? Date.now()).toISOString();
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
64
|
* Serializes an error to a basic object
|
|
65
65
|
*/
|
|
66
66
|
toJSON(): AppErrorOptions<T> & { message: string } {
|
|
67
|
-
const
|
|
67
|
+
const options: AppErrorOptions<unknown> = {
|
|
68
68
|
category: this.category,
|
|
69
69
|
...(this.cause ? { cause: `${this.cause}` } : undefined),
|
|
70
70
|
type: this.type,
|
|
@@ -73,6 +73,6 @@ export class AppError<T = Record<string, unknown> | undefined> extends Error {
|
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
76
|
-
return { message: this.message, ...
|
|
76
|
+
return { message: this.message, ...options as AppErrorOptions<T> };
|
|
77
77
|
}
|
|
78
78
|
}
|
package/src/exec.ts
CHANGED
|
@@ -64,18 +64,18 @@ export class ExecUtil {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
for (; ;) {
|
|
67
|
-
const
|
|
68
|
-
const interrupt = (): void => {
|
|
69
|
-
const toMessage = (
|
|
67
|
+
const subProcess = run();
|
|
68
|
+
const interrupt = (): void => { subProcess.kill('SIGINT'); };
|
|
69
|
+
const toMessage = (value: unknown): void => { subProcess.send?.(value!); };
|
|
70
70
|
|
|
71
71
|
// Proxy kill requests
|
|
72
72
|
process.on('message', toMessage);
|
|
73
73
|
if (relayInterrupt) {
|
|
74
74
|
process.on('SIGINT', interrupt);
|
|
75
75
|
}
|
|
76
|
-
|
|
76
|
+
subProcess.on('message', value => process.send?.(value));
|
|
77
77
|
|
|
78
|
-
const result = await this.getResult(
|
|
78
|
+
const result = await this.getResult(subProcess, { catch: true });
|
|
79
79
|
if (result.code !== this.RESTART_EXIT_CODE) {
|
|
80
80
|
return result;
|
|
81
81
|
} else {
|
|
@@ -101,15 +101,15 @@ export class ExecUtil {
|
|
|
101
101
|
* represents the entire execution. On successful completion the promise will resolve, and
|
|
102
102
|
* on failed completion the promise will reject.
|
|
103
103
|
*
|
|
104
|
-
* @param
|
|
104
|
+
* @param subProcess The process to enhance
|
|
105
105
|
* @param options The options to use to enhance the process
|
|
106
106
|
*/
|
|
107
|
-
static getResult(
|
|
108
|
-
static getResult(
|
|
109
|
-
static getResult(
|
|
110
|
-
static getResult<T extends string | Buffer>(
|
|
111
|
-
const
|
|
112
|
-
const result =
|
|
107
|
+
static getResult(subProcess: ChildProcess): Promise<ExecutionResult<string>>;
|
|
108
|
+
static getResult(subProcess: ChildProcess, options: { catch?: boolean, binary?: false }): Promise<ExecutionResult<string>>;
|
|
109
|
+
static getResult(subProcess: ChildProcess, options: { catch?: boolean, binary: true }): Promise<ExecutionResult<Buffer>>;
|
|
110
|
+
static getResult<T extends string | Buffer>(subProcess: ChildProcess, options: { catch?: boolean, binary?: boolean } = {}): Promise<ExecutionResult<T>> {
|
|
111
|
+
const typed: ChildProcess & { [ResultSymbol]?: Promise<ExecutionResult> } = subProcess;
|
|
112
|
+
const result = typed[ResultSymbol] ??= new Promise<ExecutionResult>(resolve => {
|
|
113
113
|
const stdout: Buffer[] = [];
|
|
114
114
|
const stderr: Buffer[] = [];
|
|
115
115
|
let done = false;
|
|
@@ -136,25 +136,25 @@ export class ExecUtil {
|
|
|
136
136
|
);
|
|
137
137
|
};
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
subProcess.stdout?.on('data', (data: string | Buffer) => stdout.push(Buffer.isBuffer(data) ? data : Buffer.from(data)));
|
|
140
|
+
subProcess.stderr?.on('data', (data: string | Buffer) => stderr.push(Buffer.isBuffer(data) ? data : Buffer.from(data)));
|
|
141
141
|
|
|
142
|
-
|
|
143
|
-
finish({ code: 1, message:
|
|
142
|
+
subProcess.on('error', (error: Error) =>
|
|
143
|
+
finish({ code: 1, message: error.message, valid: false }));
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
subProcess.on('close', (code: number) =>
|
|
146
146
|
finish({ code, valid: code === null || code === 0 }));
|
|
147
147
|
|
|
148
|
-
if (
|
|
149
|
-
finish({ code:
|
|
148
|
+
if (subProcess.exitCode !== null) { // We are already done
|
|
149
|
+
finish({ code: subProcess.exitCode, valid: subProcess.exitCode === 0 });
|
|
150
150
|
}
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
return castTo(options.catch ? result : result.then(
|
|
154
|
-
if (
|
|
155
|
-
return
|
|
153
|
+
return castTo(options.catch ? result : result.then(executionResult => {
|
|
154
|
+
if (executionResult.valid) {
|
|
155
|
+
return executionResult;
|
|
156
156
|
} else {
|
|
157
|
-
throw new Error(
|
|
157
|
+
throw new Error(executionResult.message);
|
|
158
158
|
}
|
|
159
159
|
}));
|
|
160
160
|
}
|
package/src/queue.ts
CHANGED
|
@@ -54,9 +54,9 @@ export class AsyncQueue<X> implements AsyncIterator<X>, AsyncIterable<X> {
|
|
|
54
54
|
/**
|
|
55
55
|
* Throw an error from the queue, rejecting and terminating immediately
|
|
56
56
|
*/
|
|
57
|
-
async throw(
|
|
57
|
+
async throw(error?: Error): Promise<IteratorResult<X>> {
|
|
58
58
|
this.#done = true;
|
|
59
|
-
this.#ready.reject(
|
|
59
|
+
this.#ready.reject(error);
|
|
60
60
|
return { value: undefined, done: this.#done };
|
|
61
61
|
}
|
|
62
62
|
}
|
package/src/resources.ts
CHANGED
|
@@ -15,9 +15,9 @@ class $RuntimeResources extends FileLoader {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
override get searchPaths(): readonly string[] {
|
|
18
|
-
if (!this.#computed || this.#env !== Env.TRV_RESOURCES.
|
|
19
|
-
this.#env = Env.TRV_RESOURCES.
|
|
20
|
-
this.#mod = Env.TRV_MODULE.
|
|
18
|
+
if (!this.#computed || this.#env !== Env.TRV_RESOURCES.value || this.#mod !== Env.TRV_MODULE.value) {
|
|
19
|
+
this.#env = Env.TRV_RESOURCES.value!;
|
|
20
|
+
this.#mod = Env.TRV_MODULE.value!;
|
|
21
21
|
this.#computed = Runtime.resourcePaths();
|
|
22
22
|
}
|
|
23
23
|
return this.#computed;
|
package/src/shutdown.ts
CHANGED
|
@@ -40,7 +40,7 @@ export class ShutdownManager {
|
|
|
40
40
|
this.#ensureExitListeners();
|
|
41
41
|
this.#handlers.push({ handler, scope });
|
|
42
42
|
return () => {
|
|
43
|
-
const idx = this.#handlers.findIndex(
|
|
43
|
+
const idx = this.#handlers.findIndex(item => item.handler === handler);
|
|
44
44
|
if (idx >= 0) {
|
|
45
45
|
this.#handlers.splice(idx, 1);
|
|
46
46
|
}
|
|
@@ -61,7 +61,7 @@ export class ShutdownManager {
|
|
|
61
61
|
|
|
62
62
|
await Util.queueMacroTask(); // Force the event loop to wait one cycle
|
|
63
63
|
|
|
64
|
-
const timeout = TimeUtil.fromValue(Env.TRV_SHUTDOWN_WAIT.
|
|
64
|
+
const timeout = TimeUtil.fromValue(Env.TRV_SHUTDOWN_WAIT.value) ?? 2000;
|
|
65
65
|
const items = this.#handlers.splice(0, this.#handlers.length);
|
|
66
66
|
console.debug('Graceful shutdown: started', { source, timeout, count: items.length });
|
|
67
67
|
const handlers = Promise.all(items.map(async ({ scope, handler }) => {
|
|
@@ -73,8 +73,8 @@ export class ShutdownManager {
|
|
|
73
73
|
if (scope) {
|
|
74
74
|
console.debug('Stopped', { scope });
|
|
75
75
|
}
|
|
76
|
-
} catch (
|
|
77
|
-
console.error('Error stopping', {
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('Error stopping', { error, scope });
|
|
78
78
|
}
|
|
79
79
|
}));
|
|
80
80
|
|
package/src/time.ts
CHANGED
|
@@ -22,8 +22,8 @@ export class TimeUtil {
|
|
|
22
22
|
* Test to see if a string is valid for relative time
|
|
23
23
|
* @param val
|
|
24
24
|
*/
|
|
25
|
-
static isTimeSpan(
|
|
26
|
-
return TIME_PATTERN.test(
|
|
25
|
+
static isTimeSpan(value: string): value is TimeSpan {
|
|
26
|
+
return TIME_PATTERN.test(value);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -36,12 +36,12 @@ export class TimeUtil {
|
|
|
36
36
|
return amount.getTime();
|
|
37
37
|
} else if (typeof amount === 'string') {
|
|
38
38
|
const groups: { amount?: string, unit?: TimeUnit } = amount.match(TIME_PATTERN)?.groups ?? {};
|
|
39
|
-
const
|
|
39
|
+
const amountString = groups.amount ?? `${amount}`;
|
|
40
40
|
unit = groups.unit ?? unit ?? 'ms';
|
|
41
41
|
if (!TIME_UNITS[unit]) {
|
|
42
42
|
return NaN;
|
|
43
43
|
}
|
|
44
|
-
amount =
|
|
44
|
+
amount = amountString.includes('.') ? parseFloat(amountString) : parseInt(amountString, 10);
|
|
45
45
|
}
|
|
46
46
|
return amount * TIME_UNITS[unit ?? 'ms'];
|
|
47
47
|
}
|
|
@@ -69,11 +69,11 @@ export class TimeUtil {
|
|
|
69
69
|
if (value === undefined) {
|
|
70
70
|
return value;
|
|
71
71
|
}
|
|
72
|
-
const
|
|
72
|
+
const result = (typeof value === 'string' && /\d{1,30}[a-z]$/i.test(value)) ?
|
|
73
73
|
(this.isTimeSpan(value) ? this.asMillis(value) : undefined) :
|
|
74
74
|
(typeof value === 'string' ? parseInt(value, 10) :
|
|
75
75
|
(value instanceof Date ? value.getTime() : value));
|
|
76
|
-
return Number.isNaN(
|
|
76
|
+
return Number.isNaN(result) ? undefined : result;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
@@ -90,11 +90,11 @@ export class TimeUtil {
|
|
|
90
90
|
* @param time Time in milliseconds
|
|
91
91
|
*/
|
|
92
92
|
static asClock(time: number): string {
|
|
93
|
-
const
|
|
93
|
+
const seconds = Math.trunc(time / 1000);
|
|
94
94
|
return [
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
`${(
|
|
98
|
-
].filter(
|
|
95
|
+
seconds > 3600 ? `${Math.trunc(seconds / 3600).toString().padStart(2, '0')}h` : '',
|
|
96
|
+
seconds > 60 ? `${Math.trunc((seconds % 3600) / 60).toString().padStart(2, '0')}m` : '',
|
|
97
|
+
`${(seconds % 60).toString().padStart(2, '0')}s`
|
|
98
|
+
].filter(part => !!part).slice(0, 2).join(' ');
|
|
99
99
|
}
|
|
100
100
|
}
|
package/src/types.ts
CHANGED
|
@@ -14,7 +14,7 @@ export type TypedFunction<R = Any, V = unknown> = (this: V, ...args: Any[]) => R
|
|
|
14
14
|
|
|
15
15
|
export type MethodDescriptor<V = Any, R = Any> = TypedPropertyDescriptor<TypedFunction<R, V>>;
|
|
16
16
|
export type AsyncMethodDescriptor<V = Any, R = Any> = TypedPropertyDescriptor<TypedFunction<Promise<R>, V>>;
|
|
17
|
-
export type
|
|
17
|
+
export type AsyncIterableMethodDescriptor<V = Any, R = Any> = TypedPropertyDescriptor<TypedFunction<AsyncIterable<R>, V>>;
|
|
18
18
|
export type ClassTDecorator<T extends Class = Class> = (target: T) => T | void;
|
|
19
19
|
|
|
20
20
|
export type Primitive = number | bigint | boolean | string | Date;
|
|
@@ -34,7 +34,7 @@ type ValidPrimitiveFields<T, Z = undefined> = {
|
|
|
34
34
|
export type RetainPrimitiveFields<T, Z = undefined> = Pick<T, ValidPrimitiveFields<T, Z>>;
|
|
35
35
|
|
|
36
36
|
export const TypedObject: {
|
|
37
|
-
keys<T = unknown, K extends keyof T = keyof T & string>(
|
|
37
|
+
keys<T = unknown, K extends keyof T = keyof T & string>(value: T): K[];
|
|
38
38
|
fromEntries<K extends string | symbol, V>(items: ([K, V] | readonly [K, V])[]): Record<K, V>;
|
|
39
39
|
entries<K extends Record<symbol | string, unknown>>(record: K): [keyof K, K[keyof K]][];
|
|
40
40
|
} & ObjectConstructor = Object;
|
|
@@ -56,8 +56,8 @@ export function classConstruct<T>(cls: Class<T>, args: unknown[] = []): ClassIns
|
|
|
56
56
|
return castTo(new cons(...args));
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
export const hasFunction = <T>(key: keyof T) => (
|
|
60
|
-
typeof
|
|
59
|
+
export const hasFunction = <T>(key: keyof T) => (value: unknown): value is T =>
|
|
60
|
+
typeof value === 'object' && value !== null && typeof value[castKey(key)] === 'function';
|
|
61
61
|
|
|
62
62
|
export const hasToJSON = hasFunction<{ toJSON(): object }>('toJSON');
|
|
63
63
|
|
|
@@ -65,13 +65,6 @@ export function toConcrete<T extends unknown>(): Class<T> {
|
|
|
65
65
|
return arguments[0];
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
export function getAllEntries<V>(obj: Record<string | symbol, V>): [string | symbol, V][] {
|
|
69
|
-
return [
|
|
70
|
-
...Object.keys(obj),
|
|
71
|
-
...Object.getOwnPropertySymbols(obj)
|
|
72
|
-
].map(k => [k, obj[k]] as const);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
68
|
/**
|
|
76
69
|
* Find parent class for a given class object
|
|
77
70
|
*/
|
|
@@ -83,7 +76,8 @@ export function getParentClass(cls: Class): Class | undefined {
|
|
|
83
76
|
/**
|
|
84
77
|
* Get the class from an instance or class
|
|
85
78
|
*/
|
|
86
|
-
export const getClass = <T = unknown>(
|
|
79
|
+
export const getClass = <T = unknown>(value: ClassInstance | Class): Class<T> =>
|
|
80
|
+
'Ⲑid' in value ? castTo(value) : asConstructable<T>(value).constructor;
|
|
87
81
|
|
|
88
82
|
/**
|
|
89
83
|
* Range of bytes, inclusive
|
package/src/util.ts
CHANGED
|
@@ -4,7 +4,7 @@ import timers from 'node:timers/promises';
|
|
|
4
4
|
import { castTo, hasToJSON } from './types.ts';
|
|
5
5
|
import { AppError } from './error.ts';
|
|
6
6
|
|
|
7
|
-
type MapFn<T, U> = (
|
|
7
|
+
type MapFn<T, U> = (value: T, i: number) => U | Promise<U>;
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Grab bag of common utilities
|
|
@@ -38,35 +38,35 @@ export class Util {
|
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* Generate a random UUID
|
|
41
|
-
* @param
|
|
41
|
+
* @param length The length of the uuid to generate
|
|
42
42
|
*/
|
|
43
|
-
static uuid(
|
|
44
|
-
const bytes = crypto.randomBytes(Math.ceil(
|
|
45
|
-
if (
|
|
43
|
+
static uuid(length: number = 32): string {
|
|
44
|
+
const bytes = crypto.randomBytes(Math.ceil(length / 2));
|
|
45
|
+
if (length === 32) { // Make valid uuid-v4
|
|
46
46
|
// eslint-disable-next-line no-bitwise
|
|
47
47
|
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
48
48
|
// eslint-disable-next-line no-bitwise
|
|
49
49
|
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
50
50
|
}
|
|
51
|
-
return bytes.toString('hex').substring(0,
|
|
51
|
+
return bytes.toString('hex').substring(0, length);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* Map an async iterable with various mapping functions
|
|
56
56
|
*/
|
|
57
|
-
static
|
|
58
|
-
static
|
|
59
|
-
static
|
|
60
|
-
static async *
|
|
57
|
+
static mapAsyncIterable<T, U, V, W>(source: AsyncIterable<T>, fn1: MapFn<T, U>, fn2: MapFn<U, V>, fn3: MapFn<V, W>): AsyncIterable<W>;
|
|
58
|
+
static mapAsyncIterable<T, U, V>(source: AsyncIterable<T>, fn1: MapFn<T, U>, fn2: MapFn<U, V>): AsyncIterable<V>;
|
|
59
|
+
static mapAsyncIterable<T, U>(source: AsyncIterable<T>, fn: MapFn<T, U>): AsyncIterable<U>;
|
|
60
|
+
static async * mapAsyncIterable<T>(input: AsyncIterable<T>, ...fns: MapFn<unknown, unknown>[]): AsyncIterable<unknown> {
|
|
61
61
|
let idx = -1;
|
|
62
|
-
for await (const
|
|
63
|
-
if (
|
|
62
|
+
for await (const item of input) {
|
|
63
|
+
if (item !== undefined) {
|
|
64
64
|
idx += 1;
|
|
65
|
-
let
|
|
65
|
+
let result = item;
|
|
66
66
|
for (const fn of fns) {
|
|
67
|
-
|
|
67
|
+
result = castTo(await fn(result, idx));
|
|
68
68
|
}
|
|
69
|
-
yield
|
|
69
|
+
yield result;
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -103,9 +103,9 @@ export class Util {
|
|
|
103
103
|
cacheKey?: (...keyInput: K) => string
|
|
104
104
|
): (...input: K) => boolean {
|
|
105
105
|
|
|
106
|
-
const rawRules = (Array.isArray(rules) ? rules : rules.split(/,/g).map(
|
|
106
|
+
const rawRules = (Array.isArray(rules) ? rules : rules.split(/,/g).map(rule => rule.trim()));
|
|
107
107
|
const convertedRules = rawRules.map(rule => this.#allowDenyRuleInput(rule, convert));
|
|
108
|
-
const unmatchedValue = !convertedRules.some(
|
|
108
|
+
const unmatchedValue = !convertedRules.some(rule => rule.positive);
|
|
109
109
|
|
|
110
110
|
if (convertedRules.length) {
|
|
111
111
|
if (cacheKey) {
|
|
@@ -127,8 +127,8 @@ export class Util {
|
|
|
127
127
|
if (value === undefined) {
|
|
128
128
|
return;
|
|
129
129
|
}
|
|
130
|
-
const
|
|
131
|
-
return Buffer.from(
|
|
130
|
+
const text = JSON.stringify(value);
|
|
131
|
+
return Buffer.from(text, 'utf8').toString('base64');
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/**
|
|
@@ -155,20 +155,20 @@ export class Util {
|
|
|
155
155
|
* Serialize to JSON
|
|
156
156
|
*/
|
|
157
157
|
static serializeToJSON<T>(out: T): string {
|
|
158
|
-
return JSON.stringify(out, function (
|
|
159
|
-
const
|
|
160
|
-
if (
|
|
158
|
+
return JSON.stringify(out, function (key, value) {
|
|
159
|
+
const objectValue = this[key];
|
|
160
|
+
if (objectValue && objectValue instanceof Error) {
|
|
161
161
|
return {
|
|
162
162
|
$: true,
|
|
163
|
-
...hasToJSON(
|
|
164
|
-
name:
|
|
165
|
-
message:
|
|
166
|
-
stack:
|
|
163
|
+
...hasToJSON(objectValue) ? objectValue.toJSON() : objectValue,
|
|
164
|
+
name: objectValue.name,
|
|
165
|
+
message: objectValue.message,
|
|
166
|
+
stack: objectValue.stack,
|
|
167
167
|
};
|
|
168
|
-
} else if (typeof
|
|
169
|
-
return `${
|
|
168
|
+
} else if (typeof value === 'bigint') {
|
|
169
|
+
return `${value.toString()}$n`;
|
|
170
170
|
} else {
|
|
171
|
-
return
|
|
171
|
+
return value;
|
|
172
172
|
}
|
|
173
173
|
});
|
|
174
174
|
}
|
|
@@ -177,42 +177,42 @@ export class Util {
|
|
|
177
177
|
* Deserialize from JSON
|
|
178
178
|
*/
|
|
179
179
|
static deserializeFromJson<T = unknown>(input: string): T {
|
|
180
|
-
return JSON.parse(input, function (
|
|
181
|
-
if (
|
|
182
|
-
const
|
|
183
|
-
if (!(
|
|
184
|
-
const { $: _, ...rest } =
|
|
185
|
-
Object.assign(
|
|
180
|
+
return JSON.parse(input, function (key, value) {
|
|
181
|
+
if (value && typeof value === 'object' && '$' in value) {
|
|
182
|
+
const error = AppError.fromJSON(value) ?? new Error();
|
|
183
|
+
if (!(error instanceof AppError)) {
|
|
184
|
+
const { $: _, ...rest } = value;
|
|
185
|
+
Object.assign(error, rest);
|
|
186
186
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
return
|
|
191
|
-
} else if (typeof
|
|
192
|
-
return BigInt(
|
|
187
|
+
error.message = value.message;
|
|
188
|
+
error.stack = value.stack;
|
|
189
|
+
error.name = value.name;
|
|
190
|
+
return error;
|
|
191
|
+
} else if (typeof value === 'string' && /^\d+[$]n$/.test(value)) {
|
|
192
|
+
return BigInt(value.split('$')[0]);
|
|
193
193
|
} else {
|
|
194
|
-
return
|
|
194
|
+
return value;
|
|
195
195
|
}
|
|
196
196
|
});
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
/**
|
|
200
200
|
* Retry an operation, with a custom conflict handler
|
|
201
|
-
* @param
|
|
201
|
+
* @param operation The operation to retry
|
|
202
202
|
* @param isHandledConflict Function to determine if the error is a handled conflict
|
|
203
203
|
* @param maxTries Maximum number of retries
|
|
204
204
|
*/
|
|
205
205
|
static async acquireWithRetry<T>(
|
|
206
|
-
|
|
207
|
-
prepareRetry: (
|
|
206
|
+
operation: () => T | Promise<T>,
|
|
207
|
+
prepareRetry: (error: unknown, count: number) => (void | undefined | boolean | Promise<(void | undefined | boolean)>),
|
|
208
208
|
maxTries = 5,
|
|
209
209
|
): Promise<T> {
|
|
210
210
|
for (let i = 0; i < maxTries; i++) {
|
|
211
211
|
try {
|
|
212
|
-
return await
|
|
213
|
-
} catch (
|
|
214
|
-
if (i === maxTries - 1 || await prepareRetry(
|
|
215
|
-
throw
|
|
212
|
+
return await operation();
|
|
213
|
+
} catch (error) {
|
|
214
|
+
if (i === maxTries - 1 || await prepareRetry(error, i) === false) {
|
|
215
|
+
throw error; // Stop retrying if we reached max tries or prepareRetry returns false
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
218
|
}
|
package/src/watch.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { Util } from './util.ts';
|
|
|
5
5
|
|
|
6
6
|
export type WatchEvent = { file: string, action: 'create' | 'update' | 'delete', output: string, module: string, time: number };
|
|
7
7
|
|
|
8
|
-
export async function* watchCompiler(
|
|
8
|
+
export async function* watchCompiler(config?: { restartOnExit?: boolean, signal?: AbortSignal }): AsyncIterable<WatchEvent> {
|
|
9
9
|
// Load at runtime
|
|
10
10
|
const { CompilerClient } = await import('@travetto/compiler/support/server/client.ts');
|
|
11
11
|
|
|
@@ -16,22 +16,22 @@ export async function* watchCompiler(cfg?: { restartOnExit?: boolean, signal?: A
|
|
|
16
16
|
info(message, ...args): void { console.error('info', message, ...args); },
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
const
|
|
20
|
-
const remove = ShutdownManager.onGracefulShutdown(async () =>
|
|
19
|
+
const controller = new AbortController();
|
|
20
|
+
const remove = ShutdownManager.onGracefulShutdown(async () => controller.abort());
|
|
21
21
|
|
|
22
|
-
await client.waitForState(['compile-end', 'watch-start'], undefined,
|
|
22
|
+
await client.waitForState(['compile-end', 'watch-start'], undefined, controller.signal);
|
|
23
23
|
|
|
24
24
|
if (!await client.isWatching()) { // If we get here, without a watch
|
|
25
25
|
while (!await client.isWatching()) { // Wait until watch starts
|
|
26
26
|
await Util.nonBlockingTimeout(1000 * 60);
|
|
27
27
|
}
|
|
28
28
|
} else {
|
|
29
|
-
yield* client.fetchEvents('change', { signal:
|
|
29
|
+
yield* client.fetchEvents('change', { signal: controller.signal, enforceIteration: true });
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
remove();
|
|
33
33
|
|
|
34
|
-
if (
|
|
34
|
+
if (config?.restartOnExit) {
|
|
35
35
|
// We are done, request restart
|
|
36
36
|
await ShutdownManager.gracefulShutdown('@travetto/runtime:watchCompiler');
|
|
37
37
|
process.exit(ExecUtil.RESTART_EXIT_CODE);
|
|
@@ -43,14 +43,14 @@ export class MetadataRegistrationUtil {
|
|
|
43
43
|
*/
|
|
44
44
|
static registerFunction(state: TransformerState & MetadataInfo,
|
|
45
45
|
node: ts.FunctionDeclaration | ts.FunctionExpression,
|
|
46
|
-
|
|
46
|
+
source?: ts.FunctionDeclaration | ts.FunctionExpression | ts.InterfaceDeclaration | ts.TypeAliasDeclaration
|
|
47
47
|
): void {
|
|
48
48
|
// If we have a class like function
|
|
49
49
|
state[RegisterImportSymbol] ??= state.importFile(this.REGISTER_IMPORT);
|
|
50
50
|
|
|
51
|
-
const tag = this.tag(state,
|
|
51
|
+
const tag = this.tag(state, source ?? node);
|
|
52
52
|
const meta = state.factory.createCallExpression(
|
|
53
|
-
state.createAccess(state[RegisterImportSymbol].
|
|
53
|
+
state.createAccess(state[RegisterImportSymbol].identifier, this.REGISTER_FN),
|
|
54
54
|
[],
|
|
55
55
|
[
|
|
56
56
|
state.createIdentifier(node.name!.text),
|
|
@@ -74,7 +74,7 @@ export class MetadataRegistrationUtil {
|
|
|
74
74
|
const name = node.name?.escapedText.toString() ?? '';
|
|
75
75
|
|
|
76
76
|
const meta = state.factory.createCallExpression(
|
|
77
|
-
state.createAccess(state[RegisterImportSymbol].
|
|
77
|
+
state.createAccess(state[RegisterImportSymbol].identifier, this.REGISTER_FN),
|
|
78
78
|
[],
|
|
79
79
|
[
|
|
80
80
|
state.createIdentifier(name),
|
|
@@ -18,7 +18,7 @@ export class ConcreteTransformer {
|
|
|
18
18
|
static #createConcreteFunction(state: TransformerState, name: string | ts.Identifier): ts.FunctionDeclaration {
|
|
19
19
|
const final = typeof name === 'string' ? name : name.getText();
|
|
20
20
|
|
|
21
|
-
const
|
|
21
|
+
const declaration = state.factory.createFunctionDeclaration(
|
|
22
22
|
// eslint-disable-next-line no-bitwise
|
|
23
23
|
state.factory.createModifiersFromModifierFlags(ts.ModifierFlags.Export | ts.ModifierFlags.Const),
|
|
24
24
|
undefined, `${final}$Concrete`, [], [], undefined,
|
|
@@ -26,13 +26,13 @@ export class ConcreteTransformer {
|
|
|
26
26
|
);
|
|
27
27
|
|
|
28
28
|
state.addStatements([
|
|
29
|
-
|
|
29
|
+
declaration,
|
|
30
30
|
state.factory.createExpressionStatement(
|
|
31
31
|
state.factory.createCallExpression(
|
|
32
32
|
state.createAccess('Object', 'defineProperty'),
|
|
33
33
|
undefined,
|
|
34
34
|
[
|
|
35
|
-
|
|
35
|
+
declaration.name!,
|
|
36
36
|
state.fromLiteral('name'),
|
|
37
37
|
state.fromLiteral({ value: final })
|
|
38
38
|
]
|
|
@@ -40,7 +40,7 @@ export class ConcreteTransformer {
|
|
|
40
40
|
)
|
|
41
41
|
]);
|
|
42
42
|
|
|
43
|
-
return
|
|
43
|
+
return declaration;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
@@ -83,26 +83,26 @@ export class ConsoleLogTransformer {
|
|
|
83
83
|
|
|
84
84
|
const chain = node.expression;
|
|
85
85
|
const name = chain.name;
|
|
86
|
-
const
|
|
86
|
+
const expr = chain.expression;
|
|
87
87
|
|
|
88
|
-
if (!ts.isIdentifier(
|
|
88
|
+
if (!ts.isIdentifier(expr) || expr.escapedText !== 'console' || !ts.isIdentifier(name)) {
|
|
89
89
|
return node;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
const level = name.escapedText!;
|
|
93
93
|
|
|
94
94
|
if (VALID_LEVELS[level]) {
|
|
95
|
-
const
|
|
95
|
+
const identifier = state.imported ??= state.importFile(CONSOLE_IMPORT).identifier;
|
|
96
96
|
return state.factory.updateCallExpression(
|
|
97
97
|
node,
|
|
98
|
-
state.createAccess(
|
|
98
|
+
state.createAccess(identifier, 'log'),
|
|
99
99
|
node.typeArguments,
|
|
100
100
|
[
|
|
101
101
|
LiteralUtil.fromLiteral(state.factory, {
|
|
102
102
|
level: state.factory.createStringLiteral(VALID_LEVELS[level]),
|
|
103
103
|
import: state.getModuleIdentifier(),
|
|
104
104
|
line: state.source.getLineAndCharacterOfPosition(node.getStart(state.source)).line + 1,
|
|
105
|
-
scope: state.scope?.map(
|
|
105
|
+
scope: state.scope?.map(part => part.name).join(':'),
|
|
106
106
|
args: node.arguments.slice(0)
|
|
107
107
|
}),
|
|
108
108
|
]
|
|
@@ -19,7 +19,7 @@ export class DebugEntryTransformer {
|
|
|
19
19
|
@OnMethod('DebugBreak')
|
|
20
20
|
static debugOnEntry(state: TransformerState & DebugState, node: ts.MethodDeclaration): ts.MethodDeclaration {
|
|
21
21
|
if (!state[DebugSymbol]) {
|
|
22
|
-
const imp = state.importFile('@travetto/runtime/src/debug.ts').
|
|
22
|
+
const imp = state.importFile('@travetto/runtime/src/debug.ts').identifier;
|
|
23
23
|
state[DebugSymbol] = CoreUtil.createAccess(state.factory, imp, 'tryDebugger');
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -52,9 +52,9 @@ export class RegisterTransformer {
|
|
|
52
52
|
return node;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
const { [MethodsSymbol]:
|
|
55
|
+
const { [MethodsSymbol]: method, [ClassSymbol]: cls } = state;
|
|
56
56
|
delete state[ClassSymbol];
|
|
57
|
-
return MetadataRegistrationUtil.registerClass(state, node,
|
|
57
|
+
return MetadataRegistrationUtil.registerClass(state, node, cls!, method);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
@@ -5,6 +5,9 @@ import { TransformerState, OnFile } from '@travetto/transformer';
|
|
|
5
5
|
const PATH_REGEX = /^['"](node:)?path['"]$/;
|
|
6
6
|
const PATH_IMPORT = '@travetto/manifest/src/path.ts';
|
|
7
7
|
|
|
8
|
+
const isImport = (node: ts.Node): node is ts.ImportDeclaration =>
|
|
9
|
+
ts.isImportDeclaration(node) && PATH_REGEX.test(node.moduleSpecifier?.getText() ?? '');
|
|
10
|
+
|
|
8
11
|
/**
|
|
9
12
|
* Rewriting path imports to use manifest's path
|
|
10
13
|
*/
|
|
@@ -15,18 +18,17 @@ export class PathImportTransformer {
|
|
|
15
18
|
*/
|
|
16
19
|
@OnFile()
|
|
17
20
|
static rewritePathImport(state: TransformerState, node: ts.SourceFile): ts.SourceFile {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
if (stmt) {
|
|
21
|
+
const statement = node.statements.find(isImport);
|
|
22
|
+
if (statement) {
|
|
21
23
|
const updated = state.factory.updateImportDeclaration(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
statement,
|
|
25
|
+
statement.modifiers,
|
|
26
|
+
statement.importClause,
|
|
25
27
|
state.factory.createStringLiteral(PATH_IMPORT),
|
|
26
|
-
|
|
28
|
+
statement.attributes
|
|
27
29
|
);
|
|
28
|
-
return state.factory.updateSourceFile(node, node.statements.map(
|
|
29
|
-
|
|
30
|
+
return state.factory.updateSourceFile(node, node.statements.map(item =>
|
|
31
|
+
item === statement ? updated : item
|
|
30
32
|
));
|
|
31
33
|
}
|
|
32
34
|
return node;
|