zeti-framework-backend 0.2.4

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.
@@ -0,0 +1,690 @@
1
+ // src/scripts/generate-registry.ts
2
+ import path from "path";
3
+ async function generateRegistry(options) {
4
+ const {
5
+ controllersPath,
6
+ indexPath,
7
+ startMarker = "// @CONTROLLERS_START",
8
+ endMarker = "// @CONTROLLERS_END",
9
+ outputMode = "inject"
10
+ } = options;
11
+ const glob = new Bun.Glob("**/*.ts");
12
+ const imports = [];
13
+ for await (const file2 of glob.scan({ cwd: controllersPath, absolute: true })) {
14
+ const name = path.basename(file2);
15
+ if (name === "index.ts" || name === "registry.ts") continue;
16
+ const relativePath = path.relative(path.dirname(indexPath), file2);
17
+ const importPath = relativePath.replace(/\\/g, "/").replace(/\.ts$/, "");
18
+ imports.push(`import "${importPath.startsWith(".") ? importPath : "./" + importPath}";`);
19
+ }
20
+ if (outputMode === "standalone") {
21
+ const content = `// Generated by zeti-framework. Do not edit.
22
+
23
+ ${imports.join("\n")}
24
+ `;
25
+ const file2 = Bun.file(indexPath);
26
+ try {
27
+ if (await file2.exists()) {
28
+ const existing = await file2.text();
29
+ if (existing === content) {
30
+ console.log(`\u2705 Registry unchanged (${imports.length} controllers)`);
31
+ return;
32
+ }
33
+ }
34
+ } catch {
35
+ }
36
+ await Bun.write(indexPath, content);
37
+ console.log(`\u2705 Generated .zeti/registry.ts with ${imports.length} controller imports`);
38
+ return;
39
+ }
40
+ const file = Bun.file(indexPath);
41
+ const indexContent = await file.exists() ? await file.text() : "";
42
+ const generatedBlock = `${startMarker}
43
+ ${imports.join("\n")}
44
+ ${endMarker}`;
45
+ let newContent;
46
+ if (indexContent.includes(startMarker)) {
47
+ const regex = new RegExp(`${startMarker}[\\s\\S]*?${endMarker}`, "g");
48
+ newContent = indexContent.replace(regex, generatedBlock);
49
+ } else {
50
+ newContent = indexContent + (indexContent ? "\n\n" : "") + generatedBlock;
51
+ }
52
+ if (newContent !== indexContent) {
53
+ await Bun.write(indexPath, newContent);
54
+ console.log(`\u2705 Injected ${imports.length} controller imports into ${indexPath}`);
55
+ } else {
56
+ console.log(`\u2705 Registry unchanged (${imports.length} controllers)`);
57
+ }
58
+ }
59
+
60
+ // src/scripts/generate-swagger-types.ts
61
+ import path2 from "path";
62
+ function splitUnionType(typeStr) {
63
+ const parts = [];
64
+ let depth = 0;
65
+ let current = "";
66
+ for (let i = 0; i < typeStr.length; i++) {
67
+ const char = typeStr[i];
68
+ if (char === "<" || char === "{" || char === "[" || char === "(") depth++;
69
+ else if (char === ">" || char === "}" || char === "]" || char === ")") depth--;
70
+ else if (char === "|" && depth === 0) {
71
+ if (current.trim()) parts.push(current.trim());
72
+ current = "";
73
+ continue;
74
+ }
75
+ current += char;
76
+ }
77
+ if (current.trim()) parts.push(current.trim());
78
+ return parts;
79
+ }
80
+ function typeToZodSchemaWithRefs(typeStr, typeDefinitions, namedSchemas, depth = 0) {
81
+ if (depth > 10) return "z.any()";
82
+ typeStr = typeStr.trim();
83
+ const promiseMatch = typeStr.match(/^Promise<(.+)>$/);
84
+ if (promiseMatch) {
85
+ typeStr = promiseMatch[1].trim();
86
+ }
87
+ if (typeStr === "string") return "z.string()";
88
+ if (typeStr === "number") return "z.number()";
89
+ if (typeStr === "boolean") return "z.boolean()";
90
+ if (typeStr === "null") return "z.null()";
91
+ if (typeStr === "undefined") return "z.undefined()";
92
+ if (typeStr === "any") return "z.any()";
93
+ if (typeStr === "unknown") return "z.unknown()";
94
+ if (typeStr === "void") return "z.void()";
95
+ if (typeStr === "Date") return "z.date()";
96
+ const stringLiteralMatch = typeStr.match(/^["'](.+)["']$/);
97
+ if (stringLiteralMatch) {
98
+ return `z.literal("${stringLiteralMatch[1]}")`;
99
+ }
100
+ if (/^\d+$/.test(typeStr)) {
101
+ return `z.literal(${typeStr})`;
102
+ }
103
+ const arrayNamedMatch = typeStr.match(/^(\w+)\[\]$/);
104
+ if (arrayNamedMatch) {
105
+ const itemType = arrayNamedMatch[1];
106
+ if (typeDefinitions.has(itemType)) {
107
+ if (!namedSchemas.has(itemType)) {
108
+ const typeDef = typeDefinitions.get(itemType);
109
+ namedSchemas.set(itemType, typeToZodSchemaWithRefs(typeDef, typeDefinitions, namedSchemas, depth + 1));
110
+ }
111
+ return `z.array(namedSchemas.${itemType})`;
112
+ }
113
+ return `z.array(${typeToZodSchemaWithRefs(itemType, typeDefinitions, namedSchemas, depth + 1)})`;
114
+ }
115
+ const arrayGenericMatch = typeStr.match(/^Array<(.+)>$/);
116
+ if (arrayGenericMatch) {
117
+ const itemType = arrayGenericMatch[1].trim();
118
+ if (typeDefinitions.has(itemType)) {
119
+ if (!namedSchemas.has(itemType)) {
120
+ const typeDef = typeDefinitions.get(itemType);
121
+ namedSchemas.set(itemType, typeToZodSchemaWithRefs(typeDef, typeDefinitions, namedSchemas, depth + 1));
122
+ }
123
+ return `z.array(namedSchemas.${itemType})`;
124
+ }
125
+ return `z.array(${typeToZodSchemaWithRefs(itemType, typeDefinitions, namedSchemas, depth + 1)})`;
126
+ }
127
+ if (/^[A-Z]\w*$/.test(typeStr) && typeDefinitions.has(typeStr)) {
128
+ if (!namedSchemas.has(typeStr)) {
129
+ const typeDef = typeDefinitions.get(typeStr);
130
+ namedSchemas.set(typeStr, typeToZodSchemaWithRefs(typeDef, typeDefinitions, namedSchemas, depth + 1));
131
+ }
132
+ return `namedSchemas.${typeStr}`;
133
+ }
134
+ if (typeStr.startsWith("{") && typeStr.endsWith("}")) {
135
+ const inner = typeStr.slice(1, -1).trim();
136
+ if (!inner) return "z.object({})";
137
+ const props = [];
138
+ let currentDepth = 0;
139
+ let current = "";
140
+ let inString = false;
141
+ let stringChar = "";
142
+ for (let i = 0; i < inner.length; i++) {
143
+ const char = inner[i];
144
+ if (!inString && (char === '"' || char === "'" || char === "`")) {
145
+ inString = true;
146
+ stringChar = char;
147
+ current += char;
148
+ } else if (inString && char === stringChar && inner[i - 1] !== "\\") {
149
+ inString = false;
150
+ current += char;
151
+ } else if (!inString) {
152
+ if (char === "{" || char === "<" || char === "[" || char === "(") currentDepth++;
153
+ else if (char === "}" || char === ">" || char === "]" || char === ")") currentDepth--;
154
+ if ((char === "," || char === ";") && currentDepth === 0) {
155
+ if (current.trim()) props.push(current.trim());
156
+ current = "";
157
+ } else {
158
+ current += char;
159
+ }
160
+ } else {
161
+ current += char;
162
+ }
163
+ }
164
+ if (current.trim()) props.push(current.trim());
165
+ const zodProps = props.map((prop) => {
166
+ let colonIndex = -1;
167
+ let propDepth = 0;
168
+ for (let i = 0; i < prop.length; i++) {
169
+ const char = prop[i];
170
+ if (char === "<" || char === "{" || char === "[") propDepth++;
171
+ else if (char === ">" || char === "}" || char === "]") propDepth--;
172
+ else if (char === ":" && propDepth === 0) {
173
+ colonIndex = i;
174
+ break;
175
+ }
176
+ }
177
+ if (colonIndex === -1) return null;
178
+ let key = prop.slice(0, colonIndex).trim();
179
+ const value = prop.slice(colonIndex + 1).trim();
180
+ const isOptional = key.endsWith("?");
181
+ if (isOptional) key = key.slice(0, -1);
182
+ const zodType = typeToZodSchemaWithRefs(value, typeDefinitions, namedSchemas, depth + 1);
183
+ return `${key}: ${isOptional ? `${zodType}.optional()` : zodType}`;
184
+ }).filter(Boolean);
185
+ return `z.object({ ${zodProps.join(", ")} })`;
186
+ }
187
+ if (typeStr.includes("|") && !typeStr.includes("{")) {
188
+ const types = splitUnionType(typeStr).map((t) => typeToZodSchemaWithRefs(t.trim(), typeDefinitions, namedSchemas, depth + 1));
189
+ if (types.length === 2 && types.includes("z.undefined()")) {
190
+ const other = types.find((t) => t !== "z.undefined()");
191
+ return `${other}.optional()`;
192
+ }
193
+ if (types.length === 2 && types.includes("z.null()")) {
194
+ const other = types.find((t) => t !== "z.null()");
195
+ return `${other}.nullable()`;
196
+ }
197
+ return `z.union([${types.join(", ")}])`;
198
+ }
199
+ if (/^[A-Z]\w*$/.test(typeStr)) {
200
+ return "z.any()";
201
+ }
202
+ return "z.any()";
203
+ }
204
+ function extractTypeDefinitions(content) {
205
+ const types = /* @__PURE__ */ new Map();
206
+ const cleanContent = content.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
207
+ const typeRegex = /(?:export\s+)?(?:type|interface)\s+(\w+)\s*=?\s*\{/g;
208
+ let match;
209
+ while ((match = typeRegex.exec(cleanContent)) !== null) {
210
+ const name = match[1];
211
+ const startIndex = match.index + match[0].length - 1;
212
+ let depth = 1;
213
+ let endIndex = startIndex + 1;
214
+ while (depth > 0 && endIndex < cleanContent.length) {
215
+ const char = cleanContent[endIndex];
216
+ if (char === "{") depth++;
217
+ else if (char === "}") depth--;
218
+ endIndex++;
219
+ }
220
+ const body = cleanContent.slice(startIndex, endIndex);
221
+ types.set(name, body);
222
+ }
223
+ return types;
224
+ }
225
+ function extractRoutes(content, typeDefinitions, namedSchemas) {
226
+ const routes = [];
227
+ const routeRegex = /(?:app\.)?(get|post|put|del|patch)\s*\(\s*["'`]([^"'`]+)["'`]/g;
228
+ let match;
229
+ while ((match = routeRegex.exec(content)) !== null) {
230
+ const [fullMatch, method, routePath] = match;
231
+ const afterMatch = content.slice(match.index + fullMatch.length, match.index + fullMatch.length + 2e3);
232
+ let returnType = "z.any()";
233
+ let schemaName;
234
+ const explicitTypeMatch = afterMatch.match(/route\s*:\s*async\s*\([^)]*\)\s*:\s*Promise\s*<([^>]+)>/);
235
+ if (explicitTypeMatch) {
236
+ const typeRef = explicitTypeMatch[1].trim();
237
+ if (typeDefinitions.has(typeRef)) {
238
+ if (!namedSchemas.has(typeRef)) {
239
+ const typeDef = typeDefinitions.get(typeRef);
240
+ namedSchemas.set(typeRef, typeToZodSchemaWithRefs(typeDef, typeDefinitions, namedSchemas));
241
+ }
242
+ returnType = `namedSchemas.${typeRef}`;
243
+ schemaName = typeRef;
244
+ } else {
245
+ returnType = typeToZodSchemaWithRefs(typeRef, typeDefinitions, namedSchemas);
246
+ }
247
+ } else {
248
+ const returnMatch = afterMatch.match(/return\s+(\{[^}]+\})/);
249
+ if (returnMatch) {
250
+ returnType = inferTypeFromValue(returnMatch[1]);
251
+ }
252
+ }
253
+ routes.push({
254
+ path: routePath,
255
+ method: method === "del" ? "delete" : method,
256
+ returnType,
257
+ schemaName,
258
+ hasFormData: content.includes("formData")
259
+ });
260
+ }
261
+ return routes;
262
+ }
263
+ function inferTypeFromValue(value) {
264
+ value = value.trim();
265
+ if (value.startsWith("{") && value.endsWith("}")) {
266
+ const inner = value.slice(1, -1).trim();
267
+ if (!inner) return "z.object({})";
268
+ const props = [];
269
+ const propRegex = /(\w+)\s*:\s*("[^"]*"|'[^']*'|`[^`]*`|\d+|true|false|null|\{[^}]*\}|\[[^\]]*\])/g;
270
+ let propMatch;
271
+ while ((propMatch = propRegex.exec(inner)) !== null) {
272
+ const [, key, val] = propMatch;
273
+ const zodType = inferTypeFromValue(val);
274
+ props.push(`${key}: ${zodType}`);
275
+ }
276
+ if (props.length > 0) {
277
+ return `z.object({ ${props.join(", ")} })`;
278
+ }
279
+ }
280
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'") || value.startsWith("`") && value.endsWith("`")) {
281
+ return "z.string()";
282
+ }
283
+ if (/^\d+(\.\d+)?$/.test(value)) {
284
+ return "z.number()";
285
+ }
286
+ if (value === "true" || value === "false") {
287
+ return "z.boolean()";
288
+ }
289
+ if (value === "null") {
290
+ return "z.null()";
291
+ }
292
+ if (value.startsWith("[") && value.endsWith("]")) {
293
+ return "z.array(z.any())";
294
+ }
295
+ return "z.any()";
296
+ }
297
+ async function generateSwaggerTypes(options) {
298
+ const { controllersPath, outputPath } = options;
299
+ const routes = [];
300
+ const namedSchemas = /* @__PURE__ */ new Map();
301
+ const glob = new Bun.Glob("**/*.ts");
302
+ const files = [];
303
+ for await (const file of glob.scan({ cwd: controllersPath, absolute: true })) {
304
+ const name = path2.basename(file);
305
+ if (name !== "index.ts" && name !== "registry.ts") {
306
+ files.push(file);
307
+ }
308
+ }
309
+ await Promise.all(files.map(async (filePath) => {
310
+ const file = Bun.file(filePath);
311
+ const content = await file.text();
312
+ const typeDefinitions = extractTypeDefinitions(content);
313
+ for (const [typeName, typeDef] of typeDefinitions) {
314
+ if (!namedSchemas.has(typeName)) {
315
+ const zodSchema = typeToZodSchemaWithRefs(typeDef, typeDefinitions, namedSchemas);
316
+ namedSchemas.set(typeName, zodSchema);
317
+ }
318
+ }
319
+ const fileRoutes = extractRoutes(content, typeDefinitions, namedSchemas);
320
+ routes.push(...fileRoutes);
321
+ }));
322
+ await writeOutput(outputPath, routes, namedSchemas);
323
+ }
324
+ function sortSchemasByDependency(schemas) {
325
+ const sorted = [];
326
+ const visited = /* @__PURE__ */ new Set();
327
+ const visiting = /* @__PURE__ */ new Set();
328
+ function getDependencies(schema) {
329
+ const deps = [];
330
+ const refMatches = schema.matchAll(/(?:namedSchemas\.|_)([A-Z]\w*)/g);
331
+ for (const match of refMatches) {
332
+ if (schemas.has(match[1])) {
333
+ deps.push(match[1]);
334
+ }
335
+ }
336
+ return deps;
337
+ }
338
+ function visit(name) {
339
+ if (visited.has(name)) return;
340
+ if (visiting.has(name)) return;
341
+ visiting.add(name);
342
+ const schema = schemas.get(name);
343
+ if (schema) {
344
+ for (const dep of getDependencies(schema)) {
345
+ visit(dep);
346
+ }
347
+ }
348
+ visiting.delete(name);
349
+ visited.add(name);
350
+ sorted.push(name);
351
+ }
352
+ for (const name of schemas.keys()) {
353
+ visit(name);
354
+ }
355
+ return sorted;
356
+ }
357
+ async function writeOutput(outputPath, routes, namedSchemas) {
358
+ const namedSchemasEntries = Array.from(namedSchemas.entries());
359
+ const sortedNames = sortSchemasByDependency(namedSchemas);
360
+ const schemaDeclarations = sortedNames.map((name) => {
361
+ const schema = namedSchemas.get(name);
362
+ const fixedSchema = schema.replace(/namedSchemas\.([A-Z]\w*)/g, "_$1");
363
+ return `const _${name} = ${fixedSchema};`;
364
+ }).join("\n");
365
+ const namedSchemasOutput = namedSchemasEntries.length > 0 ? `export const namedSchemas = {
366
+ ${sortedNames.map((name) => ` ${name}: _${name},`).join("\n")}
367
+ } as const;
368
+
369
+ ` : `export const namedSchemas = {} as const;
370
+
371
+ `;
372
+ const routeSchemasOutput = routes.map((r) => {
373
+ const hasFormDataStr = r.hasFormData ? ", hasFormData: true" : "";
374
+ if (r.schemaName) {
375
+ return ` "${r.path}": { "${r.method}": { schema: _${r.schemaName}, ref: "${r.schemaName}"${hasFormDataStr} } },`;
376
+ }
377
+ const fixedReturnType = r.returnType.replace(/namedSchemas\.([A-Z]\w*)/g, "_$1");
378
+ return ` "${r.path}": { "${r.method}": { schema: ${fixedReturnType}, ref: null${hasFormDataStr} } },`;
379
+ }).join("\n");
380
+ const output = `// Auto-generated by @zeti/framework/scripts
381
+ // Do not edit manually
382
+
383
+ import { z } from "zod";
384
+
385
+ ${schemaDeclarations}
386
+
387
+ ${namedSchemasOutput}export const routeResponseSchemas: Record<string, Record<string, { schema: z.ZodTypeAny; ref: string | null; hasFormData?: boolean }>> = {
388
+ ${routeSchemasOutput}
389
+ };
390
+ `;
391
+ const file = Bun.file(outputPath);
392
+ try {
393
+ if (await file.exists()) {
394
+ const existing = await file.text();
395
+ if (existing === output) {
396
+ console.log(`\u2705 Response schemas unchanged (${routes.length} routes)`);
397
+ return;
398
+ }
399
+ }
400
+ } catch {
401
+ }
402
+ await Bun.write(outputPath, output);
403
+ console.log(`\u2705 Generated response schemas for ${routes.length} routes (${namedSchemasEntries.length} named schemas)`);
404
+ }
405
+
406
+ // src/scripts/run-prebuild.ts
407
+ import path3 from "path";
408
+ var CACHE_FILE = ".zeti/.prebuild-cache.json";
409
+ async function getFileHash(filePath) {
410
+ try {
411
+ const file = Bun.file(filePath);
412
+ const stat = await file.stat();
413
+ return `${stat.mtime}-${stat.size}`;
414
+ } catch {
415
+ return "";
416
+ }
417
+ }
418
+ async function loadCache(projectRoot) {
419
+ const cachePath = path3.join(projectRoot, CACHE_FILE);
420
+ try {
421
+ const file = Bun.file(cachePath);
422
+ if (await file.exists()) {
423
+ const data = await file.json();
424
+ return new Map(Object.entries(data));
425
+ }
426
+ } catch {
427
+ }
428
+ return /* @__PURE__ */ new Map();
429
+ }
430
+ async function saveCache(projectRoot, cache) {
431
+ const cachePath = path3.join(projectRoot, CACHE_FILE);
432
+ try {
433
+ await Bun.write(cachePath, JSON.stringify(Object.fromEntries(cache)));
434
+ } catch {
435
+ }
436
+ }
437
+ async function hasChanges(controllersPath, cache) {
438
+ const newCache = /* @__PURE__ */ new Map();
439
+ let changed = false;
440
+ const dirFile = Bun.file(controllersPath);
441
+ try {
442
+ await dirFile.stat();
443
+ } catch {
444
+ return { changed: cache.size > 0, newCache };
445
+ }
446
+ const glob = new Bun.Glob("**/*.ts");
447
+ const files = [];
448
+ for await (const file of glob.scan({ cwd: controllersPath, absolute: true })) {
449
+ const name = path3.basename(file);
450
+ if (name !== "index.ts" && name !== "registry.ts") {
451
+ files.push(file);
452
+ }
453
+ }
454
+ await Promise.all(files.map(async (file) => {
455
+ const hash = await getFileHash(file);
456
+ newCache.set(file, hash);
457
+ if (cache.get(file) !== hash) {
458
+ changed = true;
459
+ }
460
+ }));
461
+ for (const file of cache.keys()) {
462
+ if (!newCache.has(file)) {
463
+ changed = true;
464
+ break;
465
+ }
466
+ }
467
+ return { changed, newCache };
468
+ }
469
+ var APP_FILE_CONTENT = `import { createAppProxy, getRedisClient, hasRedis } from 'zeti-framework';
470
+ import type { PrismaClient } from '@prisma/client';
471
+ import type { Redis } from 'ioredis';
472
+
473
+ export const app = createAppProxy<PrismaClient, any>();
474
+
475
+ /**
476
+ * Cliente Redis singleton via Proxy.
477
+ * Dispon\xEDvel ap\xF3s configura\xE7\xE3o em startZeti({ redis: {...} }).
478
+ *
479
+ * @example
480
+ * import { app, redis } from '@zeti/app';
481
+ *
482
+ * await redis.set('key', 'value');
483
+ * const value = await redis.get('key');
484
+ */
485
+ export const redis: Redis = new Proxy({} as Redis, {
486
+ get(_, prop) {
487
+ if (!hasRedis()) {
488
+ throw new Error('[Redis] Not configured. Add redis config to startZeti()');
489
+ }
490
+ const client = getRedisClient();
491
+ const value = (client as any)[prop];
492
+ return typeof value === 'function' ? value.bind(client) : value;
493
+ }
494
+ });
495
+
496
+ export const { get, post, put, del, patch, createMiddleware } = app;
497
+ `;
498
+ var PRISMA_HELPERS_CONTENT = `/**
499
+ * Helpers tipados para o PrismaClient do projeto
500
+ *
501
+ * Gerado automaticamente pelo zeti-framework.
502
+ * Use estes helpers em middlewares e outros lugares onde
503
+ * o tipo do PrismaClient n\xE3o \xE9 inferido automaticamente.
504
+ */
505
+
506
+ import type { Context, Next } from 'hono';
507
+ import type { PrismaClient } from '@prisma/client';
508
+ import {
509
+ getPrismaClient as _getPrismaClient,
510
+ getSpecialDatabase as _getSpecialDatabase,
511
+ createMiddleware as _createMiddleware,
512
+ } from 'zeti-framework';
513
+ import type { MiddlewareHandler as _MiddlewareHandler } from 'zeti-framework';
514
+
515
+ // Re-exporta o tipo do PrismaClient
516
+ export type { PrismaClient };
517
+ export type AppPrismaClient = PrismaClient;
518
+
519
+ /**
520
+ * Handler de middleware tipado com o PrismaClient do projeto.
521
+ */
522
+ export type MiddlewareHandler = _MiddlewareHandler<PrismaClient>;
523
+
524
+ /**
525
+ * Cria um middleware com o db tipado automaticamente.
526
+ *
527
+ * @example
528
+ * import { createMiddleware } from '.zeti/prisma';
529
+ *
530
+ * export const authMiddleware = createMiddleware(async (c, next, db) => {
531
+ * // db j\xE1 vem tipado com os models do schema.prisma
532
+ * const user = await db.user.findUnique({
533
+ * where: { id: c.req.header('x-user-id') }
534
+ * });
535
+ *
536
+ * if (!user) {
537
+ * return c.json({ error: 'Unauthorized' }, 401);
538
+ * }
539
+ *
540
+ * c.set('user', user);
541
+ * await next();
542
+ * });
543
+ */
544
+ export function createMiddleware(
545
+ handler: MiddlewareHandler
546
+ ): (c: Context, next: Next) => Promise<void | Response> {
547
+ return _createMiddleware<PrismaClient>(handler);
548
+ }
549
+
550
+ /**
551
+ * Cria uma factory de middleware com db tipado.
552
+ *
553
+ * @example
554
+ * import { createMiddlewareFactory } from '.zeti/prisma';
555
+ *
556
+ * const requirePermission = createMiddlewareFactory(
557
+ * (permissions: string[]) => async (c, next, db) => {
558
+ * const user = c.get('user');
559
+ * const userPerms = await db.permission.findMany({
560
+ * where: { userId: user.id }
561
+ * });
562
+ *
563
+ * if (!permissions.every(p => userPerms.some(up => up.name === p))) {
564
+ * return c.json({ error: 'Forbidden' }, 403);
565
+ * }
566
+ *
567
+ * await next();
568
+ * }
569
+ * );
570
+ *
571
+ * // Uso:
572
+ * app.use('/admin/*', requirePermission(['admin:read']));
573
+ */
574
+ export function createMiddlewareFactory<T extends any[]>(
575
+ factory: (...args: T) => MiddlewareHandler
576
+ ): (...args: T) => (c: Context, next: Next) => Promise<void | Response> {
577
+ return (...args: T) => _createMiddleware<PrismaClient>(factory(...args));
578
+ }
579
+
580
+ /**
581
+ * Retorna o PrismaClient com tipagem correta do projeto
582
+ *
583
+ * @param tenantId - ID do tenant (opcional, usa o padr\xE3o se n\xE3o fornecido)
584
+ * @returns PrismaClient tipado com os models do schema.prisma
585
+ *
586
+ * @example
587
+ * const db = getPrismaClient();
588
+ * const users = await db.user.findMany(); // \u2705 Tipado corretamente
589
+ */
590
+ export function getPrismaClient(tenantId?: string): PrismaClient {
591
+ return _getPrismaClient<PrismaClient>(tenantId);
592
+ }
593
+
594
+ /**
595
+ * Retorna um database especial com tipagem correta
596
+ *
597
+ * @param key - Chave do database especial configurado em specialDatabases
598
+ * @returns PrismaClient tipado
599
+ *
600
+ * @example
601
+ * const adminDb = getSpecialDatabase('admin');
602
+ * const logs = await adminDb.auditLog.findMany();
603
+ */
604
+ export function getSpecialDatabase(key: string): PrismaClient {
605
+ return _getSpecialDatabase<PrismaClient>(key);
606
+ }
607
+ `;
608
+ async function ensureAppFile(zetiDir, prismaImport) {
609
+ const appPath = path3.join(zetiDir, "app.ts");
610
+ const expectedContent = APP_FILE_CONTENT.replace("@prisma/client", prismaImport);
611
+ try {
612
+ const file = Bun.file(appPath);
613
+ if (await file.exists()) {
614
+ const currentContent = await file.text();
615
+ if (currentContent === expectedContent) {
616
+ return false;
617
+ }
618
+ }
619
+ } catch {
620
+ }
621
+ await Bun.write(appPath, expectedContent);
622
+ return true;
623
+ }
624
+ async function ensurePrismaHelpersFile(zetiDir, prismaImport) {
625
+ const helpersPath = path3.join(zetiDir, "prisma.ts");
626
+ const expectedContent = PRISMA_HELPERS_CONTENT.replace(/@prisma\/client/g, prismaImport);
627
+ try {
628
+ const file = Bun.file(helpersPath);
629
+ if (await file.exists()) {
630
+ const currentContent = await file.text();
631
+ if (currentContent === expectedContent) {
632
+ return false;
633
+ }
634
+ }
635
+ } catch {
636
+ }
637
+ await Bun.write(helpersPath, expectedContent);
638
+ return true;
639
+ }
640
+ async function runPrebuild(options = {}) {
641
+ const startTime = Bun.nanoseconds();
642
+ const projectRoot = path3.resolve(options.projectRoot || process.cwd());
643
+ const zetiConfig = options.zetiConfig;
644
+ const controllersPath = path3.join(
645
+ projectRoot,
646
+ options.controllersPath || "src/controllers"
647
+ );
648
+ const zetiDir = path3.join(projectRoot, ".zeti");
649
+ const registryPath = path3.join(zetiDir, "registry.ts");
650
+ const swaggerOutputPath = options.swaggerOutputPath || (zetiConfig?.swagger?.outputPath ? path3.isAbsolute(zetiConfig.swagger.outputPath) ? zetiConfig.swagger.outputPath : path3.join(projectRoot, zetiConfig.swagger.outputPath) : path3.join(zetiDir, "generated/swagger-schemas.ts"));
651
+ const fs = await import("fs");
652
+ if (!fs.existsSync(zetiDir)) {
653
+ fs.mkdirSync(zetiDir, { recursive: true });
654
+ console.log("\u{1F4C1} [zeti] Created .zeti directory");
655
+ }
656
+ const prismaImport = options.prismaImport || "@prisma/client";
657
+ const [appChanged, prismaHelpersChanged] = await Promise.all([
658
+ ensureAppFile(zetiDir, prismaImport),
659
+ ensurePrismaHelpersFile(zetiDir, prismaImport)
660
+ ]);
661
+ const cache = await loadCache(projectRoot);
662
+ const { changed, newCache } = await hasChanges(controllersPath, cache);
663
+ if (!options.force && !changed && !appChanged && !prismaHelpersChanged) {
664
+ const elapsed2 = ((Bun.nanoseconds() - startTime) / 1e6).toFixed(0);
665
+ console.log(`\u26A1 [zeti] No changes detected (${elapsed2}ms)`);
666
+ return;
667
+ }
668
+ console.log("\u{1F504} [zeti] Generating files...");
669
+ await Promise.all([
670
+ generateRegistry({
671
+ controllersPath,
672
+ indexPath: registryPath,
673
+ outputMode: "standalone"
674
+ }),
675
+ generateSwaggerTypes({
676
+ controllersPath,
677
+ outputPath: swaggerOutputPath
678
+ })
679
+ ]);
680
+ await saveCache(projectRoot, newCache);
681
+ const elapsed = ((Bun.nanoseconds() - startTime) / 1e6).toFixed(0);
682
+ console.log(`\u2705 [zeti] Prebuild complete! (${elapsed}ms)`);
683
+ }
684
+
685
+ export {
686
+ generateRegistry,
687
+ generateSwaggerTypes,
688
+ runPrebuild
689
+ };
690
+ //# sourceMappingURL=chunk-KIOZSPU2.js.map