@c9up/ream 0.1.24 → 0.1.26
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/dist/ConfigLoader.d.ts +46 -5
- package/dist/ConfigLoader.d.ts.map +1 -1
- package/dist/ConfigLoader.js +60 -9
- package/dist/ConfigLoader.js.map +1 -1
- package/dist/HttpKernel.d.ts.map +1 -1
- package/dist/HttpKernel.js +46 -6
- package/dist/HttpKernel.js.map +1 -1
- package/dist/Ignitor.d.ts.map +1 -1
- package/dist/Ignitor.js +21 -3
- package/dist/Ignitor.js.map +1 -1
- package/dist/container/Container.d.ts +23 -3
- package/dist/container/Container.d.ts.map +1 -1
- package/dist/container/Container.js +139 -60
- package/dist/container/Container.js.map +1 -1
- package/dist/env/Env.d.ts +29 -4
- package/dist/env/Env.d.ts.map +1 -1
- package/dist/env/Env.js +27 -4
- package/dist/env/Env.js.map +1 -1
- package/dist/env/Secret.d.ts +11 -0
- package/dist/env/Secret.d.ts.map +1 -0
- package/dist/env/Secret.js +36 -0
- package/dist/env/Secret.js.map +1 -0
- package/dist/env/interpolate.d.ts +26 -0
- package/dist/env/interpolate.d.ts.map +1 -0
- package/dist/env/interpolate.js +48 -0
- package/dist/env/interpolate.js.map +1 -0
- package/dist/env/loadEnvFiles.d.ts.map +1 -1
- package/dist/env/loadEnvFiles.js +14 -4
- package/dist/env/loadEnvFiles.js.map +1 -1
- package/dist/env/schema.d.ts +23 -6
- package/dist/env/schema.d.ts.map +1 -1
- package/dist/env/schema.js +67 -33
- package/dist/env/schema.js.map +1 -1
- package/dist/events/Emitter.d.ts +1 -1
- package/dist/events/Emitter.d.ts.map +1 -1
- package/dist/events/Emitter.js +1 -1
- package/dist/events/Emitter.js.map +1 -1
- package/dist/events/EventsProvider.d.ts +2 -2
- package/dist/events/EventsProvider.d.ts.map +1 -1
- package/dist/events/EventsProvider.js +3 -3
- package/dist/events/EventsProvider.js.map +1 -1
- package/dist/graphql/GraphQLEngine.d.ts +1 -1
- package/dist/graphql/GraphQLEngine.d.ts.map +1 -1
- package/dist/graphql/GraphQLEngine.js +2 -2
- package/dist/graphql/GraphQLEngine.js.map +1 -1
- package/dist/graphql/GraphQLProvider.d.ts.map +1 -1
- package/dist/graphql/GraphQLProvider.js +7 -3
- package/dist/graphql/GraphQLProvider.js.map +1 -1
- package/dist/http/Exception.d.ts +63 -1
- package/dist/http/Exception.d.ts.map +1 -1
- package/dist/http/Exception.js +185 -48
- package/dist/http/Exception.js.map +1 -1
- package/dist/http/HttpContext.d.ts +56 -9
- package/dist/http/HttpContext.d.ts.map +1 -1
- package/dist/http/HttpContext.js +56 -15
- package/dist/http/HttpContext.js.map +1 -1
- package/dist/http/RedirectBuilder.d.ts.map +1 -1
- package/dist/http/RedirectBuilder.js +4 -1
- package/dist/http/RedirectBuilder.js.map +1 -1
- package/dist/http/Request.d.ts +141 -10
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +244 -34
- package/dist/http/Request.js.map +1 -1
- package/dist/http/Response.d.ts +176 -9
- package/dist/http/Response.d.ts.map +1 -1
- package/dist/http/Response.js +431 -19
- package/dist/http/Response.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/middleware/Pipeline.js +1 -1
- package/dist/middleware/Pipeline.js.map +1 -1
- package/dist/openapi/OpenApiProvider.js +4 -4
- package/dist/openapi/OpenApiProvider.js.map +1 -1
- package/dist/router/Router.d.ts +223 -31
- package/dist/router/Router.d.ts.map +1 -1
- package/dist/router/Router.js +520 -102
- package/dist/router/Router.js.map +1 -1
- package/dist/rpc/RpcProvider.d.ts +1 -0
- package/dist/rpc/RpcProvider.d.ts.map +1 -1
- package/dist/rpc/RpcProvider.js +7 -3
- package/dist/rpc/RpcProvider.js.map +1 -1
- package/dist/rpc/RpcRouter.d.ts +2 -2
- package/dist/rpc/RpcRouter.d.ts.map +1 -1
- package/dist/rpc/RpcRouter.js +5 -4
- package/dist/rpc/RpcRouter.js.map +1 -1
- package/dist/scheduler/ScheduleProvider.d.ts.map +1 -1
- package/dist/scheduler/ScheduleProvider.js +7 -15
- package/dist/scheduler/ScheduleProvider.js.map +1 -1
- package/dist/server/Server.d.ts +17 -4
- package/dist/server/Server.d.ts.map +1 -1
- package/dist/server/Server.js +41 -7
- package/dist/server/Server.js.map +1 -1
- package/dist/session/SessionMiddleware.js +3 -3
- package/dist/session/SessionMiddleware.js.map +1 -1
- package/dist/storage/StaticProvider.js +1 -1
- package/dist/storage/StaticProvider.js.map +1 -1
- package/dist/testing/RequestBuilder.d.ts +60 -0
- package/dist/testing/RequestBuilder.d.ts.map +1 -1
- package/dist/testing/RequestBuilder.js +180 -1
- package/dist/testing/RequestBuilder.js.map +1 -1
- package/dist/testing/TestClient.d.ts +22 -1
- package/dist/testing/TestClient.d.ts.map +1 -1
- package/dist/testing/TestClient.js +40 -0
- package/dist/testing/TestClient.js.map +1 -1
- package/dist/utils/Macroable.d.ts +30 -0
- package/dist/utils/Macroable.d.ts.map +1 -0
- package/dist/utils/Macroable.js +43 -0
- package/dist/utils/Macroable.js.map +1 -0
- package/dist/utils/inflect.d.ts +17 -0
- package/dist/utils/inflect.d.ts.map +1 -0
- package/dist/utils/inflect.js +32 -0
- package/dist/utils/inflect.js.map +1 -0
- package/dist/utils/objectPath.d.ts +57 -0
- package/dist/utils/objectPath.d.ts.map +1 -0
- package/dist/utils/objectPath.js +141 -0
- package/dist/utils/objectPath.js.map +1 -0
- package/events.darwin-arm64.node +0 -0
- package/events.darwin-x64.node +0 -0
- package/events.linux-arm64-gnu.node +0 -0
- package/events.linux-x64-gnu.node +0 -0
- package/events.win32-x64-msvc.node +0 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +6 -1
- package/scheduler.darwin-arm64.node +0 -0
- package/scheduler.darwin-x64.node +0 -0
- package/scheduler.linux-arm64-gnu.node +0 -0
- package/scheduler.linux-x64-gnu.node +0 -0
- package/scheduler.win32-x64-msvc.node +0 -0
- package/src/ConfigLoader.ts +72 -9
- package/src/HttpKernel.ts +53 -15
- package/src/Ignitor.ts +24 -3
- package/src/container/Container.ts +145 -61
- package/src/env/Env.ts +49 -5
- package/src/env/Secret.ts +41 -0
- package/src/env/interpolate.ts +54 -0
- package/src/env/loadEnvFiles.ts +12 -4
- package/src/env/schema.ts +109 -35
- package/src/events/Emitter.ts +2 -2
- package/src/events/EventsProvider.ts +5 -5
- package/src/graphql/GraphQLEngine.ts +4 -4
- package/src/graphql/GraphQLProvider.ts +7 -3
- package/src/http/Exception.ts +214 -37
- package/src/http/HttpContext.ts +93 -20
- package/src/http/RedirectBuilder.ts +4 -1
- package/src/http/Request.ts +264 -34
- package/src/http/Response.ts +504 -30
- package/src/index.ts +11 -2
- package/src/middleware/Pipeline.ts +1 -1
- package/src/openapi/OpenApiProvider.ts +4 -4
- package/src/router/Router.ts +631 -113
- package/src/rpc/RpcProvider.ts +6 -2
- package/src/rpc/RpcRouter.ts +7 -6
- package/src/scheduler/ScheduleProvider.ts +7 -15
- package/src/server/Server.ts +49 -10
- package/src/session/SessionMiddleware.ts +4 -4
- package/src/storage/StaticProvider.ts +1 -1
- package/src/testing/RequestBuilder.ts +218 -1
- package/src/testing/TestClient.ts +57 -1
- package/src/utils/Macroable.ts +54 -0
- package/src/utils/inflect.ts +29 -0
- package/src/utils/objectPath.ts +142 -0
- package/dist/decorators/Lazy.d.ts +0 -19
- package/dist/decorators/Lazy.d.ts.map +0 -1
- package/dist/decorators/Lazy.js +0 -53
- package/dist/decorators/Lazy.js.map +0 -1
- package/src/decorators/Lazy.ts +0 -57
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inflect.d.ts","sourceRoot":"","sources":["../../src/utils/inflect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,iFAAiF;AACjF,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED,wEAAwE;AACxE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM7C"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal zero-dependency string inflection — the two operations resourceful
|
|
3
|
+
* routing needs from AdonisJS's `@poppinss/utils/string`: `snakeCase` (route
|
|
4
|
+
* name normalisation) and `singular` (nested-resource param naming, `photos`
|
|
5
|
+
* → `photo_id`).
|
|
6
|
+
*
|
|
7
|
+
* Deviation from AdonisJS (named): `singular` covers regular English plurals
|
|
8
|
+
* only (`-ies→-y`, `-(s|x|z|ch|sh)es→drop es`, trailing `-s→drop`). Irregular
|
|
9
|
+
* plurals (`people→person`, `geese→goose`) are NOT handled — resource names in
|
|
10
|
+
* REST APIs are overwhelmingly regular, and a full inflector (irregular tables,
|
|
11
|
+
* uncountables) would be a dependency-sized rabbit hole for a niche case.
|
|
12
|
+
*/
|
|
13
|
+
/** Convert `camelCase`/`PascalCase`/`kebab-case`/spaced text to `snake_case`. */
|
|
14
|
+
export function snakeCase(value) {
|
|
15
|
+
return value
|
|
16
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
17
|
+
.replace(/[-\s]+/g, '_')
|
|
18
|
+
.toLowerCase();
|
|
19
|
+
}
|
|
20
|
+
/** Singularize a regular English plural (see module deviation note). */
|
|
21
|
+
export function singular(word) {
|
|
22
|
+
if (word.length > 3 && /ies$/i.test(word))
|
|
23
|
+
return word.replace(/ies$/i, 'y');
|
|
24
|
+
if (/(?:s|x|z|ch|sh)es$/i.test(word))
|
|
25
|
+
return word.replace(/es$/i, '');
|
|
26
|
+
if (/ss$/i.test(word))
|
|
27
|
+
return word;
|
|
28
|
+
if (/s$/i.test(word))
|
|
29
|
+
return word.replace(/s$/i, '');
|
|
30
|
+
return word;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=inflect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inflect.js","sourceRoot":"","sources":["../../src/utils/inflect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,iFAAiF;AACjF,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,KAAK;SACT,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC;SACtC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;SACvB,WAAW,EAAE,CAAA;AAClB,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;IAC5E,IAAI,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IACrE,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IAClC,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;IACpD,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-dependency dot-path object access — the small subset of `lodash`
|
|
3
|
+
* (`get`/`set`/`has`/`pick`/`omit`/`merge`) that Config and Request need for
|
|
4
|
+
* AdonisJS-parity nested access (`config.get('database.mysql.host')`,
|
|
5
|
+
* `request.input('user.address.city')`).
|
|
6
|
+
*
|
|
7
|
+
* AdonisJS uses `@poppinss/utils/lodash` for this. Ream stays lean and
|
|
8
|
+
* dependency-free (same stance as aurora's hand-written `cn`/`clsx`), so we
|
|
9
|
+
* reimplement exactly the four semantics we rely on — no more.
|
|
10
|
+
*
|
|
11
|
+
* Path syntax: `a.b.c` and `a[0].c` / `a.0.c` (bracket + dotted indices) are
|
|
12
|
+
* both accepted, matching lodash's tokenizer for the shapes config/request
|
|
13
|
+
* data actually take (plain JSON objects and arrays).
|
|
14
|
+
*/
|
|
15
|
+
/** A JSON-ish record we can index by string key. */
|
|
16
|
+
type UnknownRecord = Record<string, unknown>;
|
|
17
|
+
/**
|
|
18
|
+
* Read a nested value by dot-path. Returns `defaultValue` when any segment is
|
|
19
|
+
* missing or the resolved value is `undefined` (lodash.get semantics).
|
|
20
|
+
*/
|
|
21
|
+
export declare function getPath(source: unknown, path: string, defaultValue?: unknown): unknown;
|
|
22
|
+
/**
|
|
23
|
+
* True when the full dot-path exists on `source` (each segment is an own key),
|
|
24
|
+
* even if the leaf value is `undefined` (lodash.has semantics).
|
|
25
|
+
*/
|
|
26
|
+
export declare function hasPath(source: unknown, path: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Set a nested value by dot-path, creating intermediate plain objects as
|
|
29
|
+
* needed, and return the (mutated) root.
|
|
30
|
+
*
|
|
31
|
+
* Deviation from lodash.set (named): integer segments create plain objects,
|
|
32
|
+
* NOT arrays. Config trees and request-data picks are keyed objects; ream
|
|
33
|
+
* never sets array indices through a path, so array auto-vivification would be
|
|
34
|
+
* dead behaviour.
|
|
35
|
+
*/
|
|
36
|
+
export declare function setPath(target: UnknownRecord, path: string, value: unknown): UnknownRecord;
|
|
37
|
+
/**
|
|
38
|
+
* Return a new object containing only the given dot-paths (lodash.pick).
|
|
39
|
+
* Missing paths are skipped; nested paths reconstruct their branch, so
|
|
40
|
+
* `pickPaths({a:{b:1,c:2}}, ['a.b'])` yields `{a:{b:1}}`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function pickPaths(source: unknown, paths: string[]): UnknownRecord;
|
|
43
|
+
/**
|
|
44
|
+
* Return a deep clone of `source` with the given dot-paths removed
|
|
45
|
+
* (lodash.omit). Operates on JSON-ish data (body + query), so `structuredClone`
|
|
46
|
+
* is a safe deep copy that never mutates the caller's input.
|
|
47
|
+
*/
|
|
48
|
+
export declare function omitPaths(source: UnknownRecord, paths: string[]): UnknownRecord;
|
|
49
|
+
/**
|
|
50
|
+
* Deep-merge `source` over `target` (recursively for nested plain objects;
|
|
51
|
+
* scalars and arrays from `source` replace `target`). Returns a new object;
|
|
52
|
+
* neither input is mutated. Backs `Config.defaults` — the AdonisJS semantics
|
|
53
|
+
* where existing config wins over provided defaults.
|
|
54
|
+
*/
|
|
55
|
+
export declare function mergeDeep(target: unknown, source: unknown): unknown;
|
|
56
|
+
export {};
|
|
57
|
+
//# sourceMappingURL=objectPath.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"objectPath.d.ts","sourceRoot":"","sources":["../../src/utils/objectPath.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,oDAAoD;AACpD,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAwB5C;;;GAGG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,CAQtF;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAQ9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,aAAa,CAgB1F;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,aAAa,CAMzE;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,aAAa,CAY/E;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAQnE"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-dependency dot-path object access — the small subset of `lodash`
|
|
3
|
+
* (`get`/`set`/`has`/`pick`/`omit`/`merge`) that Config and Request need for
|
|
4
|
+
* AdonisJS-parity nested access (`config.get('database.mysql.host')`,
|
|
5
|
+
* `request.input('user.address.city')`).
|
|
6
|
+
*
|
|
7
|
+
* AdonisJS uses `@poppinss/utils/lodash` for this. Ream stays lean and
|
|
8
|
+
* dependency-free (same stance as aurora's hand-written `cn`/`clsx`), so we
|
|
9
|
+
* reimplement exactly the four semantics we rely on — no more.
|
|
10
|
+
*
|
|
11
|
+
* Path syntax: `a.b.c` and `a[0].c` / `a.0.c` (bracket + dotted indices) are
|
|
12
|
+
* both accepted, matching lodash's tokenizer for the shapes config/request
|
|
13
|
+
* data actually take (plain JSON objects and arrays).
|
|
14
|
+
*/
|
|
15
|
+
/** True for plain objects and arrays (anything indexable by a path segment). */
|
|
16
|
+
function isIndexable(value) {
|
|
17
|
+
return typeof value === 'object' && value !== null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Split a dot/bracket path into segments: `a.b[0].c` → `['a','b','0','c']`.
|
|
21
|
+
* A single already-simple key (no `.`/`[`) is returned as-is, so keys that
|
|
22
|
+
* happen to contain neither delimiter cost nothing.
|
|
23
|
+
*/
|
|
24
|
+
function toSegments(path) {
|
|
25
|
+
if (path.indexOf('.') === -1 && path.indexOf('[') === -1)
|
|
26
|
+
return [path];
|
|
27
|
+
const segments = [];
|
|
28
|
+
const matcher = /[^.[\]]+/g;
|
|
29
|
+
let match = matcher.exec(path);
|
|
30
|
+
while (match !== null) {
|
|
31
|
+
segments.push(match[0]);
|
|
32
|
+
match = matcher.exec(path);
|
|
33
|
+
}
|
|
34
|
+
return segments;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Read a nested value by dot-path. Returns `defaultValue` when any segment is
|
|
38
|
+
* missing or the resolved value is `undefined` (lodash.get semantics).
|
|
39
|
+
*/
|
|
40
|
+
export function getPath(source, path, defaultValue) {
|
|
41
|
+
const segments = toSegments(path);
|
|
42
|
+
let current = source;
|
|
43
|
+
for (const segment of segments) {
|
|
44
|
+
if (!isIndexable(current))
|
|
45
|
+
return defaultValue;
|
|
46
|
+
current = current[segment];
|
|
47
|
+
}
|
|
48
|
+
return current === undefined ? defaultValue : current;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* True when the full dot-path exists on `source` (each segment is an own key),
|
|
52
|
+
* even if the leaf value is `undefined` (lodash.has semantics).
|
|
53
|
+
*/
|
|
54
|
+
export function hasPath(source, path) {
|
|
55
|
+
const segments = toSegments(path);
|
|
56
|
+
let current = source;
|
|
57
|
+
for (const segment of segments) {
|
|
58
|
+
if (!isIndexable(current) || !Object.hasOwn(current, segment))
|
|
59
|
+
return false;
|
|
60
|
+
current = current[segment];
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Set a nested value by dot-path, creating intermediate plain objects as
|
|
66
|
+
* needed, and return the (mutated) root.
|
|
67
|
+
*
|
|
68
|
+
* Deviation from lodash.set (named): integer segments create plain objects,
|
|
69
|
+
* NOT arrays. Config trees and request-data picks are keyed objects; ream
|
|
70
|
+
* never sets array indices through a path, so array auto-vivification would be
|
|
71
|
+
* dead behaviour.
|
|
72
|
+
*/
|
|
73
|
+
export function setPath(target, path, value) {
|
|
74
|
+
const segments = toSegments(path);
|
|
75
|
+
let current = target;
|
|
76
|
+
for (let i = 0; i < segments.length - 1; i += 1) {
|
|
77
|
+
const key = segments[i];
|
|
78
|
+
const next = current[key];
|
|
79
|
+
if (!isIndexable(next)) {
|
|
80
|
+
const created = {};
|
|
81
|
+
current[key] = created;
|
|
82
|
+
current = created;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
current = next;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
current[segments[segments.length - 1]] = value;
|
|
89
|
+
return target;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Return a new object containing only the given dot-paths (lodash.pick).
|
|
93
|
+
* Missing paths are skipped; nested paths reconstruct their branch, so
|
|
94
|
+
* `pickPaths({a:{b:1,c:2}}, ['a.b'])` yields `{a:{b:1}}`.
|
|
95
|
+
*/
|
|
96
|
+
export function pickPaths(source, paths) {
|
|
97
|
+
const result = {};
|
|
98
|
+
for (const path of paths) {
|
|
99
|
+
if (hasPath(source, path))
|
|
100
|
+
setPath(result, path, getPath(source, path));
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Return a deep clone of `source` with the given dot-paths removed
|
|
106
|
+
* (lodash.omit). Operates on JSON-ish data (body + query), so `structuredClone`
|
|
107
|
+
* is a safe deep copy that never mutates the caller's input.
|
|
108
|
+
*/
|
|
109
|
+
export function omitPaths(source, paths) {
|
|
110
|
+
const result = structuredClone(source);
|
|
111
|
+
for (const path of paths) {
|
|
112
|
+
const segments = toSegments(path);
|
|
113
|
+
let current = result;
|
|
114
|
+
for (let i = 0; i < segments.length - 1; i += 1) {
|
|
115
|
+
if (!isIndexable(current))
|
|
116
|
+
break;
|
|
117
|
+
current = current[segments[i]];
|
|
118
|
+
}
|
|
119
|
+
if (isIndexable(current))
|
|
120
|
+
delete current[segments[segments.length - 1]];
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Deep-merge `source` over `target` (recursively for nested plain objects;
|
|
126
|
+
* scalars and arrays from `source` replace `target`). Returns a new object;
|
|
127
|
+
* neither input is mutated. Backs `Config.defaults` — the AdonisJS semantics
|
|
128
|
+
* where existing config wins over provided defaults.
|
|
129
|
+
*/
|
|
130
|
+
export function mergeDeep(target, source) {
|
|
131
|
+
if (!isIndexable(target) || Array.isArray(target))
|
|
132
|
+
return source === undefined ? target : source;
|
|
133
|
+
if (!isIndexable(source) || Array.isArray(source))
|
|
134
|
+
return source === undefined ? target : source;
|
|
135
|
+
const out = { ...target };
|
|
136
|
+
for (const key of Object.keys(source)) {
|
|
137
|
+
out[key] = mergeDeep(out[key], source[key]);
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=objectPath.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"objectPath.js","sourceRoot":"","sources":["../../src/utils/objectPath.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,gFAAgF;AAChF,SAAS,WAAW,CAAC,KAAc;IACjC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAA;AACpD,CAAC;AAED;;;;GAIG;AACH,SAAS,UAAU,CAAC,IAAY;IAC9B,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,CAAA;IACvE,MAAM,QAAQ,GAAa,EAAE,CAAA;IAC7B,MAAM,OAAO,GAAG,WAAW,CAAA;IAC3B,IAAI,KAAK,GAA2B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACtD,OAAO,KAAK,KAAK,IAAI,EAAE,CAAC;QACtB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;QACvB,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC5B,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,MAAe,EAAE,IAAY,EAAE,YAAsB;IAC3E,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IACjC,IAAI,OAAO,GAAY,MAAM,CAAA;IAC7B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC;YAAE,OAAO,YAAY,CAAA;QAC9C,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5B,CAAC;IACD,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAA;AACvD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,MAAe,EAAE,IAAY;IACnD,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IACjC,IAAI,OAAO,GAAY,MAAM,CAAA;IAC7B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC;YAAE,OAAO,KAAK,CAAA;QAC3E,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5B,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,OAAO,CAAC,MAAqB,EAAE,IAAY,EAAE,KAAc;IACzE,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IACjC,IAAI,OAAO,GAAkB,MAAM,CAAA;IACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;QACvB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;QACzB,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,MAAM,OAAO,GAAkB,EAAE,CAAA;YACjC,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAA;YACtB,OAAO,GAAG,OAAO,CAAA;QACnB,CAAC;aAAM,CAAC;YACN,OAAO,GAAG,IAAI,CAAA;QAChB,CAAC;IACH,CAAC;IACD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAA;IAC9C,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,MAAe,EAAE,KAAe;IACxD,MAAM,MAAM,GAAkB,EAAE,CAAA;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC;YAAE,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAA;IACzE,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,MAAqB,EAAE,KAAe;IAC9D,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,CAAA;IACtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,OAAO,GAAY,MAAM,CAAA;QAC7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC;gBAAE,MAAK;YAChC,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;QAChC,CAAC;QACD,IAAI,WAAW,CAAC,OAAO,CAAC;YAAE,OAAO,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAA;IACzE,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,MAAe,EAAE,MAAe;IACxD,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;IAChG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;IAChG,MAAM,GAAG,GAAkB,EAAE,GAAG,MAAM,EAAE,CAAA;IACxC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,GAAG,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;IAC7C,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
|
package/events.darwin-arm64.node
CHANGED
|
Binary file
|
package/events.darwin-x64.node
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/index.darwin-arm64.node
CHANGED
|
Binary file
|
package/index.darwin-x64.node
CHANGED
|
Binary file
|
|
Binary file
|
package/index.linux-x64-gnu.node
CHANGED
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c9up/ream",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
4
4
|
"description": "Ream — Rust-powered Node.js application framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -127,7 +127,9 @@
|
|
|
127
127
|
"tsconfig.app.json"
|
|
128
128
|
],
|
|
129
129
|
"dependencies": {
|
|
130
|
+
"etag": "^1.8.1",
|
|
130
131
|
"file-type": "^22.0.1",
|
|
132
|
+
"mime-types": "^3.0.1",
|
|
131
133
|
"reflect-metadata": "^0.2"
|
|
132
134
|
},
|
|
133
135
|
"peerDependencies": {
|
|
@@ -141,6 +143,8 @@
|
|
|
141
143
|
"devDependencies": {
|
|
142
144
|
"@biomejs/biome": "^2.4.10",
|
|
143
145
|
"@c9up/comet": "^0.1.0",
|
|
146
|
+
"@types/etag": "^1.8.4",
|
|
147
|
+
"@types/mime-types": "^3.0.1",
|
|
144
148
|
"@types/node": "^22.19.15",
|
|
145
149
|
"typescript": "^6.0.2",
|
|
146
150
|
"vitest": "^4.1.6"
|
|
@@ -156,6 +160,7 @@
|
|
|
156
160
|
"url": "git+https://github.com/C9up/ream.git"
|
|
157
161
|
},
|
|
158
162
|
"scripts": {
|
|
163
|
+
"prebuild": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
159
164
|
"build": "tsc -p tsconfig.build.json",
|
|
160
165
|
"build:rust": "cargo build --release -p ream-http-napi -p ream-scheduler-napi -p ream-events-napi",
|
|
161
166
|
"build:napi": "pnpm build:rust && node scripts/copy-napi.mjs",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/ConfigLoader.ts
CHANGED
|
@@ -4,30 +4,64 @@
|
|
|
4
4
|
* @implements FR18, FR19
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { getPath, hasPath, mergeDeep, setPath } from './utils/objectPath.js'
|
|
8
|
+
|
|
7
9
|
/**
|
|
8
10
|
* In-memory config store. In production this is populated from config/*.ts
|
|
9
|
-
* files via `defineConfig()
|
|
10
|
-
* is
|
|
11
|
+
* files via `defineConfig()` — one top-level key per module (`config/app.ts`
|
|
12
|
+
* → `app`). The class is its own contract — `AppContext.config` is typed
|
|
13
|
+
* against it directly (structural).
|
|
14
|
+
*
|
|
15
|
+
* Values are held as a nested tree and read with AdonisJS dot-notation
|
|
16
|
+
* (`config.get('database.mysql.host')`), matching `@adonisjs/config`'s
|
|
17
|
+
* `lodash`-backed accessors — reimplemented dependency-free via
|
|
18
|
+
* {@link ../utils/objectPath}.
|
|
11
19
|
*/
|
|
12
20
|
export class ConfigStore {
|
|
13
|
-
|
|
21
|
+
#tree: Record<string, unknown> = {}
|
|
14
22
|
|
|
15
|
-
|
|
16
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Read a value by dot-notation key, returning `defaultValue` when the path
|
|
25
|
+
* is absent or resolves to `undefined` (AdonisJS `config.get`).
|
|
26
|
+
*/
|
|
27
|
+
get<T = unknown>(key: string, defaultValue?: T): T | undefined {
|
|
28
|
+
// Untyped store: the caller declares the shape it configured (same
|
|
29
|
+
// contract as `@adonisjs/config`, whose `get<T>` returns the loose value).
|
|
30
|
+
return getPath(this.#tree, key, defaultValue) as T | undefined
|
|
17
31
|
}
|
|
18
32
|
|
|
33
|
+
/** Set a value by dot-notation key, creating intermediate objects. */
|
|
19
34
|
set(key: string, value: unknown): void {
|
|
20
|
-
this
|
|
35
|
+
setPath(this.#tree, key, value)
|
|
21
36
|
}
|
|
22
37
|
|
|
38
|
+
/** True when the dot-notation key exists (AdonisJS `config.has`). */
|
|
23
39
|
has(key: string): boolean {
|
|
24
|
-
return this
|
|
40
|
+
return hasPath(this.#tree, key)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The full config tree (AdonisJS `config.all`). */
|
|
44
|
+
all(): Record<string, unknown> {
|
|
45
|
+
return this.#tree
|
|
25
46
|
}
|
|
26
47
|
|
|
27
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Merge `value` as defaults for `key` — existing config wins over the
|
|
50
|
+
* provided defaults (AdonisJS `config.defaults`).
|
|
51
|
+
*/
|
|
52
|
+
defaults(key: string, value: unknown): void {
|
|
53
|
+
const existing = this.get(key)
|
|
54
|
+
this.set(key, existing === undefined ? value : mergeDeep(value, existing))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Load config from an object (used for testing and initial setup). Each key
|
|
59
|
+
* is applied by dot-notation, so both nested modules (`{ app: {...} }`) and
|
|
60
|
+
* flat dotted keys (`{ 'db.host': '...' }`) land in the tree.
|
|
61
|
+
*/
|
|
28
62
|
loadFromObject(config: Record<string, unknown>): void {
|
|
29
63
|
for (const [key, value] of Object.entries(config)) {
|
|
30
|
-
this.
|
|
64
|
+
this.set(key, value)
|
|
31
65
|
}
|
|
32
66
|
}
|
|
33
67
|
}
|
|
@@ -47,3 +81,32 @@ export function env(key: string, defaultValue?: string): string | undefined {
|
|
|
47
81
|
export function defineModuleConfig<T>(config: T): T {
|
|
48
82
|
return config
|
|
49
83
|
}
|
|
84
|
+
|
|
85
|
+
const CONFIG_PROVIDER = Symbol('ream.configProvider')
|
|
86
|
+
|
|
87
|
+
/** A deferred config value that resolves against the application at boot. */
|
|
88
|
+
interface ConfigProviderMarker<T> {
|
|
89
|
+
readonly [CONFIG_PROVIDER]: (app: unknown) => T | Promise<T>
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isConfigProvider(value: unknown): value is ConfigProviderMarker<unknown> {
|
|
93
|
+
return typeof value === 'object' && value !== null && CONFIG_PROVIDER in value
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Deferred configuration (AdonisJS `configProvider`). A package exports
|
|
98
|
+
* `configProvider.create((app) => ...)` when its config needs the application
|
|
99
|
+
* (env, paths) at boot; the framework calls `configProvider.resolve(app, value)`
|
|
100
|
+
* to materialise it, passing plain values through untouched.
|
|
101
|
+
*/
|
|
102
|
+
export const configProvider = {
|
|
103
|
+
/** Wrap a resolver so it's materialised later by {@link resolve}. */
|
|
104
|
+
create<T>(resolver: (app: unknown) => T | Promise<T>): ConfigProviderMarker<T> {
|
|
105
|
+
return { [CONFIG_PROVIDER]: resolver }
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
/** Resolve a value: run it if it's a provider, otherwise return it as-is. */
|
|
109
|
+
async resolve<T>(app: unknown, value: ConfigProviderMarker<T> | T): Promise<T> {
|
|
110
|
+
return isConfigProvider(value) ? value[CONFIG_PROVIDER](app) : value
|
|
111
|
+
},
|
|
112
|
+
}
|
package/src/HttpKernel.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import type { Container } from './container/Container.js'
|
|
14
14
|
import type { Emitter } from './events/Emitter.js'
|
|
15
15
|
import { E_ROUTE_NOT_FOUND, ExceptionHandler } from './http/Exception.js'
|
|
16
|
-
import { HttpContext } from './http/HttpContext.js'
|
|
16
|
+
import { type ChildLoggerSource, HttpContext } from './http/HttpContext.js'
|
|
17
17
|
import type {
|
|
18
18
|
MiddlewareFunction,
|
|
19
19
|
MiddlewareRegistry,
|
|
@@ -21,6 +21,8 @@ import type {
|
|
|
21
21
|
} from './middleware/Pipeline.js'
|
|
22
22
|
import { compose } from './middleware/Pipeline.js'
|
|
23
23
|
import type { RouteDefinition, Router } from './router/Router.js'
|
|
24
|
+
import type { CookieSigner } from './security/CookieSigner.js'
|
|
25
|
+
import type { SignedUrl } from './security/SignedUrl.js'
|
|
24
26
|
import type { Dict } from './types/helpers.js'
|
|
25
27
|
|
|
26
28
|
export interface HttpKernelConfig {
|
|
@@ -91,16 +93,33 @@ export function createHttpKernel(
|
|
|
91
93
|
// creation, and `has()` avoids loading the native bus for apps without events.
|
|
92
94
|
let eventsEmitter: Emitter | undefined
|
|
93
95
|
let eventsResolved = false
|
|
94
|
-
const resolveEvents = (): Emitter | undefined => {
|
|
96
|
+
const resolveEvents = async (): Promise<Emitter | undefined> => {
|
|
95
97
|
if (!eventsResolved) {
|
|
96
98
|
eventsResolved = true
|
|
97
99
|
if (config.container?.has('events')) {
|
|
98
|
-
eventsEmitter = config.container.resolve<Emitter>('events')
|
|
100
|
+
eventsEmitter = await config.container.resolve<Emitter>('events')
|
|
99
101
|
}
|
|
100
102
|
}
|
|
101
103
|
return eventsEmitter
|
|
102
104
|
}
|
|
103
105
|
|
|
106
|
+
// APP_KEY-backed services + base logger, resolved once (the container is async
|
|
107
|
+
// now, so they can't be resolved in the HttpContext constructor) and injected
|
|
108
|
+
// per request via setters. Best-effort: absent bindings leave the feature off.
|
|
109
|
+
let servicesResolved = false
|
|
110
|
+
let encryption: CookieSigner | undefined
|
|
111
|
+
let signedUrl: SignedUrl | undefined
|
|
112
|
+
let baseLogger: ChildLoggerSource | undefined
|
|
113
|
+
const resolveServices = async (): Promise<void> => {
|
|
114
|
+
if (servicesResolved) return
|
|
115
|
+
servicesResolved = true
|
|
116
|
+
const c = config.container
|
|
117
|
+
if (!c) return
|
|
118
|
+
if (c.has('encryption')) encryption = await c.resolve<CookieSigner>('encryption')
|
|
119
|
+
if (c.has('signedUrl')) signedUrl = await c.resolve<SignedUrl>('signedUrl')
|
|
120
|
+
if (c.has('logger')) baseLogger = await c.resolve<ChildLoggerSource>('logger')
|
|
121
|
+
}
|
|
122
|
+
|
|
104
123
|
return async (reqData: HttpKernelRequest): Promise<HttpKernelResponse> => {
|
|
105
124
|
// The Rust HyperServer hands the request as a typed JS object via napi
|
|
106
125
|
// `to_js_value` (no JSON intermediate). Serverless adapters that pass a
|
|
@@ -145,7 +164,12 @@ export function createHttpKernel(
|
|
|
145
164
|
config.container,
|
|
146
165
|
)
|
|
147
166
|
ctx.setRouteUrlResolver((name, params) => config.router.urlFor(name, params))
|
|
148
|
-
|
|
167
|
+
// Inject the async-resolved APP_KEY services + base logger (resolved once).
|
|
168
|
+
await resolveServices()
|
|
169
|
+
if (encryption) ctx.setCookieSigner(encryption)
|
|
170
|
+
if (signedUrl) ctx.setSignedUrl(signedUrl)
|
|
171
|
+
if (baseLogger) ctx.setBaseLogger(baseLogger)
|
|
172
|
+
ctx.events = await resolveEvents()
|
|
149
173
|
// Core lifecycle event: a request entered the kernel. Fire-and-forget through
|
|
150
174
|
// the bus when events are wired (`?.` → zero cost when no provider). The
|
|
151
175
|
// `http:response` counterpart fires before every exit below.
|
|
@@ -174,6 +198,18 @@ export function createHttpKernel(
|
|
|
174
198
|
let cached = pipelineCache.get(match.route)
|
|
175
199
|
|
|
176
200
|
if (!cached) {
|
|
201
|
+
// Promote a lazy/string controller to a concrete controller tuple on
|
|
202
|
+
// first request (imported once, then reused). Done before handler +
|
|
203
|
+
// guard-metadata resolution so @Guard/@Role/@inject flow through the
|
|
204
|
+
// normal controller path below.
|
|
205
|
+
if (match.route.lazyController && !match.route.controller) {
|
|
206
|
+
const mod = await match.route.lazyController.loader()
|
|
207
|
+
match.route.controller = {
|
|
208
|
+
target: mod.default,
|
|
209
|
+
method: match.route.lazyController.method,
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
177
213
|
// Resolve handler (once per route)
|
|
178
214
|
let routeHandler: (typeof match.route)['handler']
|
|
179
215
|
if (match.route.controller) {
|
|
@@ -205,16 +241,18 @@ export function createHttpKernel(
|
|
|
205
241
|
// A declared validator that isn't registered is a HARD error — never
|
|
206
242
|
// silently skip validation, that's a security footgun (a typo'd
|
|
207
243
|
// `.validate('craeteUser')` must fail loudly, not pass unvalidated).
|
|
208
|
-
const validators: RuntimeValidator[] =
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
`
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
244
|
+
const validators: RuntimeValidator[] = await Promise.all(
|
|
245
|
+
match.route.validators.map((name) => {
|
|
246
|
+
const token = `validator:${name}`
|
|
247
|
+
if (!config.container?.has(token)) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
`[E_VALIDATOR_NOT_FOUND] Route validator '${name}' is not registered. ` +
|
|
250
|
+
`Bind it with container.singleton('${token}', () => schema({ ... })).`,
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
return config.container.resolve<RuntimeValidator>(token)
|
|
254
|
+
}),
|
|
255
|
+
)
|
|
218
256
|
|
|
219
257
|
// Compile pipeline once
|
|
220
258
|
const chain = config.middleware.buildChain(
|
|
@@ -307,7 +345,7 @@ function createControllerHandler(
|
|
|
307
345
|
container?: Container,
|
|
308
346
|
): (ctx: HttpContext) => Promise<void> {
|
|
309
347
|
return async (ctx: HttpContext) => {
|
|
310
|
-
const instance = container ? container.make(controller.target) : new controller.target()
|
|
348
|
+
const instance = container ? await container.make(controller.target) : new controller.target()
|
|
311
349
|
const method = (instance as Record<string, (ctx: HttpContext) => Promise<void> | void>)[
|
|
312
350
|
controller.method
|
|
313
351
|
]
|
package/src/Ignitor.ts
CHANGED
|
@@ -30,6 +30,8 @@ import { MiddlewareRegistry } from './middleware/Pipeline.js'
|
|
|
30
30
|
import type { AppContext, ProviderContract } from './Provider.js'
|
|
31
31
|
import { callProviderPhase } from './Provider.js'
|
|
32
32
|
import { Router } from './router/Router.js'
|
|
33
|
+
import { CookieSigner } from './security/CookieSigner.js'
|
|
34
|
+
import { SignedUrl } from './security/SignedUrl.js'
|
|
33
35
|
import { Server } from './server/Server.js'
|
|
34
36
|
import { clearApp, setApp } from './services/app.js'
|
|
35
37
|
import { clearRouter, setRouter } from './services/router.js'
|
|
@@ -179,6 +181,26 @@ export class Ignitor {
|
|
|
179
181
|
this.app.setAppRoot(root)
|
|
180
182
|
}
|
|
181
183
|
|
|
184
|
+
// Encryption / cookie-signing service (AdonisJS `APP_KEY` idiom). Registered
|
|
185
|
+
// only when APP_KEY is set; HttpContext hands it to Response/Request so
|
|
186
|
+
// `cookie()` can sign, `encryptedCookie()` can encrypt and `request.cookie()`
|
|
187
|
+
// can verify. Without APP_KEY, cookies stay plain (unsigned).
|
|
188
|
+
const appKey = process.env.APP_KEY
|
|
189
|
+
if (appKey) {
|
|
190
|
+
if (appKey.length < 16) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
'APP_KEY is too short — use at least a 16-character (ideally 32-byte) random key for cookie signing/encryption.',
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
const signer = new CookieSigner(appKey)
|
|
196
|
+
this.app.container.singleton('encryption', () => signer)
|
|
197
|
+
// Signed-URL helper (same APP_KEY): the router signs via makeSignedUrl,
|
|
198
|
+
// HttpContext hands it to the request so hasValidSignature() can verify.
|
|
199
|
+
const signedUrl = new SignedUrl({ secret: appKey })
|
|
200
|
+
this.app.container.singleton('signedUrl', () => signedUrl)
|
|
201
|
+
this.router.setSignedUrl(signedUrl)
|
|
202
|
+
}
|
|
203
|
+
|
|
182
204
|
// Set service singletons so route/kernel files can import them
|
|
183
205
|
setApp(this.app)
|
|
184
206
|
setRouter(this.router)
|
|
@@ -495,9 +517,8 @@ export class Ignitor {
|
|
|
495
517
|
// Core domain event: the app finished booting (all providers ready).
|
|
496
518
|
// Emitted once through the bus when events are wired — zero hot-path cost.
|
|
497
519
|
if (this.app.container.has('events')) {
|
|
498
|
-
this.app.container
|
|
499
|
-
|
|
500
|
-
.emit('app:ready', { environment: this.environment })
|
|
520
|
+
const bus = await this.app.container.resolve<Emitter>('events')
|
|
521
|
+
bus.emit('app:ready', { environment: this.environment })
|
|
501
522
|
}
|
|
502
523
|
|
|
503
524
|
// Dev-mode change watcher. IMPORTANT: this does NOT attempt an in-process
|