@c9up/ream 0.2.28 → 0.2.30

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 (293) hide show
  1. package/README.md +5 -6
  2. package/dist/Codemods.d.ts +24 -0
  3. package/dist/Codemods.d.ts.map +1 -1
  4. package/dist/Codemods.js +211 -1
  5. package/dist/Codemods.js.map +1 -1
  6. package/dist/HttpKernel.d.ts.map +1 -1
  7. package/dist/HttpKernel.js +17 -5
  8. package/dist/HttpKernel.js.map +1 -1
  9. package/dist/Ignitor.d.ts +15 -2
  10. package/dist/Ignitor.d.ts.map +1 -1
  11. package/dist/Ignitor.js +33 -23
  12. package/dist/Ignitor.js.map +1 -1
  13. package/dist/bodyparser/BodyParserMiddleware.d.ts +4 -4
  14. package/dist/bodyparser/BodyParserMiddleware.d.ts.map +1 -1
  15. package/dist/bodyparser/BodyParserMiddleware.js +41 -11
  16. package/dist/bodyparser/BodyParserMiddleware.js.map +1 -1
  17. package/dist/bodyparser/MultipartFile.d.ts +21 -15
  18. package/dist/bodyparser/MultipartFile.d.ts.map +1 -1
  19. package/dist/bodyparser/MultipartFile.js +40 -58
  20. package/dist/bodyparser/MultipartFile.js.map +1 -1
  21. package/dist/bodyparser/parseSize.d.ts +7 -4
  22. package/dist/bodyparser/parseSize.d.ts.map +1 -1
  23. package/dist/bodyparser/parseSize.js +12 -17
  24. package/dist/bodyparser/parseSize.js.map +1 -1
  25. package/dist/bodyparser/qsParse.d.ts +20 -1
  26. package/dist/bodyparser/qsParse.d.ts.map +1 -1
  27. package/dist/bodyparser/qsParse.js +78 -25
  28. package/dist/bodyparser/qsParse.js.map +1 -1
  29. package/dist/codemods/source.d.ts +57 -0
  30. package/dist/codemods/source.d.ts.map +1 -0
  31. package/dist/codemods/source.js +282 -0
  32. package/dist/codemods/source.js.map +1 -0
  33. package/dist/console/prompts.d.ts.map +1 -1
  34. package/dist/console/prompts.js +3 -1
  35. package/dist/console/prompts.js.map +1 -1
  36. package/dist/container/Container.d.ts.map +1 -1
  37. package/dist/container/Container.js +61 -71
  38. package/dist/container/Container.js.map +1 -1
  39. package/dist/events/Emitter.d.ts +12 -0
  40. package/dist/events/Emitter.d.ts.map +1 -1
  41. package/dist/events/Emitter.js +36 -9
  42. package/dist/events/Emitter.js.map +1 -1
  43. package/dist/events/helix/helpers.d.ts.map +1 -1
  44. package/dist/events/helix/helpers.js +19 -9
  45. package/dist/events/helix/helpers.js.map +1 -1
  46. package/dist/graphql/GraphQLEngine.d.ts +8 -0
  47. package/dist/graphql/GraphQLEngine.d.ts.map +1 -1
  48. package/dist/graphql/GraphQLEngine.js +79 -12
  49. package/dist/graphql/GraphQLEngine.js.map +1 -1
  50. package/dist/helpers/MessageBuilder.d.ts +4 -2
  51. package/dist/helpers/MessageBuilder.d.ts.map +1 -1
  52. package/dist/helpers/MessageBuilder.js +4 -2
  53. package/dist/helpers/MessageBuilder.js.map +1 -1
  54. package/dist/helpers/errors.d.ts +11 -0
  55. package/dist/helpers/errors.d.ts.map +1 -0
  56. package/dist/helpers/errors.js +17 -0
  57. package/dist/helpers/errors.js.map +1 -0
  58. package/dist/helpers/napi-loader.d.ts.map +1 -1
  59. package/dist/helpers/napi-loader.js +3 -2
  60. package/dist/helpers/napi-loader.js.map +1 -1
  61. package/dist/http/Exception.d.ts +14 -1
  62. package/dist/http/Exception.d.ts.map +1 -1
  63. package/dist/http/Exception.js +10 -2
  64. package/dist/http/Exception.js.map +1 -1
  65. package/dist/http/HttpContext.d.ts +31 -2
  66. package/dist/http/HttpContext.d.ts.map +1 -1
  67. package/dist/http/HttpContext.js +12 -5
  68. package/dist/http/HttpContext.js.map +1 -1
  69. package/dist/http/RedirectBuilder.d.ts +9 -3
  70. package/dist/http/RedirectBuilder.d.ts.map +1 -1
  71. package/dist/http/RedirectBuilder.js +43 -37
  72. package/dist/http/RedirectBuilder.js.map +1 -1
  73. package/dist/http/Request.d.ts +42 -17
  74. package/dist/http/Request.d.ts.map +1 -1
  75. package/dist/http/Request.js +80 -58
  76. package/dist/http/Request.js.map +1 -1
  77. package/dist/http/Response.d.ts +7 -4
  78. package/dist/http/Response.d.ts.map +1 -1
  79. package/dist/http/Response.js +14 -6
  80. package/dist/http/Response.js.map +1 -1
  81. package/dist/http/SseStream.js +6 -7
  82. package/dist/http/SseStream.js.map +1 -1
  83. package/dist/http/permissions.d.ts +25 -0
  84. package/dist/http/permissions.d.ts.map +1 -0
  85. package/dist/http/permissions.js +38 -0
  86. package/dist/http/permissions.js.map +1 -0
  87. package/dist/http-client/HttpClient.d.ts +111 -0
  88. package/dist/http-client/HttpClient.d.ts.map +1 -0
  89. package/dist/http-client/HttpClient.js +386 -0
  90. package/dist/http-client/HttpClient.js.map +1 -0
  91. package/dist/http-client/addresses.d.ts +14 -0
  92. package/dist/http-client/addresses.d.ts.map +1 -0
  93. package/dist/http-client/addresses.js +57 -0
  94. package/dist/http-client/addresses.js.map +1 -0
  95. package/dist/http-client/fake.d.ts +50 -0
  96. package/dist/http-client/fake.d.ts.map +1 -0
  97. package/dist/http-client/fake.js +95 -0
  98. package/dist/http-client/fake.js.map +1 -0
  99. package/dist/http-client/index.d.ts +7 -0
  100. package/dist/http-client/index.d.ts.map +1 -0
  101. package/dist/http-client/index.js +7 -0
  102. package/dist/http-client/index.js.map +1 -0
  103. package/dist/index.d.ts +4 -3
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +1 -1
  106. package/dist/index.js.map +1 -1
  107. package/dist/middleware/Pipeline.d.ts.map +1 -1
  108. package/dist/middleware/Pipeline.js +6 -3
  109. package/dist/middleware/Pipeline.js.map +1 -1
  110. package/dist/native/generated.d.ts +93 -36
  111. package/dist/native/generated.d.ts.map +1 -1
  112. package/dist/profiler/Profiler.d.ts.map +1 -1
  113. package/dist/profiler/Profiler.js +4 -1
  114. package/dist/profiler/Profiler.js.map +1 -1
  115. package/dist/profiler/client.generated.d.ts +5 -0
  116. package/dist/profiler/client.generated.d.ts.map +1 -0
  117. package/dist/profiler/client.generated.js +6 -0
  118. package/dist/profiler/client.generated.js.map +1 -0
  119. package/dist/profiler/collectors.d.ts +92 -13
  120. package/dist/profiler/collectors.d.ts.map +1 -1
  121. package/dist/profiler/collectors.js +106 -19
  122. package/dist/profiler/collectors.js.map +1 -1
  123. package/dist/profiler/endpoint.d.ts +2 -1
  124. package/dist/profiler/endpoint.d.ts.map +1 -1
  125. package/dist/profiler/endpoint.js +25 -1
  126. package/dist/profiler/endpoint.js.map +1 -1
  127. package/dist/profiler/index.d.ts +8 -4
  128. package/dist/profiler/index.d.ts.map +1 -1
  129. package/dist/profiler/index.js +11 -4
  130. package/dist/profiler/index.js.map +1 -1
  131. package/dist/profiler/middleware.d.ts.map +1 -1
  132. package/dist/profiler/middleware.js +76 -10
  133. package/dist/profiler/middleware.js.map +1 -1
  134. package/dist/profiler/preview.d.ts +17 -0
  135. package/dist/profiler/preview.d.ts.map +1 -0
  136. package/dist/profiler/preview.js +100 -0
  137. package/dist/profiler/preview.js.map +1 -0
  138. package/dist/profiler/toolbar.d.ts +15 -23
  139. package/dist/profiler/toolbar.d.ts.map +1 -1
  140. package/dist/profiler/toolbar.js +25 -91
  141. package/dist/profiler/toolbar.js.map +1 -1
  142. package/dist/profiler/types.d.ts +6 -1
  143. package/dist/profiler/types.d.ts.map +1 -1
  144. package/dist/router/Router.d.ts +6 -0
  145. package/dist/router/Router.d.ts.map +1 -1
  146. package/dist/router/Router.js +115 -12
  147. package/dist/router/Router.js.map +1 -1
  148. package/dist/rpc/RpcProvider.d.ts +6 -0
  149. package/dist/rpc/RpcProvider.d.ts.map +1 -1
  150. package/dist/rpc/RpcProvider.js +4 -1
  151. package/dist/rpc/RpcProvider.js.map +1 -1
  152. package/dist/rpc/RpcRouter.d.ts +27 -14
  153. package/dist/rpc/RpcRouter.d.ts.map +1 -1
  154. package/dist/rpc/RpcRouter.js +97 -40
  155. package/dist/rpc/RpcRouter.js.map +1 -1
  156. package/dist/scheduler/Scheduler.js +1 -1
  157. package/dist/scheduler/Scheduler.js.map +1 -1
  158. package/dist/scheduler/locks/RedisLockBackend.d.ts.map +1 -1
  159. package/dist/scheduler/locks/RedisLockBackend.js +13 -2
  160. package/dist/scheduler/locks/RedisLockBackend.js.map +1 -1
  161. package/dist/security/CookieSigner.d.ts.map +1 -1
  162. package/dist/security/CookieSigner.js +32 -8
  163. package/dist/security/CookieSigner.js.map +1 -1
  164. package/dist/security/SignedUrl.d.ts.map +1 -1
  165. package/dist/security/SignedUrl.js +16 -19
  166. package/dist/security/SignedUrl.js.map +1 -1
  167. package/dist/server/Server.d.ts +44 -12
  168. package/dist/server/Server.d.ts.map +1 -1
  169. package/dist/server/Server.js +31 -10
  170. package/dist/server/Server.js.map +1 -1
  171. package/dist/server/nativeGrpc.d.ts +27 -0
  172. package/dist/server/nativeGrpc.d.ts.map +1 -0
  173. package/dist/server/nativeGrpc.js +47 -0
  174. package/dist/server/nativeGrpc.js.map +1 -0
  175. package/dist/server/nativeLimits.d.ts +57 -0
  176. package/dist/server/nativeLimits.d.ts.map +1 -0
  177. package/dist/server/nativeLimits.js +75 -0
  178. package/dist/server/nativeLimits.js.map +1 -0
  179. package/dist/session/Session.d.ts +12 -0
  180. package/dist/session/Session.d.ts.map +1 -1
  181. package/dist/session/Session.js +13 -4
  182. package/dist/session/Session.js.map +1 -1
  183. package/dist/session/SessionMiddleware.d.ts.map +1 -1
  184. package/dist/session/SessionMiddleware.js +28 -9
  185. package/dist/session/SessionMiddleware.js.map +1 -1
  186. package/dist/session/config.d.ts +5 -0
  187. package/dist/session/config.d.ts.map +1 -1
  188. package/dist/session/config.js +8 -0
  189. package/dist/session/config.js.map +1 -1
  190. package/dist/session/drivers/MemoryDriver.d.ts.map +1 -1
  191. package/dist/session/drivers/MemoryDriver.js +26 -7
  192. package/dist/session/drivers/MemoryDriver.js.map +1 -1
  193. package/dist/session/errors.d.ts +17 -0
  194. package/dist/session/errors.d.ts.map +1 -1
  195. package/dist/session/errors.js +21 -0
  196. package/dist/session/errors.js.map +1 -1
  197. package/dist/session/templateTags.d.ts +14 -0
  198. package/dist/session/templateTags.d.ts.map +1 -1
  199. package/dist/session/templateTags.js +45 -17
  200. package/dist/session/templateTags.js.map +1 -1
  201. package/dist/storage/StaticMiddleware.d.ts.map +1 -1
  202. package/dist/storage/StaticMiddleware.js +49 -27
  203. package/dist/storage/StaticMiddleware.js.map +1 -1
  204. package/dist/testing/ApiResponse.d.ts.map +1 -1
  205. package/dist/testing/ApiResponse.js +7 -1
  206. package/dist/testing/ApiResponse.js.map +1 -1
  207. package/dist/testing/TestClient.d.ts.map +1 -1
  208. package/dist/testing/TestClient.js +16 -4
  209. package/dist/testing/TestClient.js.map +1 -1
  210. package/dist/utils/Macroable.d.ts +1 -1
  211. package/dist/utils/Macroable.js +2 -2
  212. package/dist/utils/Macroable.js.map +1 -1
  213. package/events.darwin-arm64.node +0 -0
  214. package/events.darwin-x64.node +0 -0
  215. package/events.linux-arm64-gnu.node +0 -0
  216. package/events.linux-x64-gnu.node +0 -0
  217. package/events.win32-x64-msvc.node +0 -0
  218. package/index.darwin-arm64.node +0 -0
  219. package/index.darwin-x64.node +0 -0
  220. package/index.linux-arm64-gnu.node +0 -0
  221. package/index.linux-x64-gnu.node +0 -0
  222. package/index.win32-x64-msvc.node +0 -0
  223. package/package.json +11 -3
  224. package/scheduler.darwin-arm64.node +0 -0
  225. package/scheduler.darwin-x64.node +0 -0
  226. package/scheduler.linux-arm64-gnu.node +0 -0
  227. package/scheduler.linux-x64-gnu.node +0 -0
  228. package/scheduler.win32-x64-msvc.node +0 -0
  229. package/src/Codemods.ts +314 -3
  230. package/src/HttpKernel.ts +18 -7
  231. package/src/Ignitor.ts +58 -26
  232. package/src/bodyparser/BodyParserMiddleware.ts +45 -16
  233. package/src/bodyparser/MultipartFile.ts +46 -67
  234. package/src/bodyparser/parseSize.ts +17 -17
  235. package/src/bodyparser/qsParse.ts +96 -26
  236. package/src/codemods/source.ts +303 -0
  237. package/src/console/prompts.ts +3 -1
  238. package/src/container/Container.ts +97 -81
  239. package/src/events/Emitter.ts +37 -10
  240. package/src/events/helix/helpers.ts +24 -19
  241. package/src/graphql/GraphQLEngine.ts +91 -14
  242. package/src/helpers/MessageBuilder.ts +4 -2
  243. package/src/helpers/errors.ts +16 -0
  244. package/src/helpers/napi-loader.ts +3 -2
  245. package/src/http/Exception.ts +17 -2
  246. package/src/http/HttpContext.ts +34 -5
  247. package/src/http/RedirectBuilder.ts +51 -37
  248. package/src/http/Request.ts +102 -58
  249. package/src/http/Response.ts +15 -7
  250. package/src/http/SseStream.ts +6 -7
  251. package/src/http/permissions.ts +47 -0
  252. package/src/http-client/HttpClient.ts +505 -0
  253. package/src/http-client/addresses.ts +58 -0
  254. package/src/http-client/fake.ts +141 -0
  255. package/src/http-client/index.ts +21 -0
  256. package/src/index.ts +6 -1
  257. package/src/middleware/Pipeline.ts +7 -3
  258. package/src/native/generated.ts +106 -48
  259. package/src/profiler/Profiler.ts +4 -1
  260. package/src/profiler/client/toolbar.css +595 -0
  261. package/src/profiler/client/toolbar.js +1299 -0
  262. package/src/profiler/client.generated.ts +7 -0
  263. package/src/profiler/collectors.ts +156 -19
  264. package/src/profiler/endpoint.ts +27 -1
  265. package/src/profiler/index.ts +17 -3
  266. package/src/profiler/middleware.ts +74 -9
  267. package/src/profiler/preview.ts +104 -0
  268. package/src/profiler/toolbar.ts +28 -91
  269. package/src/profiler/types.ts +6 -1
  270. package/src/router/Router.ts +136 -13
  271. package/src/rpc/RpcProvider.ts +10 -1
  272. package/src/rpc/RpcRouter.ts +129 -45
  273. package/src/scheduler/Scheduler.ts +1 -1
  274. package/src/scheduler/locks/RedisLockBackend.ts +12 -4
  275. package/src/security/CookieSigner.ts +30 -8
  276. package/src/security/SignedUrl.ts +18 -17
  277. package/src/server/Server.ts +60 -12
  278. package/src/server/nativeGrpc.ts +59 -0
  279. package/src/server/nativeLimits.ts +111 -0
  280. package/src/session/Session.ts +29 -4
  281. package/src/session/SessionMiddleware.ts +30 -10
  282. package/src/session/config.ts +7 -0
  283. package/src/session/drivers/MemoryDriver.ts +26 -7
  284. package/src/session/errors.ts +31 -0
  285. package/src/session/templateTags.ts +57 -0
  286. package/src/storage/StaticMiddleware.ts +46 -27
  287. package/src/testing/ApiResponse.ts +7 -1
  288. package/src/testing/TestClient.ts +14 -5
  289. package/src/utils/Macroable.ts +2 -2
  290. package/scripts/build-napi-types.mjs +0 -69
  291. package/scripts/copy-napi.mjs +0 -121
  292. package/scripts/generate-mime.mjs +0 -119
  293. package/scripts/generate-napi-types.mjs +0 -187
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Just enough of a TypeScript scanner for the configure codemods: find a
3
+ * call or an object/array literal in CODE (never inside a string, a template
4
+ * or a comment), split a literal into its top-level entries, and append to
5
+ * one. Upstream does this with a full AST; ream ships no compiler, and the
6
+ * files these codemods edit (`start/env.ts`, `reamrc.ts`) are object and
7
+ * array literals.
8
+ *
9
+ * Not handled: regular-expression literals (a quote inside one reads as the
10
+ * start of a string). Neither file has a reason to contain one.
11
+ */
12
+
13
+ const CLOSERS: Record<string, string> = { '{': '}', '[': ']', '(': ')' }
14
+
15
+ function isIdentifierChar(char: string | undefined): boolean {
16
+ return char !== undefined && /[A-Za-z0-9_$]/.test(char)
17
+ }
18
+
19
+ /**
20
+ * The index just past the string, template or comment starting at `i`, or
21
+ * `i` itself when none starts there. An unterminated one runs to the end.
22
+ */
23
+ function skipNonCode(src: string, i: number): number {
24
+ const char = src[i]
25
+ const next = src[i + 1]
26
+ if (char === '/' && next === '/') {
27
+ const end = src.indexOf('\n', i)
28
+ return end === -1 ? src.length : end
29
+ }
30
+ if (char === '/' && next === '*') {
31
+ const end = src.indexOf('*/', i + 2)
32
+ return end === -1 ? src.length : end + 2
33
+ }
34
+ if (char === "'" || char === '"') {
35
+ let j = i + 1
36
+ while (j < src.length && src[j] !== char && src[j] !== '\n') {
37
+ j += src[j] === '\\' ? 2 : 1
38
+ }
39
+ return j + 1
40
+ }
41
+ if (char === '`') {
42
+ let j = i + 1
43
+ while (j < src.length && src[j] !== '`') {
44
+ if (src[j] === '\\') {
45
+ j += 2
46
+ } else if (src[j] === '$' && src[j + 1] === '{') {
47
+ const close = matchClose(src, j + 1)
48
+ j = close === -1 ? src.length : close + 1
49
+ } else {
50
+ j += 1
51
+ }
52
+ }
53
+ return j + 1
54
+ }
55
+ return i
56
+ }
57
+
58
+ /** Whether the string, template or block comment starting at `i` is closed. */
59
+ function isTerminated(src: string, i: number): boolean {
60
+ const char = src[i]
61
+ if (char === '/') return src.indexOf('*/', i + 2) !== -1
62
+ const end = skipNonCode(src, i)
63
+ return end <= src.length && end - 1 > i && src[end - 1] === char
64
+ }
65
+
66
+ /** Index of the bracket closing the one at `open`, or -1. */
67
+ export function matchClose(src: string, open: number): number {
68
+ const first = CLOSERS[src[open] ?? '']
69
+ if (first === undefined) return -1
70
+ const stack = [first]
71
+ let i = open + 1
72
+ while (i < src.length) {
73
+ const skipped = skipNonCode(src, i)
74
+ if (skipped !== i) {
75
+ i = skipped
76
+ continue
77
+ }
78
+ const char = src[i] ?? ''
79
+ const closer = CLOSERS[char]
80
+ if (closer !== undefined) {
81
+ stack.push(closer)
82
+ } else if (char === stack[stack.length - 1]) {
83
+ stack.pop()
84
+ if (stack.length === 0) return i
85
+ } else if (char === '}' || char === ']' || char === ')') {
86
+ return -1
87
+ }
88
+ i += 1
89
+ }
90
+ return -1
91
+ }
92
+
93
+ /** First code character at or after `i` (before `end`), skipping whitespace and comments. */
94
+ function firstCode(src: string, i: number, end: number): number {
95
+ let j = i
96
+ while (j < end) {
97
+ const char = src[j] ?? ''
98
+ if (/\s/.test(char)) {
99
+ j += 1
100
+ continue
101
+ }
102
+ if (char === '/' && (src[j + 1] === '/' || src[j + 1] === '*')) {
103
+ j = skipNonCode(src, j)
104
+ continue
105
+ }
106
+ return j
107
+ }
108
+ return end
109
+ }
110
+
111
+ /** One top-level entry of a literal: the text between two commas. */
112
+ export interface Segment {
113
+ start: number
114
+ end: number
115
+ /** First code character, or `end` when the entry is only trivia. */
116
+ codeStart: number
117
+ /** Just past the last code character (comments and whitespace excluded). */
118
+ codeEnd: number
119
+ }
120
+
121
+ /** Split `src[start, end)` on its top-level commas. */
122
+ export function segments(src: string, start: number, end: number): Segment[] {
123
+ const out: Segment[] = []
124
+ let segmentStart = start
125
+ let codeEnd = start
126
+ let depth = 0
127
+ let i = start
128
+ const close = (at: number) => {
129
+ const codeStart = firstCode(src, segmentStart, at)
130
+ out.push({ start: segmentStart, end: at, codeStart, codeEnd: Math.max(codeEnd, codeStart) })
131
+ }
132
+ while (i < end) {
133
+ const char = src[i] ?? ''
134
+ if (char === '/' && (src[i + 1] === '/' || src[i + 1] === '*')) {
135
+ i = skipNonCode(src, i)
136
+ continue
137
+ }
138
+ const skipped = skipNonCode(src, i)
139
+ if (skipped !== i) {
140
+ i = skipped
141
+ codeEnd = i
142
+ continue
143
+ }
144
+ if (char === ',' && depth === 0) {
145
+ close(i)
146
+ segmentStart = i + 1
147
+ codeEnd = i + 1
148
+ i += 1
149
+ continue
150
+ }
151
+ if (CLOSERS[char] !== undefined) depth += 1
152
+ else if (char === '}' || char === ']' || char === ')') depth -= 1
153
+ if (!/\s/.test(char)) codeEnd = i + 1
154
+ i += 1
155
+ }
156
+ close(end)
157
+ return out
158
+ }
159
+
160
+ /**
161
+ * Whether `code` is one complete expression that cannot reach outside the
162
+ * slot it is written into: brackets balanced and never closing past where
163
+ * they opened, strings and comments terminated, no top-level comma.
164
+ */
165
+ export function isSingleExpression(code: string): boolean {
166
+ if (code.trim() === '') return false
167
+ let depth = 0
168
+ let i = 0
169
+ while (i < code.length) {
170
+ const skipped = skipNonCode(code, i)
171
+ if (skipped !== i) {
172
+ // A line comment would swallow the comma written after the value.
173
+ const lineComment = code[i] === '/' && code[i + 1] === '/'
174
+ if (lineComment || !isTerminated(code, i)) return false
175
+ i = skipped
176
+ continue
177
+ }
178
+ const char = code[i] ?? ''
179
+ if (CLOSERS[char] !== undefined) depth += 1
180
+ else if (char === '}' || char === ']' || char === ')') {
181
+ depth -= 1
182
+ if (depth < 0) return false
183
+ } else if (char === ',' && depth === 0) return false
184
+ i += 1
185
+ }
186
+ return depth === 0
187
+ }
188
+
189
+ /**
190
+ * Index of the `(` of the first call to `callee` in code, or -1. A mention
191
+ * inside a string or a comment, or as the tail of a longer name
192
+ * (`MyEnv.create`), does not count.
193
+ */
194
+ export function findCall(src: string, callee: string): number {
195
+ let i = 0
196
+ while (i < src.length) {
197
+ const skipped = skipNonCode(src, i)
198
+ if (skipped !== i) {
199
+ i = skipped
200
+ continue
201
+ }
202
+ const before = src[i - 1]
203
+ if (src.startsWith(callee, i) && !isIdentifierChar(before) && before !== '.') {
204
+ const paren = firstCode(src, i + callee.length, src.length)
205
+ if (src[paren] === '(') return paren
206
+ }
207
+ i += 1
208
+ }
209
+ return -1
210
+ }
211
+
212
+ /** Index of the `{` after the first `export default` in code, or -1. */
213
+ export function findExportDefaultObject(src: string): number {
214
+ let i = 0
215
+ while (i < src.length) {
216
+ const skipped = skipNonCode(src, i)
217
+ if (skipped !== i) {
218
+ i = skipped
219
+ continue
220
+ }
221
+ if (src.startsWith('export', i) && !isIdentifierChar(src[i - 1])) {
222
+ const word = firstCode(src, i + 'export'.length, src.length)
223
+ if (src.startsWith('default', word) && !isIdentifierChar(src[word + 'default'.length])) {
224
+ const brace = firstCode(src, word + 'default'.length, src.length)
225
+ return src[brace] === '{' ? brace : -1
226
+ }
227
+ }
228
+ i += 1
229
+ }
230
+ return -1
231
+ }
232
+
233
+ /** A `key: value` entry of an object literal. */
234
+ export interface Property {
235
+ key: string
236
+ valueStart: number
237
+ valueEnd: number
238
+ }
239
+
240
+ /** The `key: value` entries of the object literal opening at `open`. */
241
+ export function properties(src: string, open: number, close: number): Property[] {
242
+ const out: Property[] = []
243
+ for (const segment of segments(src, open + 1, close)) {
244
+ if (segment.codeStart >= segment.end) continue
245
+ const text = src.slice(segment.codeStart, segment.end)
246
+ const match = /^(?:([A-Za-z_$][\w$]*)|'([^'\\\n]*)'|"([^"\\\n]*)")\s*:/.exec(text)
247
+ if (match === null) continue
248
+ const key = match[1] ?? match[2] ?? match[3] ?? ''
249
+ const valueStart = firstCode(src, segment.codeStart + match[0].length, segment.end)
250
+ out.push({ key, valueStart, valueEnd: segment.codeEnd })
251
+ }
252
+ return out
253
+ }
254
+
255
+ /** Leading whitespace of the line holding `index`. */
256
+ function lineIndent(src: string, index: number): string {
257
+ const lineStart = src.lastIndexOf('\n', index - 1) + 1
258
+ return /^[ \t]*/.exec(src.slice(lineStart))?.[0] ?? ''
259
+ }
260
+
261
+ /**
262
+ * Append entries to the literal `src[open..close]` and return the new text.
263
+ *
264
+ * `build` receives the indentation of the existing entries (or one level
265
+ * deeper than the closing bracket when there are none) and returns the lines
266
+ * to add, each already ending with its comma. `blankLine` separates the new
267
+ * entries from existing ones. A missing comma after the last existing entry
268
+ * is added.
269
+ */
270
+ export function appendToLiteral(
271
+ src: string,
272
+ open: number,
273
+ close: number,
274
+ eol: string,
275
+ build: (indent: string) => string[],
276
+ blankLine = false,
277
+ ): string {
278
+ const all = segments(src, open + 1, close)
279
+ const entries = all.filter((s) => s.codeStart < s.end)
280
+ const first = entries[0]
281
+ const closeIndent = lineIndent(src, close)
282
+ const firstOnOwnLine =
283
+ first !== undefined &&
284
+ /^[ \t]*$/.test(src.slice(src.lastIndexOf('\n', first.codeStart - 1) + 1, first.codeStart))
285
+ const indent =
286
+ first !== undefined && firstOnOwnLine ? lineIndent(src, first.codeStart) : `${closeIndent} `
287
+ const lines = build(indent)
288
+ const separator = blankLine && entries.length > 0 ? eol : ''
289
+
290
+ const closeLineStart = src.lastIndexOf('\n', close - 1) + 1
291
+ const closeOnOwnLine = /^[ \t]*$/.test(src.slice(closeLineStart, close))
292
+ let out = closeOnOwnLine
293
+ ? `${src.slice(0, closeLineStart)}${separator}${lines.join(eol)}${eol}${src.slice(closeLineStart)}`
294
+ : `${src.slice(0, close)}${eol}${separator}${lines.join(eol)}${eol}${closeIndent}${src.slice(close)}`
295
+
296
+ // The comma goes in last, as it sits before every insertion point above.
297
+ // No comma after the last entry when it is also the last segment.
298
+ const last = entries[entries.length - 1]
299
+ if (last !== undefined && all[all.length - 1] === last) {
300
+ out = `${out.slice(0, last.codeEnd)},${out.slice(last.codeEnd)}`
301
+ }
302
+ return out
303
+ }
@@ -513,7 +513,9 @@ export class Prompt {
513
513
  rl.question(query, (answer) => {
514
514
  if (masked) stdout.write('\n')
515
515
  rl.close()
516
- resolve(answer.trim())
516
+ // A secret is returned as typed: a password may begin or end with a
517
+ // space, and trimming it would store or compare a different one.
518
+ resolve(masked ? answer : answer.trim())
517
519
  })
518
520
  })
