@c9up/ream 0.2.14 → 0.2.16

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 (206) hide show
  1. package/README.md +1 -0
  2. package/dist/Application.d.ts.map +1 -1
  3. package/dist/Application.js +6 -1
  4. package/dist/Application.js.map +1 -1
  5. package/dist/Ignitor.d.ts +9 -0
  6. package/dist/Ignitor.d.ts.map +1 -1
  7. package/dist/Ignitor.js +29 -3
  8. package/dist/Ignitor.js.map +1 -1
  9. package/dist/Provider.d.ts +20 -2
  10. package/dist/Provider.d.ts.map +1 -1
  11. package/dist/Provider.js.map +1 -1
  12. package/dist/bodyparser/BodyParserMiddleware.d.ts +1 -1
  13. package/dist/bodyparser/BodyParserMiddleware.d.ts.map +1 -1
  14. package/dist/bodyparser/BodyParserMiddleware.js +12 -1
  15. package/dist/bodyparser/BodyParserMiddleware.js.map +1 -1
  16. package/dist/bodyparser/MultipartFile.d.ts.map +1 -1
  17. package/dist/bodyparser/MultipartFile.js +7 -3
  18. package/dist/bodyparser/MultipartFile.js.map +1 -1
  19. package/dist/bodyparser/config.d.ts +15 -0
  20. package/dist/bodyparser/config.d.ts.map +1 -0
  21. package/dist/bodyparser/config.js +16 -0
  22. package/dist/bodyparser/config.js.map +1 -0
  23. package/dist/bodyparser/parseSize.d.ts.map +1 -1
  24. package/dist/bodyparser/parseSize.js +3 -2
  25. package/dist/bodyparser/parseSize.js.map +1 -1
  26. package/dist/commands/MigrateBase.d.ts.map +1 -1
  27. package/dist/commands/MigrateBase.js +4 -1
  28. package/dist/commands/MigrateBase.js.map +1 -1
  29. package/dist/config/moduleConfig.d.ts +15 -0
  30. package/dist/config/moduleConfig.d.ts.map +1 -0
  31. package/dist/config/moduleConfig.js +24 -0
  32. package/dist/config/moduleConfig.js.map +1 -0
  33. package/dist/console/Kernel.d.ts.map +1 -1
  34. package/dist/console/Kernel.js +7 -0
  35. package/dist/console/Kernel.js.map +1 -1
  36. package/dist/console/cliui.d.ts +2 -0
  37. package/dist/console/cliui.d.ts.map +1 -1
  38. package/dist/console/cliui.js +2 -1
  39. package/dist/console/cliui.js.map +1 -1
  40. package/dist/console/types.d.ts +14 -0
  41. package/dist/console/types.d.ts.map +1 -1
  42. package/dist/console/types.js.map +1 -1
  43. package/dist/container/Container.d.ts.map +1 -1
  44. package/dist/container/Container.js +6 -1
  45. package/dist/container/Container.js.map +1 -1
  46. package/dist/container/types.d.ts +11 -1
  47. package/dist/container/types.d.ts.map +1 -1
  48. package/dist/decorators/Service.d.ts +1 -1
  49. package/dist/decorators/Service.d.ts.map +1 -1
  50. package/dist/env/Env.d.ts.map +1 -1
  51. package/dist/env/Env.js +4 -2
  52. package/dist/env/Env.js.map +1 -1
  53. package/dist/env/interpolate.d.ts.map +1 -1
  54. package/dist/env/interpolate.js +5 -3
  55. package/dist/env/interpolate.js.map +1 -1
  56. package/dist/errors/FuzzyMatcher.d.ts.map +1 -1
  57. package/dist/errors/FuzzyMatcher.js +30 -13
  58. package/dist/errors/FuzzyMatcher.js.map +1 -1
  59. package/dist/errors/PipelineStageError.d.ts.map +1 -1
  60. package/dist/errors/PipelineStageError.js +4 -2
  61. package/dist/errors/PipelineStageError.js.map +1 -1
  62. package/dist/events/Emitter.d.ts +21 -4
  63. package/dist/events/Emitter.d.ts.map +1 -1
  64. package/dist/events/Emitter.js +6 -2
  65. package/dist/events/Emitter.js.map +1 -1
  66. package/dist/events/testing/FakeBus.d.ts +1 -1
  67. package/dist/events/testing/FakeBus.d.ts.map +1 -1
  68. package/dist/events/testing/FakeBus.js.map +1 -1
  69. package/dist/graphql/GraphQLEngine.d.ts +2 -2
  70. package/dist/graphql/GraphQLEngine.d.ts.map +1 -1
  71. package/dist/graphql/GraphQLEngine.js +4 -2
  72. package/dist/graphql/GraphQLEngine.js.map +1 -1
  73. package/dist/helpers/bytes.d.ts.map +1 -1
  74. package/dist/helpers/bytes.js +13 -2
  75. package/dist/helpers/bytes.js.map +1 -1
  76. package/dist/helpers/string.js +3 -3
  77. package/dist/helpers/string.js.map +1 -1
  78. package/dist/http/Exception.d.ts +8 -4
  79. package/dist/http/Exception.d.ts.map +1 -1
  80. package/dist/http/Exception.js +14 -8
  81. package/dist/http/Exception.js.map +1 -1
  82. package/dist/http/Request.d.ts.map +1 -1
  83. package/dist/http/Request.js +10 -7
  84. package/dist/http/Request.js.map +1 -1
  85. package/dist/http/mime.d.ts.map +1 -1
  86. package/dist/http/mime.js +2 -1
  87. package/dist/http/mime.js.map +1 -1
  88. package/dist/index.d.ts +2 -2
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +1 -1
  91. package/dist/native/generated.d.ts +12 -8
  92. package/dist/native/generated.d.ts.map +1 -1
  93. package/dist/openapi/OpenApiGenerator.d.ts.map +1 -1
  94. package/dist/openapi/OpenApiGenerator.js +6 -4
  95. package/dist/openapi/OpenApiGenerator.js.map +1 -1
  96. package/dist/router/Router.d.ts +10 -1
  97. package/dist/router/Router.d.ts.map +1 -1
  98. package/dist/router/Router.js +22 -10
  99. package/dist/router/Router.js.map +1 -1
  100. package/dist/rpc/RpcRouter.d.ts +16 -3
  101. package/dist/rpc/RpcRouter.d.ts.map +1 -1
  102. package/dist/rpc/RpcRouter.js +34 -3
  103. package/dist/rpc/RpcRouter.js.map +1 -1
  104. package/dist/scheduler/Schedule.d.ts +6 -1
  105. package/dist/scheduler/Schedule.d.ts.map +1 -1
  106. package/dist/scheduler/Schedule.js +5 -0
  107. package/dist/scheduler/Schedule.js.map +1 -1
  108. package/dist/scheduler/ScheduleProvider.d.ts +15 -11
  109. package/dist/scheduler/ScheduleProvider.d.ts.map +1 -1
  110. package/dist/scheduler/ScheduleProvider.js +21 -12
  111. package/dist/scheduler/ScheduleProvider.js.map +1 -1
  112. package/dist/security/CookieSigner.d.ts.map +1 -1
  113. package/dist/security/CookieSigner.js +12 -5
  114. package/dist/security/CookieSigner.js.map +1 -1
  115. package/dist/security/SignedUrl.js +3 -2
  116. package/dist/security/SignedUrl.js.map +1 -1
  117. package/dist/session/SessionMiddleware.d.ts +2 -4
  118. package/dist/session/SessionMiddleware.d.ts.map +1 -1
  119. package/dist/session/SessionMiddleware.js +13 -1
  120. package/dist/session/SessionMiddleware.js.map +1 -1
  121. package/dist/session/config.d.ts +25 -0
  122. package/dist/session/config.d.ts.map +1 -0
  123. package/dist/session/config.js +18 -0
  124. package/dist/session/config.js.map +1 -0
  125. package/dist/session/drivers/DatabaseDriver.d.ts +13 -1
  126. package/dist/session/drivers/DatabaseDriver.d.ts.map +1 -1
  127. package/dist/session/drivers/DatabaseDriver.js +23 -3
  128. package/dist/session/drivers/DatabaseDriver.js.map +1 -1
  129. package/dist/session/drivers/RedisDriver.d.ts.map +1 -1
  130. package/dist/session/drivers/RedisDriver.js +10 -2
  131. package/dist/session/drivers/RedisDriver.js.map +1 -1
  132. package/dist/testing/ApiResponse.d.ts.map +1 -1
  133. package/dist/testing/ApiResponse.js +10 -11
  134. package/dist/testing/ApiResponse.js.map +1 -1
  135. package/dist/testing/TestClient.js +6 -6
  136. package/dist/testing/TestClient.js.map +1 -1
  137. package/dist/utils/Macroable.d.ts +6 -2
  138. package/dist/utils/Macroable.d.ts.map +1 -1
  139. package/dist/utils/Macroable.js +4 -0
  140. package/dist/utils/Macroable.js.map +1 -1
  141. package/dist/utils/objectPath.d.ts.map +1 -1
  142. package/dist/utils/objectPath.js +14 -6
  143. package/dist/utils/objectPath.js.map +1 -1
  144. package/events.darwin-arm64.node +0 -0
  145. package/events.darwin-x64.node +0 -0
  146. package/events.linux-arm64-gnu.node +0 -0
  147. package/events.linux-x64-gnu.node +0 -0
  148. package/events.win32-x64-msvc.node +0 -0
  149. package/index.darwin-arm64.node +0 -0
  150. package/index.darwin-x64.node +0 -0
  151. package/index.linux-arm64-gnu.node +0 -0
  152. package/index.linux-x64-gnu.node +0 -0
  153. package/index.win32-x64-msvc.node +0 -0
  154. package/package.json +13 -4
  155. package/scheduler.darwin-arm64.node +0 -0
  156. package/scheduler.darwin-x64.node +0 -0
  157. package/scheduler.linux-arm64-gnu.node +0 -0
  158. package/scheduler.linux-x64-gnu.node +0 -0
  159. package/scheduler.win32-x64-msvc.node +0 -0
  160. package/scripts/build-napi-types.mjs +4 -3
  161. package/scripts/generate-napi-types.mjs +28 -12
  162. package/src/Application.ts +6 -1
  163. package/src/Ignitor.ts +42 -3
  164. package/src/Provider.ts +21 -2
  165. package/src/bodyparser/BodyParserMiddleware.ts +12 -1
  166. package/src/bodyparser/MultipartFile.ts +7 -3
  167. package/src/bodyparser/config.ts +18 -0
  168. package/src/bodyparser/parseSize.ts +3 -2
  169. package/src/commands/MigrateBase.ts +4 -1
  170. package/src/config/moduleConfig.ts +24 -0
  171. package/src/console/BaseCommand.ts +1 -1
  172. package/src/console/Kernel.ts +8 -0
  173. package/src/console/cliui.ts +2 -1
  174. package/src/console/types.ts +15 -0
  175. package/src/container/Container.ts +15 -6
  176. package/src/container/types.ts +11 -1
  177. package/src/decorators/Service.ts +1 -1
  178. package/src/env/Env.ts +4 -2
  179. package/src/env/interpolate.ts +5 -3
  180. package/src/errors/FuzzyMatcher.ts +34 -13
  181. package/src/errors/PipelineStageError.ts +4 -2
  182. package/src/events/Emitter.ts +34 -8
  183. package/src/events/testing/FakeBus.ts +12 -5
  184. package/src/graphql/GraphQLEngine.ts +11 -7
  185. package/src/helpers/bytes.ts +13 -2
  186. package/src/helpers/string.ts +3 -3
  187. package/src/http/Exception.ts +14 -8
  188. package/src/http/Request.ts +8 -5
  189. package/src/http/mime.ts +2 -1
  190. package/src/index.ts +2 -2
  191. package/src/native/generated.ts +12 -8
  192. package/src/openapi/OpenApiGenerator.ts +6 -4
  193. package/src/router/Router.ts +31 -11
  194. package/src/rpc/RpcRouter.ts +38 -8
  195. package/src/scheduler/Schedule.ts +8 -1
  196. package/src/scheduler/ScheduleProvider.ts +22 -12
  197. package/src/security/CookieSigner.ts +15 -5
  198. package/src/security/SignedUrl.ts +3 -2
  199. package/src/session/SessionMiddleware.ts +15 -2
  200. package/src/session/config.ts +29 -0
  201. package/src/session/drivers/DatabaseDriver.ts +36 -8
  202. package/src/session/drivers/RedisDriver.ts +13 -5
  203. package/src/testing/ApiResponse.ts +9 -10
  204. package/src/testing/TestClient.ts +6 -6
  205. package/src/utils/Macroable.ts +6 -2
  206. package/src/utils/objectPath.ts +12 -6
