@getsoda/core 0.1.1 → 0.2.1
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 +58 -0
- package/dist/app/app.d.ts +17 -1
- package/dist/app/app.d.ts.map +1 -1
- package/dist/app/app.js +46 -10
- package/dist/app/app.js.map +1 -1
- package/dist/di/container.d.ts +7 -0
- package/dist/di/container.d.ts.map +1 -1
- package/dist/di/container.js +24 -1
- package/dist/di/container.js.map +1 -1
- package/dist/di/graph.d.ts +2 -2
- package/dist/di/graph.d.ts.map +1 -1
- package/dist/di/graph.js +20 -10
- package/dist/di/graph.js.map +1 -1
- package/dist/http/arguments.d.ts +20 -2
- package/dist/http/arguments.d.ts.map +1 -1
- package/dist/http/arguments.js +29 -0
- package/dist/http/arguments.js.map +1 -1
- package/dist/http/body.d.ts +28 -0
- package/dist/http/body.d.ts.map +1 -1
- package/dist/http/body.js +96 -1
- package/dist/http/body.js.map +1 -1
- package/dist/http/context-key.d.ts +33 -0
- package/dist/http/context-key.d.ts.map +1 -0
- package/dist/http/context-key.js +37 -0
- package/dist/http/context-key.js.map +1 -0
- package/dist/http/context.d.ts +37 -3
- package/dist/http/context.d.ts.map +1 -1
- package/dist/http/context.js +63 -5
- package/dist/http/context.js.map +1 -1
- package/dist/http/cookies.d.ts +49 -0
- package/dist/http/cookies.d.ts.map +1 -0
- package/dist/http/cookies.js +77 -0
- package/dist/http/cookies.js.map +1 -0
- package/dist/http/cors.d.ts +41 -0
- package/dist/http/cors.d.ts.map +1 -0
- package/dist/http/cors.js +97 -0
- package/dist/http/cors.js.map +1 -0
- package/dist/http/index.d.ts +8 -2
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/index.js +4 -1
- package/dist/http/index.js.map +1 -1
- package/dist/http/middleware.d.ts +56 -0
- package/dist/http/middleware.d.ts.map +1 -0
- package/dist/http/middleware.js +83 -0
- package/dist/http/middleware.js.map +1 -0
- package/dist/http/parsers.d.ts +3 -0
- package/dist/http/parsers.d.ts.map +1 -1
- package/dist/http/parsers.js +2 -1
- package/dist/http/parsers.js.map +1 -1
- package/dist/http/routes.d.ts +10 -1
- package/dist/http/routes.d.ts.map +1 -1
- package/dist/http/routes.js +4 -1
- package/dist/http/routes.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/jwt/algorithms.d.ts +19 -0
- package/dist/jwt/algorithms.d.ts.map +1 -0
- package/dist/jwt/algorithms.js +60 -0
- package/dist/jwt/algorithms.js.map +1 -0
- package/dist/jwt/errors.d.ts +24 -0
- package/dist/jwt/errors.d.ts.map +1 -0
- package/dist/jwt/errors.js +14 -0
- package/dist/jwt/errors.js.map +1 -0
- package/dist/jwt/guard.d.ts +30 -0
- package/dist/jwt/guard.d.ts.map +1 -0
- package/dist/jwt/guard.js +61 -0
- package/dist/jwt/guard.js.map +1 -0
- package/dist/jwt/index.d.ts +14 -0
- package/dist/jwt/index.d.ts.map +1 -0
- package/dist/jwt/index.js +10 -0
- package/dist/jwt/index.js.map +1 -0
- package/dist/jwt/module.d.ts +36 -0
- package/dist/jwt/module.d.ts.map +1 -0
- package/dist/jwt/module.js +38 -0
- package/dist/jwt/module.js.map +1 -0
- package/dist/jwt/service.d.ts +66 -0
- package/dist/jwt/service.d.ts.map +1 -0
- package/dist/jwt/service.js +138 -0
- package/dist/jwt/service.js.map +1 -0
- package/dist/logger/console-logger.d.ts +36 -0
- package/dist/logger/console-logger.d.ts.map +1 -0
- package/dist/logger/console-logger.js +117 -0
- package/dist/logger/console-logger.js.map +1 -0
- package/dist/logger/index.d.ts +9 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +7 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/logger.d.ts +55 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/logger.js +66 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/routing/types.d.ts +1 -1
- package/dist/routing/types.d.ts.map +1 -1
- package/package.json +6 -12
- package/src/app/app.ts +77 -13
- package/src/di/container.ts +38 -2
- package/src/di/graph.ts +26 -11
- package/src/http/arguments.ts +42 -2
- package/src/http/body.ts +142 -1
- package/src/http/context-key.ts +35 -0
- package/src/http/context.ts +94 -8
- package/src/http/cookies.ts +96 -0
- package/src/http/cors.ts +143 -0
- package/src/http/index.ts +8 -2
- package/src/http/middleware.ts +124 -0
- package/src/http/parsers.ts +2 -1
- package/src/http/routes.ts +17 -2
- package/src/index.ts +1 -0
- package/src/jwt/algorithms.ts +113 -0
- package/src/jwt/errors.ts +30 -0
- package/src/jwt/guard.ts +74 -0
- package/src/jwt/index.ts +14 -0
- package/src/jwt/module.ts +58 -0
- package/src/jwt/service.ts +216 -0
- package/src/logger/console-logger.ts +163 -0
- package/src/logger/index.ts +9 -0
- package/src/logger/logger.ts +96 -0
- package/src/routing/types.ts +1 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Logger writing to stdout and stderr, readable for people or as JSON lines.
|
|
4
|
+
*
|
|
5
|
+
* ```
|
|
6
|
+
* [Soda] 41250 - 11/14/2026, 12:22:09 AM LOG [UsersService] User created {"id":7} +3ms
|
|
7
|
+
* {"time":"2026-11-13T21:22:09.120Z","level":"log","context":"UsersService","message":"User created","id":7}
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* Like Nest's `ConsoleLogger`: the same line layout, colored by level in a
|
|
11
|
+
* terminal, with the time since the previous line; a minimum level; a JSON
|
|
12
|
+
* mode for log collectors. `error` and `fatal` go to stderr, the rest to stdout.
|
|
13
|
+
*/
|
|
14
|
+
import { Logger } from './logger.js';
|
|
15
|
+
const SEVERITY = { verbose: 0, debug: 1, log: 2, warn: 3, error: 4, fatal: 5 };
|
|
16
|
+
const color = (code) => (text) => `\u001B[${code}m${text}\u001B[0m`;
|
|
17
|
+
const LEVEL_COLORS = {
|
|
18
|
+
verbose: color('96'),
|
|
19
|
+
debug: color('95'),
|
|
20
|
+
log: color('32'),
|
|
21
|
+
warn: color('33'),
|
|
22
|
+
error: color('31'),
|
|
23
|
+
fatal: color('1;31'),
|
|
24
|
+
};
|
|
25
|
+
const yellow = color('38;5;3');
|
|
26
|
+
const plain = (text) => text;
|
|
27
|
+
const dateTime = new Intl.DateTimeFormat(undefined, {
|
|
28
|
+
year: 'numeric',
|
|
29
|
+
month: '2-digit',
|
|
30
|
+
day: '2-digit',
|
|
31
|
+
hour: 'numeric',
|
|
32
|
+
minute: '2-digit',
|
|
33
|
+
second: '2-digit',
|
|
34
|
+
});
|
|
35
|
+
const writeToConsole = (line, level) => {
|
|
36
|
+
const stream = level === 'error' || level === 'fatal' ? process.stderr : process.stdout;
|
|
37
|
+
stream.write(`${line}\n`);
|
|
38
|
+
};
|
|
39
|
+
const serializeError = (error) => {
|
|
40
|
+
const serialized = { name: error.name, message: error.message, stack: error.stack };
|
|
41
|
+
if (error.cause !== undefined) {
|
|
42
|
+
serialized.cause = error.cause instanceof Error ? serializeError(error.cause) : error.cause;
|
|
43
|
+
}
|
|
44
|
+
return serialized;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* `JSON.stringify` that never throws: bigints become strings, an object met a
|
|
48
|
+
* second time (a cycle) becomes `"[Circular]"`. Errors are serialized in full
|
|
49
|
+
* (stack and cause) for JSON lines, or as `Name: message` when their stack is
|
|
50
|
+
* printed on its own lines.
|
|
51
|
+
*/
|
|
52
|
+
const stringify = (value, errors) => {
|
|
53
|
+
const seen = new WeakSet();
|
|
54
|
+
return JSON.stringify(value, (_key, current) => {
|
|
55
|
+
if (typeof current === 'bigint') {
|
|
56
|
+
return current.toString();
|
|
57
|
+
}
|
|
58
|
+
if (current instanceof Error) {
|
|
59
|
+
return errors === 'full' ? serializeError(current) : `${current.name}: ${current.message}`;
|
|
60
|
+
}
|
|
61
|
+
if (typeof current === 'object' && current !== null) {
|
|
62
|
+
if (seen.has(current)) {
|
|
63
|
+
return '[Circular]';
|
|
64
|
+
}
|
|
65
|
+
seen.add(current);
|
|
66
|
+
}
|
|
67
|
+
return current;
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
export class ConsoleLogger extends Logger {
|
|
71
|
+
#minimum;
|
|
72
|
+
#json;
|
|
73
|
+
#output;
|
|
74
|
+
#colors;
|
|
75
|
+
#prefix;
|
|
76
|
+
/** When the previous line was written, for the `+3ms` at the end of the next. */
|
|
77
|
+
#last;
|
|
78
|
+
constructor(options = {}) {
|
|
79
|
+
super();
|
|
80
|
+
this.#minimum = SEVERITY[options.level ?? 'log'];
|
|
81
|
+
this.#json = options.json === true;
|
|
82
|
+
this.#output = options.output ?? writeToConsole;
|
|
83
|
+
this.#colors = options.colors ?? (options.output === undefined && process.stdout.isTTY === true && process.env.NO_COLOR === undefined);
|
|
84
|
+
this.#prefix = options.prefix ?? 'Soda';
|
|
85
|
+
}
|
|
86
|
+
write(entry) {
|
|
87
|
+
if (SEVERITY[entry.level] < this.#minimum) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
this.#output(this.#json ? this.#formatJson(entry, now) : this.#formatText(entry, now), entry.level);
|
|
92
|
+
this.#last = now;
|
|
93
|
+
}
|
|
94
|
+
#formatJson(entry, now) {
|
|
95
|
+
const time = new Date(now).toISOString();
|
|
96
|
+
// Fixed fields last: data cannot overwrite them.
|
|
97
|
+
return stringify({ ...entry.data, time, level: entry.level, context: entry.context, message: entry.message }, 'full');
|
|
98
|
+
}
|
|
99
|
+
#formatText(entry, now) {
|
|
100
|
+
const level = this.#colors ? LEVEL_COLORS[entry.level] : plain;
|
|
101
|
+
const accent = this.#colors ? yellow : plain;
|
|
102
|
+
const context = entry.context === undefined ? '' : accent(`[${entry.context}] `);
|
|
103
|
+
const since = this.#last === undefined ? '' : accent(` +${now - this.#last}ms`);
|
|
104
|
+
const data = entry.data === undefined ? '' : ` ${stringify(entry.data, 'brief')}`;
|
|
105
|
+
const line = `${level(`[${this.#prefix}] ${process.pid} - `)}${dateTime.format(now)} ${level(entry.level.toUpperCase().padStart(7))} ` +
|
|
106
|
+
`${context}${level(entry.message)}${data}${since}`;
|
|
107
|
+
let stacks = '';
|
|
108
|
+
// Stacks are read better on their own lines than inside the JSON.
|
|
109
|
+
for (const value of Object.values(entry.data ?? {})) {
|
|
110
|
+
if (value instanceof Error && value.stack !== undefined) {
|
|
111
|
+
stacks += `\n${value.stack}`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return line + stacks;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=console-logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"console-logger.js","sourceRoot":"","sources":["../../src/logger/console-logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Logging: the `Logger` contract and token, and the console implementation.
|
|
4
|
+
*/
|
|
5
|
+
export { ConsoleLogger } from './console-logger.ts';
|
|
6
|
+
export type { ConsoleLoggerOptions } from './console-logger.ts';
|
|
7
|
+
export { LOG_LEVELS, Logger, SilentLogger } from './logger.ts';
|
|
8
|
+
export type { LogData, LogEntry, LogLevel } from './logger.ts';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/logger/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,YAAY,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC9D,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/logger/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* The logger contract.
|
|
4
|
+
*
|
|
5
|
+
* `Logger` is both the type and the DI token: the application provides one
|
|
6
|
+
* (a `ConsoleLogger` unless told otherwise) and any class injects it.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* class UsersService {
|
|
10
|
+
* readonly #logger = inject(Logger).child('UsersService')
|
|
11
|
+
*
|
|
12
|
+
* create(user: User) {
|
|
13
|
+
* this.#logger.log('User created', { id: user.id })
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* Levels are Nest's. Unlike Nest's `Logger`, it is not a static global: each
|
|
19
|
+
* application has its own, and tests replace it with `overrides`.
|
|
20
|
+
*/
|
|
21
|
+
/** Levels, from the most to the least verbose. */
|
|
22
|
+
export declare const LOG_LEVELS: readonly ['verbose', 'debug', 'log', 'warn', 'error', 'fatal'];
|
|
23
|
+
export type LogLevel = (typeof LOG_LEVELS)[number];
|
|
24
|
+
/** Structured data of an entry; `Error` values are serialized with their stack and cause. */
|
|
25
|
+
export type LogData = Readonly<Record<string, unknown>>;
|
|
26
|
+
/**
|
|
27
|
+
* One log entry, as a `Logger` implementation receives it.
|
|
28
|
+
*/
|
|
29
|
+
export interface LogEntry {
|
|
30
|
+
readonly level: LogLevel;
|
|
31
|
+
readonly message: string;
|
|
32
|
+
readonly data: LogData | undefined;
|
|
33
|
+
/** Where it comes from, as given to `child()`. */
|
|
34
|
+
readonly context: string | undefined;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A logger. Implementations only write entries; levels and contexts are handled here.
|
|
38
|
+
*/
|
|
39
|
+
export declare abstract class Logger {
|
|
40
|
+
/** Writes one entry. Called for every level: filtering is up to the implementation. */
|
|
41
|
+
abstract write(entry: LogEntry): void;
|
|
42
|
+
verbose(message: string, data?: LogData): void;
|
|
43
|
+
debug(message: string, data?: LogData): void;
|
|
44
|
+
log(message: string, data?: LogData): void;
|
|
45
|
+
warn(message: string, data?: LogData): void;
|
|
46
|
+
error(message: string, data?: LogData): void;
|
|
47
|
+
fatal(message: string, data?: LogData): void;
|
|
48
|
+
/** A logger writing through this one, with `context` on every entry. */
|
|
49
|
+
child(context: string): Logger;
|
|
50
|
+
}
|
|
51
|
+
/** A logger that writes nothing: `createApp(AppModule, { logger: false })`. */
|
|
52
|
+
export declare class SilentLogger extends Logger {
|
|
53
|
+
write(): void;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/logger/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,kDAAkD;AAClD,eAAO,MAAM,UAAU,YAAI,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAU,CAAA;AAExF,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAA;AAElD,6FAA6F;AAC7F,MAAM,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAEvD;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS,CAAA;IAClC,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;CACrC;AAED;;GAEG;AACH,8BAAsB,MAAM;IAC1B,uFAAuF;IACvF,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAA;IAErC,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE7C;IAED,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE3C;IAED,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAEzC;IAED,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE1C;IAED,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE3C;IAED,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE3C;IAED,wEAAwE;IACxE,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE7B;CACF;AAiBD,+EAA+E;AAC/E,qBAAa,YAAa,SAAQ,MAAM;IACtC,KAAK,IAAI,IAAI,CAAG;CACjB"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* The logger contract.
|
|
4
|
+
*
|
|
5
|
+
* `Logger` is both the type and the DI token: the application provides one
|
|
6
|
+
* (a `ConsoleLogger` unless told otherwise) and any class injects it.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* class UsersService {
|
|
10
|
+
* readonly #logger = inject(Logger).child('UsersService')
|
|
11
|
+
*
|
|
12
|
+
* create(user: User) {
|
|
13
|
+
* this.#logger.log('User created', { id: user.id })
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* Levels are Nest's. Unlike Nest's `Logger`, it is not a static global: each
|
|
19
|
+
* application has its own, and tests replace it with `overrides`.
|
|
20
|
+
*/
|
|
21
|
+
/** Levels, from the most to the least verbose. */
|
|
22
|
+
export const LOG_LEVELS = ['verbose', 'debug', 'log', 'warn', 'error', 'fatal'];
|
|
23
|
+
/**
|
|
24
|
+
* A logger. Implementations only write entries; levels and contexts are handled here.
|
|
25
|
+
*/
|
|
26
|
+
export class Logger {
|
|
27
|
+
verbose(message, data) {
|
|
28
|
+
this.write({ level: 'verbose', message, data, context: undefined });
|
|
29
|
+
}
|
|
30
|
+
debug(message, data) {
|
|
31
|
+
this.write({ level: 'debug', message, data, context: undefined });
|
|
32
|
+
}
|
|
33
|
+
log(message, data) {
|
|
34
|
+
this.write({ level: 'log', message, data, context: undefined });
|
|
35
|
+
}
|
|
36
|
+
warn(message, data) {
|
|
37
|
+
this.write({ level: 'warn', message, data, context: undefined });
|
|
38
|
+
}
|
|
39
|
+
error(message, data) {
|
|
40
|
+
this.write({ level: 'error', message, data, context: undefined });
|
|
41
|
+
}
|
|
42
|
+
fatal(message, data) {
|
|
43
|
+
this.write({ level: 'fatal', message, data, context: undefined });
|
|
44
|
+
}
|
|
45
|
+
/** A logger writing through this one, with `context` on every entry. */
|
|
46
|
+
child(context) {
|
|
47
|
+
return new ContextLogger(this, context);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
class ContextLogger extends Logger {
|
|
51
|
+
#parent;
|
|
52
|
+
#context;
|
|
53
|
+
constructor(parent, context) {
|
|
54
|
+
super();
|
|
55
|
+
this.#parent = parent;
|
|
56
|
+
this.#context = context;
|
|
57
|
+
}
|
|
58
|
+
write(entry) {
|
|
59
|
+
this.#parent.write({ ...entry, context: this.#context });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** A logger that writes nothing: `createApp(AppModule, { logger: false })`. */
|
|
63
|
+
export class SilentLogger extends Logger {
|
|
64
|
+
write() { }
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logger/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,kDAAkD;AAClD,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAU,CAAA;AAkBxF;;GAEG;AACH,MAAM,OAAgB,MAAM;IAI1B,OAAO,CAAC,OAAe,EAAE,IAAc;QACrC,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;IACrE,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAAc;QACnC,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;IACnE,CAAC;IAED,GAAG,CAAC,OAAe,EAAE,IAAc;QACjC,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;IACjE,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,IAAc;QAClC,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;IAClE,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAAc;QACnC,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;IACnE,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAAc;QACnC,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;IACnE,CAAC;IAED,wEAAwE;IACxE,KAAK,CAAC,OAAe;QACnB,OAAO,IAAI,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACzC,CAAC;CACF;AAED,MAAM,aAAc,SAAQ,MAAM;IACvB,OAAO,CAAQ;IACf,QAAQ,CAAQ;IAEzB,YAAY,MAAc,EAAE,OAAe;QACzC,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;QACrB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;IACzB,CAAC;IAED,KAAK,CAAC,KAAe;QACnB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAA;IAC1D,CAAC;CACF;AAED,+EAA+E;AAC/E,MAAM,OAAO,YAAa,SAAQ,MAAM;IACtC,KAAK,KAAU,CAAC;CACjB"}
|
package/dist/routing/types.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ export type Params = Record<string, string>;
|
|
|
30
30
|
*
|
|
31
31
|
* `params` may be the very object Bun put on `req.params`: treat it as read-only.
|
|
32
32
|
*/
|
|
33
|
-
export type Handler = (req:
|
|
33
|
+
export type Handler = (req: Bun.BunRequest, params: Params) => Response | Promise<Response>;
|
|
34
34
|
/**
|
|
35
35
|
* Route definition.
|
|
36
36
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/routing/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,eAAO,MAAM,UAAU,QAAQ,CAAA;AAE/B;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,SAAU,CAAA;AAExC;;;;;;;GAOG;AACH,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAE3C;;;;GAIG;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/routing/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,eAAO,MAAM,UAAU,QAAQ,CAAA;AAE/B;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,SAAU,CAAA;AAExC;;;;;;;GAOG;AACH,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAE3C;;;;GAIG;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;AAE3F;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,eAAe;IAC9B,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAClE,oFAAoF;IACpF,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAClC;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,UAAU,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;AAEjF;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;IAC9C,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC/D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getsoda/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Class-based web framework for Bun: modules, dependency injection and decorators like Nest, on Bun's native router",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
"types": "./dist/index.d.ts",
|
|
29
29
|
"default": "./dist/index.js"
|
|
30
30
|
},
|
|
31
|
+
"./jwt": {
|
|
32
|
+
"types": "./dist/jwt/index.d.ts",
|
|
33
|
+
"default": "./dist/jwt/index.js"
|
|
34
|
+
},
|
|
31
35
|
"./package.json": "./package.json"
|
|
32
36
|
},
|
|
33
37
|
"types": "./dist/index.d.ts",
|
|
@@ -42,16 +46,6 @@
|
|
|
42
46
|
},
|
|
43
47
|
"scripts": {
|
|
44
48
|
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
45
|
-
"
|
|
46
|
-
"lint": "oxlint",
|
|
47
|
-
"lint:fix": "oxlint --fix",
|
|
48
|
-
"test": "bun test",
|
|
49
|
-
"prepublishOnly": "bun run lint && bun run typecheck && bun run test && bun run build"
|
|
50
|
-
},
|
|
51
|
-
"devDependencies": {
|
|
52
|
-
"@stylistic/eslint-plugin": "5.10.0",
|
|
53
|
-
"@types/bun": "latest",
|
|
54
|
-
"oxlint": "1.93.0",
|
|
55
|
-
"typescript": "7.0.2"
|
|
49
|
+
"prepublishOnly": "cd ../.. && bun run lint && bun run typecheck && bun run test && cd packages/core && bun run build"
|
|
56
50
|
}
|
|
57
51
|
}
|
package/src/app/app.ts
CHANGED
|
@@ -19,11 +19,14 @@
|
|
|
19
19
|
* are being destroyed; here they cannot.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import { createContainer } from '../di/index.ts'
|
|
22
|
+
import { createContainer, provide } from '../di/index.ts'
|
|
23
23
|
import type { Container, ContainerOptions, ModuleImport } from '../di/index.ts'
|
|
24
24
|
import { destroyAll, initializeAll } from '../di/lifecycle.ts'
|
|
25
25
|
import { buildRoutes } from '../http/index.ts'
|
|
26
|
-
import type { ControllerRoutesOptions } from '../http/index.ts'
|
|
26
|
+
import type { ControllerRoutesOptions, MappedController, Middleware } from '../http/index.ts'
|
|
27
|
+
import { errorResponse } from '../http/exceptions.ts'
|
|
28
|
+
import { applyMiddleware, resolveMiddleware } from '../http/middleware.ts'
|
|
29
|
+
import { ConsoleLogger, Logger, SilentLogger } from '../logger/index.ts'
|
|
27
30
|
import { compileRoutes } from '../routing/index.ts'
|
|
28
31
|
import type { CompiledRoutes, CompileOptions } from '../routing/index.ts'
|
|
29
32
|
import { release, takeOver } from './hot.ts'
|
|
@@ -31,7 +34,18 @@ import { release, takeOver } from './hot.ts'
|
|
|
31
34
|
/**
|
|
32
35
|
* Options of `createApp`.
|
|
33
36
|
*/
|
|
34
|
-
export interface AppOptions extends ContainerOptions, ControllerRoutesOptions, CompileOptions {
|
|
37
|
+
export interface AppOptions extends ContainerOptions, ControllerRoutesOptions, CompileOptions {
|
|
38
|
+
/**
|
|
39
|
+
* Middleware run around every request, before routing (404 and preflights
|
|
40
|
+
* included), first one outermost: `[cors(), requestId]`.
|
|
41
|
+
*/
|
|
42
|
+
readonly middleware?: readonly Middleware[]
|
|
43
|
+
/**
|
|
44
|
+
* Logger of the application, injectable everywhere as `inject(Logger)`.
|
|
45
|
+
* Defaults to a `ConsoleLogger`; `false` writes nothing.
|
|
46
|
+
*/
|
|
47
|
+
readonly logger?: Logger | false
|
|
48
|
+
}
|
|
35
49
|
|
|
36
50
|
/**
|
|
37
51
|
* Where to listen. `port: 0` picks a free port.
|
|
@@ -39,6 +53,11 @@ export interface AppOptions extends ContainerOptions, ControllerRoutesOptions, C
|
|
|
39
53
|
export interface ListenOptions {
|
|
40
54
|
readonly port?: number
|
|
41
55
|
readonly hostname?: string
|
|
56
|
+
/**
|
|
57
|
+
* Largest request body accepted, in bytes; bigger ones are refused before any
|
|
58
|
+
* handler runs. Bun's default is 128 MiB.
|
|
59
|
+
*/
|
|
60
|
+
readonly maxRequestBodySize?: number
|
|
42
61
|
}
|
|
43
62
|
|
|
44
63
|
/** Signals `enableShutdownSignals()` listens to by default. */
|
|
@@ -80,11 +99,13 @@ class Application implements App {
|
|
|
80
99
|
#closing: Promise<void> | undefined
|
|
81
100
|
readonly #enhancers: readonly unknown[]
|
|
82
101
|
readonly #signals = new Map<NodeJS.Signals, () => void>()
|
|
102
|
+
readonly #logger: Logger
|
|
83
103
|
|
|
84
|
-
constructor(container: Container, routes: CompiledRoutes, enhancers: readonly unknown[]) {
|
|
104
|
+
constructor(container: Container, routes: CompiledRoutes, enhancers: readonly unknown[], logger: Logger) {
|
|
85
105
|
this.container = container
|
|
86
106
|
this.routes = routes
|
|
87
107
|
this.#enhancers = enhancers
|
|
108
|
+
this.#logger = logger
|
|
88
109
|
}
|
|
89
110
|
|
|
90
111
|
get server(): Bun.Server<undefined> | undefined {
|
|
@@ -100,9 +121,9 @@ class Application implements App {
|
|
|
100
121
|
throw new Error(`The application already listens on ${this.#server.url.href}`)
|
|
101
122
|
}
|
|
102
123
|
|
|
103
|
-
const { port = 0, hostname } = typeof options === 'number' ? { port: options } : options
|
|
124
|
+
const { port = 0, hostname, maxRequestBodySize } = typeof options === 'number' ? { port: options } : options
|
|
104
125
|
|
|
105
|
-
const server = Bun.serve({ port, hostname, ...this.routes })
|
|
126
|
+
const server = Bun.serve({ port, hostname, maxRequestBodySize, ...this.routes })
|
|
106
127
|
|
|
107
128
|
this.#server = server
|
|
108
129
|
|
|
@@ -110,6 +131,8 @@ class Application implements App {
|
|
|
110
131
|
await takeOver(server.port, this)
|
|
111
132
|
}
|
|
112
133
|
|
|
134
|
+
this.#logger.log(`Listening on ${server.url.href}`)
|
|
135
|
+
|
|
113
136
|
return server
|
|
114
137
|
}
|
|
115
138
|
|
|
@@ -151,7 +174,7 @@ class Application implements App {
|
|
|
151
174
|
process.kill(process.pid, signal)
|
|
152
175
|
},
|
|
153
176
|
(error: unknown) => {
|
|
154
|
-
|
|
177
|
+
this.#logger.fatal('Shutdown failed', { error })
|
|
155
178
|
process.exit(1)
|
|
156
179
|
}
|
|
157
180
|
)
|
|
@@ -222,6 +245,24 @@ const destroyEverything = async (enhancers: readonly unknown[], container: Conta
|
|
|
222
245
|
}
|
|
223
246
|
}
|
|
224
247
|
|
|
248
|
+
/** What Nest logs at startup: the modules, then each controller and its routes. */
|
|
249
|
+
const logStartup = (logger: Logger, container: Container, controllers: readonly MappedController[]): void => {
|
|
250
|
+
const modules = logger.child('Container')
|
|
251
|
+
const routes = logger.child('Routes')
|
|
252
|
+
|
|
253
|
+
for (const module of container.modules) {
|
|
254
|
+
modules.log(`${module.name} dependencies initialized`)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
for (const { controller, path, routes: mapped } of controllers) {
|
|
258
|
+
routes.log(`${controller.name} {${path}}:`)
|
|
259
|
+
|
|
260
|
+
for (const route of mapped) {
|
|
261
|
+
routes.log(`Mapped {${route.path}, ${route.method}} route`)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
225
266
|
/**
|
|
226
267
|
* Creates the container and the routes, and runs every `onInit` hook.
|
|
227
268
|
*
|
|
@@ -230,16 +271,39 @@ const destroyEverything = async (enhancers: readonly unknown[], container: Conta
|
|
|
230
271
|
* error first, as `createContainer` does.
|
|
231
272
|
*/
|
|
232
273
|
export const createApp = async (root: ModuleImport, options: AppOptions = {}): Promise<App> => {
|
|
233
|
-
const
|
|
234
|
-
|
|
274
|
+
const logger = options.logger === false ? new SilentLogger() : (options.logger ?? new ConsoleLogger())
|
|
275
|
+
const own = logger.child('Soda')
|
|
276
|
+
const onError =
|
|
277
|
+
options.onError ??
|
|
278
|
+
((error: unknown, req: Request) =>
|
|
279
|
+
own.error('Unhandled error while handling a request', { error, method: req.method, url: req.url }))
|
|
235
280
|
|
|
236
|
-
|
|
237
|
-
|
|
281
|
+
own.log('Starting application')
|
|
282
|
+
|
|
283
|
+
const container = await createContainer(root, {
|
|
284
|
+
...options,
|
|
285
|
+
providers: [...(options.providers ?? []), provide(Logger, { useValue: logger })],
|
|
286
|
+
})
|
|
287
|
+
const enhancers: unknown[] = []
|
|
238
288
|
|
|
239
|
-
|
|
289
|
+
try {
|
|
290
|
+
// Middleware first: it wraps everything, so it is initialized first and destroyed last.
|
|
291
|
+
const middleware = resolveMiddleware(options.middleware ?? [], (fn) => container.runInContext(fn), (instance) => {
|
|
292
|
+
enhancers.push(instance)
|
|
293
|
+
})
|
|
294
|
+
const built = buildRoutes(container, { ...options, onError })
|
|
295
|
+
|
|
296
|
+
logStartup(logger, container, built.controllers)
|
|
297
|
+
enhancers.push(...built.enhancers)
|
|
240
298
|
await initializeAll(enhancers)
|
|
299
|
+
own.log('Application started')
|
|
300
|
+
|
|
301
|
+
const compiled = compileRoutes(built.routes, options)
|
|
302
|
+
const routes = applyMiddleware(middleware, compiled.routes, compiled.fetch, (error, req) =>
|
|
303
|
+
errorResponse(error, (unknown) => onError(unknown, req))
|
|
304
|
+
)
|
|
241
305
|
|
|
242
|
-
return new Application(container,
|
|
306
|
+
return new Application(container, routes, enhancers, own)
|
|
243
307
|
} catch (error) {
|
|
244
308
|
try {
|
|
245
309
|
await destroyEverything(enhancers, container)
|
package/src/di/container.ts
CHANGED
|
@@ -19,11 +19,13 @@
|
|
|
19
19
|
import { isThenable } from '../utils/promise.ts'
|
|
20
20
|
import { runInContext } from './context.ts'
|
|
21
21
|
import type { InjectionContext } from './context.ts'
|
|
22
|
-
import
|
|
22
|
+
import { Module } from './decorators.ts'
|
|
23
|
+
import type { DynamicModule, ModuleClass, ModuleImport } from './decorators.ts'
|
|
23
24
|
import { DiError } from './errors.ts'
|
|
24
25
|
import { buildGraph } from './graph.ts'
|
|
25
26
|
import type { ModuleGraph, ModuleNode, ProviderNode } from './graph.ts'
|
|
26
27
|
import { destroyAll, initializeAll } from './lifecycle.ts'
|
|
28
|
+
import { isProviderRecord } from './provider.ts'
|
|
27
29
|
import type { Provider, ProviderDefinition } from './provider.ts'
|
|
28
30
|
import { tokenName } from './token.ts'
|
|
29
31
|
import type { Constructor, Token } from './token.ts'
|
|
@@ -43,6 +45,25 @@ export interface ContainerOptions {
|
|
|
43
45
|
* ```
|
|
44
46
|
*/
|
|
45
47
|
readonly overrides?: readonly Provider[]
|
|
48
|
+
/**
|
|
49
|
+
* Providers visible in every module, as if exported by a global module:
|
|
50
|
+
* what the application itself provides, such as its `Logger`.
|
|
51
|
+
*/
|
|
52
|
+
readonly providers?: readonly Provider[]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Module holding `ContainerOptions.providers`. */
|
|
56
|
+
@Module({})
|
|
57
|
+
class ContainerProvidersModule {}
|
|
58
|
+
|
|
59
|
+
const providersModule = (providers: readonly Provider[]): DynamicModule => {
|
|
60
|
+
const exports: Token<unknown>[] = []
|
|
61
|
+
|
|
62
|
+
for (const provider of providers) {
|
|
63
|
+
exports.push(isProviderRecord(provider) ? provider.token : provider)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { module: ContainerProvidersModule, providers, exports, global: true }
|
|
46
67
|
}
|
|
47
68
|
|
|
48
69
|
/**
|
|
@@ -81,6 +102,8 @@ export interface Container {
|
|
|
81
102
|
runInContext<R>(fn: () => R, options?: ResolveOptions): R
|
|
82
103
|
/** Every controller, in module order. */
|
|
83
104
|
readonly controllers: readonly ControllerEntry[]
|
|
105
|
+
/** Every module, imports before importers. */
|
|
106
|
+
readonly modules: readonly ModuleClass[]
|
|
84
107
|
/**
|
|
85
108
|
* Calls `onDestroy` in reverse creation order, then releases every instance.
|
|
86
109
|
* Calling it again, even from an `onDestroy` hook, returns the same promise.
|
|
@@ -398,6 +421,18 @@ class StartedContainer implements Container {
|
|
|
398
421
|
this.controllers = controllers
|
|
399
422
|
}
|
|
400
423
|
|
|
424
|
+
get modules(): readonly ModuleClass[] {
|
|
425
|
+
const modules: ModuleClass[] = []
|
|
426
|
+
|
|
427
|
+
for (const node of this.#graph.modules) {
|
|
428
|
+
if (node.moduleClass !== ContainerProvidersModule) {
|
|
429
|
+
modules.push(node.moduleClass)
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return modules
|
|
434
|
+
}
|
|
435
|
+
|
|
401
436
|
get<T>(token: Token<T>, options?: ResolveOptions & { readonly optional?: false }): T
|
|
402
437
|
get<T>(token: Token<T>, options: ResolveOptions & { readonly optional: true }): T | undefined
|
|
403
438
|
get<T>(token: Token<T>, options: ResolveOptions & { readonly optional?: boolean } = {}): T | undefined {
|
|
@@ -524,7 +559,8 @@ const createAll = async (graph: ModuleGraph, resolver: Resolver): Promise<Contro
|
|
|
524
559
|
* hooks fail too, everything is rethrown as one `AggregateError`, startup error first.
|
|
525
560
|
*/
|
|
526
561
|
export const createContainer = async (root: ModuleImport, options: ContainerOptions = {}): Promise<Container> => {
|
|
527
|
-
const
|
|
562
|
+
const providers = options.providers ?? []
|
|
563
|
+
const graph = await buildGraph(root, options.overrides ?? [], providers.length > 0 ? [providersModule(providers)] : [])
|
|
528
564
|
const resolver = new Resolver(graph)
|
|
529
565
|
|
|
530
566
|
try {
|
package/src/di/graph.ts
CHANGED
|
@@ -139,7 +139,17 @@ class GraphBuilder {
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
/**
|
|
143
|
+
* `globals` are global modules added next to `root` (the container's own
|
|
144
|
+
* providers); they are created first, before anything that may inject them.
|
|
145
|
+
*/
|
|
146
|
+
async build(root: ModuleImport, globals: readonly ModuleImport[]): Promise<ModuleGraph> {
|
|
147
|
+
const globalNodes: ModuleNode[] = []
|
|
148
|
+
|
|
149
|
+
for (let i = 0, len = globals.length; i < len; i++) {
|
|
150
|
+
globalNodes.push(await this.#module(globals[i], 'createContainer()', i))
|
|
151
|
+
}
|
|
152
|
+
|
|
143
153
|
const rootNode = await this.#module(root, 'createContainer()', 0)
|
|
144
154
|
|
|
145
155
|
// The queue grows while modules are read: breadth-first over imports.
|
|
@@ -149,8 +159,8 @@ class GraphBuilder {
|
|
|
149
159
|
|
|
150
160
|
this.#assertReplacementsUsed()
|
|
151
161
|
|
|
152
|
-
const modules = orderModules(rootNode)
|
|
153
|
-
const
|
|
162
|
+
const modules = orderModules([...globalNodes, rootNode])
|
|
163
|
+
const globalModules: ModuleNode[] = []
|
|
154
164
|
const providers: ProviderNode[] = []
|
|
155
165
|
const controllers: ProviderNode[] = []
|
|
156
166
|
|
|
@@ -160,14 +170,14 @@ class GraphBuilder {
|
|
|
160
170
|
assertExportsProvided(node)
|
|
161
171
|
|
|
162
172
|
if (node.global) {
|
|
163
|
-
|
|
173
|
+
globalModules.push(node)
|
|
164
174
|
}
|
|
165
175
|
|
|
166
176
|
providers.push(...node.providers.values())
|
|
167
177
|
controllers.push(...node.controllers)
|
|
168
178
|
}
|
|
169
179
|
|
|
170
|
-
return { root: rootNode, modules, globals, providers, controllers }
|
|
180
|
+
return { root: rootNode, modules, globals: globalModules, providers, controllers }
|
|
171
181
|
}
|
|
172
182
|
|
|
173
183
|
/** Node for an `imports` entry, created on first sight. */
|
|
@@ -320,8 +330,8 @@ const assertExportsProvided = (node: ModuleNode): void => {
|
|
|
320
330
|
}
|
|
321
331
|
}
|
|
322
332
|
|
|
323
|
-
/** Modules reachable from `
|
|
324
|
-
const orderModules = (
|
|
333
|
+
/** Modules reachable from `roots`, each after the modules it imports. */
|
|
334
|
+
const orderModules = (roots: readonly ModuleNode[]): ModuleNode[] => {
|
|
325
335
|
const ordered: ModuleNode[] = []
|
|
326
336
|
const visited = new Set<ModuleNode>()
|
|
327
337
|
|
|
@@ -339,15 +349,20 @@ const orderModules = (root: ModuleNode): ModuleNode[] => {
|
|
|
339
349
|
ordered.push(node)
|
|
340
350
|
}
|
|
341
351
|
|
|
342
|
-
|
|
352
|
+
for (const root of roots) {
|
|
353
|
+
visit(root)
|
|
354
|
+
}
|
|
343
355
|
|
|
344
356
|
return ordered
|
|
345
357
|
}
|
|
346
358
|
|
|
347
359
|
/**
|
|
348
|
-
* Builds the module graph rooted at `root
|
|
360
|
+
* Builds the module graph rooted at `root`, with `globals` as extra global modules.
|
|
349
361
|
*
|
|
350
362
|
* `overrides` replace the provider of their token in every module providing it (for tests).
|
|
351
363
|
*/
|
|
352
|
-
export const buildGraph = (
|
|
353
|
-
|
|
364
|
+
export const buildGraph = (
|
|
365
|
+
root: ModuleImport,
|
|
366
|
+
overrides: readonly Provider[],
|
|
367
|
+
globals: readonly ModuleImport[] = []
|
|
368
|
+
): Promise<ModuleGraph> => new GraphBuilder(overrides).build(root, globals)
|