@c9up/ream 0.1.25 → 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.
Files changed (172) hide show
  1. package/dist/ConfigLoader.d.ts +46 -5
  2. package/dist/ConfigLoader.d.ts.map +1 -1
  3. package/dist/ConfigLoader.js +60 -9
  4. package/dist/ConfigLoader.js.map +1 -1
  5. package/dist/HttpKernel.d.ts.map +1 -1
  6. package/dist/HttpKernel.js +46 -6
  7. package/dist/HttpKernel.js.map +1 -1
  8. package/dist/Ignitor.d.ts.map +1 -1
  9. package/dist/Ignitor.js +21 -3
  10. package/dist/Ignitor.js.map +1 -1
  11. package/dist/container/Container.d.ts +23 -3
  12. package/dist/container/Container.d.ts.map +1 -1
  13. package/dist/container/Container.js +139 -60
  14. package/dist/container/Container.js.map +1 -1
  15. package/dist/env/Env.d.ts +29 -4
  16. package/dist/env/Env.d.ts.map +1 -1
  17. package/dist/env/Env.js +27 -4
  18. package/dist/env/Env.js.map +1 -1
  19. package/dist/env/Secret.d.ts +11 -0
  20. package/dist/env/Secret.d.ts.map +1 -0
  21. package/dist/env/Secret.js +36 -0
  22. package/dist/env/Secret.js.map +1 -0
  23. package/dist/env/interpolate.d.ts +26 -0
  24. package/dist/env/interpolate.d.ts.map +1 -0
  25. package/dist/env/interpolate.js +48 -0
  26. package/dist/env/interpolate.js.map +1 -0
  27. package/dist/env/loadEnvFiles.d.ts.map +1 -1
  28. package/dist/env/loadEnvFiles.js +14 -4
  29. package/dist/env/loadEnvFiles.js.map +1 -1
  30. package/dist/env/schema.d.ts +23 -6
  31. package/dist/env/schema.d.ts.map +1 -1
  32. package/dist/env/schema.js +67 -33
  33. package/dist/env/schema.js.map +1 -1
  34. package/dist/events/Emitter.d.ts +1 -1
  35. package/dist/events/Emitter.d.ts.map +1 -1
  36. package/dist/events/Emitter.js +1 -1
  37. package/dist/events/Emitter.js.map +1 -1
  38. package/dist/events/EventsProvider.d.ts +2 -2
  39. package/dist/events/EventsProvider.d.ts.map +1 -1
  40. package/dist/events/EventsProvider.js +3 -3
  41. package/dist/events/EventsProvider.js.map +1 -1
  42. package/dist/graphql/GraphQLEngine.d.ts +1 -1
  43. package/dist/graphql/GraphQLEngine.d.ts.map +1 -1
  44. package/dist/graphql/GraphQLEngine.js +2 -2
  45. package/dist/graphql/GraphQLEngine.js.map +1 -1
  46. package/dist/graphql/GraphQLProvider.d.ts.map +1 -1
  47. package/dist/graphql/GraphQLProvider.js +7 -3
  48. package/dist/graphql/GraphQLProvider.js.map +1 -1
  49. package/dist/http/Exception.d.ts +63 -1
  50. package/dist/http/Exception.d.ts.map +1 -1
  51. package/dist/http/Exception.js +185 -48
  52. package/dist/http/Exception.js.map +1 -1
  53. package/dist/http/HttpContext.d.ts +53 -7
  54. package/dist/http/HttpContext.d.ts.map +1 -1
  55. package/dist/http/HttpContext.js +56 -15
  56. package/dist/http/HttpContext.js.map +1 -1
  57. package/dist/http/RedirectBuilder.d.ts.map +1 -1
  58. package/dist/http/RedirectBuilder.js +4 -1
  59. package/dist/http/RedirectBuilder.js.map +1 -1
  60. package/dist/http/Request.d.ts +141 -10
  61. package/dist/http/Request.d.ts.map +1 -1
  62. package/dist/http/Request.js +244 -34
  63. package/dist/http/Request.js.map +1 -1
  64. package/dist/http/Response.d.ts +176 -9
  65. package/dist/http/Response.d.ts.map +1 -1
  66. package/dist/http/Response.js +404 -16
  67. package/dist/http/Response.js.map +1 -1
  68. package/dist/index.d.ts +5 -4
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +4 -3
  71. package/dist/index.js.map +1 -1
  72. package/dist/middleware/Pipeline.js +1 -1
  73. package/dist/middleware/Pipeline.js.map +1 -1
  74. package/dist/openapi/OpenApiProvider.js +4 -4
  75. package/dist/openapi/OpenApiProvider.js.map +1 -1
  76. package/dist/router/Router.d.ts +223 -31
  77. package/dist/router/Router.d.ts.map +1 -1
  78. package/dist/router/Router.js +520 -102
  79. package/dist/router/Router.js.map +1 -1
  80. package/dist/rpc/RpcProvider.d.ts +1 -0
  81. package/dist/rpc/RpcProvider.d.ts.map +1 -1
  82. package/dist/rpc/RpcProvider.js +7 -3
  83. package/dist/rpc/RpcProvider.js.map +1 -1
  84. package/dist/rpc/RpcRouter.d.ts +2 -2
  85. package/dist/rpc/RpcRouter.d.ts.map +1 -1
  86. package/dist/rpc/RpcRouter.js +5 -4
  87. package/dist/rpc/RpcRouter.js.map +1 -1
  88. package/dist/scheduler/ScheduleProvider.d.ts.map +1 -1
  89. package/dist/scheduler/ScheduleProvider.js +7 -15
  90. package/dist/scheduler/ScheduleProvider.js.map +1 -1
  91. package/dist/server/Server.d.ts +17 -4
  92. package/dist/server/Server.d.ts.map +1 -1
  93. package/dist/server/Server.js +41 -7
  94. package/dist/server/Server.js.map +1 -1
  95. package/dist/session/SessionMiddleware.js +3 -3
  96. package/dist/session/SessionMiddleware.js.map +1 -1
  97. package/dist/storage/StaticProvider.js +1 -1
  98. package/dist/storage/StaticProvider.js.map +1 -1
  99. package/dist/testing/RequestBuilder.d.ts +60 -0
  100. package/dist/testing/RequestBuilder.d.ts.map +1 -1
  101. package/dist/testing/RequestBuilder.js +180 -1
  102. package/dist/testing/RequestBuilder.js.map +1 -1
  103. package/dist/testing/TestClient.d.ts +22 -1
  104. package/dist/testing/TestClient.d.ts.map +1 -1
  105. package/dist/testing/TestClient.js +40 -0
  106. package/dist/testing/TestClient.js.map +1 -1
  107. package/dist/utils/Macroable.d.ts +30 -0
  108. package/dist/utils/Macroable.d.ts.map +1 -0
  109. package/dist/utils/Macroable.js +43 -0
  110. package/dist/utils/Macroable.js.map +1 -0
  111. package/dist/utils/inflect.d.ts +17 -0
  112. package/dist/utils/inflect.d.ts.map +1 -0
  113. package/dist/utils/inflect.js +32 -0
  114. package/dist/utils/inflect.js.map +1 -0
  115. package/dist/utils/objectPath.d.ts +57 -0
  116. package/dist/utils/objectPath.d.ts.map +1 -0
  117. package/dist/utils/objectPath.js +141 -0
  118. package/dist/utils/objectPath.js.map +1 -0
  119. package/events.darwin-arm64.node +0 -0
  120. package/events.darwin-x64.node +0 -0
  121. package/events.linux-arm64-gnu.node +0 -0
  122. package/events.linux-x64-gnu.node +0 -0
  123. package/events.win32-x64-msvc.node +0 -0
  124. package/index.darwin-arm64.node +0 -0
  125. package/index.darwin-x64.node +0 -0
  126. package/index.linux-arm64-gnu.node +0 -0
  127. package/index.linux-x64-gnu.node +0 -0
  128. package/index.win32-x64-msvc.node +0 -0
  129. package/package.json +6 -1
  130. package/scheduler.darwin-arm64.node +0 -0
  131. package/scheduler.darwin-x64.node +0 -0
  132. package/scheduler.linux-arm64-gnu.node +0 -0
  133. package/scheduler.linux-x64-gnu.node +0 -0
  134. package/scheduler.win32-x64-msvc.node +0 -0
  135. package/src/ConfigLoader.ts +72 -9
  136. package/src/HttpKernel.ts +53 -15
  137. package/src/Ignitor.ts +24 -3
  138. package/src/container/Container.ts +145 -61
  139. package/src/env/Env.ts +49 -5
  140. package/src/env/Secret.ts +41 -0
  141. package/src/env/interpolate.ts +54 -0
  142. package/src/env/loadEnvFiles.ts +12 -4
  143. package/src/env/schema.ts +109 -35
  144. package/src/events/Emitter.ts +2 -2
  145. package/src/events/EventsProvider.ts +5 -5
  146. package/src/graphql/GraphQLEngine.ts +4 -4
  147. package/src/graphql/GraphQLProvider.ts +7 -3
  148. package/src/http/Exception.ts +214 -37
  149. package/src/http/HttpContext.ts +87 -18
  150. package/src/http/RedirectBuilder.ts +4 -1
  151. package/src/http/Request.ts +264 -34
  152. package/src/http/Response.ts +478 -27
  153. package/src/index.ts +11 -2
  154. package/src/middleware/Pipeline.ts +1 -1
  155. package/src/openapi/OpenApiProvider.ts +4 -4
  156. package/src/router/Router.ts +631 -113
  157. package/src/rpc/RpcProvider.ts +6 -2
  158. package/src/rpc/RpcRouter.ts +7 -6
  159. package/src/scheduler/ScheduleProvider.ts +7 -15
  160. package/src/server/Server.ts +49 -10
  161. package/src/session/SessionMiddleware.ts +4 -4
  162. package/src/storage/StaticProvider.ts +1 -1
  163. package/src/testing/RequestBuilder.ts +218 -1
  164. package/src/testing/TestClient.ts +57 -1
  165. package/src/utils/Macroable.ts +54 -0
  166. package/src/utils/inflect.ts +29 -0
  167. package/src/utils/objectPath.ts +142 -0
  168. package/dist/decorators/Lazy.d.ts +0 -19
  169. package/dist/decorators/Lazy.d.ts.map +0 -1
  170. package/dist/decorators/Lazy.js +0 -53
  171. package/dist/decorators/Lazy.js.map +0 -1
  172. 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":"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"}
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c9up/ream",
3
- "version": "0.1.25",
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
@@ -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()`. The class is its own contract — `AppContext.config`
10
- * is typed against it directly (structural).
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
- private store: Map<string, unknown> = new Map()
21
+ #tree: Record<string, unknown> = {}
14
22
 
15
- get<T = unknown>(key: string): T | undefined {
16
- return this.store.get(key) as T | undefined
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.store.set(key, value)
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.store.has(key)
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
- /** Load config from an object (used for testing and initial setup). */
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.store.set(key, value)
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
- ctx.events = resolveEvents()
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[] = match.route.validators.map((name) => {
209
- const token = `validator:${name}`
210
- if (!config.container?.has(token)) {
211
- throw new Error(
212
- `[E_VALIDATOR_NOT_FOUND] Route validator '${name}' is not registered. ` +
213
- `Bind it with container.singleton('${token}', () => schema({ ... })).`,
214
- )
215
- }
216
- return config.container.resolve<RuntimeValidator>(token)
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
- .resolve<Emitter>('events')
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