@buenojs/bueno 0.8.4 → 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} +412 -331
  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 +294 -232
  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 +37 -18
  47. package/src/cli/templates/database/mysql.ts +3 -3
  48. package/src/cli/templates/database/none.ts +2 -2
  49. package/src/cli/templates/database/postgresql.ts +3 -3
  50. package/src/cli/templates/database/sqlite.ts +3 -3
  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 +33 -15
  54. package/src/cli/templates/frontend/none.ts +2 -2
  55. package/src/cli/templates/frontend/react.ts +18 -18
  56. package/src/cli/templates/frontend/solid.ts +15 -15
  57. package/src/cli/templates/frontend/svelte.ts +17 -17
  58. package/src/cli/templates/frontend/vue.ts +15 -15
  59. package/src/cli/templates/generators/index.ts +29 -29
  60. package/src/cli/templates/generators/types.ts +21 -21
  61. package/src/cli/templates/index.ts +6 -6
  62. package/src/cli/templates/project/api.ts +37 -36
  63. package/src/cli/templates/project/default.ts +25 -25
  64. package/src/cli/templates/project/fullstack.ts +28 -26
  65. package/src/cli/templates/project/index.ts +55 -16
  66. package/src/cli/templates/project/minimal.ts +17 -12
  67. package/src/cli/templates/project/types.ts +10 -5
  68. package/src/cli/templates/project/website.ts +14 -14
  69. package/src/cli/utils/fs.ts +55 -41
  70. package/src/cli/utils/index.ts +3 -3
  71. package/src/cli/utils/strings.ts +47 -33
  72. package/src/cli/utils/version.ts +14 -8
  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,130 @@
