semola 0.5.1 → 0.5.2

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 (273) hide show
  1. package/README.md +68 -28
  2. package/dist/api/core/index.cjs +206 -0
  3. package/dist/api/core/index.d.cts +21 -0
  4. package/dist/api/core/index.d.cts.map +1 -0
  5. package/dist/api/core/index.d.mts +21 -0
  6. package/dist/api/core/index.d.mts.map +1 -0
  7. package/dist/api/core/index.mjs +208 -0
  8. package/dist/api/core/index.mjs.map +1 -0
  9. package/dist/api/core/types.d.cts +107 -0
  10. package/dist/api/core/types.d.cts.map +1 -0
  11. package/dist/api/core/types.d.mts +107 -0
  12. package/dist/api/core/types.d.mts.map +1 -0
  13. package/dist/api/middleware/index.cjs +8 -0
  14. package/dist/api/middleware/index.d.cts +11 -0
  15. package/dist/api/middleware/index.d.cts.map +1 -0
  16. package/dist/api/middleware/index.d.mts +11 -0
  17. package/dist/api/middleware/index.d.mts.map +1 -0
  18. package/dist/api/middleware/index.mjs +10 -0
  19. package/dist/api/middleware/index.mjs.map +1 -0
  20. package/dist/api/middleware/types.d.cts +16 -0
  21. package/dist/api/middleware/types.d.cts.map +1 -0
  22. package/dist/api/middleware/types.d.mts +16 -0
  23. package/dist/api/middleware/types.d.mts.map +1 -0
  24. package/dist/api/openapi/index.cjs +254 -0
  25. package/dist/api/openapi/index.mjs +256 -0
  26. package/dist/api/openapi/index.mjs.map +1 -0
  27. package/dist/api/openapi/types.d.cts +60 -0
  28. package/dist/api/openapi/types.d.cts.map +1 -0
  29. package/dist/api/openapi/types.d.mts +60 -0
  30. package/dist/api/openapi/types.d.mts.map +1 -0
  31. package/dist/api/validation/index.cjs +64 -0
  32. package/dist/api/validation/index.mjs +61 -0
  33. package/dist/api/validation/index.mjs.map +1 -0
  34. package/dist/cache/types.d.cts +17 -0
  35. package/dist/cache/types.d.cts.map +1 -0
  36. package/dist/cache/types.d.mts +17 -0
  37. package/dist/cache/types.d.mts.map +1 -0
  38. package/dist/cron/scanner.cjs +237 -0
  39. package/dist/cron/scanner.mjs +238 -0
  40. package/dist/cron/scanner.mjs.map +1 -0
  41. package/dist/cron/types.d.cts +11 -0
  42. package/dist/cron/types.d.cts.map +1 -0
  43. package/dist/cron/types.d.mts +11 -0
  44. package/dist/cron/types.d.mts.map +1 -0
  45. package/dist/errors/types.d.cts +5 -0
  46. package/dist/errors/types.d.cts.map +1 -0
  47. package/dist/errors/types.d.mts +5 -0
  48. package/dist/errors/types.d.mts.map +1 -0
  49. package/dist/i18n/types.d.cts +13 -0
  50. package/dist/i18n/types.d.cts.map +1 -0
  51. package/dist/i18n/types.d.mts +13 -0
  52. package/dist/i18n/types.d.mts.map +1 -0
  53. package/dist/lib/api/index.cjs +5 -0
  54. package/dist/lib/api/index.d.cts +5 -0
  55. package/dist/lib/api/index.d.mts +5 -0
  56. package/dist/lib/api/index.mjs +3 -0
  57. package/dist/lib/cache/index.cjs +74 -0
  58. package/dist/lib/cache/index.d.cts +36 -0
  59. package/dist/lib/cache/index.d.cts.map +1 -0
  60. package/dist/lib/cache/index.d.mts +36 -0
  61. package/dist/lib/cache/index.d.mts.map +1 -0
  62. package/dist/lib/cache/index.mjs +75 -0
  63. package/dist/lib/cache/index.mjs.map +1 -0
  64. package/dist/lib/cron/index.cjs +276 -0
  65. package/dist/lib/cron/index.d.cts +39 -0
  66. package/dist/lib/cron/index.d.cts.map +1 -0
  67. package/dist/lib/cron/index.d.mts +39 -0
  68. package/dist/lib/cron/index.d.mts.map +1 -0
  69. package/dist/lib/cron/index.mjs +277 -0
  70. package/dist/lib/cron/index.mjs.map +1 -0
  71. package/dist/lib/errors/index.cjs +30 -0
  72. package/dist/lib/errors/index.d.cts +13 -0
  73. package/dist/lib/errors/index.d.cts.map +1 -0
  74. package/dist/lib/errors/index.d.mts +13 -0
  75. package/dist/lib/errors/index.d.mts.map +1 -0
  76. package/dist/lib/errors/index.mjs +28 -0
  77. package/dist/lib/errors/index.mjs.map +1 -0
  78. package/dist/lib/i18n/index.cjs +37 -0
  79. package/dist/lib/i18n/index.d.cts +20 -0
  80. package/dist/lib/i18n/index.d.cts.map +1 -0
  81. package/dist/lib/i18n/index.d.mts +20 -0
  82. package/dist/lib/i18n/index.d.mts.map +1 -0
  83. package/dist/lib/i18n/index.mjs +38 -0
  84. package/dist/lib/i18n/index.mjs.map +1 -0
  85. package/dist/lib/policy/index.cjs +99 -0
  86. package/dist/lib/policy/index.d.cts +21 -0
  87. package/dist/lib/policy/index.d.cts.map +1 -0
  88. package/dist/lib/policy/index.d.mts +21 -0
  89. package/dist/lib/policy/index.d.mts.map +1 -0
  90. package/dist/lib/policy/index.mjs +81 -0
  91. package/dist/lib/policy/index.mjs.map +1 -0
  92. package/dist/lib/prompts/index.cjs +409 -0
  93. package/dist/lib/prompts/index.d.cts +31 -0
  94. package/dist/lib/prompts/index.d.cts.map +1 -0
  95. package/dist/lib/prompts/index.d.mts +31 -0
  96. package/dist/lib/prompts/index.d.mts.map +1 -0
  97. package/dist/lib/prompts/index.mjs +405 -0
  98. package/dist/lib/prompts/index.mjs.map +1 -0
  99. package/dist/lib/pubsub/index.cjs +48 -0
  100. package/dist/lib/pubsub/index.d.cts +27 -0
  101. package/dist/lib/pubsub/index.d.cts.map +1 -0
  102. package/dist/lib/pubsub/index.d.mts +27 -0
  103. package/dist/lib/pubsub/index.d.mts.map +1 -0
  104. package/dist/lib/pubsub/index.mjs +49 -0
  105. package/dist/lib/pubsub/index.mjs.map +1 -0
  106. package/dist/lib/queue/index.cjs +182 -0
  107. package/dist/lib/queue/index.d.cts +32 -0
  108. package/dist/lib/queue/index.d.cts.map +1 -0
  109. package/dist/lib/queue/index.d.mts +32 -0
  110. package/dist/lib/queue/index.d.mts.map +1 -0
  111. package/dist/lib/queue/index.mjs +183 -0
  112. package/dist/lib/queue/index.mjs.map +1 -0
  113. package/dist/node_modules/@standard-schema/spec/dist/index.d.cts +80 -0
  114. package/dist/node_modules/@standard-schema/spec/dist/index.d.cts.map +1 -0
  115. package/dist/node_modules/@standard-schema/spec/dist/index.d.mts +80 -0
  116. package/dist/node_modules/@standard-schema/spec/dist/index.d.mts.map +1 -0
  117. package/dist/policy/helpers.cjs +206 -0
  118. package/dist/policy/helpers.d.cts +50 -0
  119. package/dist/policy/helpers.d.cts.map +1 -0
  120. package/dist/policy/helpers.d.mts +50 -0
  121. package/dist/policy/helpers.d.mts.map +1 -0
  122. package/dist/policy/helpers.mjs +190 -0
  123. package/dist/policy/helpers.mjs.map +1 -0
  124. package/dist/policy/types.d.cts +16 -0
  125. package/dist/policy/types.d.cts.map +1 -0
  126. package/dist/policy/types.d.mts +16 -0
  127. package/dist/policy/types.d.mts.map +1 -0
  128. package/dist/prompts/core/keys.cjs +165 -0
  129. package/dist/prompts/core/keys.mjs +167 -0
  130. package/dist/prompts/core/keys.mjs.map +1 -0
  131. package/dist/prompts/core/runtime.cjs +104 -0
  132. package/dist/prompts/core/runtime.mjs +106 -0
  133. package/dist/prompts/core/runtime.mjs.map +1 -0
  134. package/dist/prompts/core/session.cjs +98 -0
  135. package/dist/prompts/core/session.mjs +100 -0
  136. package/dist/prompts/core/session.mjs.map +1 -0
  137. package/dist/prompts/core/types.d.cts +21 -0
  138. package/dist/prompts/core/types.d.cts.map +1 -0
  139. package/dist/prompts/core/types.d.mts +21 -0
  140. package/dist/prompts/core/types.d.mts.map +1 -0
  141. package/dist/prompts/types.d.cts +52 -0
  142. package/dist/prompts/types.d.cts.map +1 -0
  143. package/dist/prompts/types.d.mts +52 -0
  144. package/dist/prompts/types.d.mts.map +1 -0
  145. package/dist/pubsub/types.d.cts +10 -0
  146. package/dist/pubsub/types.d.cts.map +1 -0
  147. package/dist/pubsub/types.d.mts +10 -0
  148. package/dist/pubsub/types.d.mts.map +1 -0
  149. package/dist/queue/types.d.cts +47 -0
  150. package/dist/queue/types.d.cts.map +1 -0
  151. package/dist/queue/types.d.mts +47 -0
  152. package/dist/queue/types.d.mts.map +1 -0
  153. package/package.json +86 -16
  154. package/dist/lib/api/core/index.d.ts +0 -15
  155. package/dist/lib/api/core/index.d.ts.map +0 -1
  156. package/dist/lib/api/core/index.js +0 -218
  157. package/dist/lib/api/core/index.js.map +0 -1
  158. package/dist/lib/api/core/index.test.d.ts +0 -2
  159. package/dist/lib/api/core/index.test.d.ts.map +0 -1
  160. package/dist/lib/api/core/index.test.js +0 -219
  161. package/dist/lib/api/core/index.test.js.map +0 -1
  162. package/dist/lib/api/core/types.d.ts +0 -109
  163. package/dist/lib/api/core/types.d.ts.map +0 -1
  164. package/dist/lib/api/core/types.js +0 -2
  165. package/dist/lib/api/core/types.js.map +0 -1
  166. package/dist/lib/api/index.d.ts +0 -5
  167. package/dist/lib/api/index.d.ts.map +0 -1
  168. package/dist/lib/api/index.js +0 -3
  169. package/dist/lib/api/index.js.map +0 -1
  170. package/dist/lib/api/middleware/index.d.ts +0 -7
  171. package/dist/lib/api/middleware/index.d.ts.map +0 -1
  172. package/dist/lib/api/middleware/index.js +0 -7
  173. package/dist/lib/api/middleware/index.js.map +0 -1
  174. package/dist/lib/api/middleware/index.test.d.ts +0 -2
  175. package/dist/lib/api/middleware/index.test.d.ts.map +0 -1
  176. package/dist/lib/api/middleware/index.test.js +0 -67
  177. package/dist/lib/api/middleware/index.test.js.map +0 -1
  178. package/dist/lib/api/middleware/types.d.ts +0 -12
  179. package/dist/lib/api/middleware/types.d.ts.map +0 -1
  180. package/dist/lib/api/middleware/types.js +0 -2
  181. package/dist/lib/api/middleware/types.js.map +0 -1
  182. package/dist/lib/api/openapi/index.d.ts +0 -31
  183. package/dist/lib/api/openapi/index.d.ts.map +0 -1
  184. package/dist/lib/api/openapi/index.js +0 -284
  185. package/dist/lib/api/openapi/index.js.map +0 -1
  186. package/dist/lib/api/openapi/index.test.d.ts +0 -2
  187. package/dist/lib/api/openapi/index.test.d.ts.map +0 -1
  188. package/dist/lib/api/openapi/index.test.js +0 -359
  189. package/dist/lib/api/openapi/index.test.js.map +0 -1
  190. package/dist/lib/api/openapi/types.d.ts +0 -57
  191. package/dist/lib/api/openapi/types.d.ts.map +0 -1
  192. package/dist/lib/api/openapi/types.js +0 -5
  193. package/dist/lib/api/openapi/types.js.map +0 -1
  194. package/dist/lib/api/validation/index.d.ts +0 -33
  195. package/dist/lib/api/validation/index.d.ts.map +0 -1
  196. package/dist/lib/api/validation/index.js +0 -92
  197. package/dist/lib/api/validation/index.js.map +0 -1
  198. package/dist/lib/api/validation/index.test.d.ts +0 -2
  199. package/dist/lib/api/validation/index.test.d.ts.map +0 -1
  200. package/dist/lib/api/validation/index.test.js +0 -135
  201. package/dist/lib/api/validation/index.test.js.map +0 -1
  202. package/dist/lib/cache/index.d.ts +0 -25
  203. package/dist/lib/cache/index.d.ts.map +0 -1
  204. package/dist/lib/cache/index.js +0 -62
  205. package/dist/lib/cache/index.js.map +0 -1
  206. package/dist/lib/cache/index.test.d.ts +0 -2
  207. package/dist/lib/cache/index.test.d.ts.map +0 -1
  208. package/dist/lib/cache/index.test.js +0 -314
  209. package/dist/lib/cache/index.test.js.map +0 -1
  210. package/dist/lib/cache/types.d.ts +0 -5
  211. package/dist/lib/cache/types.d.ts.map +0 -1
  212. package/dist/lib/cache/types.js +0 -2
  213. package/dist/lib/cache/types.js.map +0 -1
  214. package/dist/lib/errors/index.d.ts +0 -9
  215. package/dist/lib/errors/index.d.ts.map +0 -1
  216. package/dist/lib/errors/index.js +0 -25
  217. package/dist/lib/errors/index.js.map +0 -1
  218. package/dist/lib/errors/index.test.d.ts +0 -2
  219. package/dist/lib/errors/index.test.d.ts.map +0 -1
  220. package/dist/lib/errors/index.test.js +0 -197
  221. package/dist/lib/errors/index.test.js.map +0 -1
  222. package/dist/lib/errors/types.d.ts +0 -2
  223. package/dist/lib/errors/types.d.ts.map +0 -1
  224. package/dist/lib/errors/types.js +0 -2
  225. package/dist/lib/errors/types.js.map +0 -1
  226. package/dist/lib/i18n/index.d.ts +0 -18
  227. package/dist/lib/i18n/index.d.ts.map +0 -1
  228. package/dist/lib/i18n/index.js +0 -38
  229. package/dist/lib/i18n/index.js.map +0 -1
  230. package/dist/lib/i18n/index.test.d.ts +0 -2
  231. package/dist/lib/i18n/index.test.d.ts.map +0 -1
  232. package/dist/lib/i18n/index.test.js +0 -402
  233. package/dist/lib/i18n/index.test.js.map +0 -1
  234. package/dist/lib/i18n/types.d.ts +0 -15
  235. package/dist/lib/i18n/types.d.ts.map +0 -1
  236. package/dist/lib/i18n/types.js +0 -2
  237. package/dist/lib/i18n/types.js.map +0 -1
  238. package/dist/lib/policy/index.d.ts +0 -9
  239. package/dist/lib/policy/index.d.ts.map +0 -1
  240. package/dist/lib/policy/index.js +0 -51
  241. package/dist/lib/policy/index.js.map +0 -1
  242. package/dist/lib/policy/index.test.d.ts +0 -2
  243. package/dist/lib/policy/index.test.d.ts.map +0 -1
  244. package/dist/lib/policy/index.test.js +0 -328
  245. package/dist/lib/policy/index.test.js.map +0 -1
  246. package/dist/lib/policy/types.d.ts +0 -27
  247. package/dist/lib/policy/types.d.ts.map +0 -1
  248. package/dist/lib/policy/types.js +0 -2
  249. package/dist/lib/policy/types.js.map +0 -1
  250. package/dist/lib/pubsub/index.d.ts +0 -23
  251. package/dist/lib/pubsub/index.d.ts.map +0 -1
  252. package/dist/lib/pubsub/index.js +0 -55
  253. package/dist/lib/pubsub/index.js.map +0 -1
  254. package/dist/lib/pubsub/index.test.d.ts +0 -2
  255. package/dist/lib/pubsub/index.test.d.ts.map +0 -1
  256. package/dist/lib/pubsub/index.test.js +0 -550
  257. package/dist/lib/pubsub/index.test.js.map +0 -1
  258. package/dist/lib/pubsub/types.d.ts +0 -7
  259. package/dist/lib/pubsub/types.d.ts.map +0 -1
  260. package/dist/lib/pubsub/types.js +0 -2
  261. package/dist/lib/pubsub/types.js.map +0 -1
  262. package/dist/lib/queue/index.d.ts +0 -28
  263. package/dist/lib/queue/index.d.ts.map +0 -1
  264. package/dist/lib/queue/index.js +0 -211
  265. package/dist/lib/queue/index.js.map +0 -1
  266. package/dist/lib/queue/index.test.d.ts +0 -2
  267. package/dist/lib/queue/index.test.d.ts.map +0 -1
  268. package/dist/lib/queue/index.test.js +0 -740
  269. package/dist/lib/queue/index.test.js.map +0 -1
  270. package/dist/lib/queue/types.d.ts +0 -52
  271. package/dist/lib/queue/types.d.ts.map +0 -1
  272. package/dist/lib/queue/types.js +0 -2
  273. package/dist/lib/queue/types.js.map +0 -1