519
521
  }
@@ -30,10 +30,23 @@ import type { Binding, ServiceFactory, ServiceToken } from './types.js'
30
30
  */
31
31
  const RESERVED_TOKEN_NAMES = new Set(['__proto__', 'constructor', 'prototype'])
32
32
 
33
- /** One resolution chain: the tokens on it, and a set for O(1) membership. */
33
+ /**
34
+ * Where one resolution stands: the tokens above it (a list for the message, a
35
+ * set for O(1) membership), and what it carries down.
36
+ *
37
+ * **Never mutated once in the async context.** Every resolution, and every
38
+ * construction that changes the parent, runs on its OWN copy
39
+ * (`#chain.run({ ...chain, ... })`). The chain used to be one object pushed to
40
+ * and popped from in place, and a chain opened around a whole body
41
+ * (`container.call()` around a command's `run()`, `resolveFor`,
42
+ * `runWithScopedValues`) was then shared by every concurrent task inside it:
43
+ * two tasks asking for the same singleton at once read as `catalog → catalog`,
44
+ * and one task's class could stand as another's parent for a contextual
45
+ * binding. A copy per step costs an array and a set the depth of the chain.
46
+ */
34
47
  interface ResolutionChain {
35
- stack: ServiceToken[]
36
- set: Set<ServiceToken>
48
+ readonly stack: readonly ServiceToken[]
49
+ readonly set: ReadonlySet<ServiceToken>
37
50
  /**
38
51
  * Values bound on the {@link ContainerResolver} that opened this chain.
39
52
  *
@@ -51,8 +64,13 @@ interface ResolutionChain {
51
64
  * `HttpContext`. Comparing this counter before and after a build says whether
52
65
  * it actually read one, so a singleton that touched no request state still
53
66
  * caches exactly as before.
67
+ *
68
+ * A box, the one mutable part, so the copies a chain is walked on all count
69
+ * into it: a read three dependencies down is a read by the singleton being
70
+ * built at the top. Concurrent siblings count into it too, which can only
71
+ * keep a singleton out of the cache, never put a request's into it.
54
72
  */
55
- scopedReads?: number
73
+ readonly scopedReads: { count: number }
56
74
  /**
57
75
  * The class whose dependencies are being resolved right now.
58
76
  *
@@ -60,14 +78,19 @@ interface ResolutionChain {
60
78
  * this", so the lookup needs the direct dependent. Carried on the chain for
61
79
  * the same reason the scoped values are: `#autoConstruct` knows it, and
62
80
  * every method between there and the lookup would otherwise have to pass it
63
- * down. Saved and restored around each construction, so a dependency's own
81
+ * down. Each construction runs on a copy that names it, so a dependency's own
64
82
  * dependencies see IT as their parent, not its parent.
65
83
  */
66
- parent?: abstract new (
84
+ readonly parent?: abstract new (
67
85
  ...args: never[]
68
86
  ) => unknown
69
87
  }
70
88
 
89
+ /** A chain with nothing in it yet. */
90
+ function openChain(extra: Partial<ResolutionChain> = {}): ResolutionChain {
91
+ return { stack: [], set: new Set(), scopedReads: { count: 0 }, ...extra }
92
+ }
93
+
71
94
  export class Container {
72
95
  #bindings: Map<ServiceToken, Binding> = new Map()
73
96
  #singletons: Map<ServiceToken, unknown> = new Map()
@@ -320,9 +343,7 @@ export class Container {
320
343
  // it and nothing outside it does.
321
344
  const chain = this.#chain.getStore()
322
345
  if (chain === undefined) {
323
- return this.#chain.run({ stack: [], set: new Set() }, () =>
324
- this.resolve<T>(token, runtimeValues),
325
- )
346
+ return this.#chain.run(openChain(), () => this.resolve<T>(token, runtimeValues))
326
347
  }
327
348
 
328
349
  if (chain.set.has(key)) {
@@ -332,15 +353,14 @@ export class Container {
332
353
  context: { chain: cycle },
333
354
  })
334
355
  }
335
- chain.stack.push(key)
336
- chain.set.add(key)
337
-
338
- try {
339
- return await this.#resolveInner<T>(key, token, runtimeValues)
340
- } finally {
341
- chain.stack.pop()
342
- chain.set.delete(key)
356
+ // On a copy: see `ResolutionChain`. What this resolution triggers sees the
357
+ // key above it; a sibling running at the same time does not.
358
+ const own: ResolutionChain = {
359
+ ...chain,
360
+ stack: [...chain.stack, key],
361
+ set: new Set(chain.set).add(key),
343
362
  }
363
+ return this.#chain.run(own, () => this.#resolveInner<T>(key, token, runtimeValues))
344
364
  }
345
365
 
346
366
  /**
@@ -364,52 +384,17 @@ export class Container {
364
384
  // its own with nobody in it.
365
385
  const chain = this.#chain.getStore()
366
386
  if (chain === undefined) {
367
- return this.#chain.run({ stack: [], set: new Set(), parent: target }, () =>
387
+ return this.#chain.run(openChain({ parent: target }), () =>
368
388
  this.call(instance, method, runtimeValues),
369
389
  )
370
390
  }
371
- const outerParent = chain.parent
372
- // Declared outside the try so the call below can still read it: only the
373
- // RESOLUTION is guarded, because that is what can throw with the parent
374
- // installed.
375
- const args: unknown[] = []
376
- chain.parent = target
377
- try {
378
- const paramTypes: unknown[] =
379
- Reflect.getMetadata('design:paramtypes', target.prototype, method) ?? []
380
- // `@Inject('token')` on a method parameter — a named binding the reflected
381
- // type cannot express (an interface, or a class registered under a name).
382
- const injectTokens = getMethodInjectTokens(target.prototype, method)
383
-
384
- // `@Inject` counts towards the parameter span too: a method may carry named
385
- // tokens without `design:paramtypes` being emitted for it, and computing the
386
- // span from the reflected types alone left the loop at zero iterations —
387
- // the tokens were read and then never used.
388
- const injectSpan = injectTokens.size === 0 ? 0 : Math.max(...injectTokens.keys()) + 1
389
- const len = Math.max(paramTypes.length, runtimeValues?.length ?? 0, injectSpan)
390
- // Sequential resolution keeps the shared cycle-detection stack consistent.
391
- for (let index = 0; index < len; index += 1) {
392
- // Runtime values take precedence (and fill slots beyond paramTypes).
393
- if (runtimeValues && index < runtimeValues.length) {
394
- args.push(runtimeValues[index])
395
- continue
396
- }
397
- const named = injectTokens.get(index)
398
- if (named !== undefined) {
399
- args.push(await this.resolve(named))
400
- continue
401
- }
402
- const type = paramTypes[index]
403
- args.push(isInjectableClass(type) ? await this.resolve(type) : undefined)
404
- }
405
- } finally {
406
- // RESTORED whatever happened. Without the `finally`, a parameter that
407
- // failed to resolve left the called class installed as the parent, so a
408
- // caller that caught the error saw its contextual bindings applied to
409
- // every later resolution — a binding meant for one handler leaking into
410
- // the whole application.
411
- chain.parent = outerParent
412
- }
391
+ // The parameters are resolved with `target` as the parent, on a copy of
392
+ // the chain (see `ResolutionChain`), so nothing outside them sees it: not
393
+ // the caller once they are resolved, whatever happened, and not a task
394
+ // running beside them. The method itself runs on the caller's chain.
395
+ const args = await this.#chain.run({ ...chain, parent: target }, () =>
396
+ this.#methodArgs(target, method, runtimeValues),
397
+ )
413
398
 
414
399
  const member: unknown = instance[method]
415
400
  if (!isCallable(member)) {
@@ -418,6 +403,44 @@ export class Container {
418
403
  return member.apply(instance, args)
419
404
  }
420
405
 
406
+ /** The arguments `call()` passes to `target.prototype[method]`. */
407
+ async #methodArgs(
408
+ target: new (...args: never[]) => unknown,
409
+ method: string,
410
+ runtimeValues?: unknown[],
411
+ ): Promise<unknown[]> {
412
+ const args: unknown[] = []
413
+ const paramTypes: unknown[] =
414
+ Reflect.getMetadata('design:paramtypes', target.prototype, method) ?? []
415
+ // `@Inject('token')` on a method parameter — a named binding the reflected
416
+ // type cannot express (an interface, or a class registered under a name).
417
+ const injectTokens = getMethodInjectTokens(target.prototype, method)
418
+
419
+ // `@Inject` counts towards the parameter span too: a method may carry named
420
+ // tokens without `design:paramtypes` being emitted for it, and computing the
421
+ // span from the reflected types alone left the loop at zero iterations —
422
+ // the tokens were read and then never used.
423
+ const injectSpan = injectTokens.size === 0 ? 0 : Math.max(...injectTokens.keys()) + 1
424
+ const len = Math.max(paramTypes.length, runtimeValues?.length ?? 0, injectSpan)
425
+ // One at a time, in parameter order: a factory with side effects runs
426
+ // in the order the signature reads.
427
+ for (let index = 0; index < len; index += 1) {
428
+ // Runtime values take precedence (and fill slots beyond paramTypes).
429
+ if (runtimeValues && index < runtimeValues.length) {
430
+ args.push(runtimeValues[index])
431
+ continue
432
+ }
433
+ const named = injectTokens.get(index)
434
+ if (named !== undefined) {
435
+ args.push(await this.resolve(named))
436
+ continue
437
+ }
438
+ const type = paramTypes[index]
439
+ args.push(isInjectableClass(type) ? await this.resolve(type) : undefined)
440
+ }
441
+ return args
442
+ }
443
+
421
444
  /**
422
445
  * A resolver whose {@link ContainerResolver.bindValue} values are isolated
423
446
  * from this container (AdonisJS `container.createResolver()`).
@@ -441,10 +464,7 @@ export class Container {
441
464
  fn: () => Promise<T>,
442
465
  ): Promise<T> {
443
466
  const chain = this.#chain.getStore()
444
- return this.#chain.run(
445
- chain === undefined ? { stack: [], set: new Set(), values } : { ...chain, values },
446
- fn,
447
- )
467
+ return this.#chain.run(chain === undefined ? openChain({ values }) : { ...chain, values }, fn)
448
468
  }
449
469
 
450
470
  /** @internal Validate a token and hand back the value the container keys on. */
