@buenojs/bueno 0.8.3 → 0.8.5

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 (218) hide show
  1. package/README.md +136 -16
  2. package/dist/cli/{index.js → bin.js} +3036 -1421
  3. package/dist/container/index.js +250 -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/health/index.js +364 -0
  8. package/dist/i18n/index.js +345 -0
  9. package/dist/index.js +11043 -6482
  10. package/dist/jobs/index.js +819 -0
  11. package/dist/lock/index.js +367 -0
  12. package/dist/logger/index.js +281 -0
  13. package/dist/metrics/index.js +289 -0
  14. package/dist/middleware/index.js +77 -0
  15. package/dist/migrations/index.js +571 -0
  16. package/dist/modules/index.js +3346 -0
  17. package/dist/notification/index.js +484 -0
  18. package/dist/observability/index.js +331 -0
  19. package/dist/openapi/index.js +776 -0
  20. package/dist/orm/index.js +1356 -0
  21. package/dist/router/index.js +886 -0
  22. package/dist/rpc/index.js +691 -0
  23. package/dist/schema/index.js +400 -0
  24. package/dist/telemetry/index.js +595 -0
  25. package/dist/template/index.js +640 -0
  26. package/dist/templates/index.js +640 -0
  27. package/dist/testing/index.js +1111 -0
  28. package/dist/types/index.js +60 -0
  29. package/package.json +121 -27
  30. package/src/cache/index.ts +2 -1
  31. package/src/cli/bin.ts +2 -2
  32. package/src/cli/commands/build.ts +183 -165
  33. package/src/cli/commands/dev.ts +96 -89
  34. package/src/cli/commands/generate.ts +142 -111
  35. package/src/cli/commands/help.ts +20 -16
  36. package/src/cli/commands/index.ts +3 -6
  37. package/src/cli/commands/migration.ts +124 -105
  38. package/src/cli/commands/new.ts +392 -438
  39. package/src/cli/commands/start.ts +81 -79
  40. package/src/cli/core/args.ts +68 -50
  41. package/src/cli/core/console.ts +89 -95
  42. package/src/cli/core/index.ts +4 -4
  43. package/src/cli/core/prompt.ts +65 -62
  44. package/src/cli/core/spinner.ts +23 -20
  45. package/src/cli/index.ts +46 -38
  46. package/src/cli/templates/database/index.ts +61 -0
  47. package/src/cli/templates/database/mysql.ts +14 -0
  48. package/src/cli/templates/database/none.ts +16 -0
  49. package/src/cli/templates/database/postgresql.ts +14 -0
  50. package/src/cli/templates/database/sqlite.ts +14 -0
  51. package/src/cli/templates/deploy.ts +29 -26
  52. package/src/cli/templates/docker.ts +41 -30
  53. package/src/cli/templates/frontend/index.ts +63 -0
  54. package/src/cli/templates/frontend/none.ts +17 -0
  55. package/src/cli/templates/frontend/react.ts +140 -0
  56. package/src/cli/templates/frontend/solid.ts +134 -0
  57. package/src/cli/templates/frontend/svelte.ts +131 -0
  58. package/src/cli/templates/frontend/vue.ts +130 -0
  59. package/src/cli/templates/generators/index.ts +339 -0
  60. package/src/cli/templates/generators/types.ts +56 -0
  61. package/src/cli/templates/index.ts +35 -2
  62. package/src/cli/templates/project/api.ts +81 -0
  63. package/src/cli/templates/project/default.ts +140 -0
  64. package/src/cli/templates/project/fullstack.ts +111 -0
  65. package/src/cli/templates/project/index.ts +95 -0
  66. package/src/cli/templates/project/minimal.ts +45 -0
  67. package/src/cli/templates/project/types.ts +94 -0
  68. package/src/cli/templates/project/website.ts +263 -0
  69. package/src/cli/utils/fs.ts +55 -41
  70. package/src/cli/utils/index.ts +3 -2
  71. package/src/cli/utils/strings.ts +47 -33
  72. package/src/cli/utils/version.ts +47 -0
  73. package/src/config/env-validation.ts +100 -0
  74. package/src/config/env.ts +169 -41
  75. package/src/config/index.ts +28 -20
  76. package/src/config/loader.ts +25 -16
  77. package/src/config/merge.ts +21 -10
  78. package/src/config/types.ts +545 -25
  79. package/src/config/validation.ts +215 -7
  80. package/src/container/forward-ref.ts +22 -22
  81. package/src/container/index.ts +34 -12
  82. package/src/context/index.ts +11 -1
  83. package/src/database/index.ts +7 -190
  84. package/src/database/orm/builder.ts +457 -0
  85. package/src/database/orm/casts/index.ts +130 -0
  86. package/src/database/orm/casts/types.ts +25 -0
  87. package/src/database/orm/compiler.ts +304 -0
  88. package/src/database/orm/hooks/index.ts +114 -0
  89. package/src/database/orm/index.ts +61 -0
  90. package/src/database/orm/model-registry.ts +59 -0
  91. package/src/database/orm/model.ts +821 -0
  92. package/src/database/orm/relationships/base.ts +146 -0
  93. package/src/database/orm/relationships/belongs-to-many.ts +179 -0
  94. package/src/database/orm/relationships/belongs-to.ts +56 -0
  95. package/src/database/orm/relationships/has-many.ts +45 -0
  96. package/src/database/orm/relationships/has-one.ts +41 -0
  97. package/src/database/orm/relationships/index.ts +11 -0
  98. package/src/database/orm/scopes/index.ts +55 -0
  99. package/src/events/__tests__/event-system.test.ts +235 -0
  100. package/src/events/config.ts +238 -0
  101. package/src/events/example-usage.ts +185 -0
  102. package/src/events/index.ts +278 -0
  103. package/src/events/manager.ts +385 -0
  104. package/src/events/registry.ts +182 -0
  105. package/src/events/types.ts +124 -0
  106. package/src/frontend/api-routes.ts +65 -23
  107. package/src/frontend/bundler.ts +76 -34
  108. package/src/frontend/console-client.ts +2 -2
  109. package/src/frontend/console-stream.ts +94 -38
  110. package/src/frontend/dev-server.ts +94 -46
  111. package/src/frontend/file-router.ts +61 -19
  112. package/src/frontend/frameworks/index.ts +37 -10
  113. package/src/frontend/frameworks/react.ts +10 -8
  114. package/src/frontend/frameworks/solid.ts +11 -9
  115. package/src/frontend/frameworks/svelte.ts +15 -9
  116. package/src/frontend/frameworks/vue.ts +13 -11
  117. package/src/frontend/hmr-client.ts +12 -10
  118. package/src/frontend/hmr.ts +146 -103
  119. package/src/frontend/index.ts +14 -5
  120. package/src/frontend/islands.ts +41 -22
  121. package/src/frontend/isr.ts +59 -37
  122. package/src/frontend/layout.ts +36 -21
  123. package/src/frontend/ssr/react.ts +74 -27
  124. package/src/frontend/ssr/solid.ts +54 -20
  125. package/src/frontend/ssr/svelte.ts +48 -14
  126. package/src/frontend/ssr/vue.ts +50 -18
  127. package/src/frontend/ssr.ts +83 -39
  128. package/src/frontend/types.ts +91 -56
  129. package/src/health/index.ts +21 -9
  130. package/src/i18n/engine.ts +305 -0
  131. package/src/i18n/index.ts +38 -0
  132. package/src/i18n/loader.ts +218 -0
  133. package/src/i18n/middleware.ts +164 -0
  134. package/src/i18n/negotiator.ts +162 -0
  135. package/src/i18n/types.ts +158 -0
  136. package/src/index.ts +179 -27
  137. package/src/jobs/drivers/memory.ts +315 -0
  138. package/src/jobs/drivers/redis.ts +459 -0
  139. package/src/jobs/index.ts +30 -0
  140. package/src/jobs/queue.ts +281 -0
  141. package/src/jobs/types.ts +295 -0
  142. package/src/jobs/worker.ts +380 -0
  143. package/src/logger/index.ts +1 -3
  144. package/src/logger/transports/index.ts +62 -22
  145. package/src/metrics/index.ts +25 -16
  146. package/src/migrations/index.ts +9 -0
  147. package/src/modules/filters.ts +13 -17
  148. package/src/modules/guards.ts +49 -26
  149. package/src/modules/index.ts +409 -298
  150. package/src/modules/interceptors.ts +58 -20
  151. package/src/modules/lazy.ts +11 -19
  152. package/src/modules/lifecycle.ts +15 -7
  153. package/src/modules/metadata.ts +15 -5
  154. package/src/modules/pipes.ts +94 -72
  155. package/src/notification/channels/base.ts +68 -0
  156. package/src/notification/channels/email.ts +105 -0
  157. package/src/notification/channels/push.ts +104 -0
  158. package/src/notification/channels/sms.ts +105 -0
  159. package/src/notification/channels/whatsapp.ts +104 -0
  160. package/src/notification/index.ts +48 -0
  161. package/src/notification/service.ts +354 -0
  162. package/src/notification/types.ts +344 -0
  163. package/src/observability/__tests__/observability.test.ts +483 -0
  164. package/src/observability/breadcrumbs.ts +114 -0
  165. package/src/observability/index.ts +136 -0
  166. package/src/observability/interceptor.ts +85 -0
  167. package/src/observability/service.ts +303 -0
  168. package/src/observability/trace.ts +37 -0
  169. package/src/observability/types.ts +196 -0
  170. package/src/openapi/__tests__/decorators.test.ts +335 -0
  171. package/src/openapi/__tests__/document-builder.test.ts +285 -0
  172. package/src/openapi/__tests__/route-scanner.test.ts +334 -0
  173. package/src/openapi/__tests__/schema-generator.test.ts +275 -0
  174. package/src/openapi/decorators.ts +328 -0
  175. package/src/openapi/document-builder.ts +274 -0
  176. package/src/openapi/index.ts +112 -0
  177. package/src/openapi/metadata.ts +112 -0
  178. package/src/openapi/route-scanner.ts +289 -0
  179. package/src/openapi/schema-generator.ts +256 -0
  180. package/src/openapi/swagger-module.ts +166 -0
  181. package/src/openapi/types.ts +398 -0
  182. package/src/orm/index.ts +10 -0
  183. package/src/rpc/index.ts +3 -1
  184. package/src/schema/index.ts +9 -0
  185. package/src/security/index.ts +15 -6
  186. package/src/ssg/index.ts +9 -8
  187. package/src/telemetry/index.ts +76 -22
  188. package/src/template/index.ts +7 -0
  189. package/src/templates/engine.ts +224 -0
  190. package/src/templates/index.ts +9 -0
  191. package/src/templates/loader.ts +331 -0
  192. package/src/templates/renderers/markdown.ts +212 -0
  193. package/src/templates/renderers/simple.ts +269 -0
  194. package/src/templates/types.ts +154 -0
  195. package/src/testing/index.ts +100 -27
  196. package/src/types/optional-deps.d.ts +347 -187
  197. package/src/validation/index.ts +92 -2
  198. package/src/validation/schemas.ts +536 -0
  199. package/tests/integration/fullstack.test.ts +4 -4
  200. package/tests/unit/database.test.ts +2 -72
  201. package/tests/unit/env-validation.test.ts +166 -0
  202. package/tests/unit/events.test.ts +910 -0
  203. package/tests/unit/i18n.test.ts +455 -0
  204. package/tests/unit/jobs.test.ts +493 -0
  205. package/tests/unit/notification.test.ts +988 -0
  206. package/tests/unit/observability.test.ts +453 -0
  207. package/tests/unit/orm/builder.test.ts +323 -0
  208. package/tests/unit/orm/casts.test.ts +179 -0
  209. package/tests/unit/orm/compiler.test.ts +220 -0
  210. package/tests/unit/orm/eager-loading.test.ts +285 -0
  211. package/tests/unit/orm/hooks.test.ts +191 -0
  212. package/tests/unit/orm/model.test.ts +373 -0
  213. package/tests/unit/orm/relationships.test.ts +303 -0
  214. package/tests/unit/orm/scopes.test.ts +74 -0
  215. package/tests/unit/templates-simple.test.ts +53 -0
  216. package/tests/unit/templates.test.ts +454 -0
  217. package/tests/unit/validation.test.ts +18 -24
  218. 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