package/README.md CHANGED
@@ -18,15 +18,18 @@ Type-safe APIs, Redis queues, pub/sub, i18n, caching & auth with tree-shakeable
18
18
 
19
19
  ## ✨ Features
20
20
 
21
- | Module | Description | Import |
22
- | -------------------- | ------------------------------------------------------ | --------------- |
23
- | **🚀 API Framework** | Type-safe REST API with OpenAPI & Bun-native routing | `semola/api` |
24
- | **📬 Queue** | Redis-backed job queue with timeouts & concurrency | `semola/queue` |
25
- | **📡 PubSub** | Type-safe Redis pub/sub for real-time messaging | `semola/pubsub` |
26
- | **🔐 Policy** | Policy-based authorization with type-safe guards | `semola/policy` |
27
- | **🌍 i18n** | Compile-time validated internationalization | `semola/i18n` |
28
- | **💾 Cache** | Redis cache wrapper with TTL & automatic serialization | `semola/cache` |
29
- | **⚠️ Errors** | Result-based error handling without try/catch | `semola/errors` |
21
+ | Module | Description | Import |
22
+ | -------------------- | ------------------------------------------------------ | ---------------- |
23
+ | **🚀 API Framework** | Type-safe REST API with OpenAPI & Bun-native routing | `semola/api` |
24
+ | **📬 Queue** | Redis-backed job queue with timeouts & concurrency | `semola/queue` |
25
+ | **📡 PubSub** | Type-safe Redis pub/sub for real-time messaging | `semola/pubsub` |
26
+ | **🔐 Policy** | Policy-based authorization with type-safe guards | `semola/policy` |
27
+ | **🌍 i18n** | Compile-time validated internationalization | `semola/i18n` |
28
+ | **💾 Cache** | Redis cache wrapper with TTL & automatic serialization | `semola/cache` |
29
+ | **⏰ Cron** | In-memory cron scheduler for periodic task execution | `semola/cron` |
30
+ | **⚠️ Errors** | Result-based error handling without try/catch | `semola/errors` |
31
+ | **📃 Logging** | A simple logging utility | `semola/logging` |
32
+ | **⌨️ Prompts** | Interactive zero-dependency CLI prompts | `semola/prompts` |
30
33
 