@@ -464,9 +484,8 @@ export class Container {
464
484
  ): Promise<T> {
465
485
  if (parent === null) return this.make<T>(token, runtimeValues)
466
486
  const chain = this.#chain.getStore()
467
- return this.#chain.run(
468
- chain === undefined ? { stack: [], set: new Set(), parent } : { ...chain, parent },
469
- () => this.make<T>(token, runtimeValues),
487
+ return this.#chain.run(chain === undefined ? openChain({ parent }) : { ...chain, parent }, () =>
488
+ this.make<T>(token, runtimeValues),
470
489
  )
471
490
  }
472
491
 
@@ -588,7 +607,7 @@ export class Container {
588
607
  // resolves in (`resolveFor`: swaps, resolver values, container values).
589
608
  // No `resolving` hooks: the value was not constructed here.
590
609
  if (store?.values?.has(key)) {
591
- store.scopedReads = (store.scopedReads ?? 0) + 1
610
+ store.scopedReads.count += 1
592
611
  return store.values.get(key) as T
593
612
  }
594
613
 
@@ -619,7 +638,7 @@ export class Container {
619
638
  if (this.#wasPublished(key, shared)) return shared as T
620
639
  }
621
640
 
622
- const readsBefore = store?.scopedReads ?? 0
641
+ const readsBefore = store?.scopedReads.count ?? 0
623
642
  const building = (async (): Promise<unknown> => {
624
643
  const instance = binding.factory ? await binding.factory(this) : undefined
625
644
  // Only cache while this is still the binding that started the build.
@@ -731,7 +750,7 @@ export class Container {
731
750
 
732
751
  // Same rule as the explicit bindings: see `ResolutionChain.scopedReads`.
733
752
  const store = this.#chain.getStore()
734
- const readsBefore = store?.scopedReads ?? 0
753
+ const readsBefore = store?.scopedReads.count ?? 0
735
754
  const building = (async (): Promise<unknown> => {
736
755
  const instance = await this.#autoConstruct(target, runtimeValues)
737
756
  await this.#runResolvingHooks(key, instance)
@@ -754,16 +773,13 @@ export class Container {
754
773
  runtimeValues?: unknown[],
755
774
  ): Promise<unknown> {
756
775
  // Everything resolved inside is a dependency OF `target`, which is what a
757
- // contextual binding keys on. Restored on EVERY exit — the construction has
758
- // three returns and can throw — so `target`'s own parent is back in place.
759
- const store = this.#chain.getStore()
760
- const outerParent = store?.parent
761
- if (store !== undefined) store.parent = target
762
- try {
763
- return await this.#constructWithDeps(target, runtimeValues)
764
- } finally {
765
- if (store !== undefined) store.parent = outerParent
766
- }
776
+ // contextual binding keys on. On a copy of the chain (see
777
+ // `ResolutionChain`): nothing to restore on any of the construction's
778
+ // exits, and a construction running beside this one keeps its own parent.
779
+ const store = this.#chain.getStore() ?? openChain()
780
+ return this.#chain.run({ ...store, parent: target }, () =>
781
+ this.#constructWithDeps(target, runtimeValues),
782
+ )
767
783
  }
768
784
 
769
785
  async #constructWithDeps(
@@ -902,7 +918,7 @@ export class Container {
902
918
  readsBefore: number,
903
919
  ): void {
904
920
  if (scope !== 'singleton') return
905
- if ((store?.scopedReads ?? 0) !== readsBefore) return
921
+ if ((store?.scopedReads.count ?? 0) !== readsBefore) return
906
922
  this.#singletons.set(key, instance)
907
923
  }
908
924