@buenojs/bueno 0.8.4 → 0.8.6

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 (234) hide show
  1. package/README.md +264 -17
  2. package/dist/cli/{index.js → bin.js} +413 -332
  3. package/dist/container/index.js +273 -0
  4. package/dist/context/index.js +219 -0
  5. package/dist/database/index.js +493 -0
  6. package/dist/frontend/index.js +7697 -0
  7. package/dist/graphql/index.js +2156 -0
  8. package/dist/health/index.js +364 -0
  9. package/dist/i18n/index.js +345 -0
  10. package/dist/index.js +9694 -5047
  11. package/dist/jobs/index.js +819 -0
  12. package/dist/lock/index.js +367 -0
  13. package/dist/logger/index.js +281 -0
  14. package/dist/metrics/index.js +289 -0
  15. package/dist/middleware/index.js +77 -0
  16. package/dist/migrations/index.js +571 -0
  17. package/dist/modules/index.js +3411 -0
  18. package/dist/notification/index.js +484 -0
  19. package/dist/observability/index.js +331 -0
  20. package/dist/openapi/index.js +795 -0
  21. package/dist/orm/index.js +1356 -0
  22. package/dist/router/index.js +886 -0
  23. package/dist/rpc/index.js +691 -0
  24. package/dist/schema/index.js +400 -0
  25. package/dist/telemetry/index.js +595 -0
  26. package/dist/template/index.js +640 -0
  27. package/dist/templates/index.js +640 -0
  28. package/dist/testing/index.js +1111 -0
  29. package/dist/types/index.js +60 -0
  30. package/llms.txt +231 -0
  31. package/package.json +125 -27
  32. package/src/cache/index.ts +2 -1
  33. package/src/cli/ARCHITECTURE.md +3 -3
  34. package/src/cli/bin.ts +2 -2
  35. package/src/cli/commands/build.ts +183 -165
  36. package/src/cli/commands/dev.ts +96 -89
  37. package/src/cli/commands/generate.ts +142 -111
  38. package/src/cli/commands/help.ts +20 -16
  39. package/src/cli/commands/index.ts +3 -6
  40. package/src/cli/commands/migration.ts +124 -105
  41. package/src/cli/commands/new.ts +294 -232
  42. package/src/cli/commands/start.ts +81 -79
  43. package/src/cli/core/args.ts +68 -50
  44. package/src/cli/core/console.ts +89 -95
  45. package/src/cli/core/index.ts +4 -4
  46. package/src/cli/core/prompt.ts +65 -62
  47. package/src/cli/core/spinner.ts +23 -20
  48. package/src/cli/index.ts +46 -38
  49. package/src/cli/templates/database/index.ts +37 -18
  50. package/src/cli/templates/database/mysql.ts +3 -3
  51. package/src/cli/templates/database/none.ts +2 -2
  52. package/src/cli/templates/database/postgresql.ts +3 -3
  53. package/src/cli/templates/database/sqlite.ts +3 -3
  54. package/src/cli/templates/deploy.ts +29 -26
  55. package/src/cli/templates/docker.ts +41 -30
  56. package/src/cli/templates/frontend/index.ts +33 -15
  57. package/src/cli/templates/frontend/none.ts +2 -2
  58. package/src/cli/templates/frontend/react.ts +18 -18
  59. package/src/cli/templates/frontend/solid.ts +15 -15
  60. package/src/cli/templates/frontend/svelte.ts +17 -17
  61. package/src/cli/templates/frontend/vue.ts +15 -15
  62. package/src/cli/templates/generators/index.ts +29 -29
  63. package/src/cli/templates/generators/types.ts +21 -21
  64. package/src/cli/templates/index.ts +6 -6
  65. package/src/cli/templates/project/api.ts +37 -36
  66. package/src/cli/templates/project/default.ts +25 -25
  67. package/src/cli/templates/project/fullstack.ts +28 -26
  68. package/src/cli/templates/project/index.ts +55 -16
  69. package/src/cli/templates/project/minimal.ts +17 -12
  70. package/src/cli/templates/project/types.ts +10 -5
  71. package/src/cli/templates/project/website.ts +15 -15
  72. package/src/cli/utils/fs.ts +55 -41
  73. package/src/cli/utils/index.ts +3 -3
  74. package/src/cli/utils/strings.ts +47 -33
  75. package/src/cli/utils/version.ts +14 -8
  76. package/src/config/env-validation.ts +100 -0
  77. package/src/config/env.ts +169 -41
  78. package/src/config/index.ts +28 -20
  79. package/src/config/loader.ts +25 -16
  80. package/src/config/merge.ts +21 -10
  81. package/src/config/types.ts +566 -25
  82. package/src/config/validation.ts +215 -7
  83. package/src/container/forward-ref.ts +22 -22
  84. package/src/container/index.ts +34 -12
  85. package/src/context/index.ts +11 -1
  86. package/src/database/index.ts +7 -190
  87. package/src/database/orm/builder.ts +457 -0
  88. package/src/database/orm/casts/index.ts +130 -0
  89. package/src/database/orm/casts/types.ts +25 -0
  90. package/src/database/orm/compiler.ts +304 -0
  91. package/src/database/orm/hooks/index.ts +114 -0
  92. package/src/database/orm/index.ts +61 -0
  93. package/src/database/orm/model-registry.ts +59 -0
  94. package/src/database/orm/model.ts +821 -0
  95. package/src/database/orm/relationships/base.ts +146 -0
  96. package/src/database/orm/relationships/belongs-to-many.ts +179 -0
  97. package/src/database/orm/relationships/belongs-to.ts +56 -0
  98. package/src/database/orm/relationships/has-many.ts +45 -0
  99. package/src/database/orm/relationships/has-one.ts +41 -0
  100. package/src/database/orm/relationships/index.ts +11 -0
  101. package/src/database/orm/scopes/index.ts +55 -0
  102. package/src/events/__tests__/event-system.test.ts +235 -0
  103. package/src/events/config.ts +238 -0
  104. package/src/events/example-usage.ts +185 -0
  105. package/src/events/index.ts +278 -0
  106. package/src/events/manager.ts +385 -0
  107. package/src/events/registry.ts +182 -0
  108. package/src/events/types.ts +124 -0
  109. package/src/frontend/api-routes.ts +65 -23
  110. package/src/frontend/bundler.ts +76 -34
  111. package/src/frontend/console-client.ts +2 -2
  112. package/src/frontend/console-stream.ts +94 -38
  113. package/src/frontend/dev-server.ts +94 -46
  114. package/src/frontend/file-router.ts +61 -19
  115. package/src/frontend/frameworks/index.ts +37 -10
  116. package/src/frontend/frameworks/react.ts +10 -8
  117. package/src/frontend/frameworks/solid.ts +11 -9
  118. package/src/frontend/frameworks/svelte.ts +15 -9
  119. package/src/frontend/frameworks/vue.ts +13 -11
  120. package/src/frontend/hmr-client.ts +12 -10
  121. package/src/frontend/hmr.ts +146 -103
  122. package/src/frontend/index.ts +14 -5
  123. package/src/frontend/islands.ts +41 -22
  124. package/src/frontend/isr.ts +59 -37
  125. package/src/frontend/layout.ts +36 -21
  126. package/src/frontend/ssr/react.ts +74 -27
  127. package/src/frontend/ssr/solid.ts +54 -20
  128. package/src/frontend/ssr/svelte.ts +48 -14
  129. package/src/frontend/ssr/vue.ts +50 -18
  130. package/src/frontend/ssr.ts +83 -39
  131. package/src/frontend/types.ts +91 -56
  132. package/src/graphql/built-in-engine.ts +598 -0
  133. package/src/graphql/context-builder.ts +110 -0
  134. package/src/graphql/decorators.ts +358 -0
  135. package/src/graphql/execution-pipeline.ts +227 -0
  136. package/src/graphql/graphql-module.ts +563 -0
  137. package/src/graphql/index.ts +101 -0
  138. package/src/graphql/metadata.ts +237 -0
  139. package/src/graphql/schema-builder.ts +319 -0
  140. package/src/graphql/subscription-handler.ts +283 -0
  141. package/src/graphql/types.ts +324 -0
  142. package/src/health/index.ts +21 -9
  143. package/src/i18n/engine.ts +305 -0
  144. package/src/i18n/index.ts +38 -0
  145. package/src/i18n/loader.ts +218 -0
  146. package/src/i18n/middleware.ts +164 -0
  147. package/src/i18n/negotiator.ts +162 -0
  148. package/src/i18n/types.ts +158 -0
  149. package/src/index.ts +182 -27
  150. package/src/jobs/drivers/memory.ts +315 -0
  151. package/src/jobs/drivers/redis.ts +459 -0
  152. package/src/jobs/index.ts +30 -0
  153. package/src/jobs/queue.ts +281 -0
  154. package/src/jobs/types.ts +295 -0
  155. package/src/jobs/worker.ts +380 -0
  156. package/src/logger/index.ts +1 -3
  157. package/src/logger/transports/index.ts +62 -22
  158. package/src/metrics/index.ts +25 -16
  159. package/src/migrations/index.ts +9 -0
  160. package/src/modules/filters.ts +13 -17
  161. package/src/modules/guards.ts +49 -26
  162. package/src/modules/index.ts +457 -299
  163. package/src/modules/interceptors.ts +58 -20
  164. package/src/modules/lazy.ts +11 -19
  165. package/src/modules/lifecycle.ts +15 -7
  166. package/src/modules/metadata.ts +15 -5
  167. package/src/modules/pipes.ts +94 -72
  168. package/src/notification/channels/base.ts +68 -0
  169. package/src/notification/channels/email.ts +105 -0
  170. package/src/notification/channels/push.ts +104 -0
  171. package/src/notification/channels/sms.ts +105 -0
  172. package/src/notification/channels/whatsapp.ts +104 -0
  173. package/src/notification/index.ts +48 -0
  174. package/src/notification/service.ts +354 -0
  175. package/src/notification/types.ts +344 -0
  176. package/src/observability/__tests__/observability.test.ts +483 -0
  177. package/src/observability/breadcrumbs.ts +114 -0
  178. package/src/observability/index.ts +136 -0
  179. package/src/observability/interceptor.ts +85 -0
  180. package/src/observability/service.ts +303 -0
  181. package/src/observability/trace.ts +37 -0
  182. package/src/observability/types.ts +196 -0
  183. package/src/openapi/__tests__/decorators.test.ts +335 -0
  184. package/src/openapi/__tests__/document-builder.test.ts +285 -0
  185. package/src/openapi/__tests__/route-scanner.test.ts +334 -0
  186. package/src/openapi/__tests__/schema-generator.test.ts +275 -0
  187. package/src/openapi/decorators.ts +328 -0
  188. package/src/openapi/document-builder.ts +274 -0
  189. package/src/openapi/index.ts +112 -0
  190. package/src/openapi/metadata.ts +112 -0
  191. package/src/openapi/route-scanner.ts +289 -0
  192. package/src/openapi/schema-generator.ts +256 -0
  193. package/src/openapi/swagger-module.ts +166 -0
  194. package/src/openapi/types.ts +398 -0
  195. package/src/orm/index.ts +10 -0
  196. package/src/rpc/index.ts +3 -1
  197. package/src/schema/index.ts +9 -0
  198. package/src/security/index.ts +15 -6
  199. package/src/ssg/index.ts +9 -8
  200. package/src/telemetry/index.ts +76 -22
  201. package/src/template/index.ts +7 -0
  202. package/src/templates/engine.ts +224 -0
  203. package/src/templates/index.ts +9 -0
  204. package/src/templates/loader.ts +331 -0
  205. package/src/templates/renderers/markdown.ts +212 -0
  206. package/src/templates/renderers/simple.ts +269 -0
  207. package/src/templates/types.ts +154 -0
  208. package/src/testing/index.ts +100 -27
  209. package/src/types/optional-deps.d.ts +347 -187
  210. package/src/validation/index.ts +92 -2
  211. package/src/validation/schemas.ts +536 -0
  212. package/tests/integration/cli.test.ts +19 -19
  213. package/tests/integration/fullstack.test.ts +4 -4
  214. package/tests/unit/cli.test.ts +1 -1
  215. package/tests/unit/database.test.ts +2 -72
  216. package/tests/unit/env-validation.test.ts +166 -0
  217. package/tests/unit/events.test.ts +910 -0
  218. package/tests/unit/graphql.test.ts +991 -0
  219. package/tests/unit/i18n.test.ts +455 -0
  220. package/tests/unit/jobs.test.ts +493 -0
  221. package/tests/unit/notification.test.ts +988 -0
  222. package/tests/unit/observability.test.ts +453 -0
  223. package/tests/unit/orm/builder.test.ts +323 -0
  224. package/tests/unit/orm/casts.test.ts +179 -0
  225. package/tests/unit/orm/compiler.test.ts +220 -0
  226. package/tests/unit/orm/eager-loading.test.ts +285 -0
  227. package/tests/unit/orm/hooks.test.ts +191 -0
  228. package/tests/unit/orm/model.test.ts +373 -0
  229. package/tests/unit/orm/relationships.test.ts +303 -0
  230. package/tests/unit/orm/scopes.test.ts +74 -0
  231. package/tests/unit/templates-simple.test.ts +53 -0
  232. package/tests/unit/templates.test.ts +454 -0
  233. package/tests/unit/validation.test.ts +18 -24
  234. package/tsconfig.json +11 -3