+ });
@@ -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 });
@@ -1,5 +1,6 @@
1
1
  import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
2
- import { Database, detectDriver, createConnection, QueryBuilder, table } from '../../src/database';
2
+ import { Database, detectDriver, createConnection, QueryBuilder } from '../../src/database';
3
+ import { query } from '../../src/database/orm/builder';
3
4
 
4
5
  describe('Database', () => {
5
6
  // Use SQLite for testing (no external dependencies)
@@ -102,77 +103,6 @@ describe('Database', () => {
102
103
  });
103
104
  });
104
105
 
105
- describe('QueryBuilder', () => {
106
- let db: Database;
107
- let users: QueryBuilder<{ id: number; name: string; email: string }>;
108
-
109
- beforeEach(async () => {
110
- db = new Database({ url: testDbPath });
111
- await db.connect();
112
-
113
- await db.raw(`
114
- CREATE TABLE IF NOT EXISTS users (
115
- id INTEGER PRIMARY KEY AUTOINCREMENT,
116
- name TEXT NOT NULL,
117
- email TEXT UNIQUE
118
- )
119
- `);
120
-
121
- users = table<{ id: number; name: string; email: string }>(db, 'users');
122
- });
123
-
124
- afterEach(async () => {
125
- await db.close();
126
- });
127
-
128
- test('should insert and find by id', async () => {
129
- const inserted = await users.insert({ name: 'John', email: 'john@example.com' });
130
- expect(inserted.name).toBe('John');
131
-
132
- const found = await users.findById(inserted.id);
133
- expect(found?.name).toBe('John');
134
- });
135
-
136
- test('should count rows', async () => {
137
- await users.insert({ name: 'John', email: 'john@example.com' });
138
- await users.insert({ name: 'Jane', email: 'jane@example.com' });
139
-
140
- const count = await users.count();
141
- expect(count).toBe(2);
142
- });
143
-
144
- test('should delete by id', async () => {
145
- const inserted = await users.insert({ name: 'John', email: 'john@example.com' });
146
-
147
- const deleted = await users.deleteById(inserted.id);
148
- expect(deleted).toBe(true);
149
-
150
- const found = await users.findById(inserted.id);
151
- expect(found).toBeNull();
152
- });
153
-
154
- test('should update by id', async () => {
155
- const inserted = await users.insert({ name: 'John', email: 'john@example.com' });
156
-
157
- const updated = await users.updateById(inserted.id, { name: 'Johnny' });
158
- expect(updated?.name).toBe('Johnny');
159
- });
160
-
161
- test('should paginate results', async () => {
162
- for (let i = 0; i < 25; i++) {
163
- await users.insert({ name: `User${i}`, email: `user${i}@example.com` });
164
- }
165
-
166
- const page1 = await users.paginate(1, 10);
167
- expect(page1.data.length).toBe(10);
168
- expect(page1.total).toBe(25);
169
- expect(page1.totalPages).toBe(3);
170
-
171
- const page3 = await users.paginate(3, 10);
172
- expect(page3.data.length).toBe(5);
173
- });
174
- });
175
-
176
106
  describe('createConnection', () => {
177
107
  test('should create and connect to database', async () => {
178
108
  const db = await createConnection({ url: ':memory:' });