1
+ /**
2
+ * Cast Registry and Built-in Casts
3
+ */
4
+
5
+ import type { CastDefinition, CastObject } from "./types";
6
+
7
+ export type { CastDefinition, CastObject, BuiltInCastName } from "./types";
8
+
9
+ const builtInCasts: Record<string, CastObject> = {
10
+ json: {
11
+ get(value: unknown): unknown {
12
+ if (typeof value === "string") {
13
+ try {
14
+ return JSON.parse(value);
15
+ } catch {
16
+ return value;
17
+ }
18
+ }
19
+ return value;
20
+ },
21
+ set(value: unknown): unknown {
22
+ if (typeof value === "string") return value;
23
+ return JSON.stringify(value);
24
+ },
25
+ },
26
+
27
+ boolean: {
28
+ get(value: unknown): boolean | null {
29
+ if (value === null || value === undefined) return null;
30
+ if (typeof value === "boolean") return value;
31
+ if (typeof value === "number") return value !== 0;
32
+ if (typeof value === "string") return value !== "0" && value !== "false";
33
+ return Boolean(value);
34
+ },
35
+ set(value: unknown): number {
36
+ return value ? 1 : 0;
37
+ },
38
+ },
39
+
40
+ integer: {
41
+ get(value: unknown): number {
42
+ return Number(value);
43
+ },
44
+ set(value: unknown): number {
45
+ return Number(value);
46
+ },
47
+ },
48
+
49
+ float: {
50
+ get(value: unknown): number {
51
+ return Number(value);
52
+ },
53
+ set(value: unknown): number {
54
+ return Number(value);
55
+ },
56
+ },
57
+
58
+ date: {
59
+ get(value: unknown): Date | null {
60
+ if (!value) return null;
61
+ if (value instanceof Date) return value;
62
+ return new Date(String(value));
63
+ },
64
+ set(value: unknown): string {
65
+ if (value instanceof Date) {
66
+ return value.toISOString().split("T")[0];
67
+ }
68
+ return String(value);
69
+ },
70
+ },
71
+
72
+ datetime: {
73
+ get(value: unknown): Date | null {
74
+ if (!value) return null;
75
+ if (value instanceof Date) return value;
76
+ return new Date(String(value));
77
+ },
78
+ set(value: unknown): string {
79
+ if (value instanceof Date) {
80
+ return value.toISOString();
81
+ }
82
+ return String(value);
83
+ },
84
+ },
85
+
86
+ timestamp: {
87
+ get(value: unknown): Date | null {
88
+ if (!value) return null;
89
+ if (value instanceof Date) return value;
90
+ if (typeof value === "number") return new Date(value);
91
+ return new Date(Number(value));
92
+ },
93
+ set(value: unknown): number {
94
+ if (value instanceof Date) {
95
+ return value.getTime();
96
+ }
97
+ return Number(value);
98
+ },
99
+ },
100
+ };
101
+
102
+ export class CastRegistry {
103
+ /**
104
+ * Deserialize a value from database using a cast definition
105
+ */
106
+ static deserialize(castDef: CastDefinition, value: unknown): unknown {
107
+ if (typeof castDef === "string") {
108
+ const castObj = builtInCasts[castDef];
109
+ if (!castObj) {
110
+ throw new Error(`Unknown cast: ${castDef}`);
111
+ }
112
+ return castObj.get(value);
113
+ }
114
+ return castDef.get(value);
115
+ }
116
+
117
+ /**
118
+ * Serialize a value to database using a cast definition
119
+ */
120
+ static serialize(castDef: CastDefinition, value: unknown): unknown {
121
+ if (typeof castDef === "string") {
122
+ const castObj = builtInCasts[castDef];
123
+ if (!castObj) {
124
+ throw new Error(`Unknown cast: ${castDef}`);
125
+ }
126
+ return castObj.set(value);
127
+ }
128
+ return castDef.set(value);
129
+ }
130
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Cast Type Definitions
3
+ */
4
+
5
+ export type BuiltInCastName =
6
+ | "json"
7
+ | "boolean"
8
+ | "integer"
9
+ | "float"
10
+ | "date"
11
+ | "datetime"
12
+ | "timestamp";
13
+
14
+ export interface CastObject {
15
+ /**
16
+ * Transform value from database to model attribute
17
+ */
18
+ get(value: unknown): unknown;
19
+ /**
20
+ * Transform value from model attribute to database
21
+ */
22
+ set(value: unknown): unknown;
23
+ }
24
+
25
+ export type CastDefinition = BuiltInCastName | CastObject;
@@ -0,0 +1,304 @@
1
+ /**
2
+ * SQL Query Compiler
3
+ *
4
+ * Converts a QueryState into SQL strings and parameters,
5
+ * handling driver differences (PostgreSQL $1 vs SQLite/MySQL ?)
6
+ */
7
+
8
+ export type SqlDialect = "postgresql" | "mysql" | "sqlite";
9
+
10
+ export interface CompiledQuery {
11
+ sql: string;
12
+ params: unknown[];
13
+ }
14
+
15
+ export interface WhereClause {
16
+ type: "and" | "or";
17
+ column?: string;
18
+ operator?: string;
19
+ value?: unknown;
20
+ raw?: string;
21
+ rawParams?: unknown[];
22
+ nested?: WhereClause[];
23
+ }
24
+
25
+ export interface OrderClause {
26
+ column: string;
27
+ direction: "ASC" | "DESC";
28
+ }
29
+
30
+ export interface JoinClause {
31
+ type: "INNER" | "LEFT" | "RIGHT" | "CROSS";
32
+ table: string;
33
+ on: string;
34
+ }
35
+
36
+ export interface QueryState {
37
+ table: string;
38
+ alias?: string;
39
+ selects: string[];
40
+ wheres: WhereClause[];
41
+ orders: OrderClause[];
42
+ joins: JoinClause[];
43
+ limitVal?: number;
44
+ offsetVal?: number;
45
+ groupBys: string[];
46
+ havings: string[];
47
+ distinct: boolean;
48
+ lockMode?: "share" | "update";
49
+ }
50
+
51
+ export class QueryCompiler {
52
+ private paramIndex = 0;
53
+ private params: unknown[] = [];
54
+
55
+ constructor(private dialect: SqlDialect) {}
56
+
57
+ /**
58
+ * Reset state for a new compilation
59
+ */
60
+ private reset(): void {
61
+ this.paramIndex = 0;
62
+ this.params = [];
63
+ }
64
+
65
+ /**
66
+ * Add a parameter and return the placeholder for this dialect
67
+ */
68
+ private addParam(value: unknown): string {
69
+ this.params.push(value);
70
+ if (this.dialect === "postgresql") {
71
+ return `$${++this.paramIndex}`;
72
+ }
73
+ this.paramIndex++;
74
+ return "?";
75
+ }
76
+
77
+ /**
78
+ * Compile a SELECT query
79
+ */
80
+ compileSelect(state: QueryState): CompiledQuery {
81
+ this.reset();
82
+ const parts: string[] = [];
83
+
84
+ // SELECT
85
+ const cols = state.selects.length > 0 ? state.selects.join(", ") : "*";
86
+ const distinct = state.distinct ? "DISTINCT " : "";
87
+ parts.push(`SELECT ${distinct}${cols}`);
88
+
89
+ // FROM
90
+ const alias = state.alias ? ` AS ${state.alias}` : "";
91
+ parts.push(`FROM ${state.table}${alias}`);
92
+
93
+ // JOINs
94
+ for (const j of state.joins) {
95
+ parts.push(`${j.type} JOIN ${j.table} ON ${j.on}`);
96
+ }
97
+
98
+ // WHERE
99
+ if (state.wheres.length > 0) {
100
+ parts.push(`WHERE ${this.compileWheres(state.wheres)}`);
101
+ }
102
+
103
+ // GROUP BY
104
+ if (state.groupBys.length > 0) {
105
+ parts.push(`GROUP BY ${state.groupBys.join(", ")}`);
106
+ }
107
+
108
+ // HAVING
109
+ if (state.havings.length > 0) {
110
+ parts.push(`HAVING ${state.havings.join(" AND ")}`);
111
+ }
112
+
113
+ // ORDER BY
114
+ if (state.orders.length > 0) {
115
+ const orderStr = state.orders
116
+ .map((o) => `${o.column} ${o.direction}`)
117
+ .join(", ");
118
+ parts.push(`ORDER BY ${orderStr}`);
119
+ }
120
+
121
+ // LIMIT / OFFSET
122
+ if (state.limitVal !== undefined) {
123
+ parts.push(`LIMIT ${this.addParam(state.limitVal)}`);
124
+ }
125
+ if (state.offsetVal !== undefined) {
126
+ parts.push(`OFFSET ${this.addParam(state.offsetVal)}`);
127
+ }
128
+
129
+ // Locking
130
+ if (state.lockMode === "update") parts.push("FOR UPDATE");
131
+ if (state.lockMode === "share") parts.push("FOR SHARE");
132
+
133
+ return { sql: parts.join(" "), params: [...this.params] };
134
+ }
135
+
136
+ /**
137
+ * Compile WHERE clauses into SQL
138
+ */
139
+ private compileWheres(wheres: WhereClause[]): string {
140
+ return wheres
141
+ .map((w, i) => {
142
+ const prefix = i === 0 ? "" : `${w.type.toUpperCase()} `;
143
+
144
+ if (w.raw !== undefined) {
145
+ if (w.rawParams) {
146
+ for (const p of w.rawParams) {
147
+ this.params.push(p);
148
+ }
149
+ }
150
+ return `${prefix}(${w.raw})`;
151
+ }
152
+
153
+ if (w.nested) {
154
+ return `${prefix}(${this.compileWheres(w.nested)})`;
155
+ }
156
+
157
+ if (w.operator === "IN" && Array.isArray(w.value)) {
158
+ const placeholders = w.value.map((v) => this.addParam(v)).join(", ");
159
+ return `${prefix}${w.column} IN (${placeholders})`;
160
+ }
161
+
162
+ if (w.operator === "NOT IN" && Array.isArray(w.value)) {
163
+ const placeholders = w.value.map((v) => this.addParam(v)).join(", ");
164
+ return `${prefix}${w.column} NOT IN (${placeholders})`;
165
+ }
166
+
167
+ if (w.operator === "IS NULL") {
168
+ return `${prefix}${w.column} IS NULL`;
169
+ }
170
+
171
+ if (w.operator === "IS NOT NULL") {
172
+ return `${prefix}${w.column} IS NOT NULL`;
173
+ }
174
+
175
+ if (w.operator === "BETWEEN") {
176
+ const [min, max] = w.value as [unknown, unknown];
177
+ return `${prefix}${w.column} BETWEEN ${this.addParam(min)} AND ${this.addParam(max)}`;
178
+ }
179
+
180
+ return `${prefix}${w.column} ${w.operator} ${this.addParam(w.value)}`;
181
+ })
182
+ .join(" ");
183
+ }
184
+
185
+ /**
186
+ * Compile an INSERT query
187
+ */
188
+ compileInsert(table: string, data: Record<string, unknown>): CompiledQuery {
189
+ this.reset();
190
+ const keys = Object.keys(data);
191
+ const columns = keys.join(", ");
192
+ const placeholders = keys.map((k) => this.addParam(data[k])).join(", ");
193
+
194
+ const returning = this.dialect === "postgresql" ? " RETURNING *" : "";
195
+ return {
196
+ sql: `INSERT INTO ${table} (${columns}) VALUES (${placeholders})${returning}`,
197
+ params: [...this.params],
198
+ };
199
+ }
200
+
201
+ /**
202
+ * Compile a batch INSERT query
203
+ */
204
+ compileBatchInsert(
205
+ table: string,
206
+ rows: Record<string, unknown>[],
207
+ ): CompiledQuery {
208
+ this.reset();
209
+
210
+ if (rows.length === 0) {
211
+ return { sql: "", params: [] };
212
+ }
213
+
214
+ const keys = Object.keys(rows[0]);
215
+ const columns = keys.join(", ");
216
+
217
+ const valueRows = rows.map((row) => {
218
+ const placeholders = keys.map((k) => this.addParam(row[k])).join(", ");
219
+ return `(${placeholders})`;
220
+ });
221
+
222
+ const returning = this.dialect === "postgresql" ? " RETURNING *" : "";
223
+ return {
224
+ sql: `INSERT INTO ${table} (${columns}) VALUES ${valueRows.join(", ")}${returning}`,
225
+ params: [...this.params],
226
+ };
227
+ }
228
+
229
+ /**
230
+ * Compile an UPDATE query
231
+ */
232
+ compileUpdate(
233
+ state: QueryState,
234
+ data: Record<string, unknown>,
235
+ ): CompiledQuery {
236
+ this.reset();
237
+ const sets = Object.entries(data)
238
+ .map(([col, val]) => `${col} = ${this.addParam(val)}`)
239
+ .join(", ");
240
+
241
+ const whereStr =
242
+ state.wheres.length > 0
243
+ ? ` WHERE ${this.compileWheres(state.wheres)}`
244
+ : "";
245
+
246
+ const returning = this.dialect === "postgresql" ? " RETURNING *" : "";
247
+ return {
248
+ sql: `UPDATE ${state.table} SET ${sets}${whereStr}${returning}`,
249
+ params: [...this.params],
250
+ };
251
+ }
252
+
253
+ /**
254
+ * Compile a DELETE query
255
+ */
256
+ compileDelete(state: QueryState): CompiledQuery {
257
+ this.reset();
258
+ const whereStr =
259
+ state.wheres.length > 0
260
+ ? ` WHERE ${this.compileWheres(state.wheres)}`
261
+ : "";
262
+ return {
263
+ sql: `DELETE FROM ${state.table}${whereStr}`,
264
+ params: [...this.params],
265
+ };
266
+ }
267
+
268
+ /**
269
+ * Compile a COUNT query
270
+ */
271
+ compileCount(state: QueryState, column = "*"): CompiledQuery {
272
+ this.reset();
273
+ const countCol = column === "*" ? "COUNT(*)" : `COUNT(${column})`;
274
+ const parts: string[] = [`SELECT ${countCol} AS count`];
275
+
276
+ const alias = state.alias ? ` AS ${state.alias}` : "";
277
+ parts.push(`FROM ${state.table}${alias}`);
278
+
279
+ for (const j of state.joins) {
280
+ parts.push(`${j.type} JOIN ${j.table} ON ${j.on}`);
281
+ }
282
+
283
+ if (state.wheres.length > 0) {
284
+ parts.push(`WHERE ${this.compileWheres(state.wheres)}`);
285
+ }
286
+
287
+ return { sql: parts.join(" "), params: [...this.params] };
288
+ }
289
+
290
+ /**
291
+ * Compile an EXISTS query
292
+ */
293
+ compileExists(state: QueryState): CompiledQuery {
294
+ const selectCompiled = this.compileSelect({
295
+ ...state,
296
+ selects: ["1"],
297
+ limitVal: 1,
298
+ });
299
+ return {
300
+ sql: `SELECT EXISTS(${selectCompiled.sql}) as exists`,
301
+ params: selectCompiled.params,
302
+ };
303
+ }
304
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Model Hooks / Lifecycle Events
3
+ *
4
+ * Provides lifecycle callbacks for model events:
5
+ * creating, created, updating, updated, saving, saved,
6
+ * deleting, deleted, restoring, restored
7
+ */
8
+
9
+ export type ModelHookName =
10
+ | "creating"
11
+ | "created"
12
+ | "updating"
13
+ | "updated"
14
+ | "saving"
15
+ | "saved"
16
+ | "deleting"
17
+ | "deleted"
18
+ | "restoring"
19
+ | "restored";
20
+
21
+ export type ModelHookCallback<M> = (
22
+ model: M,
23
+ ) => void | Promise<void> | boolean | Promise<boolean>;
24
+
25
+ /**
26
+ * Model hook registry for a specific model class
27
+ */
28
+ export class HookRunner<M> {
29
+ private hooks: Map<ModelHookName, ModelHookCallback<M>[]> = new Map();
30
+
31
+ constructor(private modelClass: typeof Model) {
32
+ this.initializeHooks();
33
+ }
34
+
35
+ /**
36
+ * Initialize hooks from model class static definitions
37
+ */
38
+ private initializeHooks(): void {
39
+ // Hooks are registered statically on the model class
40
+ // They're accessed via Model.on() / Model.once() methods
41
+ }
42
+
43
+ /**
44
+ * Register a hook callback
45
+ */
46
+ on(hookName: ModelHookName, callback: ModelHookCallback<M>): void {
47
+ if (!this.hooks.has(hookName)) {
48
+ this.hooks.set(hookName, []);
49
+ }
50
+ this.hooks.get(hookName)!.push(callback);
51
+ }
52
+
53
+ /**
54
+ * Run all callbacks for a hook
55
+ * Returns false if any callback explicitly returns false (to abort operation)
56
+ */
57
+ async run(hookName: ModelHookName, model: M): Promise<boolean> {
58
+ const callbacks = this.hooks.get(hookName) ?? [];
59
+
60
+ for (const callback of callbacks) {
61
+ const result = await callback(model);
62
+ if (result === false) {
63
+ return false; // Abort
64
+ }
65
+ }
66
+
67
+ return true; // Continue
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Placeholder to avoid circular dependency issues
73
+ */
74
+ export abstract class Model {
75
+ private static hookRegistry = new Map<string, Map<string, Function[]>>();
76
+
77
+ /**
78
+ * Get or create the hook registry for this model class
79
+ */
80
+ private static getHookRegistry(
81
+ modelName: string,
82
+ ): Map<ModelHookName, Function[]> {
83
+ if (!Model.hookRegistry.has(modelName)) {
84
+ Model.hookRegistry.set(modelName, new Map<ModelHookName, Function[]>());
85
+ }
86
+ return Model.hookRegistry.get(modelName)! as Map<ModelHookName, Function[]>;
87
+ }
88
+
89
+ /**
90
+ * Register a hook callback on the model class
91
+ */
92
+ static on<M extends Model>(
93
+ this: { new (): M } & typeof Model,
94
+ hookName: ModelHookName,
95
+ callback: ModelHookCallback<M>,
96
+ ): void {
97
+ const registry = Model.getHookRegistry(this.name);
98
+ if (!registry.has(hookName)) {
99
+ registry.set(hookName, []);
100
+ }
101
+ registry.get(hookName)!.push(callback as any);
102
+ }
103
+
104
+ /**
105
+ * Get all callbacks for a hook
106
+ */
107
+ static getHookCallbacks<M extends Model>(
108
+ this: { new (): M } & typeof Model,
109
+ hookName: ModelHookName,
110
+ ): ModelHookCallback<M>[] {
111
+ const registry = Model.getHookRegistry(this.name);
112
+ return (registry.get(hookName) ?? []) as any;
113
+ }
114
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Bueno ORM
3
+ *
4
+ * Eloquent-inspired ORM for the Bueno framework.
5
+ * Provides Model, QueryBuilder, relationships, and lifecycle management.
6
+ */
7
+
8
+ // ============= Query Builder =============
9
+ export { OrmQueryBuilder, query } from "./builder";
10
+ export type { PaginationResult } from "./builder";
11
+
12
+ // ============= Model =============
13
+ export {
14
+ Model,
15
+ ModelNotFoundError,
16
+ ModelOperationAbortedError,
17
+ ModelQueryBuilder,
18
+ } from "./model";
19
+
20
+ // ============= Registry =============
21
+ export {
22
+ setDefaultDatabase,
23
+ getDefaultDatabase,
24
+ registerModelDatabase,
25
+ getModelDatabase,
26
+ clearModelDatabaseRegistry,
27
+ clearDefaultDatabase,
28
+ } from "./model-registry";
29
+
30
+ // ============= Relationships =============
31
+ export {
32
+ Relationship,
33
+ HasOne,
34
+ HasMany,
35
+ BelongsTo,
36
+ BelongsToMany,
37
+ } from "./relationships";
38
+ export type { RelationshipOptions } from "./relationships/base";
39
+
40
+ // ============= Casts =============
41
+ export { CastRegistry } from "./casts";
42
+ export type { CastDefinition, CastObject, BuiltInCastName } from "./casts";
43
+
44
+ // ============= Scopes =============
45
+ export { ScopeRegistry, SoftDeleteScope } from "./scopes";
46
+ export type { ScopeDefinition } from "./scopes";
47
+
48
+ // ============= Hooks =============
49
+ export { HookRunner } from "./hooks";
50
+ export type { ModelHookName, ModelHookCallback } from "./hooks";
51
+
52
+ // ============= Compiler (advanced usage) =============
53
+ export { QueryCompiler } from "./compiler";
54
+ export type {
55
+ SqlDialect,
56
+ CompiledQuery,
57
+ WhereClause,
58
+ OrderClause,
59
+ JoinClause,
60
+ QueryState,
61
+ } from "./compiler";
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Model Registry
3
+ *
4
+ * Global registry mapping model names to their Database instances.
5
+ * Allows per-model connection selection and a default connection fallback.
6
+ */
7
+
8
+ import type { Database } from "../index";
9
+
10
+ const registry = new Map<string, Database>();
11
+ let defaultDb: Database | null = null;
12
+
13
+ /**
14
+ * Set the default database for all models
15
+ */
16
+ export function setDefaultDatabase(db: Database): void {
17
+ defaultDb = db;
18
+ }
19
+
20
+ /**
21
+ * Get the default database
22
+ */
23
+ export function getDefaultDatabase(): Database {
24
+ if (!defaultDb) {
25
+ throw new Error(
26
+ "No default database configured. Call setDefaultDatabase() before using models.",
27
+ );
28
+ }
29
+ return defaultDb;
30
+ }
31
+
32
+ /**
33
+ * Register a database connection for a specific model by name
34
+ */
35
+ export function registerModelDatabase(modelName: string, db: Database): void {
36
+ registry.set(modelName, db);
37
+ }
38
+
39
+ /**
40
+ * Get the database for a model by name
41
+ * Falls back to default database if model-specific one not registered
42
+ */
43
+ export function getModelDatabase(modelName: string): Database {
44
+ return registry.get(modelName) ?? getDefaultDatabase();
45
+ }
46
+
47
+ /**
48
+ * Clear all model database registrations (mainly for testing)
49
+ */
50
+ export function clearModelDatabaseRegistry(): void {
51
+ registry.clear();
52
+ }
53
+
54
+ /**
55
+ * Clear the default database (mainly for testing)
56
+ */
57
+ export function clearDefaultDatabase(): void {
58
+ defaultDb = null;
59
+ }