@@ -0,0 +1,536 @@
1
+ /**
2
+ * Built-in Schema System
3
+ * Lightweight validation schema builder for common use cases.
4
+ * No external dependencies required.
5
+ */
6
+
7
+ import type { StandardIssue, StandardResult } from "../types";
8
+
9
+ // ============= Schema Definition Types =============
10
+
11
+ export type FieldValidator = (value: unknown) => {
12
+ valid: boolean;
13
+ error?: string;
14
+ value?: unknown; // Coerced/transformed value
15
+ };
16
+
17
+ export interface SchemaDefinition {
18
+ [key: string]: FieldValidator | SchemaDefinition | undefined;
19
+ }
20
+
21
+ // ============= Built-in Field Validators =============
22
+
23
+ export const Fields = {
24
+ /**
25
+ * String field validators
26
+ */
27
+ string: (
28
+ options: {
29
+ min?: number;
30
+ max?: number;
31
+ pattern?: RegExp;
32
+ email?: boolean;
33
+ url?: boolean;
34
+ uuid?: boolean;
35
+ optional?: boolean;
36
+ } = {},
37
+ ) => {
38
+ return (value: unknown) => {
39
+ if (options.optional && (value === undefined || value === null)) {
40
+ return { valid: true, value: undefined };
41
+ }
42
+
43
+ if (typeof value !== "string") {
44
+ return { valid: false, error: "Must be a string" };
45
+ }
46
+
47
+ if (options.min !== undefined && value.length < options.min) {
48
+ return {
49
+ valid: false,
50
+ error: `Must be at least ${options.min} characters`,
51
+ };
52
+ }
53
+
54
+ if (options.max !== undefined && value.length > options.max) {
55
+ return {
56
+ valid: false,
57
+ error: `Must be at most ${options.max} characters`,
58
+ };
59
+ }
60
+
61
+ if (options.pattern && !options.pattern.test(value)) {
62
+ return { valid: false, error: "Does not match required pattern" };
63
+ }
64
+
65
+ if (options.email) {
66
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
67
+ if (!emailRegex.test(value)) {
68
+ return { valid: false, error: "Must be a valid email address" };
69
+ }
70
+ }
71
+
72
+ if (options.url) {
73
+ try {
74
+ new URL(value);
75
+ } catch {
76
+ return { valid: false, error: "Must be a valid URL" };
77
+ }
78
+ }
79
+
80
+ if (options.uuid) {
81
+ const uuidRegex =
82
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
83
+ if (!uuidRegex.test(value)) {
84
+ return { valid: false, error: "Must be a valid UUID" };
85
+ }
86
+ }
87
+
88
+ return { valid: true, value };
89
+ };
90
+ },
91
+
92
+ /**
93
+ * Number field validators (coerces strings to numbers)
94
+ */
95
+ number: (
96
+ options: {
97
+ min?: number;
98
+ max?: number;
99
+ integer?: boolean;
100
+ positive?: boolean;
101
+ optional?: boolean;
102
+ default?: number;
103
+ coerce?: boolean; // Default: true, coerce strings to numbers
104
+ } = {},
105
+ ) => {
106
+ const shouldCoerce = options.coerce !== false; // Default true
107
+
108
+ return (value: unknown, validateOnly = false) => {
109
+ // Apply default if value is missing
110
+ if (
111
+ (value === undefined || value === null) &&
112
+ options.default !== undefined
113
+ ) {
114
+ return { valid: true, value: options.default };
115
+ }
116
+
117
+ if (options.optional && (value === undefined || value === null)) {
118
+ return { valid: true, value: undefined };
119
+ }
120
+
121
+ let num = value;
122
+
123
+ // Coerce string to number
124
+ if (shouldCoerce && typeof value === "string") {
125
+ num = Number(value);
126
+ }
127
+
128
+ if (typeof num !== "number" || Number.isNaN(num)) {
129
+ return { valid: false, error: "Must be a number" };
130
+ }
131
+
132
+ if (options.integer && !Number.isInteger(num)) {
133
+ return { valid: false, error: "Must be an integer" };
134
+ }
135
+
136
+ if (options.positive && num <= 0) {
137
+ return { valid: false, error: "Must be a positive number" };
138
+ }
139
+
140
+ if (options.min !== undefined && num < options.min) {
141
+ return { valid: false, error: `Must be at least ${options.min}` };
142
+ }
143
+
144
+ if (options.max !== undefined && num > options.max) {
145
+ return { valid: false, error: `Must be at most ${options.max}` };
146
+ }
147
+
148
+ return { valid: true, value: num };
149
+ };
150
+ },
151
+
152
+ /**
153
+ * Boolean field validator
154
+ */
155
+ boolean: (options: { optional?: boolean } = {}) => {
156
+ return (value: unknown) => {
157
+ if (options.optional && (value === undefined || value === null)) {
158
+ return { valid: true, value: undefined };
159
+ }
160
+
161
+ if (typeof value !== "boolean") {
162
+ return { valid: false, error: "Must be a boolean" };
163
+ }
164
+
165
+ return { valid: true, value };
166
+ };
167
+ },
168
+
169
+ /**
170
+ * Array field validators
171
+ */
172
+ array: (
173
+ options: {
174
+ min?: number;
175
+ max?: number;
176
+ itemValidator?: FieldValidator;
177
+ optional?: boolean;
178
+ } = {},
179
+ ) => {
180
+ return (value: unknown) => {
181
+ if (options.optional && (value === undefined || value === null)) {
182
+ return { valid: true, value: undefined };
183
+ }
184
+
185
+ if (!Array.isArray(value)) {
186
+ return { valid: false, error: "Must be an array" };
187
+ }
188
+
189
+ if (options.min !== undefined && value.length < options.min) {
190
+ return {
191
+ valid: false,
192
+ error: `Must have at least ${options.min} items`,
193
+ };
194
+ }
195
+
196
+ if (options.max !== undefined && value.length > options.max) {
197
+ return {
198
+ valid: false,
199
+ error: `Must have at most ${options.max} items`,
200
+ };
201
+ }
202
+
203
+ if (options.itemValidator) {
204
+ for (let i = 0; i < value.length; i++) {
205
+ const result = options.itemValidator(value[i]);
206
+ if (!result.valid) {
207
+ return {
208
+ valid: false,
209
+ error: `Item ${i}: ${result.error}`,
210
+ };
211
+ }
212
+ }
213
+ }
214
+
215
+ return { valid: true, value };
216
+ };
217
+ },
218
+
219
+ /**
220
+ * Enum field validator
221
+ */
222
+ enum: (
223
+ values: readonly (string | number)[],
224
+ options: { optional?: boolean } = {},
225
+ ) => {
226
+ return (value: unknown) => {
227
+ if (options.optional && (value === undefined || value === null)) {
228
+ return { valid: true, value: undefined };
229
+ }
230
+
231
+ if (!values.includes(value as string | number)) {
232
+ return {
233
+ valid: false,
234
+ error: `Must be one of: ${values.join(", ")}`,
235
+ };
236
+ }
237
+
238
+ return { valid: true, value };
239
+ };
240
+ },
241
+
242
+ /**
243
+ * Custom validator function
244
+ */
245
+ custom: (
246
+ validate: (value: unknown) => boolean,
247
+ message = "Validation failed",
248
+ ) => {
249
+ return (value: unknown) => {
250
+ if (validate(value)) {
251
+ return { valid: true, value };
252
+ }
253
+ return { valid: false, error: message };
254
+ };
255
+ },
256
+
257
+ /**
258
+ * Environment variable validators
259
+ */
260
+ env: {
261
+ /**
262
+ * Required environment variable validator
263
+ */
264
+ required: (options: { optional?: boolean } = {}) => {
265
+ return (value: unknown) => {
266
+ if (options.optional && (value === undefined || value === null)) {
267
+ return { valid: true, value: undefined };
268
+ }
269
+
270
+ if (value === undefined || value === null || value === "") {
271
+ return {
272
+ valid: false,
273
+ error: "This environment variable is required",
274
+ };
275
+ }
276
+
277
+ return { valid: true, value };
278
+ };
279
+ },
280
+
281
+ /**
282
+ * Port number validator (1-65535)
283
+ */
284
+ port: (options: { optional?: boolean } = {}) => {
285
+ return (value: unknown) => {
286
+ if (options.optional && (value === undefined || value === null)) {
287
+ return { valid: true, value: undefined };
288
+ }
289
+
290
+ const num = Number(value);
291
+ if (isNaN(num) || num < 1 || num > 65535) {
292
+ return {
293
+ valid: false,
294
+ error: "Must be a valid port number (1-65535)",
295
+ };
296
+ }
297
+
298
+ return { valid: true, value: num };
299
+ };
300
+ },
301
+
302
+ /**
303
+ * URL validator
304
+ */
305
+ url: (options: { optional?: boolean } = {}) => {
306
+ return (value: unknown) => {
307
+ if (options.optional && (value === undefined || value === null)) {
308
+ return { valid: true, value: undefined };
309
+ }
310
+
311
+ if (typeof value !== "string") {
312
+ return { valid: false, error: "Must be a string" };
313
+ }
314
+
315
+ try {
316
+ new URL(value);
317
+ return { valid: true, value };
318
+ } catch {
319
+ return { valid: false, error: "Must be a valid URL" };
320
+ }
321
+ };
322
+ },
323
+
324
+ /**
325
+ * Database URL validator
326
+ */
327
+ databaseUrl: (options: { optional?: boolean } = {}) => {
328
+ return (value: unknown) => {
329
+ if (options.optional && (value === undefined || value === null)) {
330
+ return { valid: true, value: undefined };
331
+ }
332
+
333
+ if (typeof value !== "string") {
334
+ return { valid: false, error: "Must be a string" };
335
+ }
336
+
337
+ try {
338
+ const url = new URL(value);
339
+ const validSchemes = ["postgresql", "mysql", "sqlite", "mongodb"];
340
+ if (!validSchemes.includes(url.protocol.replace(/:$/, ""))) {
341
+ return {
342
+ valid: false,
343
+ error: `Must be a valid database URL (postgresql://, mysql://, sqlite://, mongodb://)`,
344
+ };
345
+ }
346
+ return { valid: true, value };
347
+ } catch {
348
+ return { valid: false, error: "Must be a valid database URL" };
349
+ }
350
+ };
351
+ },
352
+
353
+ /**
354
+ * Boolean validator
355
+ */
356
+ boolean: (options: { optional?: boolean } = {}) => {
357
+ return (value: unknown) => {
358
+ if (options.optional && (value === undefined || value === null)) {
359
+ return { valid: true, value: undefined };
360
+ }
361
+
362
+ if (
363
+ typeof value !== "boolean" &&
364
+ value !== "true" &&
365
+ value !== "false"
366
+ ) {
367
+ return { valid: false, error: "Must be a boolean (true/false)" };
368
+ }
369
+
370
+ return { valid: true, value: value === "true" || value === true };
371
+ };
372
+ },
373
+
374
+ /**
375
+ * Number validator
376
+ */
377
+ number: (
378
+ options: {
379
+ min?: number;
380
+ max?: number;
381
+ optional?: boolean;
382
+ integer?: boolean;
383
+ } = {},
384
+ ) => {
385
+ return (value: unknown) => {
386
+ if (options.optional && (value === undefined || value === null)) {
387
+ return { valid: true, value: undefined };
388
+ }
389
+
390
+ const num = Number(value);
391
+ if (isNaN(num)) {
392
+ return { valid: false, error: "Must be a number" };
393
+ }
394
+
395
+ if (options.integer && !Number.isInteger(num)) {
396
+ return { valid: false, error: "Must be an integer" };
397
+ }
398
+
399
+ if (options.min !== undefined && num < options.min) {
400
+ return { valid: false, error: `Must be at least ${options.min}` };
401
+ }
402
+
403
+ if (options.max !== undefined && num > options.max) {
404
+ return { valid: false, error: `Must be at most ${options.max}` };
405
+ }
406
+
407
+ return { valid: true, value: num };
408
+ };
409
+ },
410
+ },
411
+ };
412
+
413
+ // ============= Schema Class =============
414
+
415
+ /**
416
+ * Built-in schema validator
417
+ * Simple, zero-dependency schema validation
418
+ */
419
+ export class Schema {
420
+ constructor(private definition: SchemaDefinition) {}
421
+
422
+ /**
423
+ * Create a new schema from field definitions
424
+ */
425
+ static object<T extends SchemaDefinition>(definition: T): Schema {
426
+ return new Schema(definition);
427
+ }
428
+
429
+ /**
430
+ * Validate data synchronously
431
+ */
432
+ validateSync(data: unknown): StandardResult<Record<string, unknown>> {
433
+ const errors: StandardIssue[] = [];
434
+ const validated: Record<string, unknown> = {};
435
+
436
+ if (typeof data !== "object" || data === null) {
437
+ return {
438
+ issues: [{ message: "Input must be an object" }],
439
+ };
440
+ }
441
+
442
+ const obj = data as Record<string, unknown>;
443
+
444
+ for (const [key, validator] of Object.entries(this.definition)) {
445
+ if (typeof validator === "function") {
446
+ const result = validator(obj[key]);
447
+ if (!result.valid) {
448
+ errors.push({
449
+ message: result.error || `Validation failed for ${key}`,
450
+ path: [key],
451
+ });
452
+ } else {
453
+ // Use coerced value if provided, otherwise use original value
454
+ validated[key] = result.value !== undefined ? result.value : obj[key];
455
+ }
456
+ }
457
+ }
458
+
459
+ if (errors.length > 0) {
460
+ return { issues: errors };
461
+ }
462
+
463
+ return { value: validated };
464
+ }
465
+
466
+ /**
467
+ * Validate data asynchronously
468
+ * Currently same as sync, but allows for async validators in future
469
+ */
470
+ async validate(
471
+ data: unknown,
472
+ ): Promise<StandardResult<Record<string, unknown>>> {
473
+ return this.validateSync(data);
474
+ }
475
+
476
+ /**
477
+ * Implement Standard Schema interface for compatibility with @buenojs/bueno validators
478
+ */
479
+ get ["~standard"]() {
480
+ return {
481
+ validate: (data: unknown) => this.validateSync(data),
482
+ version: 1,
483
+ };
484
+ }
485
+ }
486
+
487
+ /**
488
+ * Create a schema object
489
+ * Alias for Schema.object() for convenience
490
+ */
491
+ export function schema<T extends SchemaDefinition>(definition: T): Schema {
492
+ return Schema.object(definition);
493
+ }
494
+
495
+ // ============= Environment Variable Schemas =============
496
+
497
+ /**
498
+ * Standard environment variable schema for Bueno Framework
499
+ */
500
+ export const envSchema = Schema.object({
501
+ // Required variables
502
+ NODE_ENV: Fields.env.required(),
503
+ PORT: Fields.env.port(),
504
+ HOST: Fields.string({ optional: true }),
505
+
506
+ // Database
507
+ DATABASE_URL: Fields.env.databaseUrl({ optional: true }),
508
+ DATABASE_POOL_SIZE: Fields.env.number({ min: 1, max: 100, optional: true }),
509
+
510
+ // Cache
511
+ REDIS_URL: Fields.env.url({ optional: true }),
512
+ CACHE_DRIVER: Fields.enum(["memory", "redis", "none"], { optional: true }),
513
+ CACHE_TTL: Fields.env.number({ min: 1, optional: true }),
514
+
515
+ // Logging
516
+ LOG_LEVEL: Fields.enum(["error", "warn", "info", "debug", "trace"], {
517
+ optional: true,
518
+ }),
519
+ LOG_PRETTY: Fields.env.boolean({ optional: true }),
520
+
521
+ // Health
522
+ HEALTH_ENABLED: Fields.env.boolean({ optional: true }),
523
+
524
+ // Metrics
525
+ METRICS_ENABLED: Fields.env.boolean({ optional: true }),
526
+
527
+ // Telemetry
528
+ TELEMETRY_ENABLED: Fields.env.boolean({ optional: true }),
529
+ TELEMETRY_SERVICE_NAME: Fields.string({ optional: true }),
530
+ TELEMETRY_ENDPOINT: Fields.env.url({ optional: true }),
531
+
532
+ // Frontend
533
+ FRONTEND_DEV_SERVER: Fields.env.boolean({ optional: true }),
534
+ FRONTEND_HMR: Fields.env.boolean({ optional: true }),
535
+ FRONTEND_PORT: Fields.env.port({ optional: true }),
536
+ });
@@ -119,7 +119,7 @@ function createMinimalBuenoProject(projectDir: string): void {
119
119
  );
