@travetto/runtime 5.0.0-rc.10

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.
@@ -0,0 +1,61 @@
1
+ export type FunctionMetadataTag = { hash: number, lines: [start: number, end: number, bodyStart?: number] };
2
+ export type FunctionMetadata = FunctionMetadataTag & {
3
+ id: string;
4
+ import: string;
5
+ module: string;
6
+ modulePath: string;
7
+ methods?: Record<string, FunctionMetadataTag>;
8
+ synthetic?: boolean;
9
+ class?: boolean;
10
+ abstract?: boolean;
11
+ };
12
+
13
+ const METADATA = Symbol.for('@travetto/runtime:function-metadata');
14
+
15
+ const pending = new Set<Function>([]);
16
+
17
+ /**
18
+ * Initialize the meta data for a function/class
19
+ * @param fn Class
20
+ * @param `file` Filename
21
+ * @param `hash` Hash of class contents
22
+ * @param `line` Line number in source
23
+ * @param `methods` Methods and their hashes
24
+ * @param `abstract` Is the class abstract
25
+ * @param `synthetic` Is this code generated at build time
26
+ * @private
27
+ */
28
+ export function registerFunction(
29
+ fn: Function, [pkg, pth]: [string, string], tag: FunctionMetadataTag,
30
+ methods?: Record<string, FunctionMetadataTag>, abstract?: boolean, synthetic?: boolean
31
+ ): void {
32
+ const modulePath = pth.replace(/[.][cm]?[tj]sx?$/, '');
33
+
34
+ const metadata: FunctionMetadata = {
35
+ id: (fn.name ? `${pkg}:${modulePath}○${fn.name}` : `${pkg}:${modulePath}`),
36
+ import: `${pkg}/${pth}`,
37
+ module: pkg,
38
+ modulePath,
39
+ ...tag, methods, abstract, synthetic, class: abstract !== undefined
40
+ };
41
+ pending.add(fn);
42
+ Object.defineProperties(fn, { Ⲑid: { value: metadata.id }, [METADATA]: { value: metadata } });
43
+ }
44
+
45
+ /**
46
+ * Flush all pending function registers
47
+ */
48
+ export function flushPendingFunctions(): Function[] {
49
+ const fns = [...pending];
50
+ pending.clear();
51
+ return fns;
52
+ }
53
+
54
+ /**
55
+ * Read metadata
56
+ */
57
+ export function describeFunction(fn: Function): FunctionMetadata;
58
+ export function describeFunction(fn?: Function): FunctionMetadata | undefined {
59
+ const _fn: (Function & { [METADATA]?: FunctionMetadata }) | undefined = fn;
60
+ return _fn?.[METADATA];
61
+ }
@@ -0,0 +1,20 @@
1
+ import { BlobMeta } from './types';
2
+
3
+ // https://github.com/microsoft/TypeScript/issues/59012
4
+ declare const write: unique symbol;
5
+ declare global {
6
+ interface WritableStreamDefaultWriter<W = any> {
7
+ [write]?: (a: W) => void;
8
+ }
9
+ interface Function {
10
+ Ⲑid: string;
11
+ }
12
+
13
+ interface Blob {
14
+ readonly meta?: Readonly<BlobMeta>;
15
+ }
16
+
17
+ interface File {
18
+ readonly meta?: Readonly<BlobMeta>;
19
+ }
20
+ }
@@ -0,0 +1,4 @@
1
+ import { ManifestIndex } from '@travetto/manifest';
2
+
3
+ /** Runtime manifest index */
4
+ export const RuntimeIndex = new ManifestIndex();
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
+ }
@@ -0,0 +1,28 @@
1
+ import { Runtime } from './context';
2
+ import { Env } from './env';
3
+ import { FileLoader } from './file-loader';
4
+
5
+ /**
6
+ * Environment aware file loader
7
+ */
8
+ class $RuntimeResources extends FileLoader {
9
+ #computed: string[];
10
+ #env: string;
11
+ #mod: string;
12
+
13
+ constructor() {
14
+ super([]);
15
+ }
16
+
17
+ override get searchPaths(): readonly string[] {
18
+ if (!this.#computed || this.#env !== Env.TRV_RESOURCES.val || this.#mod !== Env.TRV_MODULE.val) {
19
+ this.#env = Env.TRV_RESOURCES.val!;
20
+ this.#mod = Env.TRV_MODULE.val!;
21
+ this.#computed = Runtime.resourcePaths();
22
+ }
23
+ return this.#computed;
24
+ }
25
+ }
26
+
27
+ /** Runtime resources */
28
+ export const RuntimeResources = new $RuntimeResources();
@@ -0,0 +1,65 @@
1
+ import { Env } from './env';
2
+ import { Util } from './util';
3
+ import { TimeUtil } from './time';
4
+
5
+ /**
6
+ * Shutdown manager, allowing for listening for graceful shutdowns
7
+ */
8
+ export class ShutdownManager {
9
+
10
+ static #registered = false;
11
+ static #handlers: { name?: string, handler: () => Promise<void> }[] = [];
12
+
13
+ /**
14
+ * On Shutdown requested
15
+ * @param name name to log for
16
+ * @param handler synchronous or asynchronous handler
17
+ */
18
+ static onGracefulShutdown(handler: () => Promise<void>, name?: string | { constructor: { Ⲑid: string } }): () => void {
19
+ if (!this.#registered) {
20
+ this.#registered = true;
21
+ const done = (): void => { this.gracefulShutdown(0); };
22
+ process.on('SIGUSR2', done).on('SIGTERM', done).on('SIGINT', done);
23
+ }
24
+ this.#handlers.push({ handler, name: typeof name === 'string' ? name : name?.constructor.Ⲑid });
25
+ return () => {
26
+ const idx = this.#handlers.findIndex(x => x.handler === handler);
27
+ if (idx >= 0) {
28
+ this.#handlers.splice(idx, 1);
29
+ }
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Wait for graceful shutdown to run and complete
35
+ */
36
+ static async gracefulShutdown(code: number | string | undefined = process.exitCode): Promise<void> {
37
+ if (code !== undefined) {
38
+ process.exitCode = code;
39
+ }
40
+
41
+ if (this.#handlers.length) {
42
+ console.debug('Graceful shutdown: started');
43
+
44
+ const items = this.#handlers.splice(0, this.#handlers.length);
45
+ const handlers = Promise.all(items.map(({ name, handler }) => {
46
+ if (name) {
47
+ console.debug('Stopping', { name });
48
+ }
49
+ return handler().catch(err => {
50
+ console.error('Error shutting down', { name, err });
51
+ });
52
+ }));
53
+
54
+ await Promise.race([
55
+ Util.nonBlockingTimeout(TimeUtil.fromValue(Env.TRV_SHUTDOWN_WAIT.val) ?? 2000), // Wait 2s and then force finish
56
+ handlers,
57
+ ]);
58
+
59
+ console.debug('Graceful shutdown: completed');
60
+ }
61
+ if (code !== undefined) {
62
+ process.exit();
63
+ }
64
+ }
65
+ }
package/src/time.ts ADDED
@@ -0,0 +1,100 @@
1
+ const MIN = 1000 * 60;
2
+ const DAY = 24 * MIN * 60;
3
+ const TIME_UNITS = {
4
+ y: DAY * 365,
5
+ M: DAY * 30,
6
+ w: DAY * 7,
7
+ d: DAY,
8
+ h: MIN * 60,
9
+ m: MIN,
10
+ s: 1000,
11
+ ms: 1
12
+ };
13
+
14
+ export type TimeSpan = `${number}${keyof typeof TIME_UNITS}`;
15
+ export type TimeUnit = keyof typeof TIME_UNITS;
16
+
17
+ export class TimeUtil {
18
+
19
+ static #timePattern = new RegExp(`^(?<amount>-?[0-9.]+)(?<unit>${Object.keys(TIME_UNITS).join('|')})$`);
20
+
21
+ /**
22
+ * Test to see if a string is valid for relative time
23
+ * @param val
24
+ */
25
+ static isTimeSpan(val: string): val is TimeSpan {
26
+ return this.#timePattern.test(val);
27
+ }
28
+
29
+ /**
30
+ * Returns time units convert to ms
31
+ * @param amount Number of units to extend
32
+ * @param unit Time unit to extend ('ms', 's', 'm', 'h', 'd', 'w', 'y')
33
+ */
34
+ static asMillis(amount: Date | number | TimeSpan, unit?: TimeUnit): number {
35
+ if (amount instanceof Date) {
36
+ return amount.getTime();
37
+ } else if (typeof amount === 'string') {
38
+ const groups: { amount?: string, unit?: TimeUnit } = amount.match(this.#timePattern)?.groups ?? {};
39
+ const amountStr = groups.amount ?? `${amount}`;
40
+ unit = groups.unit ?? unit ?? 'ms';
41
+ if (!TIME_UNITS[unit]) {
42
+ return NaN;
43
+ }
44
+ amount = amountStr.includes('.') ? parseFloat(amountStr) : parseInt(amountStr, 10);
45
+ }
46
+ return amount * TIME_UNITS[unit ?? 'ms'];
47
+ }
48
+
49
+ /**
50
+ * Returns the time converted to seconds
51
+ * @param date The date to convert
52
+ */
53
+ static asSeconds(date: Date | number | TimeSpan, unit?: TimeUnit): number {
54
+ return Math.trunc(this.asMillis(date, unit) / 1000);
55
+ }
56
+
57
+ /**
58
+ * Returns the time converted to a Date
59
+ * @param date The date to convert
60
+ */
61
+ static asDate(date: Date | number | TimeSpan, unit?: TimeUnit): Date {
62
+ return new Date(this.asMillis(date, unit));
63
+ }
64
+
65
+ /**
66
+ * Resolve time or span to possible time
67
+ */
68
+ static fromValue(value: Date | number | string | undefined): number | undefined {
69
+ if (value === undefined) {
70
+ return value;
71
+ }
72
+ const val = (typeof value === 'string' && /\d+[a-z]$/i.test(value)) ?
73
+ (this.isTimeSpan(value) ? this.asMillis(value) : undefined) :
74
+ (typeof value === 'string' ? parseInt(value, 10) :
75
+ (value instanceof Date ? value.getTime() : value));
76
+ return Number.isNaN(val) ? undefined : val;
77
+ }
78
+
79
+ /**
80
+ * Returns a new date with `amount` units into the future
81
+ * @param amount Number of units to extend
82
+ * @param unit Time unit to extend ('ms', 's', 'm', 'h', 'd', 'w', 'y')
83
+ */
84
+ static fromNow(amount: number | TimeSpan, unit: TimeUnit = 'ms'): Date {
85
+ return new Date(Date.now() + this.asMillis(amount, unit));
86
+ }
87
+
88
+ /**
89
+ * Returns a pretty timestamp
90
+ * @param time Time in milliseconds
91
+ */
92
+ static asClock(time: number): string {
93
+ const s = Math.trunc(time / 1000);
94
+ return [
95
+ s > 3600 ? `${Math.trunc(s / 3600).toString().padStart(2, '0')}h` : '',
96
+ s > 60 ? `${Math.trunc((s % 3600) / 60).toString().padStart(2, '0')}m` : '',
97
+ `${(s % 60).toString().padStart(2, '0')}s`
98
+ ].filter(x => !!x).slice(0, 2).join(' ');
99
+ }
100
+ }
package/src/trv.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ import type { ManifestModuleRole } from '@travetto/manifest';
2
+
3
+ import type { TimeSpan } from './time';
4
+
5
+ type Role = Exclude<ManifestModuleRole, 'std' | 'compile'>;
6
+
7
+
8
+ declare global {
9
+ interface TravettoEnv {
10
+ /**
11
+ * The node environment we are running in
12
+ * @default development
13
+ */
14
+ NODE_ENV: 'development' | 'production';
15
+ /**
16
+ * Outputs all console.debug messages, defaults to `local` in dev, and `off` in prod.
17
+ */
18
+ DEBUG: boolean | string;
19
+ /**
20
+ * Environment to deploy, defaults to `NODE_ENV` if not `TRV_ENV` is not specified.
21
+ */
22
+ TRV_ENV: string;
23
+ /**
24
+ * Special role to run as, used to access additional files from the manifest during runtime.
25
+ */
26
+ TRV_ROLE: Role;
27
+ /**
28
+ * Whether or not to run the program in dynamic mode, allowing for real-time updates
29
+ */
30
+ TRV_DYNAMIC: boolean;
31
+ /**
32
+ * The folders to use for resource lookup
33
+ */
34
+ TRV_RESOURCES: string[];
35
+ /**
36
+ * Resource path overrides
37
+ * @private
38
+ */
39
+ TRV_RESOURCE_OVERRIDES: Record<string, string>;
40
+ /**
41
+ * The max time to wait for shutdown to finish after initial SIGINT,
42
+ * @default 2s
43
+ */
44
+ TRV_SHUTDOWN_WAIT: TimeSpan | number;
45
+ /**
46
+ * The desired runtime module
47
+ */
48
+ TRV_MODULE: string;
49
+ /**
50
+ * The location of the manifest file
51
+ * @default undefined
52
+ */
53
+ TRV_MANIFEST: string;
54
+ /**
55
+ * trvc log level
56
+ */
57
+ TRV_BUILD: 'none' | 'info' | 'debug' | 'error' | 'warn',
58
+ /**
59
+ * Should break on first line of a method when using the @DebugBreak decorator
60
+ * @default false
61
+ */
62
+ TRV_DEBUG_BREAK: boolean;
63
+ }
64
+ }
package/src/types.ts ADDED
@@ -0,0 +1,73 @@
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;
6
+
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 };
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
+
22
+ export type Primitive = number | bigint | boolean | string | Date;
23
+
24
+ export type DeepPartial<T> = {
25
+ [P in keyof T]?: (T[P] extends (Primitive | undefined) ? (T[P] | undefined) :
26
+ (T[P] extends Any[] ? (DeepPartial<T[P][number]> | null | undefined)[] : DeepPartial<T[P]>));
27
+ };
28
+
29
+ export const TypedObject: {
30
+ keys<T = unknown, K extends keyof T = keyof T & string>(o: T): K[];
31
+ fromEntries<K extends string | symbol, V>(items: ([K, V] | readonly [K, V])[]): Record<K, V>;
32
+ entries<K extends Record<symbol | string, unknown>>(record: K): [keyof K, K[keyof K]][];
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 ADDED
@@ -0,0 +1,134 @@
1
+ import crypto from 'node:crypto';
2
+ import timers from 'node:timers/promises';
3
+
4
+ import { castTo } from './types';
5
+
6
+ type PromiseWithResolvers<T> = {
7
+ resolve: (v: T) => void;
8
+ reject: (err?: unknown) => void;
9
+ promise: Promise<T>;
10
+ };
11
+
12
+ type MapFn<T, U> = (val: T, i: number) => U | Promise<U>;
13
+
14
+ /**
15
+ * Grab bag of common utilities
16
+ */
17
+ export class Util {
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
+
44
+ /**
45
+ * Generate a random UUID
46
+ * @param len The length of the uuid to generate
47
+ */
48
+ static uuid(len: number = 32): string {
49
+ const bytes = crypto.randomBytes(Math.ceil(len / 2));
50
+ // eslint-disable-next-line no-bitwise
51
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
52
+ // eslint-disable-next-line no-bitwise
53
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
54
+ return bytes.toString('hex').substring(0, len);
55
+ }
56
+
57
+ /**
58
+ * Produce a promise that is externally resolvable
59
+ */
60
+ static resolvablePromise<T = void>(): PromiseWithResolvers<T> {
61
+ let ops: Pick<PromiseWithResolvers<T>, 'reject' | 'resolve'>;
62
+ const prom = new Promise<T>((resolve, reject) => ops = { resolve, reject });
63
+ return { ...ops!, promise: prom };
64
+ }
65
+
66
+ /**
67
+ * Map an async iterable with various mapping functions
68
+ */
69
+ static mapAsyncItr<T, U, V, W>(source: AsyncIterable<T>, fn1: MapFn<T, U>, fn2: MapFn<U, V>, fn3: MapFn<V, W>): AsyncIterable<W>;
70
+ static mapAsyncItr<T, U, V>(source: AsyncIterable<T>, fn1: MapFn<T, U>, fn2: MapFn<U, V>): AsyncIterable<V>;
71
+ static mapAsyncItr<T, U>(source: AsyncIterable<T>, fn: MapFn<T, U>): AsyncIterable<U>;
72
+ static async * mapAsyncItr<T>(source: AsyncIterable<T>, ...fns: MapFn<unknown, unknown>[]): AsyncIterable<unknown> {
73
+ let idx = -1;
74
+ for await (const el of source) {
75
+ if (el !== undefined) {
76
+ idx += 1;
77
+ let m = el;
78
+ for (const fn of fns) {
79
+ m = castTo(await fn(m, idx));
80
+ }
81
+ yield m;
82
+ }
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Non-blocking timeout
88
+ */
89
+ static nonBlockingTimeout(time: number): Promise<void> {
90
+ return timers.setTimeout(time, undefined, { ref: false }).catch(() => { });
91
+ }
92
+
93
+ /**
94
+ * Blocking timeout
95
+ */
96
+ static blockingTimeout(time: number): Promise<void> {
97
+ return timers.setTimeout(time, undefined, { ref: true }).catch(() => { });
98
+ }
99
+
100
+ /**
101
+ * Queue new macro task
102
+ */
103
+ static queueMacroTask(): Promise<void> {
104
+ return timers.setImmediate(undefined);
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
+ }
134
+ }
package/src/watch.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { RuntimeIndex } from './manifest-index';
2
+ import { ExecUtil } from './exec';
3
+ import { ShutdownManager } from './shutdown';
4
+ import { Util } from './util';
5
+
6
+ export type WatchEvent = { file: string, action: 'create' | 'update' | 'delete', output: string, module: string, time: number };
7
+
8
+ export async function* watchCompiler(cfg?: { restartOnExit?: boolean, signal?: AbortSignal }): AsyncIterable<WatchEvent> {
9
+ // Load at runtime
10
+ const { CompilerClient } = await import('@travetto/compiler/support/server/client');
11
+
12
+ const client = new CompilerClient(RuntimeIndex.manifest, {
13
+ warn(message, ...args): void { console.error('warn', message, ...args); },
14
+ debug(message, ...args): void { console.error('debug', message, ...args); },
15
+ error(message, ...args): void { console.error('error', message, ...args); },
16
+ info(message, ...args): void { console.error('info', message, ...args); },
17
+ });
18
+
19
+ const ctrl = new AbortController();
20
+ const remove = ShutdownManager.onGracefulShutdown(async () => ctrl.abort(), watchCompiler);
21
+
22
+ await client.waitForState(['compile-end', 'watch-start'], undefined, ctrl.signal);
23
+
24
+ if (!await client.isWatching()) { // If we get here, without a watch
25
+ while (!await client.isWatching()) { // Wait until watch starts
26
+ await Util.nonBlockingTimeout(1000 * 60);
27
+ }
28
+ } else {
29
+ yield* client.fetchEvents('change', { signal: ctrl.signal, enforceIteration: true });
30
+ }
31
+
32
+ remove();
33
+
34
+ if (cfg?.restartOnExit) {
35
+ // We are done, request restart
36
+ await ShutdownManager.gracefulShutdown(ExecUtil.RESTART_EXIT_CODE);
37
+ }
38
+ }