31
34
  ---
32
35
 
@@ -135,24 +138,49 @@ const [error, user] = await cache.get("user:123");
135
138
  if (!error) console.log(user);
136
139
  ```
137
140
 
141
+ ### Schedule Recurring Tasks
142
+
143
+ ```typescript
144
+ import { Cron } from "semola/cron";
145
+
146
+ const cleanup = new Cron({
147
+ name: "daily-cleanup",
148
+ schedule: "0 0 * * *", // Daily at midnight
149
+ handler: async () => {
150
+ await deleteOldLogs();
151
+ await archiveInactiveUsers();
152
+ },
153
+ });
154
+
155
+ cleanup.start();
156
+ ```
157
+
138
158
  ### Check Permissions
139
159
 
140
160
  ```typescript
141
- import { Policy } from "semola/policy";
161
+ import { Policy, eq, has } from "semola/policy";
162
+
163
+ type User = { id: number; role: string; permissions: string[] };
142
164
 
143
- const policy = new Policy();
165
+ const policy = new Policy<User>();
144
166
 
145
- // Allow admins to edit any post
167
+ // Allow admins full access
146
168
  policy.allow({
147
- action: "update",
148
- entity: "post",
149
- conditions: { role: "admin" },
150
- reason: "Admins can edit any post",
169
+ action: ["create", "update", "delete"],
170
+ conditions: { role: eq("admin") },
171
+ reason: "Admins have full access",
151
172
  });
