@c9up/ream 0.1.25 → 0.1.27

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 (302) hide show
  1. package/README.md +65 -0
  2. package/dist/ConfigLoader.d.ts +46 -5
  3. package/dist/ConfigLoader.d.ts.map +1 -1
  4. package/dist/ConfigLoader.js +60 -9
  5. package/dist/ConfigLoader.js.map +1 -1
  6. package/dist/HttpKernel.d.ts.map +1 -1
  7. package/dist/HttpKernel.js +46 -6
  8. package/dist/HttpKernel.js.map +1 -1
  9. package/dist/Ignitor.d.ts +115 -7
  10. package/dist/Ignitor.d.ts.map +1 -1
  11. package/dist/Ignitor.js +254 -40
  12. package/dist/Ignitor.js.map +1 -1
  13. package/dist/console/BaseCommand.d.ts +217 -0
  14. package/dist/console/BaseCommand.d.ts.map +1 -0
  15. package/dist/console/BaseCommand.js +340 -0
  16. package/dist/console/BaseCommand.js.map +1 -0
  17. package/dist/console/Console.d.ts +86 -0
  18. package/dist/console/Console.d.ts.map +1 -0
  19. package/dist/console/Console.js +134 -0
  20. package/dist/console/Console.js.map +1 -0
  21. package/dist/console/ExceptionHandler.d.ts +28 -0
  22. package/dist/console/ExceptionHandler.d.ts.map +1 -0
  23. package/dist/console/ExceptionHandler.js +76 -0
  24. package/dist/console/ExceptionHandler.js.map +1 -0
  25. package/dist/console/HelpCommand.d.ts +16 -0
  26. package/dist/console/HelpCommand.d.ts.map +1 -0
  27. package/dist/console/HelpCommand.js +40 -0
  28. package/dist/console/HelpCommand.js.map +1 -0
  29. package/dist/console/IndexGenerator.d.ts +24 -0
  30. package/dist/console/IndexGenerator.d.ts.map +1 -0
  31. package/dist/console/IndexGenerator.js +65 -0
  32. package/dist/console/IndexGenerator.js.map +1 -0
  33. package/dist/console/Kernel.d.ts +298 -0
  34. package/dist/console/Kernel.d.ts.map +1 -0
  35. package/dist/console/Kernel.js +1120 -0
  36. package/dist/console/Kernel.js.map +1 -0
  37. package/dist/console/ListCommand.d.ts +18 -0
  38. package/dist/console/ListCommand.d.ts.map +1 -0
  39. package/dist/console/ListCommand.js +60 -0
  40. package/dist/console/ListCommand.js.map +1 -0
  41. package/dist/console/assertions.d.ts +20 -0
  42. package/dist/console/assertions.d.ts.map +1 -0
  43. package/dist/console/assertions.js +84 -0
  44. package/dist/console/assertions.js.map +1 -0
  45. package/dist/console/cliui.d.ts +248 -0
  46. package/dist/console/cliui.d.ts.map +1 -0
  47. package/dist/console/cliui.js +587 -0
  48. package/dist/console/cliui.js.map +1 -0
  49. package/dist/console/decorators.d.ts +90 -0
  50. package/dist/console/decorators.d.ts.map +1 -0
  51. package/dist/console/decorators.js +142 -0
  52. package/dist/console/decorators.js.map +1 -0
  53. package/dist/console/index.d.ts +32 -0
  54. package/dist/console/index.d.ts.map +1 -0
  55. package/dist/console/index.js +24 -0
  56. package/dist/console/index.js.map +1 -0
  57. package/dist/console/loaders.d.ts +52 -0
  58. package/dist/console/loaders.d.ts.map +1 -0
  59. package/dist/console/loaders.js +131 -0
  60. package/dist/console/loaders.js.map +1 -0
  61. package/dist/console/parser.d.ts +86 -0
  62. package/dist/console/parser.d.ts.map +1 -0
  63. package/dist/console/parser.js +440 -0
  64. package/dist/console/parser.js.map +1 -0
  65. package/dist/console/prompts.d.ts +124 -0
  66. package/dist/console/prompts.d.ts.map +1 -0
  67. package/dist/console/prompts.js +356 -0
  68. package/dist/console/prompts.js.map +1 -0
  69. package/dist/console/tracing.d.ts +17 -0
  70. package/dist/console/tracing.d.ts.map +1 -0
  71. package/dist/console/tracing.js +11 -0
  72. package/dist/console/tracing.js.map +1 -0
  73. package/dist/console/types.d.ts +255 -0
  74. package/dist/console/types.d.ts.map +1 -0
  75. package/dist/console/types.js +25 -0
  76. package/dist/console/types.js.map +1 -0
  77. package/dist/console/ui.d.ts +20 -0
  78. package/dist/console/ui.d.ts.map +1 -0
  79. package/dist/console/ui.js +31 -0
  80. package/dist/console/ui.js.map +1 -0
  81. package/dist/console/utils.d.ts +27 -0
  82. package/dist/console/utils.d.ts.map +1 -0
  83. package/dist/console/utils.js +56 -0
  84. package/dist/console/utils.js.map +1 -0
  85. package/dist/container/Container.d.ts +23 -3
  86. package/dist/container/Container.d.ts.map +1 -1
  87. package/dist/container/Container.js +177 -70
  88. package/dist/container/Container.js.map +1 -1
  89. package/dist/container/types.d.ts +14 -2
  90. package/dist/container/types.d.ts.map +1 -1
  91. package/dist/decorators/Service.d.ts +7 -0
  92. package/dist/decorators/Service.d.ts.map +1 -1
  93. package/dist/decorators/Service.js +23 -3
  94. package/dist/decorators/Service.js.map +1 -1
  95. package/dist/env/Env.d.ts +29 -4
  96. package/dist/env/Env.d.ts.map +1 -1
  97. package/dist/env/Env.js +27 -4
  98. package/dist/env/Env.js.map +1 -1
  99. package/dist/env/Secret.d.ts +11 -0
  100. package/dist/env/Secret.d.ts.map +1 -0
  101. package/dist/env/Secret.js +36 -0
  102. package/dist/env/Secret.js.map +1 -0
  103. package/dist/env/interpolate.d.ts +26 -0
  104. package/dist/env/interpolate.d.ts.map +1 -0
  105. package/dist/env/interpolate.js +48 -0
  106. package/dist/env/interpolate.js.map +1 -0
  107. package/dist/env/loadEnvFiles.d.ts.map +1 -1
  108. package/dist/env/loadEnvFiles.js +14 -4
  109. package/dist/env/loadEnvFiles.js.map +1 -1
  110. package/dist/env/schema.d.ts +23 -6
  111. package/dist/env/schema.d.ts.map +1 -1
  112. package/dist/env/schema.js +67 -33
  113. package/dist/env/schema.js.map +1 -1
  114. package/dist/errors/ReamError.d.ts +25 -0
  115. package/dist/errors/ReamError.d.ts.map +1 -1
  116. package/dist/errors/ReamError.js +5 -0
  117. package/dist/errors/ReamError.js.map +1 -1
  118. package/dist/errors/prettyPrintError.d.ts +9 -0
  119. package/dist/errors/prettyPrintError.d.ts.map +1 -0
  120. package/dist/errors/prettyPrintError.js +22 -0
  121. package/dist/errors/prettyPrintError.js.map +1 -0
  122. package/dist/events/Emitter.d.ts +1 -1
  123. package/dist/events/Emitter.d.ts.map +1 -1
  124. package/dist/events/Emitter.js +1 -1
  125. package/dist/events/Emitter.js.map +1 -1
  126. package/dist/events/EventsProvider.d.ts +2 -2
  127. package/dist/events/EventsProvider.d.ts.map +1 -1
  128. package/dist/events/EventsProvider.js +3 -3
  129. package/dist/events/EventsProvider.js.map +1 -1
  130. package/dist/graphql/GraphQLEngine.d.ts +1 -1
  131. package/dist/graphql/GraphQLEngine.d.ts.map +1 -1
  132. package/dist/graphql/GraphQLEngine.js +2 -2
  133. package/dist/graphql/GraphQLEngine.js.map +1 -1
  134. package/dist/graphql/GraphQLProvider.d.ts.map +1 -1
  135. package/dist/graphql/GraphQLProvider.js +7 -3
  136. package/dist/graphql/GraphQLProvider.js.map +1 -1
  137. package/dist/http/Exception.d.ts +63 -1
  138. package/dist/http/Exception.d.ts.map +1 -1
  139. package/dist/http/Exception.js +185 -48
  140. package/dist/http/Exception.js.map +1 -1
  141. package/dist/http/HttpContext.d.ts +53 -7
  142. package/dist/http/HttpContext.d.ts.map +1 -1
  143. package/dist/http/HttpContext.js +56 -15
  144. package/dist/http/HttpContext.js.map +1 -1
  145. package/dist/http/RedirectBuilder.d.ts.map +1 -1
  146. package/dist/http/RedirectBuilder.js +4 -1
  147. package/dist/http/RedirectBuilder.js.map +1 -1
  148. package/dist/http/Request.d.ts +141 -10
  149. package/dist/http/Request.d.ts.map +1 -1
  150. package/dist/http/Request.js +244 -34
  151. package/dist/http/Request.js.map +1 -1
  152. package/dist/http/Response.d.ts +176 -9
  153. package/dist/http/Response.d.ts.map +1 -1
  154. package/dist/http/Response.js +404 -16
  155. package/dist/http/Response.js.map +1 -1
  156. package/dist/index.d.ts +19 -8
  157. package/dist/index.d.ts.map +1 -1
  158. package/dist/index.js +20 -5
  159. package/dist/index.js.map +1 -1
  160. package/dist/middleware/Pipeline.d.ts +29 -8
  161. package/dist/middleware/Pipeline.d.ts.map +1 -1
  162. package/dist/middleware/Pipeline.js +40 -2
  163. package/dist/middleware/Pipeline.js.map +1 -1
  164. package/dist/openapi/OpenApiProvider.js +4 -4
  165. package/dist/openapi/OpenApiProvider.js.map +1 -1
  166. package/dist/router/Router.d.ts +223 -31
  167. package/dist/router/Router.d.ts.map +1 -1
  168. package/dist/router/Router.js +520 -102
  169. package/dist/router/Router.js.map +1 -1
  170. package/dist/rpc/RpcProvider.d.ts +1 -0
  171. package/dist/rpc/RpcProvider.d.ts.map +1 -1
  172. package/dist/rpc/RpcProvider.js +7 -3
  173. package/dist/rpc/RpcProvider.js.map +1 -1
  174. package/dist/rpc/RpcRouter.d.ts +2 -2
  175. package/dist/rpc/RpcRouter.d.ts.map +1 -1
  176. package/dist/rpc/RpcRouter.js +17 -5
  177. package/dist/rpc/RpcRouter.js.map +1 -1
  178. package/dist/scheduler/ScheduleProvider.d.ts.map +1 -1
  179. package/dist/scheduler/ScheduleProvider.js +7 -15
  180. package/dist/scheduler/ScheduleProvider.js.map +1 -1
  181. package/dist/server/Server.d.ts +17 -4
  182. package/dist/server/Server.d.ts.map +1 -1
  183. package/dist/server/Server.js +41 -7
  184. package/dist/server/Server.js.map +1 -1
  185. package/dist/services/app.d.ts +1 -1
  186. package/dist/services/app.js +1 -1
  187. package/dist/services/console.d.ts +21 -0
  188. package/dist/services/console.d.ts.map +1 -0
  189. package/dist/services/console.js +35 -0
  190. package/dist/services/console.js.map +1 -0
  191. package/dist/session/SessionMiddleware.js +3 -3
  192. package/dist/session/SessionMiddleware.js.map +1 -1
  193. package/dist/storage/StaticProvider.js +1 -1
  194. package/dist/storage/StaticProvider.js.map +1 -1
  195. package/dist/testing/ApiResponse.d.ts +218 -0
  196. package/dist/testing/ApiResponse.d.ts.map +1 -0
  197. package/dist/testing/ApiResponse.js +576 -0
  198. package/dist/testing/ApiResponse.js.map +1 -0
  199. package/dist/testing/RequestBuilder.d.ts +155 -35
  200. package/dist/testing/RequestBuilder.d.ts.map +1 -1
  201. package/dist/testing/RequestBuilder.js +455 -109
  202. package/dist/testing/RequestBuilder.js.map +1 -1
  203. package/dist/testing/TestClient.d.ts +53 -37
  204. package/dist/testing/TestClient.d.ts.map +1 -1
  205. package/dist/testing/TestClient.js +84 -52
  206. package/dist/testing/TestClient.js.map +1 -1
  207. package/dist/utils/Macroable.d.ts +30 -0
  208. package/dist/utils/Macroable.d.ts.map +1 -0
  209. package/dist/utils/Macroable.js +43 -0
  210. package/dist/utils/Macroable.js.map +1 -0
  211. package/dist/utils/inflect.d.ts +17 -0
  212. package/dist/utils/inflect.d.ts.map +1 -0
  213. package/dist/utils/inflect.js +32 -0
  214. package/dist/utils/inflect.js.map +1 -0
  215. package/dist/utils/objectPath.d.ts +57 -0
  216. package/dist/utils/objectPath.d.ts.map +1 -0
  217. package/dist/utils/objectPath.js +141 -0
  218. package/dist/utils/objectPath.js.map +1 -0
  219. package/events.darwin-arm64.node +0 -0
  220. package/events.darwin-x64.node +0 -0
  221. package/events.linux-arm64-gnu.node +0 -0
  222. package/events.linux-x64-gnu.node +0 -0
  223. package/events.win32-x64-msvc.node +0 -0
  224. package/index.darwin-arm64.node +0 -0
  225. package/index.darwin-x64.node +0 -0
  226. package/index.linux-arm64-gnu.node +0 -0
  227. package/index.linux-x64-gnu.node +0 -0
  228. package/index.win32-x64-msvc.node +0 -0
  229. package/package.json +20 -2
  230. package/scheduler.darwin-arm64.node +0 -0
  231. package/scheduler.darwin-x64.node +0 -0
  232. package/scheduler.linux-arm64-gnu.node +0 -0
  233. package/scheduler.linux-x64-gnu.node +0 -0
  234. package/scheduler.win32-x64-msvc.node +0 -0
  235. package/src/ConfigLoader.ts +72 -9
  236. package/src/HttpKernel.ts +53 -15
  237. package/src/Ignitor.ts +364 -45
  238. package/src/console/BaseCommand.ts +467 -0
  239. package/src/console/Console.ts +174 -0
  240. package/src/console/ExceptionHandler.ts +91 -0
  241. package/src/console/HelpCommand.ts +37 -0
  242. package/src/console/IndexGenerator.ts +79 -0
  243. package/src/console/Kernel.ts +1382 -0
  244. package/src/console/ListCommand.ts +55 -0
  245. package/src/console/assertions.ts +114 -0
  246. package/src/console/cliui.ts +756 -0
  247. package/src/console/decorators.ts +199 -0
  248. package/src/console/index.ts +67 -0
  249. package/src/console/loaders.ts +145 -0
  250. package/src/console/parser.ts +598 -0
  251. package/src/console/prompts.ts +548 -0
  252. package/src/console/tracing.ts +22 -0
  253. package/src/console/types.ts +280 -0
  254. package/src/console/ui.ts +37 -0
  255. package/src/console/utils.ts +69 -0
  256. package/src/container/Container.ts +198 -72
  257. package/src/container/types.ts +15 -2
  258. package/src/decorators/Service.ts +28 -3
  259. package/src/env/Env.ts +49 -5
  260. package/src/env/Secret.ts +41 -0
  261. package/src/env/interpolate.ts +54 -0
  262. package/src/env/loadEnvFiles.ts +12 -4
  263. package/src/env/schema.ts +109 -35
  264. package/src/errors/ReamError.ts +31 -0
  265. package/src/errors/prettyPrintError.ts +20 -0
  266. package/src/events/Emitter.ts +2 -2
  267. package/src/events/EventsProvider.ts +5 -5
  268. package/src/graphql/GraphQLEngine.ts +4 -4
  269. package/src/graphql/GraphQLProvider.ts +7 -3
  270. package/src/http/Exception.ts +214 -37
  271. package/src/http/HttpContext.ts +87 -18
  272. package/src/http/RedirectBuilder.ts +4 -1
  273. package/src/http/Request.ts +264 -34
  274. package/src/http/Response.ts +478 -27
  275. package/src/index.ts +41 -6
  276. package/src/middleware/Pipeline.ts +77 -6
  277. package/src/openapi/OpenApiProvider.ts +4 -4
  278. package/src/router/Router.ts +631 -113
  279. package/src/rpc/RpcProvider.ts +6 -2
  280. package/src/rpc/RpcRouter.ts +22 -7
  281. package/src/scheduler/ScheduleProvider.ts +7 -15
  282. package/src/server/Server.ts +49 -10
  283. package/src/services/app.ts +1 -1
  284. package/src/services/console.ts +42 -0
  285. package/src/session/SessionMiddleware.ts +4 -4
  286. package/src/storage/StaticProvider.ts +1 -1
  287. package/src/testing/ApiResponse.ts +691 -0
  288. package/src/testing/RequestBuilder.ts +577 -137
  289. package/src/testing/TestClient.ts +110 -77
  290. package/src/utils/Macroable.ts +54 -0
  291. package/src/utils/inflect.ts +29 -0
  292. package/src/utils/objectPath.ts +142 -0
  293. package/dist/console/CommandRunner.d.ts +0 -18
  294. package/dist/console/CommandRunner.d.ts.map +0 -1
  295. package/dist/console/CommandRunner.js +0 -65
  296. package/dist/console/CommandRunner.js.map +0 -1
  297. package/dist/decorators/Lazy.d.ts +0 -19
  298. package/dist/decorators/Lazy.d.ts.map +0 -1
  299. package/dist/decorators/Lazy.js +0 -53
  300. package/dist/decorators/Lazy.js.map +0 -1
  301. package/src/console/CommandRunner.ts +0 -82
  302. package/src/decorators/Lazy.ts +0 -57