120
120
 
121
121
  // Create main.ts
122
- const mainTs = `import { BuenoFactory } from 'bueno';
122
+ const mainTs = `import { BuenoFactory } from '@buenojs/bueno';
123
123
  import { AppModule } from './app.module';
124
124
 
125
125
  async function bootstrap() {
@@ -133,7 +133,7 @@ bootstrap();
133
133
  fs.writeFileSync(path.join(projectDir, 'server', 'main.ts'), mainTs);
134
134
 
135
135
  // Create app.module.ts
136
- const appModule = `import { Module } from 'bueno';
136
+ const appModule = `import { Module } from '@buenojs/bueno';
137
137
  import { AppController } from './app.controller';
138
138
  import { AppService } from './app.service';
139
139
 
@@ -149,8 +149,8 @@ export class AppModule {}
149
149
  );
150
150
 
151
151
  // Create app.controller.ts
152
- const appController = `import { Controller, Get } from 'bueno';
153
- import type { Context } from 'bueno';
152
+ const appController = `import { Controller, Get } from '@buenojs/bueno';
153
+ import type { Context } from '@buenojs/bueno';
154
154
 
155
155
  @Controller()
156
156
  export class AppController {
@@ -166,7 +166,7 @@ export class AppController {
166
166
  );
167
167
 
168
168
  // Create app.service.ts
169
- const appService = `import { Injectable } from 'bueno';
169
+ const appService = `import { Injectable } from '@buenojs/bueno';
170
170
 
171
171
  @Injectable()
172
172
  export class AppService {
@@ -181,7 +181,7 @@ export class AppService {
181
181
  );
182
182
 
183
183
  // Create bueno.config.ts
184
- const buenoConfig = `import { defineConfig } from 'bueno';
184
+ const buenoConfig = `import { defineConfig } from '@buenojs/bueno';
185
185
 
186
186
  export default defineConfig({
187
187
  server: {
@@ -320,8 +320,8 @@ describe('Code Generation', () => {
320
320
  );
321
321
 
322
322
  // Generate controller using template
323
- const template = `import { Controller, Get, Post, Put, Delete } from 'bueno';
324
- import type { Context } from 'bueno';
323
+ const template = `import { Controller, Get, Post, Put, Delete } from '@buenojs/bueno';
324
+ import type { Context } from '@buenojs/bueno';
325
325
 
326
326
  @Controller('{{path}}')
327
327
  export class {{pascalCase name}}Controller {
@@ -387,7 +387,7 @@ export class {{pascalCase name}}Controller {}`;
387
387
  'users.service.ts',
388
388
  );
389
389
 
390
- const template = `import { Injectable } from 'bueno';
390
+ const template = `import { Injectable } from '@buenojs/bueno';
391
391
 
392
392
  @Injectable()
393
393
  export class {{pascalCase name}}Service {
@@ -418,7 +418,7 @@ export class {{pascalCase name}}Service {
418
418
  'posts.module.ts',
419
419
  );
420
420
 
421
- const template = `import { Module } from 'bueno';
421
+ const template = `import { Module } from '@buenojs/bueno';
422
422
  import { {{pascalCase name}}Controller } from './{{kebabCase name}}.controller';
423
423
  import { {{pascalCase name}}Service } from './{{kebabCase name}}.service';
424
424
 
@@ -452,7 +452,7 @@ export class {{pascalCase name}}Module {}`;
452
452
  'auth.guard.ts',
453
453
  );
454
454
 
455
- const template = `import { Injectable, type CanActivate, type Context } from 'bueno';
455
+ const template = `import { Injectable, type CanActivate, type Context } from '@buenojs/bueno';
456
456
 
457
457
  @Injectable()
458
458
  export class {{pascalCase name}}Guard implements CanActivate {
@@ -483,7 +483,7 @@ export class {{pascalCase name}}Guard implements CanActivate {
483
483
  'logging.interceptor.ts',
484
484
  );
485
485
 
486
- const template = `import { Injectable, type NestInterceptor, type CallHandler, type Context } from 'bueno';
486
+ const template = `import { Injectable, type NestInterceptor, type CallHandler, type Context } from '@buenojs/bueno';
487
487
 
488
488
  @Injectable()
489
489
  export class {{pascalCase name}}Interceptor implements NestInterceptor {
@@ -513,7 +513,7 @@ export class {{pascalCase name}}Interceptor implements NestInterceptor {
513
513
  'validation.pipe.ts',
514
514
  );
515
515
 
516
- const template = `import { Injectable, type PipeTransform, type Context } from 'bueno';
516
+ const template = `import { Injectable, type PipeTransform, type Context } from '@buenojs/bueno';
517
517
 
518
518
  @Injectable()
519
519
  export class {{pascalCase name}}Pipe implements PipeTransform {
@@ -543,7 +543,7 @@ export class {{pascalCase name}}Pipe implements PipeTransform {
543
543
  'http-exception.filter.ts',
544
544
  );
545
545
 
546
- const template = `import { Injectable, type ExceptionFilter, type Context } from 'bueno';
546
+ const template = `import { Injectable, type ExceptionFilter, type Context } from '@buenojs/bueno';
547
547
 
548
548
  @Injectable()
549
549
  export class {{pascalCase name}}Filter implements ExceptionFilter {
@@ -606,7 +606,7 @@ export interface Update{{pascalCase name}}Dto extends Partial<Create{{pascalCase
606
606
  'logger.middleware.ts',
607
607
  );
608
608
 
609
- const template = `import type { Middleware, Context, Handler } from 'bueno';
609
+ const template = `import type { Middleware, Context, Handler } from '@buenojs/bueno';
610
610
 
611
611
  export const {{camelCase name}}Middleware: Middleware = async (ctx: Context, next: Handler) => {
612
612
  return next();
@@ -639,7 +639,7 @@ export const {{camelCase name}}Middleware: Middleware = async (ctx: Context, nex
639
639
 
640
640
  const expectedFileName = `${migrationId}_${kebabCase(migrationName)}.ts`;
641
641
 
642
- const template = `import { createMigration, type MigrationRunner } from 'bueno';
642
+ const template = `import { createMigration, type MigrationRunner } from '@buenojs/bueno';
643
643
 
644
644
  export default createMigration('{{migrationId}}', '{{migrationName}}')
645
645
  .up(async (db: MigrationRunner) => {
@@ -730,8 +730,8 @@ describe('File Structure', () => {
730
730
 
731
731
  describe('Template Processing Integration', () => {
732
732
  test('should process complex controller template', () => {
733
- const template = `import { Controller{{#if path}}, Get, Post{{/if}} } from 'bueno';
734
- import type { Context } from 'bueno';
733
+ const template = `import { Controller{{#if path}}, Get, Post{{/if}} } from '@buenojs/bueno';
734
+ import type { Context } from '@buenojs/bueno';
735
735
  {{#if service}}
736
736
  import { {{pascalCase service}}Service } from './{{kebabCase service}}.service';
737
737
  {{/if}}
@@ -763,7 +763,7 @@ export class {{pascalCase name}}Controller {
763
763
  });
764
764
 
765
765
  test('should process module template with multiple dependencies', () => {
766
- const template = `import { Module } from 'bueno';
766
+ const template = `import { Module } from '@buenojs/bueno';
767
767
  import { {{pascalCase name}}Controller } from './{{kebabCase name}}.controller';
768
768
  import { {{pascalCase name}}Service } from './{{kebabCase name}}.service';
769
769
 
@@ -1,5 +1,4 @@
1
1
  import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
2
- import { z } from 'zod';
3
2
  import {
4
3
  Router,
5
4
  Context,
@@ -11,6 +10,7 @@ import {
11
10
  Database,
12
11
  createServer,
13
12
  } from '../../src';
13
+ import { Schema, Fields } from '../../src/validation/schemas';
14
14
 
15
15
  // ============= Integration Tests =============
16
16
 
@@ -107,9 +107,9 @@ describe('Integration Tests', () => {
107
107
 
108
108
  describe('Validation Integration', () => {
109
109
  test('should validate request body', async () => {
110
- const schema = z.object({
111
- email: z.string().email(),
112
- age: z.number().min(0),
110
+ const schema = Schema.object({
111
+ email: Fields.string({ email: true }),
112
+ age: Fields.number({ min: 0 }),
113
113
  });
114
114
 
115
115
  const validator = createValidator({ body: schema });
@@ -1086,7 +1086,7 @@ describe('processTemplate', () => {
1086
1086
  });
1087
1087
 
1088
1088
  test('should handle complex template', () => {
1089
- const template = `import { Controller{{#if path}}, Get{{/if}} } from 'bueno';
1089
+ const template = `import { Controller{{#if path}}, Get{{/if}} } from '@buenojs/bueno';
1090
1090
 
1091
1091
  @Controller('{{path}}')
1092
1092
  export class {{pascalCase name}}Controller {