152
173
 
153
- // Check if user can edit
154
- const result = policy.can("update", "post", { role: user.role });
155
- console.log(result.allowed); // true or false
174
+ // Allow users with a specific permission
175
+ policy.allow({
176
+ action: "read",
177
+ conditions: { permissions: has("posts:read") },
178
+ });
179
+
180
+ // Check if user can perform an action
181
+ const user: User = { id: 1, role: "admin", permissions: [] };
182
+ const result = policy.can("update", user);
183
+ console.log(result.allowed); // true
156
184
  ```
157
185
 
158
186
  ### Internationalize Your App
@@ -171,6 +199,15 @@ const i18n = new I18n({
171
199
  console.log(i18n.translate("greeting", { name: "World" }));
172
200
  ```
173
201
 
202
+ ### Log your messages
203
+
204
+ ```typescript
205
+ import { ConsoleProvider, Logger } from "semola/logging";
206
+
207
+ const logger = new Logger("database", [new ConsoleProvider()]);
208
+ logger.info("Hello!");
209
+ ```
210
+
174
211
  ---
175
212
 
176
213
  ## 📦 Installation
@@ -206,14 +243,14 @@ Stop piecing together half-baked solutions from npm. Stop wrestling with type de
206
243
 
207
244
  Semola API is the **fastest API framework** for Bun.
208
245
 
209
- | Framework | Avg Req/Sec | Latency Avg (ms) | vs Semola |
210
- | :--------- | ----------: | ---------------: | :-----------: |
211
- | **Semola** | **40,050** | **1.88** | **baseline** |
212
- | Elysia | 37,185 | 2.13 | 1.1x slower |
213
- | Hono | 34,611 | 2.31 | 1.2x slower |
214
- | Fastify | 26,330 | 3.70 | 1.5x slower |
215
- | Express | 20,031 | 5.02 | **2x slower** |
216
- | NestJS | 16,118 | 6.21 | 2.5x slower |
246
+ | Framework | Avg Req/Sec | Latency Avg (ms) | vs Semola |
247
+ | :--------- | ----------: | ---------------: | :----------: |
248
+ | **Semola** | **40,050** | **1.88** | **baseline** |
249
+ | Elysia | 37,185 | 2.13 | 1.1x slower |
250
+ | Hono | 34,611 | 2.31 | 1.2x slower |
251
+ | Fastify | 26,330 | 3.70 | 1.5x slower |
252
+ | Express | 20,031 | 5.02 | 2x slower |
253
+ | NestJS | 16,118 | 6.21 | 2.5x slower |
217
254
 
218
255
  _Higher is better for req/sec, lower is better for latency._
219
256
 
@@ -234,10 +271,13 @@ _Higher is better for req/sec, lower is better for latency._
234
271
  - [API Framework](./docs/api.md) - Type-safe REST API framework with OpenAPI
235
272
  - [Queue](./docs/queue.md) - Redis-backed job queue with timeouts & concurrency
236
273
  - [PubSub](./docs/pubsub.md) - Type-safe Redis pub/sub
274
+ - [Cron](./docs/cron.md) - In-memory cron scheduler for periodic task execution
237
275
  - [Policy](./docs/policy.md) - Policy-based authorization
238
276
  - [i18n](./docs/i18n.md) - Type-safe internationalization
239
277
  - [Cache](./docs/cache.md) - Redis cache wrapper with TTL
240
278
  - [Errors](./docs/errors.md) - Result-based error handling
279
+ - [Logging](./docs/logging.md) - Logging utility
280
+ - [Prompts](./docs/prompts.md) - Interactive CLI prompts
241
281
 
242
282
  ---
243
283
 
@@ -263,6 +303,6 @@ bun check
263
303
 
264
304
  This package uses GitHub Actions for automated publishing. To release:
265
305
 
266
- 1. Bump version: `bun version <major|minor|patch>`
306
+ 1. Bump version: `bun pm version <major|minor|patch>`
267
307
  2. Create a GitHub release with a new tag (e.g., `v0.4.0`)
268
308
  3. The GitHub Action automatically publishes to npm with provenance