@@ -10,8 +10,13 @@
10
10
  */
11
11
 
12
12
  import 'reflect-metadata'
13
- import { createLazyProxy, getLazyParams } from '../decorators/Lazy.js'
14
- import { getInjectTokens, getServiceMetadata, getServiceRegistry } from '../decorators/Service.js'
13
+ import { AsyncLocalStorage } from 'node:async_hooks'
14
+ import {
15
+ getInjectTokens,
16
+ getMethodInjectTokens,
17
+ getServiceMetadata,
18
+ getServiceRegistry,
19
+ } from '../decorators/Service.js'
15
20
  import { didYouMean } from '../errors/FuzzyMatcher.js'
16
21
  import { ReamError } from '../errors/ReamError.js'
17
22
  import type { Binding, ServiceFactory, ServiceToken } from './types.js'
@@ -23,6 +28,12 @@ import type { Binding, ServiceFactory, ServiceToken } from './types.js'
23
28
  */
24
29
  const RESERVED_TOKEN_NAMES = new Set(['__proto__', 'constructor', 'prototype'])
25
30
 
31
+ /** One resolution chain: the tokens on it, and a set for O(1) membership. */
32
+ interface ResolutionChain {
33
+ stack: string[]
34
+ set: Set<string>
35
+ }
36
+
26
37
  export class Container {
27
38
  #bindings: Map<string, Binding> = new Map()
28
39
  #singletons: Map<string, unknown> = new Map()
@@ -34,8 +45,20 @@ export class Container {
34
45
  * pointing at the same object after the test ends.
35
46
  */
36
47
  #singletonBackup: Map<string, unknown> = new Map()
37
- #resolutionStack: string[] = []
38
- #resolutionSet: Set<string> = new Set()
48
+ /** Alias key → the token it forwards to (AdonisJS `container.alias`). */
49
+ #aliases: Map<string, ServiceToken> = new Map()
50
+ /** token key → post-resolution callbacks (AdonisJS `container.resolving`). */
51
+ #resolvingHooks: Map<string, Array<(value: unknown) => void | Promise<void>>> = new Map()
52
+ /**
53
+ * The resolution chain currently being walked, for cycle detection.
54
+ *
55
+ * Scoped to the async context, NOT to the container: `resolve()` is async, so
56
+ * two independent chains interleave, and a stack held on the instance would
57
+ * splice them together and report a cycle neither of them has. Per-container
58
+ * storage keeps a chain that crosses two containers from colliding on a token
59
+ * name they happen to share.
60
+ */
61
+ readonly #chain = new AsyncLocalStorage<ResolutionChain>()
39
62
 
40
63
  // ─── Explicit bindings ────────────────────────────────────
41
64
 
@@ -58,6 +81,33 @@ export class Container {
58
81
  this.#bindings.set(key, { token, factory: () => value, scope: 'singleton', dependencies: [] })
59
82
  }
60
83
 
84
+ /**
85
+ * Register an alias — resolving `alias` forwards to `target` (AdonisJS
86
+ * `container.alias('db', Database)`). The target may be a class, string, or
87
+ * symbol that itself resolves to a binding or an auto-constructable class.
88
+ */
89
+ alias(alias: ServiceToken, target: ServiceToken): void {
90
+ this.#aliases.set(this.#tokenToKey(alias), target)
91
+ }
92
+
93
+ /**
94
+ * Register a callback that runs each time `token` is constructed (AdonisJS
95
+ * `container.resolving`) — for lazy init (`db.connect()`) or decorating the
96
+ * resolved instance. Runs after construction of a binding/auto-constructed
97
+ * class; NOT for raw `bindValue` values or an already-cached singleton.
98
+ */
99
+ resolving(token: ServiceToken, callback: (value: unknown) => void | Promise<void>): void {
100
+ const key = this.#tokenToKey(token)
101
+ const hooks = this.#resolvingHooks.get(key)
102
+ if (hooks) hooks.push(callback)
103
+ else this.#resolvingHooks.set(key, [callback])
104
+ }
105
+
106
+ async #runResolvingHooks(key: string, value: unknown): Promise<void> {
107
+ const hooks = this.#resolvingHooks.get(key)
108
+ if (hooks) for (const hook of hooks) await hook(value)
109
+ }
110
+
61
111
  // ─── Testing ──────────────────────────────────────────────
62
112
 
63
113
  /**
@@ -115,29 +165,47 @@ export class Container {
115
165
  * 4. Auto-construct if class has @inject() or @Service()
116
166
  * 5. Auto-construct any class (plain `new Class()`) as fallback
117
167
  */
118
- make<T>(token: ServiceToken): T {
119
- return this.resolve<T>(token)
168
+ make<T>(token: ServiceToken, runtimeValues?: unknown[]): Promise<T> {
169
+ return this.resolve<T>(token, runtimeValues)
120
170
  }
121
171
 
122
- /** Alias for make() — backward compatible. */
123
- resolve<T>(token: ServiceToken): T {
172
+ /**
173
+ * Resolve/construct a class or binding (AdonisJS `container.make` — async, so
174
+ * factories, providers and `resolving` hooks may be async). `runtimeValues`
175
+ * fill the resolved class's constructor slots by index (`make(Class, [req,
176
+ * res])`); only the `undefined` slots are container-resolved, top-level only.
177
+ */
178
+ async resolve<T>(token: ServiceToken, runtimeValues?: unknown[]): Promise<T> {
179
+ // Dereference an alias to its target before anything else.
180
+ const aliasTarget = this.#aliases.get(this.#tokenToKey(token))
181
+ if (aliasTarget !== undefined) return this.resolve<T>(aliasTarget, runtimeValues)
182
+
124
183
  const key = this.#tokenToKey(token)
125
184
 
126
- if (this.#resolutionSet.has(key)) {
127
- const cycle = [...this.#resolutionStack, key].join(' → ')
185
+ // Top of a chain: open one, so everything this resolution triggers shares
186
+ // it and nothing outside it does.
187
+ const chain = this.#chain.getStore()
188
+ if (chain === undefined) {
189
+ return this.#chain.run({ stack: [], set: new Set() }, () =>
190
+ this.resolve<T>(token, runtimeValues),
191
+ )
192
+ }
193
+
194
+ if (chain.set.has(key)) {
195
+ const cycle = [...chain.stack, key].join(' → ')
128
196
  throw new ReamError('CIRCULAR_DEPENDENCY', `Circular dependency detected: ${cycle}`, {
129
- hint: 'Use @Lazy() on one of the constructor parameters to break the cycle.',
197
+ hint: 'Break the cycle by resolving one dependency lazily inside a method (`await container.make(Dep)`) instead of injecting it in the constructor.',
130
198
  context: { chain: cycle },
131
199
  })
132
200
  }
133
- this.#resolutionStack.push(key)
134
- this.#resolutionSet.add(key)
201
+ chain.stack.push(key)
202
+ chain.set.add(key)
135
203
 
136
204
  try {
137
- return this.#resolveInner<T>(key, token)
205
+ return await this.#resolveInner<T>(key, token, runtimeValues)
138
206
  } finally {
139
- this.#resolutionStack.pop()
140
- this.#resolutionSet.delete(key)
207
+ chain.stack.pop()
208
+ chain.set.delete(key)
141
209
  }
142
210
  }
143
211
 
@@ -157,15 +225,32 @@ export class Container {
157
225
  const target: Ctor = Object.getPrototypeOf(instance).constructor
158
226
  const paramTypes: unknown[] =
159
227
  Reflect.getMetadata('design:paramtypes', target.prototype, method) ?? []
160
-
161
- const len = Math.max(paramTypes.length, runtimeValues?.length ?? 0)
162
- const args = Array.from({ length: len }, (_, index) => {
163
- // Runtime values take precedence (and fill slots beyond paramTypes)
164
- if (runtimeValues && index < runtimeValues.length) return runtimeValues[index]
228
+ // `@Inject('token')` on a method parameter — a named binding the reflected
229
+ // type cannot express (an interface, or a class registered under a name).
230
+ const injectTokens = getMethodInjectTokens(target.prototype, method)
231
+
232
+ // `@Inject` counts towards the parameter span too: a method may carry named
233
+ // tokens without `design:paramtypes` being emitted for it, and computing the
234
+ // span from the reflected types alone left the loop at zero iterations —
235
+ // the tokens were read and then never used.
236
+ const injectSpan = injectTokens.size === 0 ? 0 : Math.max(...injectTokens.keys()) + 1
237
+ const len = Math.max(paramTypes.length, runtimeValues?.length ?? 0, injectSpan)
238
+ // Sequential resolution keeps the shared cycle-detection stack consistent.
239
+ const args: unknown[] = []
240
+ for (let index = 0; index < len; index += 1) {
241
+ // Runtime values take precedence (and fill slots beyond paramTypes).
242
+ if (runtimeValues && index < runtimeValues.length) {
243
+ args.push(runtimeValues[index])
244
+ continue
245
+ }
246
+ const named = injectTokens.get(index)
247
+ if (named !== undefined) {
248
+ args.push(await this.resolve(named))
249
+ continue
250
+ }
165
251
  const type = paramTypes[index]
166
- if (isClassConstructor(type)) return this.resolve(type)
167
- return undefined
168
- })
252
+ args.push(isInjectableClass(type) ? await this.resolve(type) : undefined)
253
+ }
169
254
 
170
255
  const member: unknown = instance[method]
171
256
  if (!isCallable(member)) {
@@ -183,10 +268,16 @@ export class Container {
183
268
  this.#bindings.has(key) ||
184
269
  this.#overrides.has(key) ||
185
270
  this.#singletons.has(key) ||
271
+ this.#aliases.has(key) ||
186
272
  (typeof token === 'function' && getServiceMetadata(token) !== undefined)
187
273
  )
188
274
  }
189
275
 
276
+ /** True only when EVERY token is registered/resolvable (AdonisJS `hasAllBindings`). */
277
+ hasAllBindings(tokens: ServiceToken[]): boolean {
278
+ return tokens.every((token) => this.has(token))
279
+ }
280
+
190
281
  get size(): number {
191
282
  return this.#bindings.size
192
283
  }
@@ -209,34 +300,35 @@ export class Container {
209
300
 
210
301
  // ─── Internal resolution ──────────────────────────────────
211
302
 
212
- #resolveInner<T>(key: string, token: ServiceToken): T {
213
- // 1. Check swaps (test overrides)
303
+ async #resolveInner<T>(key: string, token: ServiceToken, runtimeValues?: unknown[]): Promise<T> {
304
+ // 1. Check swaps (test overrides) — a swap factory may be async.
214
305
  if (this.#overrides.has(key)) {
215
- // biome-ignore lint/suspicious/noExplicitAny: IoC container factory returns unknown; caller brands via T
216
- return this.#overrides.get(key)?.() as any as T
306
+ const swapped = await this.#overrides.get(key)?.(this)
307
+ return swapped as T
217
308
  }
218
309
 
219
310
  // 2. Check cached singletons
220
311
  if (this.#singletons.has(key)) {
221
- // biome-ignore lint/suspicious/noExplicitAny: IoC singleton stored as unknown; caller brands via T
222
- return this.#singletons.get(key) as any as T
312
+ return this.#singletons.get(key) as T
223
313
  }
224
314
 
225
- // 3. Check explicit bindings
315
+ // 3. Check explicit bindings — the factory may be async.
226
316
  const binding = this.#bindings.get(key)
227
317
  if (binding) {
228
- const instance = binding.factory ? binding.factory() : undefined
318
+ const instance = binding.factory ? await binding.factory(this) : undefined
229
319
  if (binding.scope === 'singleton') {
230
320
  this.#singletons.set(key, instance)
231
321
  }
232
- // biome-ignore lint/suspicious/noExplicitAny: IoC binding factory returns unknown; caller brands via T
233
- return instance as any as T
322
+ await this.#runResolvingHooks(key, instance)
323
+ // Boundary cast from `unknown` — the caller brands the resolved type via T.
324
+ return instance as T
234
325
  }
235
326
 
236
327
  // 4. Auto-construct if it's a class
237
328
  if (typeof token === 'function') {
238
- // biome-ignore lint/suspicious/noExplicitAny: IoC auto-construct returns unknown; caller brands via T
239
- return this.#autoConstruct(token) as any as T
329
+ const instance = (await this.#autoConstruct(token, runtimeValues)) as T
330
+ await this.#runResolvingHooks(key, instance)
331
+ return instance
240
332
  }
241
333
 
242
334
  // 5. Not found
@@ -259,7 +351,10 @@ export class Container {
259
351
  * 2. Reflect.getMetadata('design:paramtypes') — decorator metadata (requires SWC/tsc)
260
352
  * 3. No params → plain `new Class()`
261
353
  */
262
- #autoConstruct(target: new (...args: unknown[]) => unknown): unknown {
354
+ async #autoConstruct(
355
+ target: new (...args: unknown[]) => unknown,
356
+ runtimeValues?: unknown[],
357
+ ): Promise<unknown> {
263
358
  const metadata = getServiceMetadata(target)
264
359
  const scope = metadata?.scope ?? 'transient'
265
360
  const key = metadata?.as ?? target.name
@@ -268,10 +363,12 @@ export class Container {
268
363
  return this.#singletons.get(key)
269
364
  }
270
365
 
366
+ // A runtime value at this index (from `make(Class, [req, res])`) wins over
367
+ // container resolution for that constructor slot.
368
+ const runtimeAt = (index: number): unknown => runtimeValues?.[index]
369
+
271
370
  // 1. Check static containerInjections (AdonisJS Fold-compatible, works
272
- // without emitDecoratorMetadata). The `in` operator narrows the optional
273
- // field to `unknown`, then `readContainerInjectionDeps` validates the
274
- // shape at runtime — no type-level cast required.
371
+ // without emitDecoratorMetadata).
275
372
  const explicitDeps = readContainerInjectionDeps(target)
276
373
 
277
374
  // 2. Fallback to reflect-metadata
@@ -279,7 +376,6 @@ export class Container {
279
376
  explicitDeps ?? Reflect.getMetadata('design:paramtypes', target) ?? []
280
377
 
281
378
  const injectTokens = getInjectTokens(target)
282
- const lazyIndices = getLazyParams(target)
283
379
 
284
380
  // No `design:paramtypes` (a dev transpiler may not emit decorator metadata —
285
381
  // esbuild / tsx don't). Recover what we can, and NEVER construct with
@@ -287,22 +383,34 @@ export class Container {
287
383
  if (paramTypes.length === 0) {
288
384
  // (a) `@Inject(token)` records its tokens INDEPENDENTLY of decorator
289
385
  // metadata, so even without `design:paramtypes` we can resolve the
290
- // constructor from that map alone.
386
+ // constructor from that map alone. Deps resolve SEQUENTIALLY so the
387
+ // shared cycle-detection stack stays consistent.
291
388
  if (injectTokens.size > 0) {
292
- const maxIndex = Math.max(...injectTokens.keys())
293
- const deps = Array.from({ length: maxIndex + 1 }, (_value, index) => {
294
- const depToken = injectTokens.get(index)
295
- if (!depToken) return undefined
296
- if (lazyIndices.includes(index)) {
297
- return createLazyProxy(() => this.resolve<object>(depToken))
389
+ const maxIndex = Math.max(...injectTokens.keys(), (runtimeValues?.length ?? 0) - 1)
390
+ const deps: unknown[] = []
391
+ for (let index = 0; index <= maxIndex; index += 1) {
392
+ const rt = runtimeAt(index)
393
+ if (rt !== undefined) {
394
+ deps.push(rt)
395
+ continue
298
396
  }
299
- return this.resolve(depToken)
300
- })
397
+ const depToken = injectTokens.get(index)
398
+ deps.push(depToken ? await this.resolve(depToken) : undefined)
399
+ }
301
400
  const instance = new target(...deps)
302
401
  if (scope === 'singleton') this.#singletons.set(key, instance)
303
402
  return instance
304
403
  }
305
- // (b) The constructor declares parameters but we have no way to resolve
404
+ // (b) No @Inject/metadata, but the caller supplied runtime values — build
405
+ // from those (the `make(Controller, [req, res])` pattern with a plain
406
+ // constructor). Slots beyond the runtime values stay undefined.
407
+ if (target.length > 0 && (runtimeValues?.length ?? 0) > 0) {
408
+ const deps = Array.from({ length: target.length }, (_value, index) => runtimeAt(index))
409
+ const instance = new target(...deps)
410
+ if (scope === 'singleton') this.#singletons.set(key, instance)
411
+ return instance
412
+ }
413
+ // (c) The constructor declares parameters but we have no way to resolve
306
414
  // them (no metadata, no @Inject, no containerInjections). Fail LOUDLY
307
415
  // instead of `new target()` with undefined deps.
308
416
  if (target.length > 0) {
@@ -314,29 +422,29 @@ export class Container {
314
422
  },
315
423
  )
316
424
  }
317
- // (c) Genuine zero-argument class.
425
+ // (d) Genuine zero-argument class.
318
426
  const instance = new target()
319
427
  if (scope === 'singleton') this.#singletons.set(key, instance)
320
428
  return instance
321
429
  }
322
430
 
323
- const deps = paramTypes.map((type, index) => {
431
+ // Resolve constructor deps SEQUENTIALLY (the cycle-detection stack is
432
+ // instance-shared, so parallel Promise.all would interleave and corrupt it).
433
+ const deps: unknown[] = []
434
+ for (let index = 0; index < paramTypes.length; index += 1) {
435
+ const rt = runtimeAt(index)
436
+ if (rt !== undefined) {
437
+ deps.push(rt)
438
+ continue
439
+ }
324
440
  const namedToken = injectTokens.get(index)
325
- // After the typeof check, type is a constructor function and valid as ServiceToken
441
+ const type = paramTypes[index]
442
+ // A native primitive constructor (String/Number/Boolean/…) is NOT
443
+ // injectable — leave the slot undefined so a constructor default can fill it.
326
444
  const depToken: ServiceToken | undefined =
327
- namedToken ??
328
- // biome-ignore lint/suspicious/noExplicitAny: reflect-metadata returns unknown; constructor functions are valid ServiceTokens
329
- (typeof type === 'function' && type !== Object ? (type as any as ServiceToken) : undefined)
330
-
331
- if (!depToken) return undefined
332
-
333
- if (lazyIndices.includes(index)) {
334
- // biome-ignore lint/suspicious/noExplicitAny: lazy proxy target must be object; IoC resolution returns unknown
335
- return createLazyProxy(() => this.resolve(depToken) as any as object)
336
- }
337
-
338
- return this.resolve(depToken)
339
- })
445
+ namedToken ?? (isInjectableClass(type) ? type : undefined)
446
+ deps.push(depToken ? await this.resolve(depToken) : undefined)
447
+ }
340
448
 
341
449
  const instance = new target(...deps)
342
450
  if (scope === 'singleton') this.#singletons.set(key, instance)
@@ -378,11 +486,29 @@ export class Container {
378
486
  }
379
487
  }
380
488
 
381
- // `design:paramtypes` returns `unknown[]` per our annotation; we narrow each
382
- // entry before treating it as a class token. `Object` slips through because
383
- // emitDecoratorMetadata uses it for `any`/`unknown` parameters.
384
- function isClassConstructor(value: unknown): value is new (...args: unknown[]) => unknown {
385
- return typeof value === 'function' && value !== Object
489
+ /**
490
+ * Native constructors emitDecoratorMetadata uses for primitive / `any` /
491
+ * `unknown` parameters. They are NOT injectable — resolving them would try to
492
+ * auto-construct `String`/`Number`/… (or silently inject `undefined`), so we
493
+ * skip them and let a constructor default fill the slot instead.
494
+ */
495
+ const NON_INJECTABLE_CONSTRUCTORS = new Set<unknown>([
496
+ Object,
497
+ String,
498
+ Number,
499
+ Boolean,
500
+ Array,
501
+ Function,
502
+ Symbol,
503
+ Date,
504
+ RegExp,
505
+ Promise,
506
+ Error,
507
+ ])
508
+
509
+ /** True for a real, injectable class token (excludes native primitive constructors). */
510
+ function isInjectableClass(value: unknown): value is new (...args: unknown[]) => unknown {
511
+ return typeof value === 'function' && !NON_INJECTABLE_CONSTRUCTORS.has(value)
386
512
  }
387
513
 
388
514
  function isCallable(value: unknown): value is (...args: unknown[]) => unknown {
@@ -17,8 +17,21 @@ export interface ServiceMetadata {
17
17
  // biome-ignore lint/suspicious/noExplicitAny: IoC container tokens must accept any constructor signature
18
18
  export type ServiceToken = (new (...args: any[]) => unknown) | string | symbol
19
19
 
20
- /** Factory function for creating service instances. */
21
- export type ServiceFactory = () => unknown
20
+ /**
21
+ * Resolver handed to a binding factory so it can resolve its own dependencies
22
+ * (AdonisJS parity: `container.singleton(token, async (resolver) =>
23
+ * await resolver.make(Dep))`). The Container itself satisfies this contract.
24
+ */
25
+ export interface ContainerResolver {
26
+ make<T>(token: ServiceToken, runtimeValues?: unknown[]): Promise<T>
27
+ }
28
+
29
+ /**
30
+ * Factory function for creating service instances. Receives a {@link
31
+ * ContainerResolver} (AdonisJS parity) — the async container means a factory
32
+ * that needs another binding must `await resolver.make(Dep)`.
33
+ */
34
+ export type ServiceFactory = (resolver: ContainerResolver) => unknown
22
35
 
23
36
  /** Binding entry in the container. */
24
37
  export interface Binding {
@@ -51,11 +51,23 @@ export function Service(options: { scope?: ServiceScope; as?: string } = {}): Cl
51
51
  * @implements FR16
52
52
  */
53
53
  export function Inject(token: ServiceToken): ParameterDecorator {
54
- return (target, _propertyKey, parameterIndex) => {
54
+ return (target, propertyKey, parameterIndex) => {
55
+ // A constructor parameter reports no property key and lands on the class;
56
+ // a method parameter lands on the prototype and MUST be keyed by method
57
+ // name, or every method of the class would share one map and overwrite
58
+ // each other's tokens.
55
59
  const existingTokens: Map<number, ServiceToken> =
56
- Reflect.getOwnMetadata(INJECT_METADATA_KEY, target) ?? new Map()
60
+ (propertyKey === undefined
61
+ ? Reflect.getOwnMetadata(INJECT_METADATA_KEY, target)
62
+ : Reflect.getOwnMetadata(INJECT_METADATA_KEY, target, propertyKey)) ?? new Map()
63
+
57
64
  existingTokens.set(parameterIndex, token)
58
- Reflect.defineMetadata(INJECT_METADATA_KEY, existingTokens, target)
65
+
66
+ if (propertyKey === undefined) {
67
+ Reflect.defineMetadata(INJECT_METADATA_KEY, existingTokens, target)
68
+ } else {
69
+ Reflect.defineMetadata(INJECT_METADATA_KEY, existingTokens, target, propertyKey)
70
+ }
59
71
  }
60
72
  }
61
73
 
@@ -66,6 +78,19 @@ export function getInjectTokens(target: AnyConstructor): Map<number, ServiceToke
66
78
  return Reflect.getOwnMetadata(INJECT_METADATA_KEY, target) ?? new Map()
67
79
  }
68
80
 
81
+ /**
82
+ * Get named injection tokens for one method's parameters.
83
+ *
84
+ * `target` is the prototype carrying the method — that is where a parameter
85
+ * decorator on a method writes.
86
+ */
87
+ export function getMethodInjectTokens(
88
+ target: object,
89
+ propertyKey: string | symbol,
90
+ ): Map<number, ServiceToken> {
91
+ return Reflect.getOwnMetadata(INJECT_METADATA_KEY, target, propertyKey) ?? new Map()
92
+ }
93
+
69
94
  /**
70
95
  * @inject() class decorator — marks a controller (or service) for transient IoC resolution.
71
96
  *
package/src/env/Env.ts CHANGED
@@ -1,3 +1,9 @@
1
+ import {
2
+ defineIdentifier,
3
+ defineIdentifierIfMissing,
4
+ type IdentifierResolver,
5
+ removeIdentifier,
6
+ } from './interpolate.js'
1
7
  import { loadEnvFiles } from './loadEnvFiles.js'
2
8
  import { type SchemaNode, schema } from './schema.js'
3
9
 
@@ -45,6 +51,21 @@ export class Env<Values extends Record<string, unknown>> {
45
51
  /** The validation schema surface — `Env.schema.string()`, `.number()`, … */
46
52
  static readonly schema = schema
47
53
 
54
+ /** Register an interpolation identifier (AdonisJS `Env.defineIdentifier`). */
55
+ static defineIdentifier(name: string, resolver: IdentifierResolver): void {
56
+ defineIdentifier(name, resolver)
57
+ }
58
+
59
+ /** Register an identifier only if the name is free (AdonisJS `defineIdentifierIfMissing`). */
60
+ static defineIdentifierIfMissing(name: string, resolver: IdentifierResolver): void {
61
+ defineIdentifierIfMissing(name, resolver)
62
+ }
63
+
64
+ /** Remove a registered interpolation identifier (AdonisJS `Env.removeIdentifier`). */
65
+ static removeIdentifier(name: string): void {
66
+ removeIdentifier(name)
67
+ }
68
+
48
69
  readonly #values: Values
49
70
 
50
71
  private constructor(values: Values) {
@@ -80,12 +101,35 @@ export class Env<Values extends Record<string, unknown>> {
80
101
  return new Env(collected as EnvRecord<S>)
81
102
  }
82
103
 
83
- /** Read a validated variable. Required keys are always present; optional keys may be `undefined`. */
104
+ /**
105
+ * Read a variable. Validated keys are typed; for any other key the raw
106
+ * `process.env` value is returned as a fallback (AdonisJS `env.get`
107
+ * semantics). Required keys are always present; optional keys may be
108
+ * `undefined`. The second argument is the default when nothing is found.
109
+ */
84
110
  get<K extends keyof Values>(key: K): Values[K]
85
111
  get<K extends keyof Values>(key: K, fallback: Values[K]): Values[K]
86
- get<K extends keyof Values>(key: K, fallback?: Values[K]): Values[K] {
87
- const value = this.#values[key]
88
- if (value === undefined && fallback !== undefined) return fallback
89
- return value
112
+ get(key: string): string | undefined
113
+ get(key: string, fallback: string): string
114
+ get(key: string, fallback?: unknown): unknown {
115
+ const cached = this.#values[key]
116
+ if (cached !== undefined) return cached
117
+ // Fallback to raw process.env for keys outside the validated schema.
118
+ const fromEnv = process.env[key]
119
+ if (fromEnv !== undefined && fromEnv !== '') return fromEnv
120
+ return fallback
121
+ }
122
+
123
+ /**
124
+ * Set/override a variable at runtime (AdonisJS `env.set`). Updates the cache
125
+ * AND `process.env`. The value is NOT re-validated — pass the correct type.
126
+ */
127
+ set<K extends keyof Values>(key: K, value: Values[K]): void
128
+ set(key: string, value: string): void
129
+ set(key: string, value: unknown): void {
130
+ // Reflect.set writes to the generic-typed cache without a cast (a direct
131
+ // `this.#values[key] = value` is rejected: a generic type is read-only-indexed).
132
+ Reflect.set(this.#values, key, value)
133
+ process.env[key] = String(value)
90
134
  }
91
135
  }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Secret — wraps a sensitive value so it never leaks through logs, JSON, or
3
+ * `util.inspect`. Mirrors `@poppinss/utils`'s `Secret` (zero-dep here). Read the
4
+ * underlying value explicitly with {@link release}.
5
+ *
6
+ * const key = env.get('APP_KEY') // Secret<string> when declared via schema.secret
7
+ * key.toString() // '[redacted]'
8
+ * JSON.stringify({ key }) // '{"key":"[redacted]"}'
9
+ * key.release() // the real value — only when you deliberately need it
10
+ */
11
+ const REDACTED = '[redacted]'
12
+
13
+ export class Secret<T> {
14
+ readonly #value: T
15
+ readonly #keyword: string
16
+
17
+ constructor(value: T, redactedKeyword: string = REDACTED) {
18
+ this.#value = value
19
+ this.#keyword = redactedKeyword
20
+ }
21
+
22
+ /** Redacted placeholder (so `JSON.stringify` never emits the secret). */
23
+ toJSON(): string {
24
+ return this.#keyword
25
+ }
26
+
27
+ /** Redacted placeholder for string coercion + template literals. */
28
+ toString(): string {
29
+ return this.#keyword
30
+ }
31
+
32
+ /** Redacted placeholder for `console.log` / `util.inspect`. */
33
+ [Symbol.for('nodejs.util.inspect.custom')](): string {
34
+ return this.#keyword
35
+ }
36
+
37
+ /** Release the underlying value — the one deliberate way to read the secret. */
38
+ release(): T {
39
+ return this.#value
40
+ }
41
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Env value interpolation — AdonisJS-style variable substitution + identifiers,
3
+ * applied by {@link loadEnvFiles} as each `.env` value is loaded.
4
+ *
5
+ * - `$VAR` / `${VAR}` are replaced with the value of `VAR` (looked up against
6
+ * the values already loaded + `process.env`). A missing reference expands to
7
+ * an empty string. `\$` is a literal dollar sign (escape).
8
+ * - `identifier:rest` — when `identifier` was registered via
9
+ * {@link defineIdentifier}, the registered callback transforms `rest`
10
+ * (e.g. a `base64:` decoder). Unregistered prefixes (like `postgres://…`)
11
+ * are left untouched.
12
+ */
13
+
14
+ /** Transform the remainder of an `identifier:rest` value into its final form. */
15
+ export type IdentifierResolver = (value: string) => string
16
+
17
+ const identifiers = new Map<string, IdentifierResolver>()
18
+
19
+ /** Register an identifier resolver (AdonisJS `Env.defineIdentifier`). */
20
+ export function defineIdentifier(name: string, resolver: IdentifierResolver): void {
21
+ identifiers.set(name, resolver)
22
+ }
23
+
24
+ /** Register an identifier resolver only if the name is free (AdonisJS `defineIdentifierIfMissing`). */
25
+ export function defineIdentifierIfMissing(name: string, resolver: IdentifierResolver): void {
26
+ if (!identifiers.has(name)) identifiers.set(name, resolver)
27
+ }
28
+
29
+ /** Remove a registered identifier (AdonisJS `Env.removeIdentifier`). */
30
+ export function removeIdentifier(name: string): void {
31
+ identifiers.delete(name)
32
+ }
33
+
34
+ const REFERENCE = /(\\)?\$(?:\{([^}]+)\}|([A-Za-z_][A-Za-z0-9_]*))/g
35
+
36
+ /**
37
+ * Expand `$VAR` / `${VAR}` references and apply an identifier prefix. `lookup`
38
+ * resolves a referenced variable name to its current value (or undefined).
39
+ */
40
+ export function interpolate(value: string, lookup: (name: string) => string | undefined): string {
41
+ const substituted = value.replace(REFERENCE, (full, escaped, braced, bare) => {
42
+ // `\$FOO` → literal `$FOO` (drop only the escaping backslash).
43
+ if (escaped) return full.slice(1)
44
+ const name = braced ?? bare
45
+ return lookup(name) ?? ''
46
+ })
47
+
48
+ const prefixed = /^([A-Za-z_][A-Za-z0-9_]*):([\s\S]*)$/.exec(substituted)
49
+ if (prefixed) {
50
+ const resolver = identifiers.get(prefixed[1])
51
+ if (resolver) return resolver(prefixed[2])
52
+ }
53
+ return substituted
54
+ }