@travetto/runtime 5.0.0-rc.8 → 5.0.0
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 +10 -8
- package/__index__.ts +2 -0
- package/package.json +5 -5
- package/src/binary.ts +132 -0
- package/src/env.ts +8 -8
- package/src/error.ts +4 -4
- package/src/exec.ts +5 -5
- package/src/function.ts +2 -2
- package/src/global.d.ts +11 -2
- package/src/queue.ts +64 -0
- package/src/time.ts +3 -4
- package/src/types.ts +60 -10
- package/src/util.ts +57 -35
- package/support/transformer.console-log.ts +1 -2
- package/support/transformer.function-metadata.ts +3 -3
- package/support/transformer.type-helpers.ts +25 -0
package/README.md
CHANGED
|
@@ -71,7 +71,7 @@ class $Runtime {
|
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
## Environment Support
|
|
74
|
-
The functionality we support for testing and retrieving environment information for known environment variables. They can be accessed directly on the [Env](https://github.com/travetto/travetto/tree/main/module/runtime/src/env.ts#
|
|
74
|
+
The functionality we support for testing and retrieving environment information for known environment variables. They can be accessed directly on the [Env](https://github.com/travetto/travetto/tree/main/module/runtime/src/env.ts#L109) object, and will return a scoped [EnvProp](https://github.com/travetto/travetto/tree/main/module/runtime/src/env.ts#L6), that is compatible with the property definition. E.g. only showing boolean related fields when the underlying flag supports `true` or `false`
|
|
75
75
|
|
|
76
76
|
**Code: Base Known Environment Flags**
|
|
77
77
|
```typescript
|
|
@@ -133,7 +133,7 @@ interface TravettoEnv {
|
|
|
133
133
|
```
|
|
134
134
|
|
|
135
135
|
### Environment Property
|
|
136
|
-
For a given [EnvProp](https://github.com/travetto/travetto/tree/main/module/runtime/src/env.ts#
|
|
136
|
+
For a given [EnvProp](https://github.com/travetto/travetto/tree/main/module/runtime/src/env.ts#L6), we support the ability to access different properties as a means to better facilitate environment variable usage.
|
|
137
137
|
|
|
138
138
|
**Code: EnvProp Shape**
|
|
139
139
|
```typescript
|
|
@@ -165,9 +165,9 @@ export class EnvProp<T> {
|
|
|
165
165
|
```
|
|
166
166
|
|
|
167
167
|
## Standard Error Support
|
|
168
|
-
While the framework is 100 % compatible with standard `Error` instances, there are cases in which additional functionality is desired. Within the framework we use [AppError](https://github.com/travetto/travetto/tree/main/module/runtime/src/error.ts#
|
|
168
|
+
While the framework is 100 % compatible with standard `Error` instances, there are cases in which additional functionality is desired. Within the framework we use [AppError](https://github.com/travetto/travetto/tree/main/module/runtime/src/error.ts#L15) (or its derivatives) to represent framework errors. This class is available for use in your own projects. Some of the additional benefits of using this class is enhanced error reporting, as well as better integration with other modules (e.g. the [RESTful API](https://github.com/travetto/travetto/tree/main/module/rest#readme "Declarative api for RESTful APIs with support for the dependency injection module.") module and HTTP status codes).
|
|
169
169
|
|
|
170
|
-
The [AppError](https://github.com/travetto/travetto/tree/main/module/runtime/src/error.ts#
|
|
170
|
+
The [AppError](https://github.com/travetto/travetto/tree/main/module/runtime/src/error.ts#L15) takes in a message, and an optional payload and / or error classification. The currently supported error classifications are:
|
|
171
171
|
* `general` - General purpose errors
|
|
172
172
|
* `system` - Synonym for `general`
|
|
173
173
|
* `data` - Data format, content, etc are incorrect. Generally correlated to bad input.
|
|
@@ -214,6 +214,7 @@ export function work() {
|
|
|
214
214
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
215
215
|
exports.work = work;
|
|
216
216
|
const tslib_1 = require("tslib");
|
|
217
|
+
const Ⲑ_function_1 = tslib_1.__importStar(require("@travetto/runtime/src/function.js"));
|
|
217
218
|
const ᚕ_c = tslib_1.__importStar(require("@travetto/runtime/src/console.js"));
|
|
218
219
|
var ᚕm = ["@travetto/runtime", "doc/transpile.ts"];
|
|
219
220
|
function work() {
|
|
@@ -226,6 +227,7 @@ function work() {
|
|
|
226
227
|
}
|
|
227
228
|
ᚕ_c.log({ level: "debug", import: ᚕm, line: 9, scope: "work", args: ['End Work'] });
|
|
228
229
|
}
|
|
230
|
+
Ⲑ_function_1.registerFunction(work, ᚕm, { hash: 1030247697, lines: [1, 10, 2] });
|
|
229
231
|
```
|
|
230
232
|
|
|
231
233
|
#### Filtering Debug
|
|
@@ -253,12 +255,12 @@ $ DEBUG=express:*,@travetto/rest npx trv run rest
|
|
|
253
255
|
## Resource Access
|
|
254
256
|
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.
|
|
255
257
|
|
|
256
|
-
The [FileLoader](https://github.com/travetto/travetto/tree/main/module/runtime/src/file-loader.ts#L11) 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#
|
|
258
|
+
The [FileLoader](https://github.com/travetto/travetto/tree/main/module/runtime/src/file-loader.ts#L11) 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#L15) with a category of 'notfound'.
|
|
257
259
|
|
|
258
|
-
The [FileLoader](https://github.com/travetto/travetto/tree/main/module/runtime/src/file-loader.ts#L11) also supports tying itself to [Env](https://github.com/travetto/travetto/tree/main/module/runtime/src/env.ts#
|
|
260
|
+
The [FileLoader](https://github.com/travetto/travetto/tree/main/module/runtime/src/file-loader.ts#L11) also supports tying itself to [Env](https://github.com/travetto/travetto/tree/main/module/runtime/src/env.ts#L109)'s `TRV_RESOURCES` information on where to attempt to find a requested resource.
|
|
259
261
|
|
|
260
262
|
## Common Utilities
|
|
261
|
-
Common utilities used throughout the framework. Currently [Util](https://github.com/travetto/travetto/tree/main/module/runtime/src/util.ts#
|
|
263
|
+
Common utilities used throughout the framework. Currently [Util](https://github.com/travetto/travetto/tree/main/module/runtime/src/util.ts#L17) includes:
|
|
262
264
|
* `uuid(len: number)` generates a simple uuid for use within the application.
|
|
263
265
|
* `allowDenyMatcher(rules[])` builds a matching function that leverages the rules as an allow/deny list, where order of the rules matters. Negative rules are prefixed by '!'.
|
|
264
266
|
* `hash(text: string, size?: number)` produces a full sha512 hash.
|
|
@@ -318,7 +320,7 @@ export class TimeUtil {
|
|
|
318
320
|
```
|
|
319
321
|
|
|
320
322
|
## Process Execution
|
|
321
|
-
[ExecUtil](https://github.com/travetto/travetto/tree/main/module/runtime/src/exec.ts#
|
|
323
|
+
[ExecUtil](https://github.com/travetto/travetto/tree/main/module/runtime/src/exec.ts#L42) 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.
|
|
322
324
|
|
|
323
325
|
A simple example would be:
|
|
324
326
|
|
package/__index__.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/// <reference path="./src/trv.d.ts" />
|
|
2
2
|
/// <reference path="./src/global.d.ts" />
|
|
3
|
+
export * from './src/binary';
|
|
3
4
|
export * from './src/console';
|
|
4
5
|
export * from './src/context';
|
|
5
6
|
export * from './src/debug';
|
|
@@ -9,6 +10,7 @@ export * from './src/env';
|
|
|
9
10
|
export * from './src/file-loader';
|
|
10
11
|
export * from './src/function';
|
|
11
12
|
export * from './src/manifest-index';
|
|
13
|
+
export * from './src/queue';
|
|
12
14
|
export * from './src/resources';
|
|
13
15
|
export * from './src/shutdown';
|
|
14
16
|
export * from './src/time';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/runtime",
|
|
3
|
-
"version": "5.0.0
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Runtime for travetto applications.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"console-manager",
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
"node": ">=22.0.0"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@travetto/manifest": "^5.0.0
|
|
31
|
+
"@travetto/manifest": "^5.0.0",
|
|
32
32
|
"@types/debug": "^4.1.12",
|
|
33
|
-
"@types/node": "^22.
|
|
34
|
-
"debug": "^4.3.
|
|
33
|
+
"@types/node": "^22.3.0",
|
|
34
|
+
"debug": "^4.3.6"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@travetto/transformer": "^5.0.0
|
|
37
|
+
"@travetto/transformer": "^5.0.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependenciesMeta": {
|
|
40
40
|
"@travetto/transformer": {
|
package/src/binary.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import { PassThrough, Readable, Transform } from 'node:stream';
|
|
6
|
+
import { pipeline } from 'node:stream/promises';
|
|
7
|
+
import { ReadableStream } from 'node:stream/web';
|
|
8
|
+
import { text as toText, arrayBuffer as toBuffer } from 'node:stream/consumers';
|
|
9
|
+
|
|
10
|
+
import { BinaryInput, BlobMeta } from './types';
|
|
11
|
+
import { AppError } from './error';
|
|
12
|
+
import { Util } from './util';
|
|
13
|
+
|
|
14
|
+
const BlobMetaⲐ = Symbol.for('@travetto/runtime:blob-meta');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Common functions for dealing with binary data/streams
|
|
18
|
+
*/
|
|
19
|
+
export class BinaryUtil {
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generate a proper sha512 hash from a src value
|
|
23
|
+
* @param src The seed value to build the hash from
|
|
24
|
+
* @param len The optional length of the hash to generate
|
|
25
|
+
*/
|
|
26
|
+
static hash(src: string, len: number = -1): string {
|
|
27
|
+
const hash = crypto.createHash('sha512');
|
|
28
|
+
hash.update(src);
|
|
29
|
+
const ret = hash.digest('hex');
|
|
30
|
+
return len > 0 ? ret.substring(0, len) : ret;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Compute hash from an input blob, buffer or readable stream.
|
|
35
|
+
*/
|
|
36
|
+
static async hashInput(input: BinaryInput): Promise<string> {
|
|
37
|
+
const hash = crypto.createHash('sha256').setEncoding('hex');
|
|
38
|
+
if (Buffer.isBuffer(input)) {
|
|
39
|
+
hash.write(input);
|
|
40
|
+
} else if (input instanceof Blob) {
|
|
41
|
+
await pipeline(Readable.fromWeb(input.stream()), hash);
|
|
42
|
+
} else {
|
|
43
|
+
await pipeline(input, hash);
|
|
44
|
+
}
|
|
45
|
+
return hash.digest('hex').toString();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Write file and copy over when ready
|
|
50
|
+
*/
|
|
51
|
+
static async bufferedFileWrite(file: string, content: string, checkHash = false): Promise<void> {
|
|
52
|
+
if (checkHash) {
|
|
53
|
+
const current = await fs.readFile(file, 'utf8').catch(() => '');
|
|
54
|
+
if (this.hash(current) === this.hash(content)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const temp = path.resolve(os.tmpdir(), `${process.hrtime()[1]}.${path.basename(file)}`);
|
|
60
|
+
await fs.writeFile(temp, content, 'utf8');
|
|
61
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
62
|
+
await fs.rename(temp, file);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Make a blob, and assign metadata
|
|
67
|
+
*/
|
|
68
|
+
static readableBlob(input: () => (Readable | Promise<Readable>), metadata: Omit<BlobMeta, 'filename'> & { filename: string }): File;
|
|
69
|
+
static readableBlob(input: () => (Readable | Promise<Readable>), metadata?: BlobMeta): Blob;
|
|
70
|
+
static readableBlob(input: () => (Readable | Promise<Readable>), metadata: BlobMeta = {}): Blob | File {
|
|
71
|
+
const go = (): Readable => {
|
|
72
|
+
const stream = new PassThrough();
|
|
73
|
+
Promise.resolve(input()).then(v => v.pipe(stream), (err) => stream.destroy(err));
|
|
74
|
+
return stream;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const size = metadata.range ? (metadata.range.end - metadata.range.start) + 1 : metadata.size;
|
|
78
|
+
const out: Blob = metadata.filename ?
|
|
79
|
+
new File([], path.basename(metadata.filename), { type: metadata.contentType }) :
|
|
80
|
+
new Blob([], { type: metadata.contentType });
|
|
81
|
+
|
|
82
|
+
return Object.defineProperties(out, {
|
|
83
|
+
size: { value: size },
|
|
84
|
+
stream: { value: () => ReadableStream.from(go()) },
|
|
85
|
+
arrayBuffer: { value: () => toBuffer(go()) },
|
|
86
|
+
text: { value: () => toText(go()) },
|
|
87
|
+
bytes: { value: () => toBuffer(go()).then(v => new Uint8Array(v)) },
|
|
88
|
+
[BlobMetaⲐ]: { value: metadata }
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get blob metadata
|
|
94
|
+
*/
|
|
95
|
+
static getBlobMeta(blob: Blob): BlobMeta | undefined {
|
|
96
|
+
const withMeta: Blob & { [BlobMetaⲐ]?: BlobMeta } = blob;
|
|
97
|
+
return withMeta[BlobMetaⲐ];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Write limiter
|
|
102
|
+
* @returns
|
|
103
|
+
*/
|
|
104
|
+
static limitWrite(maxSize: number): Transform {
|
|
105
|
+
let read = 0;
|
|
106
|
+
return new Transform({
|
|
107
|
+
transform(chunk, encoding, callback): void {
|
|
108
|
+
read += (Buffer.isBuffer(chunk) || typeof chunk === 'string') ? chunk.length : (chunk instanceof Uint8Array ? chunk.byteLength : 0);
|
|
109
|
+
if (read > maxSize) {
|
|
110
|
+
callback(new AppError('File size exceeded', 'data', { read, size: maxSize }));
|
|
111
|
+
} else {
|
|
112
|
+
callback(null, chunk);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get a hashed location/path for a blob
|
|
120
|
+
*/
|
|
121
|
+
static hashedBlobLocation(meta: BlobMeta): string {
|
|
122
|
+
const hash = meta.hash ?? Util.uuid();
|
|
123
|
+
|
|
124
|
+
let parts = hash.match(/(.{1,4})/g)!.slice();
|
|
125
|
+
if (parts.length > 4) {
|
|
126
|
+
parts = [...parts.slice(0, 4), parts.slice(4).join('')];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const ext = path.extname(meta.filename ?? '') || '.bin';
|
|
130
|
+
return `${parts.join('/')}${ext}`;
|
|
131
|
+
}
|
|
132
|
+
}
|
package/src/env.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { castKey, castTo } from './types';
|
|
2
|
+
|
|
1
3
|
const IS_TRUE = /^(true|yes|on|1)$/i;
|
|
2
4
|
const IS_FALSE = /^(false|no|off|0)$/i;
|
|
3
5
|
|
|
@@ -26,8 +28,7 @@ export class EnvProp<T> {
|
|
|
26
28
|
} else if (Array.isArray(val)) {
|
|
27
29
|
out = val.join(',');
|
|
28
30
|
} else if (typeof val === 'object') {
|
|
29
|
-
|
|
30
|
-
out = Object.entries(val as Record<string, string>).map(([k, v]) => `${k}=${v}`).join(',');
|
|
31
|
+
out = Object.entries(val).map(([k, v]) => `${k}=${v}`).join(',');
|
|
31
32
|
} else {
|
|
32
33
|
out = `${val}`;
|
|
33
34
|
}
|
|
@@ -52,8 +53,7 @@ export class EnvProp<T> {
|
|
|
52
53
|
|
|
53
54
|
/** Add values to list */
|
|
54
55
|
add(...items: string[]): void {
|
|
55
|
-
|
|
56
|
-
this.set([... new Set([...this.list ?? [], ...items])] as T);
|
|
56
|
+
process.env[this.key] = [... new Set([...this.list ?? [], ...items])].join(',');
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/** Read value as int */
|
|
@@ -95,12 +95,12 @@ type AllType = {
|
|
|
95
95
|
};
|
|
96
96
|
|
|
97
97
|
function delegate<T extends object>(base: T): AllType & T {
|
|
98
|
-
|
|
99
|
-
return new Proxy(base as AllType & T, {
|
|
98
|
+
return new Proxy(castTo(base), {
|
|
100
99
|
get(target, prop): unknown {
|
|
101
100
|
return typeof prop !== 'string' ? undefined :
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
(prop in base ? base[castKey(prop)] :
|
|
102
|
+
target[castKey<typeof target>(prop)] ??= castTo(new EnvProp(prop))
|
|
103
|
+
);
|
|
104
104
|
}
|
|
105
105
|
});
|
|
106
106
|
}
|
package/src/error.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { castTo } from './types';
|
|
2
|
+
|
|
1
3
|
export type ErrorCategory =
|
|
2
4
|
'general' |
|
|
3
5
|
'notfound' |
|
|
@@ -20,8 +22,7 @@ export class AppError<T = unknown> extends Error {
|
|
|
20
22
|
('type' in e && typeof e.type === 'string') &&
|
|
21
23
|
('at' in e && typeof e.at === 'number')
|
|
22
24
|
) {
|
|
23
|
-
|
|
24
|
-
const err = new AppError(e.message, e.category as ErrorCategory, 'details' in e ? e.details : undefined);
|
|
25
|
+
const err = new AppError(e.message, castTo(e.category), 'details' in e ? e.details : undefined);
|
|
25
26
|
err.at = new Date(e.at);
|
|
26
27
|
err.type = e.type;
|
|
27
28
|
return err;
|
|
@@ -59,8 +60,7 @@ export class AppError<T = unknown> extends Error {
|
|
|
59
60
|
category: this.category,
|
|
60
61
|
type: this.type,
|
|
61
62
|
at: this.at.toISOString(),
|
|
62
|
-
|
|
63
|
-
details: this.details as Record<string, unknown>,
|
|
63
|
+
details: castTo(this.details),
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
66
|
}
|
package/src/exec.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ChildProcess } from 'node:child_process';
|
|
2
2
|
import { Readable } from 'node:stream';
|
|
3
3
|
import { createInterface } from 'node:readline/promises';
|
|
4
|
+
import { castTo } from './types';
|
|
4
5
|
|
|
5
6
|
const MINUTE = (1000 * 60);
|
|
6
7
|
|
|
@@ -93,8 +94,8 @@ export class ExecUtil {
|
|
|
93
94
|
static getResult(proc: ChildProcess, options: { catch?: boolean, binary?: false }): Promise<ExecutionResult<string>>;
|
|
94
95
|
static getResult(proc: ChildProcess, options: { catch?: boolean, binary: true }): Promise<ExecutionResult<Buffer>>;
|
|
95
96
|
static getResult<T extends string | Buffer>(proc: ChildProcess, options: { catch?: boolean, binary?: boolean } = {}): Promise<ExecutionResult<T>> {
|
|
96
|
-
|
|
97
|
-
const res =
|
|
97
|
+
const _proc: ChildProcess & { [RESULT]?: Promise<ExecutionResult> } = proc;
|
|
98
|
+
const res = _proc[RESULT] ??= new Promise<ExecutionResult>(resolve => {
|
|
98
99
|
const stdout: Buffer[] = [];
|
|
99
100
|
const stderr: Buffer[] = [];
|
|
100
101
|
let done = false;
|
|
@@ -135,14 +136,13 @@ export class ExecUtil {
|
|
|
135
136
|
}
|
|
136
137
|
});
|
|
137
138
|
|
|
138
|
-
|
|
139
|
-
return (options.catch ? res : res.then(v => {
|
|
139
|
+
return castTo(options.catch ? res : res.then(v => {
|
|
140
140
|
if (v.valid) {
|
|
141
141
|
return v;
|
|
142
142
|
} else {
|
|
143
143
|
throw new Error(v.message);
|
|
144
144
|
}
|
|
145
|
-
}))
|
|
145
|
+
}));
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
/**
|
package/src/function.ts
CHANGED
|
@@ -56,6 +56,6 @@ export function flushPendingFunctions(): Function[] {
|
|
|
56
56
|
*/
|
|
57
57
|
export function describeFunction(fn: Function): FunctionMetadata;
|
|
58
58
|
export function describeFunction(fn?: Function): FunctionMetadata | undefined {
|
|
59
|
-
|
|
60
|
-
return
|
|
59
|
+
const _fn: (Function & { [METADATA]?: FunctionMetadata }) | undefined = fn;
|
|
60
|
+
return _fn?.[METADATA];
|
|
61
61
|
}
|
package/src/global.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import './types';
|
|
2
|
+
|
|
3
|
+
declare const write: unique symbol;
|
|
4
|
+
declare global {
|
|
5
|
+
// https://github.com/microsoft/TypeScript/issues/59012
|
|
6
|
+
interface WritableStreamDefaultWriter<W = any> {
|
|
7
|
+
[write]?: (a: W) => void;
|
|
8
|
+
}
|
|
9
|
+
interface Function {
|
|
10
|
+
Ⲑid: string;
|
|
11
|
+
}
|
|
3
12
|
}
|
package/src/queue.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Util } from './util';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* An asynchronous queue
|
|
5
|
+
*/
|
|
6
|
+
export class AsyncQueue<X> implements AsyncIterator<X>, AsyncIterable<X> {
|
|
7
|
+
|
|
8
|
+
#queue: X[] = [];
|
|
9
|
+
#done = false;
|
|
10
|
+
#ready = Util.resolvablePromise();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Initial set of items
|
|
14
|
+
*/
|
|
15
|
+
constructor(initial: Iterable<X> = [], signal?: AbortSignal) {
|
|
16
|
+
this.#queue.push(...initial);
|
|
17
|
+
signal?.addEventListener('abort', () => this.close());
|
|
18
|
+
if (signal?.aborted) {
|
|
19
|
+
this.close();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Allow for iteration
|
|
24
|
+
[Symbol.asyncIterator](): AsyncIterator<X> {
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Wait for next event to fire
|
|
30
|
+
*/
|
|
31
|
+
async next(): Promise<IteratorResult<X>> {
|
|
32
|
+
while (!this.#done && !this.#queue.length) {
|
|
33
|
+
await this.#ready.promise;
|
|
34
|
+
this.#ready = Util.resolvablePromise();
|
|
35
|
+
}
|
|
36
|
+
return { value: (this.#queue.length ? this.#queue.shift() : undefined)!, done: this.#done };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Queue next event to fire
|
|
41
|
+
* @param {boolean} immediate Determines if item(s) should be append or prepended to the queue
|
|
42
|
+
*/
|
|
43
|
+
add(item: X, immediate = false): void {
|
|
44
|
+
this.#queue[immediate ? 'unshift' : 'push'](item);
|
|
45
|
+
this.#ready.resolve();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Close the iterator
|
|
50
|
+
*/
|
|
51
|
+
close(): void {
|
|
52
|
+
this.#done = true;
|
|
53
|
+
this.#ready.resolve();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Throw an error from the queue, rejecting and terminating immediately
|
|
58
|
+
*/
|
|
59
|
+
async throw(e?: Error): Promise<IteratorResult<X>> {
|
|
60
|
+
this.#done = true;
|
|
61
|
+
this.#ready.reject(e);
|
|
62
|
+
return { value: undefined, done: this.#done };
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/time.ts
CHANGED
|
@@ -35,10 +35,9 @@ export class TimeUtil {
|
|
|
35
35
|
if (amount instanceof Date) {
|
|
36
36
|
return amount.getTime();
|
|
37
37
|
} else if (typeof amount === 'string') {
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
unit = groups?.unit ?? unit ?? 'ms';
|
|
38
|
+
const groups: { amount?: string, unit?: TimeUnit } = amount.match(this.#timePattern)?.groups ?? {};
|
|
39
|
+
const amountStr = groups.amount ?? `${amount}`;
|
|
40
|
+
unit = groups.unit ?? unit ?? 'ms';
|
|
42
41
|
if (!TIME_UNITS[unit]) {
|
|
43
42
|
return NaN;
|
|
44
43
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,23 +1,73 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
};
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
import { ReadableStream } from 'node:stream/web';
|
|
3
|
+
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
|
+
export type Any = any;
|
|
7
6
|
|
|
8
|
-
export type AnyMap = {
|
|
9
|
-
|
|
7
|
+
export type AnyMap = { [key: string]: Any };
|
|
8
|
+
export type Class<T = Any> = abstract new (...args: Any[]) => T;
|
|
9
|
+
export type ClassInstance<T = Any> = T & {
|
|
10
|
+
constructor: Class<T> & { Ⲑid: string };
|
|
10
11
|
};
|
|
11
12
|
|
|
13
|
+
export type BinaryInput = Blob | Buffer | Readable | ReadableStream;
|
|
14
|
+
|
|
15
|
+
export type TypedFunction<R = Any, V = unknown> = (this: V, ...args: Any[]) => R;
|
|
16
|
+
|
|
17
|
+
export type MethodDescriptor<V = Any, R = Any> = TypedPropertyDescriptor<TypedFunction<R, V>>;
|
|
18
|
+
export type AsyncMethodDescriptor<V = Any, R = Any> = TypedPropertyDescriptor<TypedFunction<Promise<R>, V>>;
|
|
19
|
+
export type AsyncItrMethodDescriptor<V = Any, R = Any> = TypedPropertyDescriptor<TypedFunction<AsyncIterable<R>, V>>;
|
|
20
|
+
export type ClassTDecorator<T extends Class = Class> = (target: T) => T | void;
|
|
21
|
+
|
|
12
22
|
export type Primitive = number | bigint | boolean | string | Date;
|
|
13
23
|
|
|
14
24
|
export type DeepPartial<T> = {
|
|
15
25
|
[P in keyof T]?: (T[P] extends (Primitive | undefined) ? (T[P] | undefined) :
|
|
16
|
-
(T[P] extends
|
|
26
|
+
(T[P] extends Any[] ? (DeepPartial<T[P][number]> | null | undefined)[] : DeepPartial<T[P]>));
|
|
17
27
|
};
|
|
18
28
|
|
|
19
29
|
export const TypedObject: {
|
|
20
|
-
keys<T = unknown, K extends keyof T = keyof T>(o: T): K[];
|
|
30
|
+
keys<T = unknown, K extends keyof T = keyof T & string>(o: T): K[];
|
|
21
31
|
fromEntries<K extends string | symbol, V>(items: ([K, V] | readonly [K, V])[]): Record<K, V>;
|
|
22
32
|
entries<K extends Record<symbol | string, unknown>>(record: K): [keyof K, K[keyof K]][];
|
|
23
33
|
} & ObjectConstructor = Object;
|
|
34
|
+
|
|
35
|
+
export function castTo<T>(input: unknown): T {
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
37
|
+
return input as T;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const castKey = <T>(input: string | number | symbol): keyof T => castTo(input);
|
|
41
|
+
export const asFull = <T>(input: Partial<T>): T => castTo(input);
|
|
42
|
+
export const asConstructable = <Z = unknown>(input: Class | unknown): { constructor: Class<Z> } => castTo(input);
|
|
43
|
+
|
|
44
|
+
export function classConstruct<T>(cls: Class<T>, args: unknown[] = []): ClassInstance<T> {
|
|
45
|
+
const cons: { new(..._args: Any[]): T } = castTo(cls);
|
|
46
|
+
return castTo(new cons(...args));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Range of bytes, inclusive
|
|
51
|
+
*/
|
|
52
|
+
export type ByteRange = { start: number, end?: number };
|
|
53
|
+
|
|
54
|
+
export interface BlobMeta {
|
|
55
|
+
/** Size of blob */
|
|
56
|
+
size?: number;
|
|
57
|
+
/** Mime type of the content */
|
|
58
|
+
contentType?: string;
|
|
59
|
+
/** Hash of blob contents */
|
|
60
|
+
hash?: string;
|
|
61
|
+
/** The original base filename of the file */
|
|
62
|
+
filename?: string;
|
|
63
|
+
/** Filenames title, optional for elements like images, audio, videos */
|
|
64
|
+
title?: string;
|
|
65
|
+
/** Content encoding */
|
|
66
|
+
contentEncoding?: string;
|
|
67
|
+
/** Content language */
|
|
68
|
+
contentLanguage?: string;
|
|
69
|
+
/** Cache control */
|
|
70
|
+
cacheControl?: string;
|
|
71
|
+
/** Byte range for blob */
|
|
72
|
+
range?: Required<ByteRange>;
|
|
73
|
+
}
|
package/src/util.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import timers from 'node:timers/promises';
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
import os from 'node:os';
|
|
3
|
+
|
|
4
|
+
import { castTo } from './types';
|
|
6
5
|
|
|
7
6
|
type PromiseWithResolvers<T> = {
|
|
8
7
|
resolve: (v: T) => void;
|
|
@@ -17,6 +16,31 @@ type MapFn<T, U> = (val: T, i: number) => U | Promise<U>;
|
|
|
17
16
|
*/
|
|
18
17
|
export class Util {
|
|
19
18
|
|
|
19
|
+
static #match<T, K extends unknown[]>(
|
|
20
|
+
rules: { value: T, positive: boolean }[],
|
|
21
|
+
compare: (rule: T, ...compareInput: K) => boolean,
|
|
22
|
+
unmatchedValue: boolean,
|
|
23
|
+
...input: K
|
|
24
|
+
): boolean {
|
|
25
|
+
for (const rule of rules) {
|
|
26
|
+
if (compare(rule.value, ...input)) {
|
|
27
|
+
return rule.positive;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return unmatchedValue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static #allowDenyRuleInput<T>(
|
|
34
|
+
rule: (string | T | [value: T, positive: boolean] | [value: T]),
|
|
35
|
+
convert: (inputRule: string) => T
|
|
36
|
+
): { value: T, positive: boolean } {
|
|
37
|
+
return typeof rule === 'string' ?
|
|
38
|
+
{ value: convert(rule.replace(/^!/, '')), positive: !rule.startsWith('!') } :
|
|
39
|
+
Array.isArray(rule) ?
|
|
40
|
+
{ value: rule[0], positive: rule[1] ?? true } :
|
|
41
|
+
{ value: rule, positive: true };
|
|
42
|
+
}
|
|
43
|
+
|
|
20
44
|
/**
|
|
21
45
|
* Generate a random UUID
|
|
22
46
|
* @param len The length of the uuid to generate
|
|
@@ -30,18 +54,6 @@ export class Util {
|
|
|
30
54
|
return bytes.toString('hex').substring(0, len);
|
|
31
55
|
}
|
|
32
56
|
|
|
33
|
-
/**
|
|
34
|
-
* Generate a proper sha512 hash from a src value
|
|
35
|
-
* @param src The seed value to build the hash from
|
|
36
|
-
* @param len The optional length of the hash to generate
|
|
37
|
-
*/
|
|
38
|
-
static hash(src: string, len: number = -1): string {
|
|
39
|
-
const hash = crypto.createHash('sha512');
|
|
40
|
-
hash.update(src);
|
|
41
|
-
const ret = hash.digest('hex');
|
|
42
|
-
return len > 0 ? ret.substring(0, len) : ret;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
57
|
/**
|
|
46
58
|
* Produce a promise that is externally resolvable
|
|
47
59
|
*/
|
|
@@ -64,31 +76,13 @@ export class Util {
|
|
|
64
76
|
idx += 1;
|
|
65
77
|
let m = el;
|
|
66
78
|
for (const fn of fns) {
|
|
67
|
-
|
|
68
|
-
m = (await fn(m, idx)) as typeof m;
|
|
79
|
+
m = castTo(await fn(m, idx));
|
|
69
80
|
}
|
|
70
81
|
yield m;
|
|
71
82
|
}
|
|
72
83
|
}
|
|
73
84
|
}
|
|
74
85
|
|
|
75
|
-
/**
|
|
76
|
-
* Write file and copy over when ready
|
|
77
|
-
*/
|
|
78
|
-
static async bufferedFileWrite(file: string, content: string, checkHash = false): Promise<void> {
|
|
79
|
-
if (checkHash) {
|
|
80
|
-
const current = await fs.readFile(file, 'utf8').catch(() => '');
|
|
81
|
-
if (Util.hash(current) === Util.hash(content)) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const temp = path.resolve(os.tmpdir(), `${process.hrtime()[1]}.${path.basename(file)}`);
|
|
87
|
-
await fs.writeFile(temp, content, 'utf8');
|
|
88
|
-
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
89
|
-
await fs.rename(temp, file);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
86
|
/**
|
|
93
87
|
* Non-blocking timeout
|
|
94
88
|
*/
|
|
@@ -104,9 +98,37 @@ export class Util {
|
|
|
104
98
|
}
|
|
105
99
|
|
|
106
100
|
/**
|
|
107
|
-
* Queue new
|
|
101
|
+
* Queue new macro task
|
|
108
102
|
*/
|
|
109
103
|
static queueMacroTask(): Promise<void> {
|
|
110
104
|
return timers.setImmediate(undefined);
|
|
111
105
|
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Simple check against allow/deny rules
|
|
109
|
+
* @param rules
|
|
110
|
+
*/
|
|
111
|
+
static allowDeny<T, K extends unknown[]>(
|
|
112
|
+
rules: string | (string | T | [value: T, positive: boolean])[],
|
|
113
|
+
convert: (rule: string) => T,
|
|
114
|
+
compare: (rule: T, ...compareInput: K) => boolean,
|
|
115
|
+
cacheKey?: (...keyInput: K) => string
|
|
116
|
+
): (...input: K) => boolean {
|
|
117
|
+
|
|
118
|
+
const rawRules = (Array.isArray(rules) ? rules : rules.split(/\s*,\s*/g));
|
|
119
|
+
const convertedRules = rawRules.map(rule => this.#allowDenyRuleInput(rule, convert));
|
|
120
|
+
const unmatchedValue = !convertedRules.some(r => r.positive);
|
|
121
|
+
|
|
122
|
+
if (convertedRules.length) {
|
|
123
|
+
if (cacheKey) {
|
|
124
|
+
const cache: Record<string, boolean> = {};
|
|
125
|
+
return (...input: K) =>
|
|
126
|
+
cache[cacheKey(...input)] ??= this.#match(convertedRules, compare, unmatchedValue, ...input);
|
|
127
|
+
} else {
|
|
128
|
+
return (...input: K) => this.#match(convertedRules, compare, unmatchedValue, ...input);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
return () => true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
112
134
|
}
|
|
@@ -22,8 +22,7 @@ const VALID_LEVELS: Record<string, string> = {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
* in prod mode.
|
|
25
|
+
* Logging support with code-location aware messages.
|
|
27
26
|
*/
|
|
28
27
|
export class ConsoleLogTransformer {
|
|
29
28
|
|
|
@@ -2,7 +2,7 @@ import ts from 'typescript';
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
TransformerState, OnMethod, OnClass, AfterClass,
|
|
5
|
-
|
|
5
|
+
CoreUtil, SystemUtil, Import, OnFunction
|
|
6
6
|
} from '@travetto/transformer';
|
|
7
7
|
|
|
8
8
|
import type { FunctionMetadataTag } from '../src/function';
|
|
@@ -40,7 +40,7 @@ export class RegisterTransformer {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
return { hash, lines: range };
|
|
43
|
-
} catch
|
|
43
|
+
} catch {
|
|
44
44
|
return { hash, lines: [0, 0] };
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -118,7 +118,7 @@ export class RegisterTransformer {
|
|
|
118
118
|
/**
|
|
119
119
|
* Give proper functions a file name
|
|
120
120
|
*/
|
|
121
|
-
@
|
|
121
|
+
@OnFunction()
|
|
122
122
|
static registerFunctionMetadata(state: TransformerState & MetadataInfo, node: ts.FunctionDeclaration | ts.FunctionExpression): typeof node {
|
|
123
123
|
if (!this.#valid(state)) {
|
|
124
124
|
return node;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
|
|
3
|
+
import { TransformerState, OnCall } from '@travetto/transformer';
|
|
4
|
+
|
|
5
|
+
const SRC = '@travetto/runtime/src/types.ts';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Allows for removal of type helpers at compile time
|
|
9
|
+
*/
|
|
10
|
+
export class TypeHelpersTransformer {
|
|
11
|
+
@OnCall()
|
|
12
|
+
static onTypeHelper(state: TransformerState, node: ts.CallExpression): ts.Node {
|
|
13
|
+
if (
|
|
14
|
+
ts.isIdentifier(node.expression) &&
|
|
15
|
+
node.arguments.length === 1 &&
|
|
16
|
+
/as(Class|Constructable|Full)|cast(To|Key)/.test(node.expression.escapedText.toString())
|
|
17
|
+
) {
|
|
18
|
+
const type = state.resolveType(node.expression);
|
|
19
|
+
if (type.key === 'unknown' && 'importName' in type && type.importName === SRC) {
|
|
20
|
+
// return node.arguments[0];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return node;
|
|
24
|
+
}
|
|
25
|
+
}
|