@@ -0,0 +1,206 @@
1
+ const require_lib_errors_index = require("../../lib/errors/index.cjs");
2
+ const require_index = require("../openapi/index.cjs");
3
+ const require_index$1 = require("../validation/index.cjs");
4
+ //#region src/lib/api/core/index.ts
5
+ const defaultValidated = Object.freeze({
6
+ body: void 0,
7
+ query: void 0,
8
+ headers: void 0,
9
+ cookies: void 0,
10
+ params: void 0
11
+ });
12
+ const responseHelpers = {
13
+ json: (status, data) => Response.json(data, { status }),
14
+ text: (status, text) => new Response(text, { status }),
15
+ html: (status, html) => new Response(html, {
16
+ status,
17
+ headers: { "Content-Type": "text/html" }
18
+ }),
19
+ redirect: (status, url) => Response.redirect(url, status)
20
+ };
21
+ const noopGet = () => void 0;
22
+ const stripTrailingSlash = (path) => {
23
+ if (path !== "/" && path.endsWith("/")) return path.slice(0, -1);
24
+ return path;
25
+ };
26
+ const hasSchemas = (schema) => schema && (schema.body || schema.query || schema.headers || schema.cookies || schema.params);
27
+ const needsBodyCache = (schema) => schema?.body !== void 0;
28
+ const shouldCreateBodyCache = (hasMiddlewares, allMiddlewares, request) => {
29
+ if (needsBodyCache(request)) return true;
30
+ if (!hasMiddlewares) return false;
31
+ return allMiddlewares.some((mw) => needsBodyCache(mw.options.request));
32
+ };
33
+ const resolveValidation = (v) => {
34
+ if (v === void 0 || v === true) return {
35
+ input: true,
36
+ output: true
37
+ };
38
+ if (v === false) return {
39
+ input: false,
40
+ output: false
41
+ };
42
+ return {
43
+ input: v.input !== false,
44
+ output: v.output !== false
45
+ };
46
+ };
47
+ var Api = class {
48
+ options;
49
+ routes = [];
50
+ constructor(options = {}) {
51
+ this.options = options;
52
+ }
53
+ getFullPath(path) {
54
+ const normalizedPath = stripTrailingSlash(path) || "/";
55
+ if (!this.options.prefix) return normalizedPath;
56
+ const normalizedPrefix = stripTrailingSlash(this.options.prefix);
57
+ if (normalizedPrefix === "/") return normalizedPath;
58
+ if (normalizedPath === "/") return normalizedPrefix;
59
+ return normalizedPrefix + normalizedPath;
60
+ }
61
+ async validateRequestSchema(req, schema, bodyCache) {
62
+ if (!schema) return {
63
+ success: true,
64
+ data: {}
65
+ };
66
+ const v = {};
67
+ if (schema.body) {
68
+ const [err, val] = await require_index$1.validateBody(req, schema.body, bodyCache);
69
+ if (err) return {
70
+ success: false,
71
+ error: err
72
+ };
73
+ v.body = val;
74
+ }
75
+ if (schema.query) {
76
+ const [err, val] = await require_index$1.validateQuery(req, schema.query);
77
+ if (err) return {
78
+ success: false,
79
+ error: err
80
+ };
81
+ v.query = val;
82
+ }
83
+ if (schema.headers) {
84
+ const [err, val] = await require_index$1.validateHeaders(req, schema.headers);
85
+ if (err) return {
86
+ success: false,
87
+ error: err
88
+ };
89
+ v.headers = val;
90
+ }
91
+ if (schema.cookies) {
92
+ const [err, val] = await require_index$1.validateCookies(req, schema.cookies);
93
+ if (err) return {
94
+ success: false,
95
+ error: err
96
+ };
97
+ v.cookies = val;
98
+ }
99
+ if (schema.params) {
100
+ const [err, val] = await require_index$1.validateParams(req, schema.params);
101
+ if (err) return {
102
+ success: false,
103
+ error: err
104
+ };
105
+ v.params = val;
106
+ }
107
+ return {
108
+ success: true,
109
+ data: v
110
+ };
111
+ }
112
+ createContext(req, validated, extensions) {
113
+ return {
114
+ raw: req,
115
+ req: validated,
116
+ ...responseHelpers,
117
+ get: (key) => extensions[key]
118
+ };
119
+ }
120
+ async validateResponseBody(response, schema) {
121
+ if (!schema) return response;
122
+ const statusSchema = schema[response.status];
123
+ if (!statusSchema) return response;
124
+ if (!(response.headers.get("content-type") ?? "").includes("application/json")) return response;
125
+ const [parseError, body] = await require_lib_errors_index.mightThrow(response.clone().json());
126
+ if (parseError) return responseHelpers.json(400, { message: "Invalid response body" });
127
+ const [validationError] = await require_index$1.validateSchema(statusSchema, body);
128
+ if (validationError) return responseHelpers.json(400, { message: validationError.message });
129
+ return response;
130
+ }
131
+ buildBunRoutes() {
132
+ const bunRoutes = {};
133
+ const validationConfig = resolveValidation(this.options.validation);
134
+ for (const route of this.routes) {
135
+ const { path, method, handler, request, response, middlewares } = route;
136
+ const fullPath = this.getFullPath(path);
137
+ if (!bunRoutes[fullPath]) bunRoutes[fullPath] = {};
138
+ const allMiddlewares = [...this.options.middlewares ?? [], ...middlewares ?? []];
139
+ const hasMiddlewares = allMiddlewares.length > 0;
140
+ const hasRouteSchemas = hasSchemas(request);
141
+ const effectiveOutputValidation = validationConfig.output && !!response;
142
+ if (!hasMiddlewares && !(validationConfig.input && hasRouteSchemas) && !effectiveOutputValidation) bunRoutes[fullPath][method] = (req) => {
143
+ const context = Object.create(responseHelpers);
144
+ context.raw = req;
145
+ context.req = defaultValidated;
146
+ context.get = noopGet;
147
+ return handler(context);
148
+ };
149
+ else bunRoutes[fullPath][method] = async (req) => {
150
+ const extensions = {};
151
+ const bodyCache = validationConfig.input && shouldCreateBodyCache(hasMiddlewares, allMiddlewares, request) ? {
152
+ parsed: false,
153
+ value: void 0
154
+ } : void 0;
155
+ for (const mw of allMiddlewares) {
156
+ const { request: reqSchema, handler: mwHandler } = mw.options;
157
+ let validated = defaultValidated;
158
+ if (validationConfig.input && hasSchemas(reqSchema)) {
159
+ const result = await this.validateRequestSchema(req, reqSchema, bodyCache);
160
+ if (!result.success) return responseHelpers.json(400, { message: result.error?.message });
161
+ validated = result.data;
162
+ }
163
+ const mwResult = await mwHandler(this.createContext(req, validated, extensions));
164
+ if (mwResult instanceof Response) return mwResult;
165
+ if (mwResult) Object.assign(extensions, mwResult);
166
+ }
167
+ let routeValidated = defaultValidated;
168
+ if (validationConfig.input && hasRouteSchemas) {
169
+ const result = await this.validateRequestSchema(req, request, bodyCache);
170
+ if (!result.success) return responseHelpers.json(400, { message: result.error?.message });
171
+ routeValidated = result.data;
172
+ }
173
+ const handlerResponse = await handler(this.createContext(req, routeValidated, extensions));
174
+ if (effectiveOutputValidation) return this.validateResponseBody(handlerResponse, response);
175
+ return handlerResponse;
176
+ };
177
+ }
178
+ return bunRoutes;
179
+ }
180
+ defineRoute(config) {
181
+ this.routes.push(config);
182
+ }
183
+ getOpenApiSpec() {
184
+ return require_index.generateOpenApiSpec({
185
+ title: this.options.openapi?.title ?? "API",
186
+ description: this.options.openapi?.description,
187
+ version: this.options.openapi?.version ?? "1.0.0",
188
+ prefix: this.options.prefix,
189
+ servers: this.options.openapi?.servers,
190
+ securitySchemes: this.options.openapi?.securitySchemes,
191
+ routes: this.routes,
192
+ globalMiddlewares: this.options.middlewares
193
+ });
194
+ }
195
+ serve(port, callback) {
196
+ const bunRoutes = this.buildBunRoutes();
197
+ const server = Bun.serve({
198
+ port,
199
+ routes: bunRoutes,
200
+ fetch: () => new Response("Not found", { status: 404 })
201
+ });
202
+ if (callback) callback(server);
203
+ }
204
+ };
205
+ //#endregion
206
+ exports.Api = Api;
@@ -0,0 +1,21 @@
1
+ import { OpenApiSpec } from "../openapi/types.cjs";
2
+ import { ApiOptions, RequestSchema, ResponseSchema, RouteConfig } from "./types.cjs";
3
+ import { Middleware } from "../middleware/index.cjs";
4
+
5
+ //#region src/lib/api/core/index.d.ts
6
+ declare class Api<TMiddlewares extends readonly Middleware[] = readonly []> {
7
+ private options;
8
+ private routes;
9
+ constructor(options?: ApiOptions<TMiddlewares>);
10
+ private getFullPath;
11
+ private validateRequestSchema;
12
+ private createContext;
13
+ private validateResponseBody;
14
+ private buildBunRoutes;
15
+ defineRoute<TReq extends RequestSchema = RequestSchema, TRes extends ResponseSchema = ResponseSchema, TRouteMiddlewares extends readonly Middleware[] = readonly []>(config: RouteConfig<TReq, TRes, TMiddlewares, TRouteMiddlewares>): void;
16
+ getOpenApiSpec(): Promise<OpenApiSpec>;
17
+ serve(port: number, callback?: (server: Bun.Server<unknown>) => void): void;
18
+ }
19
+ //#endregion
20
+ export { Api };
21
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../../src/lib/api/core/index.ts"],"mappings":";;;;;cA4Ea,GAAA,+BAAkC,UAAA;EAAA,QACrC,OAAA;EAAA,QACA,MAAA;cAOW,OAAA,GAAS,UAAA,CAAW,YAAA;EAAA,QAI/B,WAAA;EAAA,QAaM,qBAAA;EAAA,QA+EN,aAAA;EAAA,QAaM,oBAAA;EAAA,QA6BN,cAAA;EAuHD,WAAA,cACQ,aAAA,GAAgB,aAAA,eAChB,cAAA,GAAiB,cAAA,qCACK,UAAA,iBAAA,CACnC,MAAA,EAAQ,WAAA,CAAY,IAAA,EAAM,IAAA,EAAM,YAAA,EAAc,iBAAA;EAWzC,cAAA,CAAA,GAAc,OAAA,CAXA,WAAA;EAwBd,KAAA,CAAM,IAAA,UAAc,QAAA,IAAY,MAAA,EAAQ,GAAA,CAAI,MAAA;AAAA"}
@@ -0,0 +1,21 @@
1
+ import { OpenApiSpec } from "../openapi/types.mjs";
2
+ import { ApiOptions, RequestSchema, ResponseSchema, RouteConfig } from "./types.mjs";
3
+ import { Middleware } from "../middleware/index.mjs";
4
+
5
+ //#region src/lib/api/core/index.d.ts
6
+ declare class Api<TMiddlewares extends readonly Middleware[] = readonly []> {
7
+ private options;
8
+ private routes;
9
+ constructor(options?: ApiOptions<TMiddlewares>);
10
+ private getFullPath;
11
+ private validateRequestSchema;
12
+ private createContext;
13
+ private validateResponseBody;
14
+ private buildBunRoutes;
15
+ defineRoute<TReq extends RequestSchema = RequestSchema, TRes extends ResponseSchema = ResponseSchema, TRouteMiddlewares extends readonly Middleware[] = readonly []>(config: RouteConfig<TReq, TRes, TMiddlewares, TRouteMiddlewares>): void;
16
+ getOpenApiSpec(): Promise<OpenApiSpec>;
17
+ serve(port: number, callback?: (server: Bun.Server<unknown>) => void): void;
18
+ }
19
+ //#endregion
20
+ export { Api };
21
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/lib/api/core/index.ts"],"mappings":";;;;;cA4Ea,GAAA,+BAAkC,UAAA;EAAA,QACrC,OAAA;EAAA,QACA,MAAA;cAOW,OAAA,GAAS,UAAA,CAAW,YAAA;EAAA,QAI/B,WAAA;EAAA,QAaM,qBAAA;EAAA,QA+EN,aAAA;EAAA,QAaM,oBAAA;EAAA,QA6BN,cAAA;EAuHD,WAAA,cACQ,aAAA,GAAgB,aAAA,eAChB,cAAA,GAAiB,cAAA,qCACK,UAAA,iBAAA,CACnC,MAAA,EAAQ,WAAA,CAAY,IAAA,EAAM,IAAA,EAAM,YAAA,EAAc,iBAAA;EAWzC,cAAA,CAAA,GAAc,OAAA,CAXA,WAAA;EAwBd,KAAA,CAAM,IAAA,UAAc,QAAA,IAAY,MAAA,EAAQ,GAAA,CAAI,MAAA;AAAA"}
@@ -0,0 +1,208 @@
1
+ import { mightThrow } from "../../lib/errors/index.mjs";
2
+ import { generateOpenApiSpec } from "../openapi/index.mjs";
3
+ import { validateBody, validateCookies, validateHeaders, validateParams, validateQuery, validateSchema } from "../validation/index.mjs";
4
+ //#region src/lib/api/core/index.ts
5
+ const defaultValidated = Object.freeze({
6
+ body: void 0,
7
+ query: void 0,
8
+ headers: void 0,
9
+ cookies: void 0,
10
+ params: void 0
11
+ });
12
+ const responseHelpers = {
13
+ json: (status, data) => Response.json(data, { status }),
14
+ text: (status, text) => new Response(text, { status }),
15
+ html: (status, html) => new Response(html, {
16
+ status,
17
+ headers: { "Content-Type": "text/html" }
18
+ }),
19
+ redirect: (status, url) => Response.redirect(url, status)
20
+ };
21
+ const noopGet = () => void 0;
22
+ const stripTrailingSlash = (path) => {
23
+ if (path !== "/" && path.endsWith("/")) return path.slice(0, -1);
24
+ return path;
25
+ };
26
+ const hasSchemas = (schema) => schema && (schema.body || schema.query || schema.headers || schema.cookies || schema.params);
27
+ const needsBodyCache = (schema) => schema?.body !== void 0;
28
+ const shouldCreateBodyCache = (hasMiddlewares, allMiddlewares, request) => {
29
+ if (needsBodyCache(request)) return true;
30
+ if (!hasMiddlewares) return false;
31
+ return allMiddlewares.some((mw) => needsBodyCache(mw.options.request));
32
+ };
33
+ const resolveValidation = (v) => {
34
+ if (v === void 0 || v === true) return {
35
+ input: true,
36
+ output: true
37
+ };
38
+ if (v === false) return {
39
+ input: false,
40
+ output: false
41
+ };
42
+ return {
43
+ input: v.input !== false,
44
+ output: v.output !== false
45
+ };
46
+ };
47
+ var Api = class {
48
+ options;
49
+ routes = [];
50
+ constructor(options = {}) {
51
+ this.options = options;
52
+ }
53
+ getFullPath(path) {
54
+ const normalizedPath = stripTrailingSlash(path) || "/";
55
+ if (!this.options.prefix) return normalizedPath;
56
+ const normalizedPrefix = stripTrailingSlash(this.options.prefix);
57
+ if (normalizedPrefix === "/") return normalizedPath;
58
+ if (normalizedPath === "/") return normalizedPrefix;
59
+ return normalizedPrefix + normalizedPath;
60
+ }
61
+ async validateRequestSchema(req, schema, bodyCache) {
62
+ if (!schema) return {
63
+ success: true,
64
+ data: {}
65
+ };
66
+ const v = {};
67
+ if (schema.body) {
68
+ const [err, val] = await validateBody(req, schema.body, bodyCache);
69
+ if (err) return {
70
+ success: false,
71
+ error: err
72
+ };
73
+ v.body = val;
74
+ }
75
+ if (schema.query) {
76
+ const [err, val] = await validateQuery(req, schema.query);
77
+ if (err) return {
78
+ success: false,
79
+ error: err
80
+ };
81
+ v.query = val;
82
+ }
83
+ if (schema.headers) {
84
+ const [err, val] = await validateHeaders(req, schema.headers);
85
+ if (err) return {
86
+ success: false,
87
+ error: err
88
+ };
89
+ v.headers = val;
90
+ }
91
+ if (schema.cookies) {
92
+ const [err, val] = await validateCookies(req, schema.cookies);
93
+ if (err) return {
94
+ success: false,
95
+ error: err
96
+ };
97
+ v.cookies = val;
98
+ }
99
+ if (schema.params) {
100
+ const [err, val] = await validateParams(req, schema.params);
101
+ if (err) return {
102
+ success: false,
103
+ error: err
104
+ };
105
+ v.params = val;
106
+ }
107
+ return {
108
+ success: true,
109
+ data: v
110
+ };
111
+ }
112
+ createContext(req, validated, extensions) {
113
+ return {
114
+ raw: req,
115
+ req: validated,
116
+ ...responseHelpers,
117
+ get: (key) => extensions[key]
118
+ };
119
+ }
120
+ async validateResponseBody(response, schema) {
121
+ if (!schema) return response;
122
+ const statusSchema = schema[response.status];
123
+ if (!statusSchema) return response;
124
+ if (!(response.headers.get("content-type") ?? "").includes("application/json")) return response;
125
+ const [parseError, body] = await mightThrow(response.clone().json());
126
+ if (parseError) return responseHelpers.json(400, { message: "Invalid response body" });
127
+ const [validationError] = await validateSchema(statusSchema, body);
128
+ if (validationError) return responseHelpers.json(400, { message: validationError.message });
129
+ return response;
130
+ }
131
+ buildBunRoutes() {
132
+ const bunRoutes = {};
133
+ const validationConfig = resolveValidation(this.options.validation);
134
+ for (const route of this.routes) {
135
+ const { path, method, handler, request, response, middlewares } = route;
136
+ const fullPath = this.getFullPath(path);
137
+ if (!bunRoutes[fullPath]) bunRoutes[fullPath] = {};
138
+ const allMiddlewares = [...this.options.middlewares ?? [], ...middlewares ?? []];
139
+ const hasMiddlewares = allMiddlewares.length > 0;
140
+ const hasRouteSchemas = hasSchemas(request);
141
+ const effectiveOutputValidation = validationConfig.output && !!response;
142
+ if (!hasMiddlewares && !(validationConfig.input && hasRouteSchemas) && !effectiveOutputValidation) bunRoutes[fullPath][method] = (req) => {
143
+ const context = Object.create(responseHelpers);
144
+ context.raw = req;
145
+ context.req = defaultValidated;
146
+ context.get = noopGet;
147
+ return handler(context);
148
+ };
149
+ else bunRoutes[fullPath][method] = async (req) => {
150
+ const extensions = {};
151
+ const bodyCache = validationConfig.input && shouldCreateBodyCache(hasMiddlewares, allMiddlewares, request) ? {
152
+ parsed: false,
153
+ value: void 0
154
+ } : void 0;
155
+ for (const mw of allMiddlewares) {
156
+ const { request: reqSchema, handler: mwHandler } = mw.options;
157
+ let validated = defaultValidated;
158
+ if (validationConfig.input && hasSchemas(reqSchema)) {
159
+ const result = await this.validateRequestSchema(req, reqSchema, bodyCache);
160
+ if (!result.success) return responseHelpers.json(400, { message: result.error?.message });
161
+ validated = result.data;
162
+ }
163
+ const mwResult = await mwHandler(this.createContext(req, validated, extensions));
164
+ if (mwResult instanceof Response) return mwResult;
165
+ if (mwResult) Object.assign(extensions, mwResult);
166
+ }
167
+ let routeValidated = defaultValidated;
168
+ if (validationConfig.input && hasRouteSchemas) {
169
+ const result = await this.validateRequestSchema(req, request, bodyCache);
170
+ if (!result.success) return responseHelpers.json(400, { message: result.error?.message });
171
+ routeValidated = result.data;
172
+ }
173
+ const handlerResponse = await handler(this.createContext(req, routeValidated, extensions));
174
+ if (effectiveOutputValidation) return this.validateResponseBody(handlerResponse, response);
175
+ return handlerResponse;
176
+ };
177
+ }
178
+ return bunRoutes;
179
+ }
180
+ defineRoute(config) {
181
+ this.routes.push(config);
182
+ }
183
+ getOpenApiSpec() {
184
+ return generateOpenApiSpec({
185
+ title: this.options.openapi?.title ?? "API",
186
+ description: this.options.openapi?.description,
187
+ version: this.options.openapi?.version ?? "1.0.0",
188
+ prefix: this.options.prefix,
189
+ servers: this.options.openapi?.servers,
190
+ securitySchemes: this.options.openapi?.securitySchemes,
191
+ routes: this.routes,
192
+ globalMiddlewares: this.options.middlewares
193
+ });
194
+ }
195
+ serve(port, callback) {
196
+ const bunRoutes = this.buildBunRoutes();
197
+ const server = Bun.serve({
198
+ port,
199
+ routes: bunRoutes,
200
+ fetch: () => new Response("Not found", { status: 404 })
201
+ });
202
+ if (callback) callback(server);
203
+ }
204
+ };
205
+ //#endregion
206
+ export { Api };
207
+
208
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/lib/api/core/index.ts"],"sourcesContent":["import { mightThrow } from \"../../errors/index.js\";\nimport type { Middleware } from \"../middleware/index.js\";\nimport { generateOpenApiSpec } from \"../openapi/index.js\";\nimport {\n type BodyCache,\n validateBody,\n validateCookies,\n validateHeaders,\n validateParams,\n validateQuery,\n validateSchema,\n} from \"../validation/index.js\";\nimport type {\n ApiOptions,\n MethodRoutes,\n RequestSchema,\n ResponseSchema,\n RouteConfig,\n ValidatedRequest,\n ValidationOptions,\n} from \"./types.js\";\n\n// Shared defaults reused across requests to avoid per-request allocations\nconst defaultValidated: ValidatedRequest = Object.freeze({\n body: undefined,\n query: undefined,\n headers: undefined,\n cookies: undefined,\n params: undefined,\n});\n\nconst responseHelpers = {\n json: (status: number, data: unknown) => Response.json(data, { status }),\n text: (status: number, text: string) => new Response(text, { status }),\n html: (status: number, html: string) =>\n new Response(html, { status, headers: { \"Content-Type\": \"text/html\" } }),\n redirect: (status: number, url: string) => Response.redirect(url, status),\n} as const;\n\nconst noopGet = () => undefined;\n\nconst stripTrailingSlash = (path: string) => {\n if (path !== \"/\" && path.endsWith(\"/\")) {\n return path.slice(0, -1);\n }\n\n return path;\n};\n\nconst hasSchemas = (schema?: RequestSchema) =>\n schema &&\n (schema.body ||\n schema.query ||\n schema.headers ||\n schema.cookies ||\n schema.params);\n\nconst needsBodyCache = (schema?: RequestSchema) => schema?.body !== undefined;\n\nconst shouldCreateBodyCache = (\n hasMiddlewares: boolean,\n allMiddlewares: Middleware[],\n request?: RequestSchema,\n) => {\n if (needsBodyCache(request)) return true;\n if (!hasMiddlewares) return false;\n\n return allMiddlewares.some((mw) => needsBodyCache(mw.options.request));\n};\n\nconst resolveValidation = (v?: ValidationOptions) => {\n if (v === undefined || v === true) return { input: true, output: true };\n if (v === false) return { input: false, output: false };\n return { input: v.input !== false, output: v.output !== false };\n};\n\nexport class Api<TMiddlewares extends readonly Middleware[] = readonly []> {\n private options: ApiOptions<TMiddlewares>;\n private routes: RouteConfig<\n RequestSchema,\n ResponseSchema,\n TMiddlewares,\n readonly Middleware[]\n >[] = [];\n\n public constructor(options: ApiOptions<TMiddlewares> = {}) {\n this.options = options;\n }\n\n private getFullPath(path: string) {\n const normalizedPath = stripTrailingSlash(path) || \"/\";\n\n if (!this.options.prefix) return normalizedPath;\n\n const normalizedPrefix = stripTrailingSlash(this.options.prefix);\n\n if (normalizedPrefix === \"/\") return normalizedPath;\n if (normalizedPath === \"/\") return normalizedPrefix;\n\n return normalizedPrefix + normalizedPath;\n }\n\n private async validateRequestSchema(\n req: Bun.BunRequest,\n schema: RequestSchema | undefined,\n bodyCache?: BodyCache,\n ) {\n if (!schema) {\n return { success: true, data: {} };\n }\n\n const v: Record<string, unknown> = {};\n\n if (schema.body) {\n const [err, val] = await validateBody(req, schema.body, bodyCache);\n\n if (err) {\n return {\n success: false,\n error: err,\n };\n }\n\n v.body = val;\n }\n\n if (schema.query) {\n const [err, val] = await validateQuery(req, schema.query);\n\n if (err) {\n return {\n success: false,\n error: err,\n };\n }\n\n v.query = val;\n }\n\n if (schema.headers) {\n const [err, val] = await validateHeaders(req, schema.headers);\n\n if (err) {\n return {\n success: false,\n error: err,\n };\n }\n\n v.headers = val;\n }\n\n if (schema.cookies) {\n const [err, val] = await validateCookies(req, schema.cookies);\n\n if (err) {\n return {\n success: false,\n error: err,\n };\n }\n\n v.cookies = val;\n }\n\n if (schema.params) {\n const [err, val] = await validateParams(req, schema.params);\n\n if (err) {\n return {\n success: false,\n error: err,\n };\n }\n\n v.params = val;\n }\n\n return { success: true, data: v };\n }\n\n private createContext(\n req: Bun.BunRequest,\n validated: ValidatedRequest,\n extensions: Record<string, unknown>,\n ) {\n return {\n raw: req,\n req: validated,\n ...responseHelpers,\n get: (key: string) => extensions[key],\n };\n }\n\n private async validateResponseBody(\n response: Response,\n schema: ResponseSchema | undefined,\n ) {\n if (!schema) return response;\n\n const statusSchema = schema[response.status];\n\n if (!statusSchema) return response;\n\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n\n if (!contentType.includes(\"application/json\")) return response;\n\n const [parseError, body] = await mightThrow(response.clone().json());\n\n if (parseError) {\n return responseHelpers.json(400, { message: \"Invalid response body\" });\n }\n\n const [validationError] = await validateSchema(statusSchema, body);\n\n if (validationError) {\n return responseHelpers.json(400, { message: validationError.message });\n }\n\n return response;\n }\n\n private buildBunRoutes() {\n const bunRoutes: MethodRoutes = {};\n const validationConfig = resolveValidation(this.options.validation);\n\n for (const route of this.routes) {\n const { path, method, handler, request, response, middlewares } = route;\n\n const fullPath = this.getFullPath(path);\n\n if (!bunRoutes[fullPath]) {\n bunRoutes[fullPath] = {};\n }\n\n const allMiddlewares = [\n ...(this.options.middlewares ?? []),\n ...(middlewares ?? []),\n ];\n\n const hasMiddlewares = allMiddlewares.length > 0;\n const hasRouteSchemas = hasSchemas(request);\n const effectiveOutputValidation = validationConfig.output && !!response;\n\n if (\n !hasMiddlewares &&\n !(validationConfig.input && hasRouteSchemas) &&\n !effectiveOutputValidation\n ) {\n // Zero-allocation path for simple routes using prototype chain\n // Avoids object spread overhead by inheriting response helpers via prototype\n bunRoutes[fullPath][method] = (req: Bun.BunRequest) => {\n // Create fresh per-request context to avoid cross-request contamination\n const context = Object.create(responseHelpers);\n context.raw = req;\n context.req = defaultValidated;\n context.get = noopGet;\n\n return handler(context as unknown as Parameters<typeof handler>[0]);\n };\n } else {\n bunRoutes[fullPath][method] = async (req: Bun.BunRequest) => {\n const extensions: Record<string, unknown> = {};\n\n // Only create bodyCache if input validation is enabled and any schema has body validation\n const bodyCache: BodyCache | undefined =\n validationConfig.input &&\n shouldCreateBodyCache(hasMiddlewares, allMiddlewares, request)\n ? { parsed: false, value: undefined }\n : undefined;\n\n for (const mw of allMiddlewares) {\n const { request: reqSchema, handler: mwHandler } = mw.options;\n\n let validated = defaultValidated;\n\n if (validationConfig.input && hasSchemas(reqSchema)) {\n const result = await this.validateRequestSchema(\n req,\n reqSchema,\n bodyCache,\n );\n\n if (!result.success) {\n return responseHelpers.json(400, {\n message: result.error?.message,\n });\n }\n\n validated = result.data as ValidatedRequest;\n }\n\n const context = this.createContext(req, validated, extensions);\n const mwResult = await mwHandler(\n context as Parameters<typeof mwHandler>[0],\n );\n\n if (mwResult instanceof Response) {\n return mwResult;\n }\n\n if (mwResult) {\n Object.assign(extensions, mwResult);\n }\n }\n\n let routeValidated = defaultValidated;\n\n if (validationConfig.input && hasRouteSchemas) {\n const result = await this.validateRequestSchema(\n req,\n request,\n bodyCache,\n );\n\n if (!result.success) {\n return responseHelpers.json(400, {\n message: result.error?.message,\n });\n }\n\n routeValidated = result.data as ValidatedRequest;\n }\n\n const context = this.createContext(req, routeValidated, extensions);\n const handlerResponse = await handler(\n context as Parameters<typeof handler>[0],\n );\n\n if (effectiveOutputValidation) {\n return this.validateResponseBody(handlerResponse, response);\n }\n\n return handlerResponse;\n };\n }\n }\n\n return bunRoutes;\n }\n\n public defineRoute<\n TReq extends RequestSchema = RequestSchema,\n TRes extends ResponseSchema = ResponseSchema,\n TRouteMiddlewares extends readonly Middleware[] = readonly [],\n >(config: RouteConfig<TReq, TRes, TMiddlewares, TRouteMiddlewares>) {\n this.routes.push(\n config as RouteConfig<\n RequestSchema,\n ResponseSchema,\n TMiddlewares,\n readonly Middleware[]\n >,\n );\n }\n\n public getOpenApiSpec() {\n return generateOpenApiSpec({\n title: this.options.openapi?.title ?? \"API\",\n description: this.options.openapi?.description,\n version: this.options.openapi?.version ?? \"1.0.0\",\n prefix: this.options.prefix,\n servers: this.options.openapi?.servers,\n securitySchemes: this.options.openapi?.securitySchemes,\n routes: this.routes,\n globalMiddlewares: this.options.middlewares,\n });\n }\n\n public serve(port: number, callback?: (server: Bun.Server<unknown>) => void) {\n const bunRoutes = this.buildBunRoutes();\n\n const server = Bun.serve({\n port,\n routes: bunRoutes,\n fetch: () => new Response(\"Not found\", { status: 404 }),\n });\n\n if (callback) {\n callback(server);\n }\n }\n}\n"],"mappings":"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"}