@@ -72,9 +72,14 @@ export function hasPath(source, path) {
72
72
  */
73
73
  export function setPath(target, path, value) {
74
74
  const segments = toSegments(path);
75
+ // The last segment is the one written; everything before it is walked. Split
76
+ // off by name rather than by index, so neither has to be read back out of
77
+ // the array afterwards and re-checked for existence.
78
+ const last = segments.pop();
79
+ if (last === undefined)
80
+ return target;
75
81
  let current = target;
76
- for (let i = 0; i < segments.length - 1; i += 1) {
77
- const key = segments[i];
82
+ for (const key of segments) {
78
83
  const next = current[key];
79
84
  if (!isIndexable(next)) {
80
85
  const created = {};
@@ -85,7 +90,7 @@ export function setPath(target, path, value) {
85
90
  current = next;
86
91
  }
87
92
  }
88
- current[segments[segments.length - 1]] = value;
93
+ current[last] = value;
89
94
  return target;
90
95
  }
91
96
  /**
@@ -110,14 +115,17 @@ export function omitPaths(source, paths) {
110
115
  const result = structuredClone(source);
111
116
  for (const path of paths) {
112
117
  const segments = toSegments(path);
118
+ const last = segments.pop();
119
+ if (last === undefined)
120
+ continue;
113
121
  let current = result;
114
- for (let i = 0; i < segments.length - 1; i += 1) {
122
+ for (const key of segments) {
115
123
  if (!isIndexable(current))
116
124
  break;
117
- current = current[segments[i]];
125
+ current = current[key];
118
126
  }
119
127
  if (isIndexable(current))
120
- delete current[segments[segments.length - 1]];
128
+ delete current[last];
121
129
  }
122
130
  return result;
123
131
  }
@@ -1 +1 @@
1
- {"version":3,"file":"objectPath.js","sourceRoot":"","sources":["../../src/utils/objectPath.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,gFAAgF;AAChF,SAAS,WAAW,CAAC,KAAc;IACjC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAA;AACpD,CAAC;AAED;;;;GAIG;AACH,SAAS,UAAU,CAAC,IAAY;IAC9B,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,CAAA;IACvE,MAAM,QAAQ,GAAa,EAAE,CAAA;IAC7B,MAAM,OAAO,GAAG,WAAW,CAAA;IAC3B,IAAI,KAAK,GAA2B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACtD,OAAO,KAAK,KAAK,IAAI,EAAE,CAAC;QACtB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;QACvB,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC5B,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,MAAe,EAAE,IAAY,EAAE,YAAsB;IAC3E,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IACjC,IAAI,OAAO,GAAY,MAAM,CAAA;IAC7B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC;YAAE,OAAO,YAAY,CAAA;QAC9C,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5B,CAAC;IACD,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAA;AACvD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,MAAe,EAAE,IAAY;IACnD,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IACjC,IAAI,OAAO,GAAY,MAAM,CAAA;IAC7B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC;YAAE,OAAO,KAAK,CAAA;QAC3E,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5B,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,OAAO,CAAC,MAAqB,EAAE,IAAY,EAAE,KAAc;IACzE,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;IACjC,IAAI,OAAO,GAAkB,MAAM,CAAA;IACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;QACvB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;QACzB,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,MAAM,OAAO,GAAkB,EAAE,CAAA;YACjC,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAA;YACtB,OAAO,GAAG,OAAO,CAAA;QACnB,CAAC;aAAM,CAAC;YACN,OAAO,GAAG,IAAI,CAAA;QAChB,CAAC;IACH,CAAC;IACD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAA;IAC9C,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,MAAe,EAAE,KAAe;IACxD,MAAM,MAAM,GAAkB,EAAE,CAAA;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC;YAAE,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAA;IACzE,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,MAAqB,EAAE,KAAe;IAC9D,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,CAAA;IACtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,OAAO,GAAY,MAAM,CAAA;QAC7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC;gBAAE,MAAK;YAChC,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;QAChC,CAAC;QACD,IAAI,WAAW,CAAC,OAAO,CAAC;YAAE,OAAO,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAA;IACzE,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,MAAe,EAAE,MAAe;IACxD,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;IAChG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;IAChG,MAAM,GAAG,GAAkB,EAAE,GAAG,MAAM,EAAE,CAAA;IACxC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,GAAG,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;IAC7C,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
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.2.14",
3
+ "version": "0.2.16",
4
4
  "description": "Ream — Rust-powered Node.js application framework",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -187,6 +187,14 @@
187
187
  "./swcrc.app.json": "./swcrc.app.json",
188
188
  "./tsconfig.package.json": "./tsconfig.package.json",
189
189
  "./tsconfig.client.json": "./tsconfig.client.json",
190
+ "./session/config": {
191
+ "types": "./dist/session/config.d.ts",
192
+ "import": "./dist/session/config.js"
193
+ },
194
+ "./bodyparser/config": {
195
+ "types": "./dist/bodyparser/config.d.ts",
196
+ "import": "./dist/bodyparser/config.js"
197
+ },
190
198
  "./session_middleware": {
191
199
  "types": "./dist/session/SessionMiddleware.d.ts",
192
200
  "import": "./dist/session/SessionMiddleware.js"
@@ -228,9 +236,10 @@
228
236
  }
229
237
  },
230
238
  "devDependencies": {
231
- "@biomejs/biome": "^2.4.10",
239
+ "@biomejs/biome": "2.5.12",
232
240
  "@c9up/comet": "^0.1.0",
233
241
  "@types/node": "^22.19.15",
242
+ "@vitest/coverage-v8": "^4.1.6",
234
243
  "mime-db": "^1.54.0",
235
244
  "mime-types": "^3.0.2",
236
245
  "typescript": "^6.0.2",
@@ -254,9 +263,9 @@
254
263
  "build:napi": "pnpm build:rust && pnpm build:napi-types && node scripts/copy-napi.mjs",
255
264
  "test": "vitest run",
256
265
  "test:rust": "cargo test --all",
257
- "lint": "biome check src/",
266
+ "lint": "biome check src/ tests/",
258
267
  "test:coverage": "vitest run --coverage",
259
268
  "typecheck": "tsc --noEmit",
260
- "typecheck:console-tests": "tsc --noEmit -p tsconfig.tests.json"
269
+ "typecheck:tests": "tsc --noEmit -p tsconfig.tests.json"
261
270
  }
262
271
  }
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -7,7 +7,8 @@
7
7
  * cargo a `/tmp/...` path the native proc-macro cannot write to, so the
8
8
  * type-def file comes back empty and the build fails for no visible reason.
9
9
  *
10
- * One crate at a time on purpose: napi-derive APPENDS to `TYPE_DEF_TMP_PATH`
10
+ * One crate at a time on purpose: with napi-derive 2 the writes all went to a
11
+ * single `TYPE_DEF_TMP_PATH` and a parallel build interleaved them
11
12
  * while cargo compiles, and a parallel build interleaves the writes —
12
13
  * definitions go missing, silently, and the generated file comes out short.
13
14
  */
@@ -28,11 +29,11 @@ const scratch = mkdtempSync(join(tmpdir(), 'napi-types-'))
28
29
  try {
29
30
  const lines = []
30
31
  for (const crate of CRATES) {
31
- const perCrate = join(scratch, `${crate}.jsonl`)
32
+ const perCrate = join(scratch, crate)
32
33
  writeFileSync(perCrate, '')
33
34
  execFileSync('cargo', ['build', '-p', crate], {
34
35
  cwd: packageRoot,
35
- env: { ...process.env, TYPE_DEF_TMP_PATH: perCrate },
36
+ env: { ...process.env, NAPI_TYPE_DEF_TMP_FOLDER: scratch },
36
37
  stdio: ['ignore', 'ignore', 'inherit'],
37
38
  })
38
39
  const emitted = readFileSync(perCrate, 'utf8').split('\n').filter(Boolean)
@@ -29,27 +29,40 @@ import { readFileSync, writeFileSync } from 'node:fs'
29
29
  * entry must match, or the script fails: a stale refinement is how a hand
30
30
  * annotation quietly stops describing the Rust.
31
31
  */
32
+ /**
33
+ * napi-derive 3 emits `callback: (arg: unknown) => unknown` where 2 emitted
34
+ * `(...args: any[]) => any` — better, since it no longer reaches for `any`,
35
+ * but a different string to match on. These refinements exist because the
36
+ * generated shape says nothing about what the callback actually receives.
37
+ */
32
38
  const CALLBACK_REFINEMENTS = {
33
39
  'EventBus.subscribe': {
34
- from: 'callback: (...args: any[]) => any',
40
+ from: 'callback: (arg: unknown) => unknown',
35
41
  to: 'callback: (eventJson: string) => void',
36
42
  },
37
43
  'EventBus.onRequest': {
38
- from: 'callback: (...args: any[]) => any',
44
+ from: 'callback: (arg: unknown) => unknown',
39
45
  to: 'callback: (eventJson: string, reply: (response: string) => void) => void',
40
46
  },
41
47
  'HyperServer.onRequest': {
42
- from: 'callback: (...args: any[]) => any',
43
- to: 'callback: (request: string) => Promise<string> | string',
48
+ // Only the argument is named. The `to` used to say
49
+ // `(request: string) => Promise<string> | string`, which described the
50
+ // boundary as it was BEFORE it stopped serialising: the Rust hands the
51
+ // handler a JsObject and awaits a `NapiResponse`, and napi's own output
52
+ // already said so. The refinement was overwriting a correct shape with a
53
+ // stale one, and nothing noticed because every caller of `onRequest` was
54
+ // outside the typecheck.
55
+ from: 'callback: (arg: unknown) => Promise<NapiResponse>',
56
+ to: 'callback: (request: Record<string, unknown>) => Promise<NapiResponse>',
44
57
  },
45
58
  'HyperServer.onStreamDisconnect': {
46
- from: 'callback: (...args: any[]) => any',
59
+ from: 'callback: (arg: unknown) => unknown',
47
60
  to: 'callback: (streamId: string) => void',
48
61
  },
49
62
  // The Rust hands over a serialised JsTaskPayload — `{ task_name,
50
63
  // scheduled_for_ms }` renamed to camelCase by serde.
51
64
  'RustScheduler.register': {
52
- from: 'callback: (...args: any[]) => any',
65
+ from: 'callback: (arg: unknown) => unknown',
53
66
  to: 'callback: (invocation: { taskName: string; scheduledForMs: number }) => void',
54
67
  },
55
68
  }
@@ -150,12 +163,15 @@ for (const fn of fns) {
150
163
  // napi-derive emits the whole declaration for a function, unlike a struct
151
164
  // where `def` holds only the members.
152
165
  const declaration = fn.def.trim()
153
- out.push(
154
- declaration.startsWith('export declare function')
155
- ? `${declaration};`
156
- : `export declare function ${fn.name}${declaration};`,
157
- '',
158
- )
166
+ // napi-derive 3 emits a bare `function name(...)` where 2 emitted the
167
+ // signature after the name; concatenating onto the former produced
168
+ // `function xfunction x(...)`.
169
+ const rendered = declaration.startsWith('export declare function')
170
+ ? declaration
171
+ : declaration.startsWith('function ')
172
+ ? `export declare ${declaration}`
173
+ : `export declare function ${fn.name}${declaration}`
174
+ out.push(`${rendered};`, '')
159
175
  }
160
176
 
161
177
  const stale = Object.keys(CALLBACK_REFINEMENTS).filter((k) => !used.has(k))
@@ -181,7 +181,12 @@ export class Application implements AppContext {
181
181
 
182
182
  /** Resolve inside one of the conventional directories. */
183
183
  #inDirectory(key: keyof DirectoriesNode, paths: string[]): string {
184
- return this.makePath(this.#directories[key], ...paths)
184
+ // `DirectoriesNode` is an index signature, so a key nobody registered
185
+ // reads as undefined rather than failing to compile. Falling back to the
186
+ // key itself keeps the convention: `configPath()` under a node with no
187
+ // `config` entry resolves to `<root>/config`, which is where it would
188
+ // have been anyway.
189
+ return this.makePath(this.#directories[key] ?? String(key), ...paths)
185
190
  }
186
191
 
187
192
  /** Absolute path inside the `config/` directory. */
package/src/Ignitor.ts CHANGED
@@ -210,6 +210,15 @@ export interface HyperServerLike {
210
210
  }
211
211
 
212
212
  export interface IgnitorConfig {
213
+ /**
214
+ * Bind port. Falls back to `process.env.PORT`, then to `3000`. Prefer the
215
+ * environment: the fallback is read when the server binds, AFTER `booting()`
216
+ * has loaded `#start/env`, whereas a value passed here is fixed at
217
+ * construction — before `.env` has been read at all.
218
+ *
219
+ * It stays available because a test harness booting several applications in
220
+ * one process needs to hand each one an ephemeral port it has already picked.
221
+ */
213
222
  port?: number
214
223
  /**
215
224
  * Bind address of the HTTP server. Falls back to `process.env.HOST`, then to
@@ -690,12 +699,31 @@ export class Ignitor {
690
699
  this.#phase = 'started'
691
700
  }
692
701
 
702
+ /**
703
+ * Whether this process should bind an HTTP server.
704
+ *
705
+ * Not the same question as "is the environment 'web'". A test harness needs
706
+ * the HTTP surface — it passes a `serverFactory` precisely to get one — but
707
+ * it also needs to declare itself as `test`, because that is what an
708
+ * environment-scoped provider entry reads. Tying the two together forced a
709
+ * test bootstrap to call `httpServer()`, which set the environment to
710
+ * `'web'`, so `{ environment: ['web'] }` excluded nothing from a test run and
711
+ * a scheduled task fired in the middle of the suite.
712
+ *
713
+ * A test process that passes no factory still gets no server, and no error.
714
+ */
715
+ #servesHttp(): boolean {
716
+ const environment = this.#app.getEnvironment()
717
+ if (environment === 'web') return true
718
+ return environment === 'test' && this.#config.serverFactory !== undefined
719
+ }
720
+
693
721
  async #phaseReady(): Promise<void> {
694
722
  // Boot the Server (resolves lazy error handler etc.)
695
723
  await this.#server.boot()
696
724
 
697
- // Start HTTP server if in web mode
698
- if (this.#app.getEnvironment() === 'web' && this.#config.serverFactory) {
725
+ // Start HTTP server if this process serves one
726
+ if (this.#servesHttp() && this.#config.serverFactory) {
699
727
  // Build the HttpKernel with server middleware + router middleware.
700
728
  // The streaming backend is resolved lazily — the HyperServer is
701
729
  // created a few lines down, after the kernel — so the kernel
@@ -737,7 +765,18 @@ export class Ignitor {
737
765
  },
738
766
  })
739
767
 
740
- const desiredPort = this.#config.port ?? 3000
768
+ // Read HERE, not at construction: `#start/env` runs in `booting()`, so a
769
+ // PORT living in `.env` does not exist in `process.env` until this point.
770
+ // Reading it early bound 3000 while every later reader — the boot banner
771
+ // included — saw 3007 and said so. Same lazy rule as `host` below.
772
+ const desiredPort = this.#config.port ?? Number(process.env.PORT || 3000)
773
+ if (!Number.isInteger(desiredPort) || desiredPort < 0 || desiredPort > 65535) {
774
+ throw new ReamError(
775
+ 'E_IGNITOR_INVALID_PORT',
776
+ `PORT must be an integer between 0 and 65535, received '${process.env.PORT}'.`,
777
+ { hint: 'Set PORT in .env, or pass { port } to the Ignitor.' },
778
+ )
779
+ }
741
780
  // Port-scan fallback (+1..+19) is a DEV convenience only. In production
742
781
  // a silent drift to :3001 while the LB targets :3000 is an outage —
743
782
  // bind the configured port and let EADDRINUSE fail loudly. The probe
package/src/Provider.ts CHANGED
@@ -7,12 +7,31 @@
7
7
  * register() → boot() → start() → ready() → ... → shutdown()
8
8
  */
9
9
 
10
- import type { ConfigStore } from './ConfigLoader.js'
11
10
  import type { Container } from './container/Container.js'
12
11
 
12
+ /**
13
+ * The config a provider reads, as a CONTRACT rather than as the class.
14
+ *
15
+ * `ConfigStore` satisfies it, so nothing that holds a real one changes. But
16
+ * typed as the class, `AppContext` demanded a private `#tree` no one outside
17
+ * `ConfigLoader` can produce — so a test could not substitute a config, and an
18
+ * independently-published package could not describe an `AppContext` at all
19
+ * without importing `@c9up/ream`, which is the coupling the comment below this
20
+ * one exists to avoid. bay, station and blackhole each re-declared their own
21
+ * for exactly that reason.
22
+ *
23
+ * `get` and `set` are what providers actually use; a `ConfigStore`'s other
24
+ * methods stay reachable on the concrete type.
25
+ */
26
+ export interface ConfigReader {
27
+ get<T = unknown>(key: string): T | undefined
28
+ get<T = unknown>(key: string, defaultValue: T): T
29
+ set(key: string, value: unknown): void
30
+ }
31
+
13
32
  export interface AppContext {
14
33
  container: Container
15
- config: ConfigStore
34
+ config: ConfigReader
16
35
  }
17
36
 
18
37
  /**
@@ -6,6 +6,7 @@
6
6
  * Configured via config/bodyparser.ts.
7
7
  */
8
8
 
9
+ import { readModuleConfig } from '../config/moduleConfig.js'
9
10
  import { Exception } from '../http/Exception.js'
10
11
  import type { HttpContext } from '../http/HttpContext.js'
11
12
  import { hydrateMultipartPayload } from './MultipartFile.js'
@@ -98,7 +99,17 @@ const DEFAULT_CONFIG: ResolvedBodyParserConfig = {
98
99
  export default class BodyParserMiddleware {
99
100
  #config: ResolvedBodyParserConfig
100
101
 
101
- constructor(config?: BodyParserConfig) {
102
+ constructor(explicitConfig: BodyParserConfig | undefined = undefined) {
103
+ // The default value is load-bearing: an OPTIONAL parameter still counts
104
+ // toward `Function.length`, and the container refuses to auto-construct a
105
+ // class that declares parameters it has no metadata for. A parameter with a
106
+ // default counts for nothing, which is what lets
107
+ // `router.use([() => import('…')])` build this middleware.
108
+ // Nothing is passed when the container builds this middleware from
109
+ // `router.use([() => import('@c9up/ream/bodyparser_middleware')])`, so the
110
+ // settings come from `config/bodyparser.ts` — the file this class's own
111
+ // documentation has always named.
112
+ const config = explicitConfig ?? readModuleConfig<BodyParserConfig>('bodyparser')
102
113
  assertNoEnabledFlag(config)
103
114
  this.#config = {
104
115
  allowedMethods: config?.allowedMethods ?? DEFAULT_CONFIG.allowedMethods,
@@ -125,7 +125,10 @@ export class MultipartFile {
125
125
  #mimeParts(): [string | undefined, string | undefined] {
126
126
  const source = this.#detected?.mime ?? this.#headerMime
127
127
  if (!source) return [undefined, undefined]
128
- const [type, subtype] = source.split(';')[0].trim().split('/')
128
+ // `split` always yields a first element; naming it is how that stops
129
+ // being something the compiler has to be told.
130
+ const [head = ''] = source.split(';')
131
+ const [type, subtype] = head.trim().split('/')
129
132
  return [type || undefined, subtype || undefined]
130
133
  }
131
134
 
@@ -432,8 +435,9 @@ function isSafeFilename(name: string): boolean {
432
435
  function parseFileSize(size: string): number {
433
436
  const match = size.match(/^(\d+)(kb|mb|gb)?$/i)
434
437
  if (!match) return 1024 * 1024
435
- const num = parseInt(match[1], 10)
436
- switch (match[2]?.toLowerCase()) {
438
+ const [, digits, unit] = match
439
+ const num = parseInt(digits ?? '0', 10)
440
+ switch (unit?.toLowerCase()) {
437
441
  case 'kb':
438
442
  return num * 1024
439
443
  case 'mb':
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `config/bodyparser.ts` — body-parsing limits and types, in the file the
3
+ * config loader reads (`config/<name>.ts` → `config.get('<name>')`).
4
+ *
5
+ * @example
6
+ * // config/bodyparser.ts
7
+ * import { defineConfig } from '@c9up/ream/bodyparser/config'
8
+ *
9
+ * export default defineConfig({
10
+ * multipart: { limit: '20mb' },
11
+ * })
12
+ */
13
+
14
+ import type { BodyParserConfig } from './BodyParserMiddleware.js'
15
+
16
+ export function defineConfig(config: BodyParserConfig): BodyParserConfig {
17
+ return config
18
+ }
@@ -6,8 +6,9 @@
6
6
  export function parseSize(size: string): number {
7
7
  const match = size.match(/^(\d+)(kb|mb|gb)?$/i)
8
8
  if (!match) return 1024 * 1024 // default 1mb
9
- const num = parseInt(match[1], 10)
10
- switch (match[2]?.toLowerCase()) {
9
+ const [, digits, unit] = match
10
+ const num = parseInt(digits ?? '0', 10)
11
+ switch (unit?.toLowerCase()) {
11
12
  case 'kb':
12
13
  return num * 1024
13
14
  case 'mb':
@@ -19,7 +19,10 @@ import type { MigrationRegistry } from '../migrations/MigrationRegistry.js'
19
19
  import type { RegisteredMigrationSource } from '../migrations/types.js'
20
20
 
21
21
  export abstract class MigrateBase extends BaseCommand {
22
- static override options: CommandOptions = { startApp: true }
22
+ // `drivesMigrations` because these three ARE the migration run: without it the
23
+ // boot applies everything first, so `migrate` says "nothing to migrate" after
24
+ // the work is done, and `migrate:status` mutates the schema it reports on.
25
+ static override options: CommandOptions = { startApp: true, drivesMigrations: true }
23
26
 
24
27
  @flags.string({
25
28
  description: 'Only this migration source, by the name its package registered',
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Reading a module's own `config/<name>.ts` entry without demanding one.
3
+ *
4
+ * A middleware constructed by the container gets nothing passed to it — the
5
+ * resolver calls `make(Class)`, which is what makes `router.use([() =>
6
+ * import('…/session_middleware')])` possible. The configuration therefore has
7
+ * to come from the config store, the way it does everywhere else.
8
+ *
9
+ * The read is deliberately forgiving. The same classes are also constructed
10
+ * directly (`new BodyParserMiddleware()`) in tests and in embedding hosts that
11
+ * never boot an Application, and there the store does not exist. Throwing there
12
+ * would trade a working default for a crash.
13
+ */
14
+
15
+ import { getApp } from '../services/app.js'
16
+
17
+ export function readModuleConfig<T extends object>(key: string): T | undefined {
18
+ const store = getApp()?.config
19
+ if (store === undefined || !store.has(key)) return undefined
20
+ const value = store.get<T>(key)
21
+ // A config file exporting something that is not an object (a stray
22
+ // `export default 42`) is ignored rather than spread over the defaults.
23
+ return typeof value === 'object' && value !== null ? value : undefined
24
+ }
@@ -117,7 +117,7 @@ export abstract class BaseCommand {
117
117
  error?: unknown
118
118
 
119
119
  /** Assigned by the kernel — see {@link terminate}. */
120
- protected declare onTerminate?: () => void | Promise<void>
120
+ declare protected onTerminate?: () => void | Promise<void>
121
121
 
122
122
  /**
123
123
  * Lifecycle hooks, run in this order around `run()`: `prepare`, `interact`,
@@ -999,6 +999,14 @@ export class Kernel {
999
999
  )
1000
1000
  }
1001
1001
 
1002
+ // Before the boot, not after: a data package that migrates on boot for the
1003
+ // convenience of `dev` would otherwise apply everything a moment before the
1004
+ // command that exists to apply it runs — leaving `migrate` with nothing to
1005
+ // report and `migrate:status` having changed what it was asked to look at.
1006
+ if (Command.options?.drivesMigrations === true) {
1007
+ process.env.REAM_SKIP_BOOT_MIGRATE = '1'
1008
+ }
1009
+
1002
1010
  return this.#startApp()
1003
1011
  }
1004
1012
 
@@ -750,7 +750,8 @@ function pad(text: string, width: number): string {
750
750
  return text + ' '.repeat(Math.max(0, width - visibleWidth(text)))
751
751
  }
752
752
 
753
- function stripAnsi(text: string): string {
753
+ /** Removes the SGR escape sequences `colors` writes, leaving the visible text. */
754
+ export function stripAnsi(text: string): string {
754
755
  // biome-ignore lint/suspicious/noControlCharactersInRegex: matching ANSI escapes is the point
755
756
  return text.replace(/\u001B\[[0-9;]*m/g, '')
756
757
  }
@@ -37,6 +37,21 @@ export interface CommandOptions {
37
37
  * default so a typo (`--warm` for `--warn`) is reported, not swallowed.
38
38
  */
39
39
  allowUnknownFlags?: boolean
40
+
41
+ /**
42
+ * This command drives migrations itself, so booting must not run them.
43
+ *
44
+ * A data package may migrate on boot for the convenience of `dev` — atlas
45
+ * does, outside production. For the commands that exist to migrate, that
46
+ * convenience is the bug: the boot applies everything, and then `migrate`
47
+ * reports "nothing to migrate" while `migrate:status`, asked only to look,
48
+ * has changed the schema it is reporting on.
49
+ *
50
+ * Declaring this sets `REAM_SKIP_BOOT_MIGRATE=1` before the application
51
+ * starts, which is the flag a data package watches for. Any package's own
52
+ * migration command opts in the same way; the framework names none of them.
53
+ */
54
+ drivesMigrations?: boolean
40
55
  }
41
56
 
42
57
  /** Metadata recorded by an `@args.*` decorator. */
@@ -63,7 +63,7 @@ interface ResolutionChain {
63
63
  * down. Saved and restored around each construction, so a dependency's own
64
64
  * dependencies see IT as their parent, not its parent.
65
65
  */
66
- parent?: new (
66
+ parent?: abstract new (
67
67
  ...args: never[]
68
68
  ) => unknown
69
69
  }
@@ -91,8 +91,12 @@ export class Container {
91
91
  /** Alias key → the token it forwards to (AdonisJS `container.alias`). */
92
92
  #aliases: Map<ServiceToken, ServiceToken> = new Map()
93
93
  /** parent class → (binding key → the factory that class gets instead). */
94
- #contextualBindings: Map<new (...args: never[]) => unknown, Map<ServiceToken, ServiceFactory>> =
95
- new Map()
94
+ #contextualBindings: Map<
95
+ abstract new (
96
+ ...args: never[]
97
+ ) => unknown,
98
+ Map<ServiceToken, ServiceFactory>
99
+ > = new Map()
96
100
  /** token key → post-resolution callbacks (AdonisJS `container.resolving`). */
97
101
  #resolvingHooks: Map<ServiceToken, Array<(value: unknown) => void | Promise<void>>> = new Map()
98
102
  /**
@@ -625,7 +629,7 @@ export class Container {
625
629
  * 3. No params → plain `new Class()`
626
630
  */
627
631
  async #autoConstruct(
628
- target: new (...args: never[]) => unknown,
632
+ target: abstract new (...args: never[]) => unknown,
629
633
  runtimeValues?: unknown[],
630
634
  ): Promise<unknown> {
631
635
  // Everything resolved inside is a dependency OF `target`, which is what a
@@ -642,7 +646,7 @@ export class Container {
642
646
  }
643
647
 
644
648
  async #constructWithDeps(
645
- target: new (...args: never[]) => unknown,
649
+ target: abstract new (...args: never[]) => unknown,
646
650
  runtimeValues?: unknown[],
647
651
  ): Promise<unknown> {
648
652
  const metadata = getServiceMetadata(target)
@@ -719,7 +723,12 @@ export class Container {
719
723
  )
720
724
  }
721
725
  // (d) Genuine zero-argument class.
722
- const instance = new target()
726
+ //
727
+ // `Reflect.construct`, not `new target()`: a token may be an abstract
728
+ // class (that is what a contextual binding binds against), and `new` on
729
+ // one is a compile error even though the runtime constructs it happily —
730
+ // `abstract` exists only at compile time.
731
+ const instance = Reflect.construct(target, [])
723
732
  this.#cacheIfAppWide(scope, key, instance, store, readsBefore)
724
733
  return instance
725
734
  }
@@ -16,7 +16,17 @@ export interface ServiceMetadata {
16
16
  /** Token used to resolve a service — class constructor, string name, or registered symbol. */
17
17
  // `never[]` accepts every constructor shape — parameters are contravariant, so
18
18
  // a rest of `never` is assignable from any concrete list.
19
- export type ServiceToken = (new (...args: never[]) => unknown) | string | symbol
19
+ /**
20
+ * What the container accepts as a token.
21
+ *
22
+ * `abstract new`, not `new`: binding an implementation against an abstract base
23
+ * is the whole point of a contextual binding — `@Inject(Hash)` where `Hash` is
24
+ * the abstract class and `Bcrypt` is what the container provides. An abstract
25
+ * class has no construct signature, so spelt `new (...)` the type rejected
26
+ * exactly the pattern the feature exists for. `abstract new` admits concrete
27
+ * classes too.
28
+ */
29
+ export type ServiceToken = (abstract new (...args: never[]) => unknown) | string | symbol
20
30
 
21
31
  /**
22
32
  * What a binding factory is handed so it can resolve its own dependencies
@@ -14,7 +14,7 @@ const INJECT_METADATA_KEY = Symbol('ream:inject')
14
14
  // `never[]` accepts every constructor shape: parameters are contravariant, so a
15
15
  // rest of `never` is assignable from any concrete list. Same spelling as the
16
16
  // router's, so a class flows between the two without an assertion.
17
- type AnyConstructor = new (...args: never[]) => unknown
17
+ type AnyConstructor = abstract new (...args: never[]) => unknown
18
18
 
19
19
  /** Registry of all decorated services. */
20
20
  const serviceRegistry: Map<AnyConstructor, ServiceMetadata> = new Map()