@travetto/runtime 7.0.0-rc.1 → 7.0.0-rc.3
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 +24 -20
- package/__index__.ts +1 -0
- package/package.json +3 -3
- package/src/binary.ts +14 -14
- package/src/console.ts +15 -15
- package/src/context.ts +18 -22
- package/src/env.ts +30 -30
- package/src/error.ts +15 -15
- package/src/exec.ts +38 -87
- package/src/file-loader.ts +9 -0
- package/src/json.ts +74 -0
- 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/trv.d.ts +0 -4
- package/src/types.ts +13 -12
- package/src/util.ts +25 -101
- package/src/watch.ts +63 -15
- package/support/transformer/metadata.ts +5 -5
- 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
|
@@ -19,6 +19,7 @@ Runtime is the foundation of all [Travetto](https://travetto.dev) applications.
|
|
|
19
19
|
* Standard Error Support
|
|
20
20
|
* Console Management
|
|
21
21
|
* Resource Access
|
|
22
|
+
* JSON Utilities
|
|
22
23
|
* Common Utilities
|
|
23
24
|
* Time Utilities
|
|
24
25
|
* Process Execution
|
|
@@ -38,8 +39,6 @@ class $Runtime {
|
|
|
38
39
|
get env(): string | undefined;
|
|
39
40
|
/** Are we in development mode */
|
|
40
41
|
get production(): boolean;
|
|
41
|
-
/** Is the app in dynamic mode? */
|
|
42
|
-
get dynamic(): boolean;
|
|
43
42
|
/** Get debug value */
|
|
44
43
|
get debug(): false | string;
|
|
45
44
|
/** Manifest main */
|
|
@@ -51,11 +50,11 @@ class $Runtime {
|
|
|
51
50
|
/** Main source path */
|
|
52
51
|
get mainSourcePath(): string;
|
|
53
52
|
/** Produce a workspace relative path */
|
|
54
|
-
workspaceRelative(...
|
|
53
|
+
workspaceRelative(...parts: string[]): string;
|
|
55
54
|
/** Strip off the workspace path from a file */
|
|
56
55
|
stripWorkspacePath(full: string): string;
|
|
57
56
|
/** Produce a workspace path for tooling, with '@' being replaced by node_module/name folder */
|
|
58
|
-
toolPath(...
|
|
57
|
+
toolPath(...parts: string[]): string;
|
|
59
58
|
/** Resolve single module path */
|
|
60
59
|
modulePath(modulePath: string, overrides?: Record<string, string>): string;
|
|
61
60
|
/** Resolve resource paths */
|
|
@@ -95,10 +94,6 @@ interface EnvData {
|
|
|
95
94
|
* Special role to run as, used to access additional files from the manifest during runtime.
|
|
96
95
|
*/
|
|
97
96
|
TRV_ROLE: Role;
|
|
98
|
-
/**
|
|
99
|
-
* Whether or not to run the program in dynamic mode, allowing for real-time updates
|
|
100
|
-
*/
|
|
101
|
-
TRV_DYNAMIC: boolean;
|
|
102
97
|
/**
|
|
103
98
|
* The folders to use for resource lookup
|
|
104
99
|
*/
|
|
@@ -145,9 +140,9 @@ export class EnvProp<T> {
|
|
|
145
140
|
/** Remove value */
|
|
146
141
|
clear(): void;
|
|
147
142
|
/** Export value */
|
|
148
|
-
export(
|
|
143
|
+
export(value?: T | undefined | null): Record<string, string>;
|
|
149
144
|
/** Read value as string */
|
|
150
|
-
get
|
|
145
|
+
get value(): string | undefined;
|
|
151
146
|
/** Read value as list */
|
|
152
147
|
get list(): string[] | undefined;
|
|
153
148
|
/** Read value as object */
|
|
@@ -204,8 +199,8 @@ export function work() {
|
|
|
204
199
|
|
|
205
200
|
try {
|
|
206
201
|
1 / 0;
|
|
207
|
-
} catch (
|
|
208
|
-
console.error('Divide by zero', { error
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error('Divide by zero', { error });
|
|
209
204
|
}
|
|
210
205
|
console.debug('End Work');
|
|
211
206
|
}
|
|
@@ -225,12 +220,12 @@ function work() {
|
|
|
225
220
|
try {
|
|
226
221
|
1 / 0;
|
|
227
222
|
}
|
|
228
|
-
catch (
|
|
229
|
-
Δconsole.log({ level: "error", import: mod_1, line: 7, scope: "work", args: ['Divide by zero', { error
|
|
223
|
+
catch (error) {
|
|
224
|
+
Δconsole.log({ level: "error", import: mod_1, line: 7, scope: "work", args: ['Divide by zero', { error }] });
|
|
230
225
|
}
|
|
231
226
|
Δconsole.log({ level: "debug", import: mod_1, line: 9, scope: "work", args: ['End Work'] });
|
|
232
227
|
}
|
|
233
|
-
Δfunction.registerFunction(work, mod_1, { hash:
|
|
228
|
+
Δfunction.registerFunction(work, mod_1, { hash: 159357293, lines: [1, 10, 2] });
|
|
234
229
|
```
|
|
235
230
|
|
|
236
231
|
#### Filtering Debug
|
|
@@ -258,9 +253,18 @@ $ DEBUG=express:*,@travetto/web npx trv run web
|
|
|
258
253
|
## Resource Access
|
|
259
254
|
The primary access patterns for resources, is to directly request a file, and to resolve that file either via file-system look up or leveraging the [Manifest](https://github.com/travetto/travetto/tree/main/module/manifest#readme "Support for project indexing, manifesting, along with file watching")'s data for what resources were found at manifesting time.
|
|
260
255
|
|
|
261
|
-
The [FileLoader](https://github.com/travetto/travetto/tree/main/module/runtime/src/file-loader.ts#
|
|
256
|
+
The [FileLoader](https://github.com/travetto/travetto/tree/main/module/runtime/src/file-loader.ts#L12) allows for accessing information about the resources, and subsequently reading the file as text/binary or to access the resource as a `Readable` stream. If a file is not found, it will throw an [AppError](https://github.com/travetto/travetto/tree/main/module/runtime/src/error.ts#L26) with a category of 'notfound'.
|
|
257
|
+
|
|
258
|
+
The [FileLoader](https://github.com/travetto/travetto/tree/main/module/runtime/src/file-loader.ts#L12) also supports tying itself to [Env](https://github.com/travetto/travetto/tree/main/module/runtime/src/env.ts#L114)'s `TRV_RESOURCES` information on where to attempt to find a requested resource.
|
|
259
|
+
|
|
260
|
+
## JSON Utilities
|
|
261
|
+
The framework provides utilities for working with JSON data. This module provides methods for reading and writing JSON files, as well as serializing and deserializing JSON data. It also provides support for working with Base64 encoded data for web safe transfer. The primary goal is ease of use, but also a centralized location for performance and security improvements over time.
|
|
262
262
|
|
|
263
|
-
|
|
263
|
+
* `parseSafe(input: string | Buffer)` parses JSON safely from a string or Buffer.
|
|
264
|
+
* `stringifyBase64(value: any)` encodes a JSON value as a base64 encoded string.
|
|
265
|
+
* `parseBase64(input: string)` decodes a JSON value from a base64 encoded string.
|
|
266
|
+
* `readFile(file: string)` reads a JSON file asynchronously.
|
|
267
|
+
* `readFileSync(file: string, onMissing?: any)` reads a JSON file synchronously.
|
|
264
268
|
|
|
265
269
|
## Common Utilities
|
|
266
270
|
Common utilities used throughout the framework. Currently [Util](https://github.com/travetto/travetto/tree/main/module/runtime/src/util.ts#L12) includes:
|
|
@@ -271,7 +275,7 @@ Common utilities used throughout the framework. Currently [Util](https://github.
|
|
|
271
275
|
|
|
272
276
|
**Code: Sample makeTemplate Usage**
|
|
273
277
|
```typescript
|
|
274
|
-
const tpl = makeTemplate((name: 'age'|'name',
|
|
278
|
+
const tpl = makeTemplate((name: 'age'|'name', value) => `**${name}: ${value}**`);
|
|
275
279
|
tpl`{{age:20}} {{name: 'bob'}}</>;
|
|
276
280
|
// produces
|
|
277
281
|
'**age: 20** **name: bob**'
|
|
@@ -287,7 +291,7 @@ export class TimeUtil {
|
|
|
287
291
|
* Test to see if a string is valid for relative time
|
|
288
292
|
* @param val
|
|
289
293
|
*/
|
|
290
|
-
static isTimeSpan(
|
|
294
|
+
static isTimeSpan(value: string): value is TimeSpan;
|
|
291
295
|
/**
|
|
292
296
|
* Returns time units convert to ms
|
|
293
297
|
* @param amount Number of units to extend
|
|
@@ -323,7 +327,7 @@ export class TimeUtil {
|
|
|
323
327
|
```
|
|
324
328
|
|
|
325
329
|
## Process Execution
|
|
326
|
-
[ExecUtil](https://github.com/travetto/travetto/tree/main/module/runtime/src/exec.ts#
|
|
330
|
+
[ExecUtil](https://github.com/travetto/travetto/tree/main/module/runtime/src/exec.ts#L40) exposes `getResult` as a means to wrap [child_process](https://nodejs.org/api/child_process.html)'s process object. This wrapper allows for a promise-based resolution of the subprocess with the ability to capture the stderr/stdout.
|
|
327
331
|
|
|
328
332
|
A simple example would be:
|
|
329
333
|
|
package/__index__.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from './src/exec.ts';
|
|
|
9
9
|
export * from './src/env.ts';
|
|
10
10
|
export * from './src/file-loader.ts';
|
|
11
11
|
export * from './src/function.ts';
|
|
12
|
+
export * from './src/json.ts';
|
|
12
13
|
export * from './src/manifest-index.ts';
|
|
13
14
|
export * from './src/queue.ts';
|
|
14
15
|
export * from './src/resources.ts';
|
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.3",
|
|
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.2",
|
|
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.3"
|
|
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
|
@@ -7,6 +7,7 @@ import { Env } from './env.ts';
|
|
|
7
7
|
import { RuntimeIndex } from './manifest-index.ts';
|
|
8
8
|
import { describeFunction } from './function.ts';
|
|
9
9
|
import { castTo } from './types.ts';
|
|
10
|
+
import { JSONUtil } from './json.ts';
|
|
10
11
|
|
|
11
12
|
/** Constrained version of {@type ManifestContext} */
|
|
12
13
|
class $Runtime {
|
|
@@ -28,7 +29,7 @@ class $Runtime {
|
|
|
28
29
|
|
|
29
30
|
/** Get env name, with support for the default env */
|
|
30
31
|
get env(): string | undefined {
|
|
31
|
-
return Env.TRV_ENV.
|
|
32
|
+
return Env.TRV_ENV.value || (!this.production ? this.#idx.manifest.workspace.defaultEnv : undefined);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/** Are we in development mode */
|
|
@@ -36,15 +37,10 @@ class $Runtime {
|
|
|
36
37
|
return process.env.NODE_ENV === 'production';
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
/** Is the app in dynamic mode? */
|
|
40
|
-
get dynamic(): boolean {
|
|
41
|
-
return Env.TRV_DYNAMIC.isTrue;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
40
|
/** Get debug value */
|
|
45
41
|
get debug(): false | string {
|
|
46
|
-
const
|
|
47
|
-
return (!
|
|
42
|
+
const value = Env.DEBUG.value ?? '';
|
|
43
|
+
return (!value && this.production) || Env.DEBUG.isFalse ? false : value;
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
/** Manifest main */
|
|
@@ -68,8 +64,8 @@ class $Runtime {
|
|
|
68
64
|
}
|
|
69
65
|
|
|
70
66
|
/** Produce a workspace relative path */
|
|
71
|
-
workspaceRelative(...
|
|
72
|
-
return path.resolve(this.workspace.path, ...
|
|
67
|
+
workspaceRelative(...parts: string[]): string {
|
|
68
|
+
return path.resolve(this.workspace.path, ...parts);
|
|
73
69
|
}
|
|
74
70
|
|
|
75
71
|
/** Strip off the workspace path from a file */
|
|
@@ -78,23 +74,23 @@ class $Runtime {
|
|
|
78
74
|
}
|
|
79
75
|
|
|
80
76
|
/** 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, ...
|
|
77
|
+
toolPath(...parts: string[]): string {
|
|
78
|
+
parts = parts.flatMap(part => part === '@' ? ['node_modules', this.#idx.manifest.main.name] : [part]);
|
|
79
|
+
return path.resolve(this.workspace.path, this.#idx.manifest.build.toolFolder, ...parts);
|
|
84
80
|
}
|
|
85
81
|
|
|
86
82
|
/** Resolve single module path */
|
|
87
83
|
modulePath(modulePath: string, overrides?: Record<string, string>): string {
|
|
88
84
|
const combined = { ...this.#resourceOverrides, ...overrides };
|
|
89
85
|
const [base, sub] = (combined[modulePath] ?? modulePath)
|
|
90
|
-
.replace(/^([^#]*)(#|$)/g, (_,
|
|
86
|
+
.replace(/^([^#]*)(#|$)/g, (_, module, relativePath) => `${this.#moduleAliases[module] ?? module}${relativePath}`)
|
|
91
87
|
.split('#');
|
|
92
88
|
return path.resolve(this.#idx.getModule(base)?.sourcePath ?? base, sub ?? '.');
|
|
93
89
|
}
|
|
94
90
|
|
|
95
91
|
/** Resolve resource paths */
|
|
96
92
|
resourcePaths(paths: string[] = []): string[] {
|
|
97
|
-
return [...new Set([...paths, ...Env.TRV_RESOURCES.list ?? [], '@#resources', '@@#resources'].map(
|
|
93
|
+
return [...new Set([...paths, ...Env.TRV_RESOURCES.list ?? [], '@#resources', '@@#resources'].map(module => this.modulePath(module)))];
|
|
98
94
|
}
|
|
99
95
|
|
|
100
96
|
/** Get source for function */
|
|
@@ -118,10 +114,10 @@ class $Runtime {
|
|
|
118
114
|
throw new Error(`Unable to find ${imp}, not in the manifest`);
|
|
119
115
|
} else if (imp.endsWith('.json')) {
|
|
120
116
|
imp = this.#idx.getFromImport(imp)?.sourceFile ?? imp;
|
|
121
|
-
return fs.readFile(imp, 'utf8').then(
|
|
117
|
+
return fs.readFile(imp, 'utf8').then(JSONUtil.parseSafe<T>);
|
|
122
118
|
}
|
|
123
119
|
|
|
124
|
-
if (!ManifestModuleUtil.
|
|
120
|
+
if (!ManifestModuleUtil.SOURCE_EXT_REGEX.test(imp)) {
|
|
125
121
|
if (imp.startsWith('@')) {
|
|
126
122
|
if (/[/].*?[/]/.test(imp)) {
|
|
127
123
|
imp = `${imp}.ts`;
|
|
@@ -132,16 +128,16 @@ class $Runtime {
|
|
|
132
128
|
}
|
|
133
129
|
|
|
134
130
|
imp = ManifestModuleUtil.withOutputExtension(imp);
|
|
135
|
-
const
|
|
136
|
-
if (
|
|
131
|
+
const imported = await import(imp);
|
|
132
|
+
if (imported?.default?.default) {
|
|
137
133
|
// Unpack default.default, typescript does this in a way that requires recreating the whole object
|
|
138
|
-
const def =
|
|
134
|
+
const def = imported?.default?.default;
|
|
139
135
|
return Object.defineProperties(castTo({}), {
|
|
140
|
-
...Object.getOwnPropertyDescriptors(
|
|
136
|
+
...Object.getOwnPropertyDescriptors(imported),
|
|
141
137
|
default: { get: () => def, configurable: false }
|
|
142
138
|
});
|
|
143
139
|
}
|
|
144
|
-
return
|
|
140
|
+
return imported;
|
|
145
141
|
}
|
|
146
142
|
}
|
|
147
143
|
|
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
|
}
|