@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
@@ -42,6 +42,7 @@ export interface RpcConfig {
42
42
 
43
43
  export class RpcProvider extends Provider {
44
44
  readonly rpc: RpcRouter
45
+ #registered = false
45
46
 
46
47
  constructor(app: AppContext, options: RpcProviderOptions = {}) {
47
48
  super(app)
@@ -51,7 +52,9 @@ export class RpcProvider extends Provider {
51
52
  override register(): void {
52
53
  const container = this.app.container
53
54
  if (container.has('rpc')) {
54
- if (container.resolve('rpc') === this.rpc) return // re-register is idempotent
55
+ // Idempotent re-register: tracked with a flag rather than
56
+ // `container.resolve('rpc')` because resolution is now async.
57
+ if (this.#registered) return
55
58
  throw new ReamError(
56
59
  'RPC_PROVIDER_ALREADY_REGISTERED',
57
60
  "Container token 'rpc' is already bound to a different instance",
@@ -62,12 +65,13 @@ export class RpcProvider extends Provider {
62
65
  }
63
66
  this.rpc.useContainer(container)
64
67
  container.singleton('rpc', () => this.rpc)
68
+ this.#registered = true
65
69
  }
66
70
 
67
71
  override async boot(): Promise<void> {
68
72
  const config = this.app.config.get<RpcConfig>('rpc')
69
73
  const path = config?.path ?? '/rpc'
70
- const router = this.app.container.make<Router>('router')
74
+ const router = await this.app.container.make<Router>('router')
71
75
  // RpcRouter.handle() reads the registered methods at request time, so the
72
76
  // mount can happen before apps finish registering methods.
73
77
  const route = router.post(path, (ctx: HttpContext) => this.rpc.handle(ctx))
@@ -20,7 +20,7 @@ import {
20
20
  } from '@c9up/comet'
21
21
  import type { HttpContext } from '../http/HttpContext.js'
22
22
  import type { MiddlewareRegistry, RuntimeValidator } from '../middleware/Pipeline.js'
23
- import { compose } from '../middleware/Pipeline.js'
23
+ import { compose, runValidator } from '../middleware/Pipeline.js'
24
24
 
25
25
  export type RpcHandler = (ctx: HttpContext, params: unknown) => Promise<unknown> | unknown
26
26
 
@@ -29,8 +29,8 @@ export type RpcHandler = (ctx: HttpContext, params: unknown) => Promise<unknown>
29
29
  * the shared `'middleware'` registry, and `validator:<name>` schemas.
30
30
  */
31
31
  interface RpcContainer {
32
- make<T>(target: new (...args: unknown[]) => T): T
33
- resolve<T>(token: string): T
32
+ make<T>(target: new (...args: unknown[]) => T): Promise<T>
33
+ resolve<T>(token: string): Promise<T>
34
34
  has(token: string): boolean
35
35
  }
36
36
 
@@ -101,7 +101,7 @@ function checkRpcAuthorization(
101
101
  def: RpcMethodDefinition,
102
102
  ): { code: number; message: string } | undefined {
103
103
  const needsAuth = def.guards.length > 0 || def.roles.length > 0 || def.permissions.length > 0
104
- if (needsAuth && !ctx.auth?.authenticated) {
104
+ if (needsAuth && !ctx.auth?.isAuthenticated) {
105
105
  return { code: -32003, message: 'Unauthorized' }
106
106
  }
107
107
  if (def.roles.length > 0) {
@@ -163,7 +163,7 @@ export class RpcRouter {
163
163
  this.method(`${prefix}.${methodName}`, async (ctx, params) => {
164
164
  // Resolve through the container on every call (fresh DI per request,
165
165
  // like GraphQLEngine), falling back to a bare `new` when unset.
166
- const instance = this.#container ? this.#container.make(controller) : new controller()
166
+ const instance = this.#container ? await this.#container.make(controller) : new controller()
167
167
  return instance[methodName](ctx, params)
168
168
  })
169
169
  }
@@ -238,7 +238,7 @@ export class RpcRouter {
238
238
  */
239
239
  async #runMiddleware(ctx: HttpContext, names: string[]): Promise<void> {
240
240
  const registry = this.#container?.has('middleware')
241
- ? this.#container.resolve<MiddlewareRegistry>('middleware')
241
+ ? await this.#container.resolve<MiddlewareRegistry>('middleware')
242
242
  : undefined
243
243
  if (!registry) {
244
244
  throw new Error(
@@ -285,7 +285,22 @@ export class RpcRouter {
285
285
  id,
286
286
  )
287
287
  }
288
- const outcome = this.#container.resolve<RuntimeValidator>(token).validate(params)
288
+ const validator = await this.#container.resolve<RuntimeValidator>(token)
289
+ // `runValidator` picks the async result form first — the synchronous
290
+ // one cannot run `unique` / `exists` and throws outright when the schema
291
+ // carries them.
292
+ let outcome: Awaited<ReturnType<typeof runValidator>>
293
+ try {
294
+ outcome = await runValidator(validator, params)
295
+ } catch (err) {
296
+ return rpcError(
297
+ -32603,
298
+ err instanceof TypeError
299
+ ? `Validator '${def.validator}' exposes no usable validate method.`
300
+ : `Validator '${def.validator}' failed: ${err instanceof Error ? err.message : String(err)}`,
301
+ id,
302
+ )
303
+ }
289
304
  if (!outcome.valid) return rpcError(-32602, 'Invalid params', id, outcome.errors)
290
305
  effectiveParams = outcome.data ?? params
291
306
  }
@@ -41,6 +41,7 @@ let activeProvider: ScheduleProvider | undefined
41
41
  export class ScheduleProvider extends Provider {
42
42
  readonly scheduler: Scheduler
43
43
  #booted = false
44
+ #registered = false
44
45
 
45
46
  constructor(app: AppContext, options: ScheduleProviderOptions = {}) {
46
47
  super(app)
@@ -65,20 +66,10 @@ export class ScheduleProvider extends Provider {
65
66
  override register(): void {
66
67
  const scheduler = this.scheduler
67
68
  const container = this.app.container
68
- // Probe for an existing binding. The `make` / `resolve` API
69
- // throws when unbound; use it to distinguish "same instance"
70
- // from "another provider already claimed the token".
71
- let existing: unknown
72
- try {
73
- existing = container.resolve('scheduler')
74
- } catch {
75
- existing = undefined
76
- }
77
- if (existing !== undefined) {
78
- if (existing === scheduler) {
79
- // Same provider re-running its register phase — no-op.
80
- return
81
- }
69
+ if (container.has('scheduler')) {
70
+ // Idempotent re-register: tracked with a flag rather than
71
+ // `container.resolve('scheduler')` because resolution is now async.
72
+ if (this.#registered) return
82
73
  throw new ReamError(
83
74
  'SCHEDULE_PROVIDER_ALREADY_REGISTERED',
84
75
  "Container token 'scheduler' is already bound to a different instance",
@@ -88,6 +79,7 @@ export class ScheduleProvider extends Provider {
88
79
  )
89
80
  }
90
81
  container.singleton('scheduler', () => scheduler)
82
+ this.#registered = true
91
83
  }
92
84
 
93
85
  override async boot(): Promise<void> {
@@ -130,7 +122,7 @@ export class ScheduleProvider extends Provider {
130
122
  this.scheduler.register(taskName, cronExpr, async () => {
131
123
  // Resolve from the container on every fire so transient
132
124
  // services get a fresh instance each run.
133
- const instance = this.app.container.make<Record<string, unknown>>(target)
125
+ const instance = await this.app.container.make<Record<string, unknown>>(target)
134
126
  const method = instance[methodName]
135
127
  if (typeof method !== 'function') {
136
128
  throw new ReamError(
@@ -12,11 +12,12 @@ import type { MiddlewareFunction } from '../middleware/Pipeline.js'
12
12
  import type { Router } from '../router/Router.js'
13
13
 
14
14
  /**
15
- * A middleware class with a handle method.
16
- * This is the pattern used by AdonisJS middleware.
15
+ * A middleware class with a handle method. The optional third argument carries
16
+ * per-route configuration passed through a named-middleware factory (AdonisJS
17
+ * `handle(ctx, next, args)`).
17
18
  */
18
19
  export interface MiddlewareClass {
19
- handle(ctx: HttpContext, next: () => Promise<void>): Promise<void> | void
20
+ handle(ctx: HttpContext, next: () => Promise<void>, args?: unknown): Promise<void> | void
20
21
  }
21
22
 
22
23
  /** Lazy import returning a module with a default export. */
@@ -109,7 +110,11 @@ export class Server {
109
110
 
110
111
  /**
111
112
  * Convert a MiddlewareEntry (lazy import or function) into a MiddlewareFunction.
112
- * Lazy imports are resolved on first call and cached.
113
+ * The imported class is cached on first call; the INSTANCE is resolved through
114
+ * the request's IoC container so `@inject()` constructor dependencies are wired
115
+ * (AdonisJS resolves middleware via the container — a plain `new Class()` gives
116
+ * every injected dependency `undefined`). Falls back to `new Class()` only when
117
+ * no container is present (mock contexts in unit tests).
113
118
  */
114
119
  export function resolveMiddlewareEntry(entry: MiddlewareEntry): MiddlewareFunction {
115
120
  // Direct middleware function (2 params: ctx, next)
@@ -117,16 +122,50 @@ export function resolveMiddlewareEntry(entry: MiddlewareEntry): MiddlewareFuncti
117
122
  return entry
118
123
  }
119
124
 
120
- // Lazy import of a middleware class — resolve + cache on first invocation
121
- // After isMiddlewareFunction returns false, entry is narrowed to LazyImport<MiddlewareClassConstructor>
125
+ // Lazy import of a middleware class — cache the CLASS on first invocation,
126
+ // then resolve the instance per request through the container (DI). The
127
+ // container's own singleton bindings still cache instances where desired.
122
128
  const lazyImport = entry
123
- let cached: MiddlewareClass | undefined
129
+ let cachedClass: MiddlewareClassConstructor | undefined
124
130
  return async (ctx: HttpContext, next: () => Promise<void>) => {
125
- if (!cached) {
131
+ if (!cachedClass) {
126
132
  const mod = await lazyImport()
127
- cached = new mod.default()
133
+ cachedClass = mod.default
134
+ }
135
+ const instance = ctx.containerResolver
136
+ ? await ctx.containerResolver.make<MiddlewareClass>(cachedClass)
137
+ : new cachedClass()
138
+ await instance.handle(ctx, next)
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Turn a middleware entry into a FACTORY `(args?) => MiddlewareFunction`
144
+ * (AdonisJS named-middleware factories). The returned function bakes `args` in
145
+ * and forwards them to the class's `handle(ctx, next, args)`. A plain function
146
+ * middleware ignores `args` (it has no third parameter). Class resolution +
147
+ * caching mirror {@link resolveMiddlewareEntry} (imported once, instance via DI).
148
+ */
149
+ export function resolveParametrizedMiddlewareEntry(
150
+ entry: MiddlewareEntry,
151
+ ): (args?: unknown) => MiddlewareFunction {
152
+ if (isMiddlewareFunction(entry)) {
153
+ return () => entry
154
+ }
155
+
156
+ const lazyImport = entry
157
+ let cachedClass: MiddlewareClassConstructor | undefined
158
+ return (args?: unknown): MiddlewareFunction => {
159
+ return async (ctx: HttpContext, next: () => Promise<void>) => {
160
+ if (!cachedClass) {
161
+ const mod = await lazyImport()
162
+ cachedClass = mod.default
163
+ }
164
+ const instance = ctx.containerResolver
165
+ ? await ctx.containerResolver.make<MiddlewareClass>(cachedClass)
166
+ : new cachedClass()
167
+ await instance.handle(ctx, next, args)
128
168
  }
129
- await cached.handle(ctx, next)
130
169
  }
131
170
  }
132
171
 
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Usage:
5
5
  * import app from '@c9up/ream/services/app'
6
- * const logger = app.container.make('logger')
6
+ * const logger = await app.container.make('logger')
7
7
  * if (app.inProduction) { ... }
8
8
  */
9
9
 
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Console singleton service.
3
+ *
4
+ * Usage:
5
+ * import consoleApp from '@c9up/ream/services/console'
6
+ * if (await consoleApp.hasCommand('make:controller')) {
7
+ * const command = await consoleApp.exec('make:controller', ['user', '--resource'])
8
+ * }
9
+ */
10
+
11
+ import type { Console } from '../console/Console.js'
12
+
13
+ let instance: Console | undefined
14
+
15
+ /** @internal Set the consoleApp instance (called by Ignitor). */
16
+ export function setConsole(consoleApp: Console): void {
17
+ instance = consoleApp
18
+ }
19
+
20
+ /**
21
+ * @internal Unset the locator IF it still points at `consoleApp` (called by
22
+ * Ignitor.stop()) — same ownership guard as the app service: a second Ignitor
23
+ * rebinding the locator must not be torn down by the first one stopping.
24
+ */
25
+ export function clearConsole(consoleApp: Console): void {
26
+ if (instance === consoleApp) instance = undefined
27
+ }
28
+
29
+ const consoleApp: Console = new Proxy({} as Console, {
30
+ get(_target, prop) {
31
+ if (!instance) {
32
+ throw new Error(
33
+ 'Console accessed before initialization. ' +
34
+ 'Ensure this code runs during or after the boot phase.',
35
+ )
36
+ }
37
+ const value = Reflect.get(instance, prop, instance)
38
+ return typeof value === 'function' ? value.bind(instance) : value
39
+ },
40
+ })
41
+
42
+ export default consoleApp
@@ -56,7 +56,7 @@ export default class SessionMiddleware {
56
56
  const isProduction = process.env.NODE_ENV === 'production'
57
57
 
58
58
  // Read session ID from cookie — pre-parsed by the Rust HyperServer.
59
- let sessionId = ctx.request.cookie(cookieName)
59
+ let sessionId = ctx.request.plainCookie(cookieName)
60
60
  const hadIncomingCookie = sessionId !== null
61
61
 
62
62
  // Cookie driver: the cookie value IS the session data (encrypted)
@@ -65,7 +65,7 @@ export default class SessionMiddleware {
65
65
  sessionId = sessionId ?? generateSessionId()
66
66
  const session = new Session(sessionId, data)
67
67
  ctx.store.set('session', session)
68
- ctx.session = session
68
+ ctx.session = session
69
69
 
70
70
  await next()
71
71
 
@@ -85,7 +85,7 @@ export default class SessionMiddleware {
85
85
  'Switch to a server-side driver (memory/redis) or reduce session data.\n',
86
86
  )
87
87
  }
88
- ctx.response.cookie(cookieName, encoded, {
88
+ ctx.response.plainCookie(cookieName, encoded, {
89
89
  maxAge: this.#config.clearWithBrowser ? undefined : maxAge,
90
90
  path: '/',
91
91
  httpOnly: true,
@@ -145,7 +145,7 @@ export default class SessionMiddleware {
145
145
  // defeats HTTP caching, and leaks a Set-Cookie to CDNs. This mirrors
146
146
  // the cookie-driver path above (which already omits `isNew`).
147
147
  if (session.isDirty() || regenerated || (rolling && hadIncomingCookie)) {
148
- ctx.response.cookie(cookieName, session.sessionId, {
148
+ ctx.response.plainCookie(cookieName, session.sessionId, {
149
149
  maxAge: this.#config.clearWithBrowser ? undefined : maxAge,
150
150
  path: '/',
151
151
  httpOnly: true,
@@ -34,7 +34,7 @@ export class StaticProvider extends Provider {
34
34
  const config = this.app.config.get<StaticConfig>('static')
35
35
  const middleware = this.#override ?? (config?.root ? new StaticMiddleware(config) : undefined)
36
36
  if (!middleware) return // not configured — opt-out
37
- const server = this.app.container.make<Server>('server')
37
+ const server = await this.app.container.make<Server>('server')
38
38
  server.use([(ctx, next) => middleware.handle(ctx, next)])
39
39
  }
40
40
  }