@aruvili/api 0.1.0

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 (158) hide show
  1. package/dist/config.d.ts +22 -0
  2. package/dist/config.d.ts.map +1 -0
  3. package/dist/config.js +34 -0
  4. package/dist/config.js.map +1 -0
  5. package/dist/context.d.ts +7 -0
  6. package/dist/context.d.ts.map +1 -0
  7. package/dist/context.js +3 -0
  8. package/dist/context.js.map +1 -0
  9. package/dist/controllers/index.d.ts +39 -0
  10. package/dist/controllers/index.d.ts.map +1 -0
  11. package/dist/controllers/index.js +39 -0
  12. package/dist/controllers/index.js.map +1 -0
  13. package/dist/db.d.ts +6 -0
  14. package/dist/db.d.ts.map +1 -0
  15. package/dist/db.js +74 -0
  16. package/dist/db.js.map +1 -0
  17. package/dist/index.d.ts +17 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +154 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/middleware/auth.d.ts +15 -0
  22. package/dist/middleware/auth.d.ts.map +1 -0
  23. package/dist/middleware/auth.js +93 -0
  24. package/dist/middleware/auth.js.map +1 -0
  25. package/dist/middleware/body-limit.d.ts +9 -0
  26. package/dist/middleware/body-limit.d.ts.map +1 -0
  27. package/dist/middleware/body-limit.js +15 -0
  28. package/dist/middleware/body-limit.js.map +1 -0
  29. package/dist/middleware/rate-limit.d.ts +6 -0
  30. package/dist/middleware/rate-limit.d.ts.map +1 -0
  31. package/dist/middleware/rate-limit.js +40 -0
  32. package/dist/middleware/rate-limit.js.map +1 -0
  33. package/dist/middleware/rbac.d.ts +10 -0
  34. package/dist/middleware/rbac.d.ts.map +1 -0
  35. package/dist/middleware/rbac.js +61 -0
  36. package/dist/middleware/rbac.js.map +1 -0
  37. package/dist/middleware/tenant.d.ts +3 -0
  38. package/dist/middleware/tenant.d.ts.map +1 -0
  39. package/dist/middleware/tenant.js +19 -0
  40. package/dist/middleware/tenant.js.map +1 -0
  41. package/dist/registry.d.ts +26 -0
  42. package/dist/registry.d.ts.map +1 -0
  43. package/dist/registry.js +112 -0
  44. package/dist/registry.js.map +1 -0
  45. package/dist/routes/auth.d.ts +3 -0
  46. package/dist/routes/auth.d.ts.map +1 -0
  47. package/dist/routes/auth.js +141 -0
  48. package/dist/routes/auth.js.map +1 -0
  49. package/dist/routes/crud.d.ts +7 -0
  50. package/dist/routes/crud.d.ts.map +1 -0
  51. package/dist/routes/crud.js +845 -0
  52. package/dist/routes/crud.js.map +1 -0
  53. package/dist/routes/files.d.ts +7 -0
  54. package/dist/routes/files.d.ts.map +1 -0
  55. package/dist/routes/files.js +123 -0
  56. package/dist/routes/files.js.map +1 -0
  57. package/dist/routes/meta.d.ts +3 -0
  58. package/dist/routes/meta.d.ts.map +1 -0
  59. package/dist/routes/meta.js +352 -0
  60. package/dist/routes/meta.js.map +1 -0
  61. package/dist/scheduler.d.ts +33 -0
  62. package/dist/scheduler.d.ts.map +1 -0
  63. package/dist/scheduler.js +97 -0
  64. package/dist/scheduler.js.map +1 -0
  65. package/dist/utils/link-validator.d.ts +7 -0
  66. package/dist/utils/link-validator.d.ts.map +1 -0
  67. package/dist/utils/link-validator.js +33 -0
  68. package/dist/utils/link-validator.js.map +1 -0
  69. package/dist/utils/resolver.d.ts +5 -0
  70. package/dist/utils/resolver.d.ts.map +1 -0
  71. package/dist/utils/resolver.js +58 -0
  72. package/dist/utils/resolver.js.map +1 -0
  73. package/package.json +24 -0
  74. package/src/api.test.ts +362 -0
  75. package/src/config.d.ts +22 -0
  76. package/src/config.d.ts.map +1 -0
  77. package/src/config.js +34 -0
  78. package/src/config.js.map +1 -0
  79. package/src/config.ts +38 -0
  80. package/src/context.d.ts +7 -0
  81. package/src/context.d.ts.map +1 -0
  82. package/src/context.js +3 -0
  83. package/src/context.js.map +1 -0
  84. package/src/context.ts +8 -0
  85. package/src/controllers/index.d.ts +39 -0
  86. package/src/controllers/index.d.ts.map +1 -0
  87. package/src/controllers/index.js +39 -0
  88. package/src/controllers/index.js.map +1 -0
  89. package/src/controllers/index.ts +51 -0
  90. package/src/db.d.ts +6 -0
  91. package/src/db.d.ts.map +1 -0
  92. package/src/db.js +74 -0
  93. package/src/db.js.map +1 -0
  94. package/src/db.ts +73 -0
  95. package/src/index.ts +178 -0
  96. package/src/integration.test.ts +453 -0
  97. package/src/middleware/auth.d.ts +15 -0
  98. package/src/middleware/auth.d.ts.map +1 -0
  99. package/src/middleware/auth.js +93 -0
  100. package/src/middleware/auth.js.map +1 -0
  101. package/src/middleware/auth.ts +109 -0
  102. package/src/middleware/body-limit.d.ts +9 -0
  103. package/src/middleware/body-limit.d.ts.map +1 -0
  104. package/src/middleware/body-limit.js +15 -0
  105. package/src/middleware/body-limit.js.map +1 -0
  106. package/src/middleware/body-limit.ts +16 -0
  107. package/src/middleware/rate-limit.d.ts +6 -0
  108. package/src/middleware/rate-limit.d.ts.map +1 -0
  109. package/src/middleware/rate-limit.js +40 -0
  110. package/src/middleware/rate-limit.js.map +1 -0
  111. package/src/middleware/rate-limit.ts +47 -0
  112. package/src/middleware/rbac.d.ts +10 -0
  113. package/src/middleware/rbac.d.ts.map +1 -0
  114. package/src/middleware/rbac.js +61 -0
  115. package/src/middleware/rbac.js.map +1 -0
  116. package/src/middleware/rbac.ts +71 -0
  117. package/src/middleware/tenant.d.ts +3 -0
  118. package/src/middleware/tenant.d.ts.map +1 -0
  119. package/src/middleware/tenant.js +19 -0
  120. package/src/middleware/tenant.js.map +1 -0
  121. package/src/middleware/tenant.ts +24 -0
  122. package/src/registry.d.ts +26 -0
  123. package/src/registry.d.ts.map +1 -0
  124. package/src/registry.js +112 -0
  125. package/src/registry.js.map +1 -0
  126. package/src/registry.ts +123 -0
  127. package/src/routes/auth.d.ts +3 -0
  128. package/src/routes/auth.d.ts.map +1 -0
  129. package/src/routes/auth.js +141 -0
  130. package/src/routes/auth.js.map +1 -0
  131. package/src/routes/auth.ts +164 -0
  132. package/src/routes/crud.d.ts +7 -0
  133. package/src/routes/crud.d.ts.map +1 -0
  134. package/src/routes/crud.js +845 -0
  135. package/src/routes/crud.js.map +1 -0
  136. package/src/routes/crud.ts +1029 -0
  137. package/src/routes/files.d.ts +7 -0
  138. package/src/routes/files.d.ts.map +1 -0
  139. package/src/routes/files.js +123 -0
  140. package/src/routes/files.js.map +1 -0
  141. package/src/routes/files.ts +143 -0
  142. package/src/routes/meta.d.ts +3 -0
  143. package/src/routes/meta.d.ts.map +1 -0
  144. package/src/routes/meta.js +352 -0
  145. package/src/routes/meta.js.map +1 -0
  146. package/src/routes/meta.ts +448 -0
  147. package/src/scheduler.ts +118 -0
  148. package/src/utils/link-validator.d.ts +7 -0
  149. package/src/utils/link-validator.d.ts.map +1 -0
  150. package/src/utils/link-validator.js +33 -0
  151. package/src/utils/link-validator.js.map +1 -0
  152. package/src/utils/link-validator.ts +45 -0
  153. package/src/utils/resolver.d.ts +5 -0
  154. package/src/utils/resolver.d.ts.map +1 -0
  155. package/src/utils/resolver.js +58 -0
  156. package/src/utils/resolver.js.map +1 -0
  157. package/src/utils/resolver.ts +65 -0
  158. package/tsconfig.json +9 -0
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Meta Framework - Centralized Configuration Module
3
+ * Validates all required environment variables at startup.
4
+ */
5
+ export declare const config: {
6
+ port: number;
7
+ nodeEnv: string;
8
+ isProduction: boolean;
9
+ databaseUrl: string;
10
+ dbPoolMax: number;
11
+ dbIdleTimeout: number;
12
+ dbConnectTimeout: number;
13
+ dbStatementTimeout: string;
14
+ jwtSecret: string;
15
+ jwtExpiresIn: number;
16
+ bcryptRounds: number;
17
+ corsOrigins: string[];
18
+ rateLimitMax: number;
19
+ rateLimitWindowMs: number;
20
+ bodyLimitBytes: number;
21
+ };
22
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;CAuBlB,CAAC"}
package/src/config.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Meta Framework - Centralized Configuration Module
3
+ * Validates all required environment variables at startup.
4
+ */
5
+ function requireEnv(key, fallback) {
6
+ const value = process.env[key] || fallback;
7
+ if (!value && process.env.NODE_ENV === 'production') {
8
+ console.error(`[CONFIG FATAL] Required environment variable ${key} is not set.`);
9
+ process.exit(1);
10
+ }
11
+ return value || '';
12
+ }
13
+ export const config = {
14
+ // Server
15
+ port: parseInt(process.env.PORT || '3000'),
16
+ nodeEnv: process.env.NODE_ENV || 'development',
17
+ isProduction: process.env.NODE_ENV === 'production',
18
+ // Database
19
+ databaseUrl: requireEnv('DATABASE_URL', 'postgresql://postgres:postgres@localhost:5432/meta_db'),
20
+ dbPoolMax: parseInt(process.env.DB_POOL_MAX || '20'),
21
+ dbIdleTimeout: parseInt(process.env.DB_IDLE_TIMEOUT || '30000'),
22
+ dbConnectTimeout: parseInt(process.env.DB_CONNECT_TIMEOUT || '5000'),
23
+ dbStatementTimeout: process.env.DB_STATEMENT_TIMEOUT || '30000',
24
+ // Auth
25
+ jwtSecret: requireEnv('JWT_SECRET', 'dev-secret-change-in-production'),
26
+ jwtExpiresIn: parseInt(process.env.JWT_EXPIRES_IN || '86400'), // 24 hours
27
+ bcryptRounds: parseInt(process.env.BCRYPT_ROUNDS || '12'),
28
+ // Security
29
+ corsOrigins: (process.env.CORS_ORIGINS || '*').split(',').map(s => s.trim()),
30
+ rateLimitMax: parseInt(process.env.RATE_LIMIT_MAX || '100'),
31
+ rateLimitWindowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS || '60000'),
32
+ bodyLimitBytes: parseInt(process.env.BODY_LIMIT_BYTES || '1048576'), // 1MB
33
+ };
34
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,SAAS,UAAU,CAAC,GAAW,EAAE,QAAiB;IAChD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC;IAC3C,IAAI,CAAC,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;QACpD,OAAO,CAAC,KAAK,CAAC,gDAAgD,GAAG,cAAc,CAAC,CAAC;QACjF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,KAAK,IAAI,EAAE,CAAC;AACrB,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,SAAS;IACT,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,MAAM,CAAC;IAC1C,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,aAAa;IAC9C,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY;IAEnD,WAAW;IACX,WAAW,EAAE,UAAU,CAAC,cAAc,EAAE,uDAAuD,CAAC;IAChG,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,IAAI,CAAC;IACpD,aAAa,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,OAAO,CAAC;IAC/D,gBAAgB,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,MAAM,CAAC;IACpE,kBAAkB,EAAE,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,OAAO;IAE/D,OAAO;IACP,SAAS,EAAE,UAAU,CAAC,YAAY,EAAE,iCAAiC,CAAC;IACtE,YAAY,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,OAAO,CAAC,EAAE,WAAW;IAC1E,YAAY,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,IAAI,CAAC;IAEzD,WAAW;IACX,WAAW,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5E,YAAY,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,KAAK,CAAC;IAC3D,iBAAiB,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,OAAO,CAAC;IACxE,cAAc,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,SAAS,CAAC,EAAE,MAAM;CAC5E,CAAC"}
package/src/config.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Meta Framework - Centralized Configuration Module
3
+ * Validates all required environment variables at startup.
4
+ */
5
+
6
+ function requireEnv(key: string, fallback?: string): string {
7
+ const value = process.env[key] || fallback;
8
+ if (!value && process.env.NODE_ENV === 'production') {
9
+ console.error(`[CONFIG FATAL] Required environment variable ${key} is not set.`);
10
+ process.exit(1);
11
+ }
12
+ return value || '';
13
+ }
14
+
15
+ export const config = {
16
+ // Server
17
+ port: parseInt(process.env.PORT || '3000'),
18
+ nodeEnv: process.env.NODE_ENV || 'development',
19
+ isProduction: process.env.NODE_ENV === 'production',
20
+
21
+ // Database
22
+ databaseUrl: requireEnv('DATABASE_URL', 'postgresql://postgres:postgres@localhost:5432/meta_db'),
23
+ dbPoolMax: parseInt(process.env.DB_POOL_MAX || '20'),
24
+ dbIdleTimeout: parseInt(process.env.DB_IDLE_TIMEOUT || '30000'),
25
+ dbConnectTimeout: parseInt(process.env.DB_CONNECT_TIMEOUT || '5000'),
26
+ dbStatementTimeout: process.env.DB_STATEMENT_TIMEOUT || '30000',
27
+
28
+ // Auth
29
+ jwtSecret: requireEnv('JWT_SECRET', 'dev-secret-change-in-production'),
30
+ jwtExpiresIn: parseInt(process.env.JWT_EXPIRES_IN || '86400'), // 24 hours
31
+ bcryptRounds: parseInt(process.env.BCRYPT_ROUNDS || '12'),
32
+
33
+ // Security
34
+ corsOrigins: (process.env.CORS_ORIGINS || '*').split(',').map(s => s.trim()),
35
+ rateLimitMax: parseInt(process.env.RATE_LIMIT_MAX || '100'),
36
+ rateLimitWindowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS || '60000'),
37
+ bodyLimitBytes: parseInt(process.env.BODY_LIMIT_BYTES || '1048576'), // 1MB
38
+ };
@@ -0,0 +1,7 @@
1
+ import { AsyncLocalStorage } from 'async_hooks';
2
+ export interface RequestContextStore {
3
+ tenantId?: string;
4
+ user?: any;
5
+ }
6
+ export declare const requestContext: AsyncLocalStorage<RequestContextStore>;
7
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ;AAED,eAAO,MAAM,cAAc,wCAA+C,CAAC"}
package/src/context.js ADDED
@@ -0,0 +1,3 @@
1
+ import { AsyncLocalStorage } from 'async_hooks';
2
+ export const requestContext = new AsyncLocalStorage();
3
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAOhD,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,iBAAiB,EAAuB,CAAC"}
package/src/context.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { AsyncLocalStorage } from 'async_hooks';
2
+
3
+ export interface RequestContextStore {
4
+ tenantId?: string;
5
+ user?: any;
6
+ }
7
+
8
+ export const requestContext = new AsyncLocalStorage<RequestContextStore>();
@@ -0,0 +1,39 @@
1
+ import pg from 'pg';
2
+ export interface ControllerContext {
3
+ db: pg.PoolClient;
4
+ user: {
5
+ email: string;
6
+ roles: string[];
7
+ };
8
+ }
9
+ export declare abstract class DocTypeController {
10
+ before_insert(doc: any, ctx: ControllerContext): Promise<void>;
11
+ after_insert(doc: any, ctx: ControllerContext): Promise<void>;
12
+ before_save(doc: any, ctx: ControllerContext): Promise<void>;
13
+ on_update(doc: any, oldDoc: any, ctx: ControllerContext): Promise<void>;
14
+ before_delete(doc: any, ctx: ControllerContext): Promise<void>;
15
+ on_trash(doc: any, ctx: ControllerContext): Promise<void>;
16
+ before_submit(doc: any, ctx: ControllerContext): Promise<void>;
17
+ on_submit(doc: any, ctx: ControllerContext): Promise<void>;
18
+ before_cancel(doc: any, ctx: ControllerContext): Promise<void>;
19
+ on_cancel(doc: any, ctx: ControllerContext): Promise<void>;
20
+ }
21
+ declare class ControllerRegistry {
22
+ private controllers;
23
+ register(doctypeName: string, controller: DocTypeController): void;
24
+ get(doctypeName: string): DocTypeController | undefined;
25
+ private trigger;
26
+ triggerBeforeInsert(n: string, doc: any, ctx: ControllerContext): Promise<void>;
27
+ triggerAfterInsert(n: string, doc: any, ctx: ControllerContext): Promise<void>;
28
+ triggerBeforeSave(n: string, doc: any, ctx: ControllerContext): Promise<void>;
29
+ triggerOnUpdate(n: string, doc: any, old: any, ctx: ControllerContext): Promise<void>;
30
+ triggerBeforeDelete(n: string, doc: any, ctx: ControllerContext): Promise<void>;
31
+ triggerOnTrash(n: string, doc: any, ctx: ControllerContext): Promise<void>;
32
+ triggerBeforeSubmit(n: string, doc: any, ctx: ControllerContext): Promise<void>;
33
+ triggerOnSubmit(n: string, doc: any, ctx: ControllerContext): Promise<void>;
34
+ triggerBeforeCancel(n: string, doc: any, ctx: ControllerContext): Promise<void>;
35
+ triggerOnCancel(n: string, doc: any, ctx: ControllerContext): Promise<void>;
36
+ }
37
+ export declare const controllerRegistry: ControllerRegistry;
38
+ export {};
39
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC;IAClB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CAC1C;AAED,8BAAsB,iBAAiB;IAC/B,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAC9D,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAC7D,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAC5D,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IACvE,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAC9D,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IACzD,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAC9D,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAC1D,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAC9D,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;CACjE;AAED,cAAM,kBAAkB;IACtB,OAAO,CAAC,WAAW,CAAwC;IAEpD,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,iBAAiB;IAI3D,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;YAIhD,OAAO;IAOf,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB;IAC/D,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB;IAC9D,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB;IAC7D,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB;IACrE,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB;IAC/D,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB;IAC1D,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB;IAC/D,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB;IAC3D,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB;IAC/D,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,iBAAiB;CAClE;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC"}
@@ -0,0 +1,39 @@
1
+ export class DocTypeController {
2
+ async before_insert(doc, ctx) { }
3
+ async after_insert(doc, ctx) { }
4
+ async before_save(doc, ctx) { }
5
+ async on_update(doc, oldDoc, ctx) { }
6
+ async before_delete(doc, ctx) { }
7
+ async on_trash(doc, ctx) { }
8
+ async before_submit(doc, ctx) { }
9
+ async on_submit(doc, ctx) { }
10
+ async before_cancel(doc, ctx) { }
11
+ async on_cancel(doc, ctx) { }
12
+ }
13
+ class ControllerRegistry {
14
+ controllers = new Map();
15
+ register(doctypeName, controller) {
16
+ this.controllers.set(doctypeName, controller);
17
+ }
18
+ get(doctypeName) {
19
+ return this.controllers.get(doctypeName);
20
+ }
21
+ async trigger(name, method, ...args) {
22
+ const ctrl = this.get(name);
23
+ if (ctrl && typeof ctrl[method] === 'function') {
24
+ await ctrl[method](...args);
25
+ }
26
+ }
27
+ async triggerBeforeInsert(n, doc, ctx) { await this.trigger(n, 'before_insert', doc, ctx); }
28
+ async triggerAfterInsert(n, doc, ctx) { await this.trigger(n, 'after_insert', doc, ctx); }
29
+ async triggerBeforeSave(n, doc, ctx) { await this.trigger(n, 'before_save', doc, ctx); }
30
+ async triggerOnUpdate(n, doc, old, ctx) { await this.trigger(n, 'on_update', doc, old, ctx); }
31
+ async triggerBeforeDelete(n, doc, ctx) { await this.trigger(n, 'before_delete', doc, ctx); }
32
+ async triggerOnTrash(n, doc, ctx) { await this.trigger(n, 'on_trash', doc, ctx); }
33
+ async triggerBeforeSubmit(n, doc, ctx) { await this.trigger(n, 'before_submit', doc, ctx); }
34
+ async triggerOnSubmit(n, doc, ctx) { await this.trigger(n, 'on_submit', doc, ctx); }
35
+ async triggerBeforeCancel(n, doc, ctx) { await this.trigger(n, 'before_cancel', doc, ctx); }
36
+ async triggerOnCancel(n, doc, ctx) { await this.trigger(n, 'on_cancel', doc, ctx); }
37
+ }
38
+ export const controllerRegistry = new ControllerRegistry();
39
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import pg from 'pg';
2
+
3
+ export interface ControllerContext {
4
+ db: pg.PoolClient;
5
+ user: { email: string; roles: string[] };
6
+ }
7
+
8
+ export abstract class DocTypeController {
9
+ async before_insert(doc: any, ctx: ControllerContext): Promise<void> {}
10
+ async after_insert(doc: any, ctx: ControllerContext): Promise<void> {}
11
+ async before_save(doc: any, ctx: ControllerContext): Promise<void> {}
12
+ async on_update(doc: any, oldDoc: any, ctx: ControllerContext): Promise<void> {}
13
+ async before_delete(doc: any, ctx: ControllerContext): Promise<void> {}
14
+ async on_trash(doc: any, ctx: ControllerContext): Promise<void> {}
15
+ async before_submit(doc: any, ctx: ControllerContext): Promise<void> {}
16
+ async on_submit(doc: any, ctx: ControllerContext): Promise<void> {}
17
+ async before_cancel(doc: any, ctx: ControllerContext): Promise<void> {}
18
+ async on_cancel(doc: any, ctx: ControllerContext): Promise<void> {}
19
+ }
20
+
21
+ class ControllerRegistry {
22
+ private controllers = new Map<string, DocTypeController>();
23
+
24
+ public register(doctypeName: string, controller: DocTypeController) {
25
+ this.controllers.set(doctypeName, controller);
26
+ }
27
+
28
+ public get(doctypeName: string): DocTypeController | undefined {
29
+ return this.controllers.get(doctypeName);
30
+ }
31
+
32
+ private async trigger(name: string, method: string, ...args: any[]) {
33
+ const ctrl = this.get(name);
34
+ if (ctrl && typeof (ctrl as any)[method] === 'function') {
35
+ await (ctrl as any)[method](...args);
36
+ }
37
+ }
38
+
39
+ async triggerBeforeInsert(n: string, doc: any, ctx: ControllerContext) { await this.trigger(n, 'before_insert', doc, ctx); }
40
+ async triggerAfterInsert(n: string, doc: any, ctx: ControllerContext) { await this.trigger(n, 'after_insert', doc, ctx); }
41
+ async triggerBeforeSave(n: string, doc: any, ctx: ControllerContext) { await this.trigger(n, 'before_save', doc, ctx); }
42
+ async triggerOnUpdate(n: string, doc: any, old: any, ctx: ControllerContext) { await this.trigger(n, 'on_update', doc, old, ctx); }
43
+ async triggerBeforeDelete(n: string, doc: any, ctx: ControllerContext) { await this.trigger(n, 'before_delete', doc, ctx); }
44
+ async triggerOnTrash(n: string, doc: any, ctx: ControllerContext) { await this.trigger(n, 'on_trash', doc, ctx); }
45
+ async triggerBeforeSubmit(n: string, doc: any, ctx: ControllerContext) { await this.trigger(n, 'before_submit', doc, ctx); }
46
+ async triggerOnSubmit(n: string, doc: any, ctx: ControllerContext) { await this.trigger(n, 'on_submit', doc, ctx); }
47
+ async triggerBeforeCancel(n: string, doc: any, ctx: ControllerContext) { await this.trigger(n, 'before_cancel', doc, ctx); }
48
+ async triggerOnCancel(n: string, doc: any, ctx: ControllerContext) { await this.trigger(n, 'on_cancel', doc, ctx); }
49
+ }
50
+
51
+ export const controllerRegistry = new ControllerRegistry();
package/src/db.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import pg from 'pg';
2
+ export declare const pool: import("pg").Pool;
3
+ export declare function query(text: string, params?: any[]): Promise<any>;
4
+ export declare function withTransaction<T>(callback: (client: pg.PoolClient) => Promise<T>): Promise<T>;
5
+ export declare function closePool(): Promise<void>;
6
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAIpB,eAAO,MAAM,IAAI,mBAMf,CAAC;AAMH,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,gBA2BvD;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAuBpG;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAE/C"}
package/src/db.js ADDED
@@ -0,0 +1,74 @@
1
+ import pg from 'pg';
2
+ import { config } from './config.js';
3
+ import { requestContext } from './context.js';
4
+ export const pool = new pg.Pool({
5
+ connectionString: config.databaseUrl,
6
+ max: config.dbPoolMax,
7
+ idleTimeoutMillis: config.dbIdleTimeout,
8
+ connectionTimeoutMillis: config.dbConnectTimeout,
9
+ allowExitOnIdle: false
10
+ });
11
+ pool.on('error', (err) => {
12
+ console.error('[DB POOL ERROR] Unexpected error on idle client:', err);
13
+ });
14
+ export async function query(text, params) {
15
+ if (globalThis.dbMock) {
16
+ return globalThis.dbMock.query(text, params);
17
+ }
18
+ const start = Date.now();
19
+ const client = await pool.connect();
20
+ try {
21
+ const store = requestContext.getStore();
22
+ const tenantId = store?.tenantId || 'public';
23
+ await client.query(`SET search_path TO ${tenantId}, public`);
24
+ const result = await client.query(text, params);
25
+ const duration = Date.now() - start;
26
+ if (duration > 1000) {
27
+ console.warn(`[DB SLOW QUERY] ${duration}ms: ${text.substring(0, 120)}...`);
28
+ }
29
+ return result;
30
+ }
31
+ catch (error) {
32
+ const duration = Date.now() - start;
33
+ console.error(`[DB QUERY ERROR] ${duration}ms | ${text.substring(0, 120)} | ${error.message}`);
34
+ throw error;
35
+ }
36
+ finally {
37
+ try {
38
+ await client.query('RESET search_path');
39
+ }
40
+ catch (_) { }
41
+ client.release();
42
+ }
43
+ }
44
+ export async function withTransaction(callback) {
45
+ if (globalThis.dbMock) {
46
+ return globalThis.dbMock.withTransaction(callback);
47
+ }
48
+ const client = await pool.connect();
49
+ try {
50
+ const store = requestContext.getStore();
51
+ const tenantId = store?.tenantId || 'public';
52
+ await client.query('BEGIN');
53
+ await client.query(`SET search_path TO ${tenantId}, public`);
54
+ await client.query(`SET LOCAL statement_timeout = '${config.dbStatementTimeout}'`);
55
+ const result = await callback(client);
56
+ await client.query('COMMIT');
57
+ return result;
58
+ }
59
+ catch (error) {
60
+ await client.query('ROLLBACK');
61
+ throw error;
62
+ }
63
+ finally {
64
+ try {
65
+ await client.query('RESET search_path');
66
+ }
67
+ catch (_) { }
68
+ client.release();
69
+ }
70
+ }
71
+ export async function closePool() {
72
+ await pool.end();
73
+ }
74
+ //# sourceMappingURL=db.js.map
package/src/db.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["db.ts"],"names":[],"mappings":"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"}
package/src/db.ts ADDED
@@ -0,0 +1,73 @@
1
+ import pg from 'pg';
2
+ import { config } from './config.js';
3
+ import { requestContext } from './context.js';
4
+
5
+ export const pool = new pg.Pool({
6
+ connectionString: config.databaseUrl,
7
+ max: config.dbPoolMax,
8
+ idleTimeoutMillis: config.dbIdleTimeout,
9
+ connectionTimeoutMillis: config.dbConnectTimeout,
10
+ allowExitOnIdle: false
11
+ });
12
+
13
+ pool.on('error', (err) => {
14
+ console.error('[DB POOL ERROR] Unexpected error on idle client:', err);
15
+ });
16
+
17
+ export async function query(text: string, params?: any[]) {
18
+ if ((globalThis as any).dbMock) {
19
+ return (globalThis as any).dbMock.query(text, params);
20
+ }
21
+ const start = Date.now();
22
+ const client = await pool.connect();
23
+ try {
24
+ const store = requestContext.getStore();
25
+ const tenantId = store?.tenantId || 'public';
26
+ await client.query(`SET search_path TO ${tenantId}, public`);
27
+
28
+ const result = await client.query(text, params);
29
+ const duration = Date.now() - start;
30
+ if (duration > 1000) {
31
+ console.warn(`[DB SLOW QUERY] ${duration}ms: ${text.substring(0, 120)}...`);
32
+ }
33
+ return result;
34
+ } catch (error: any) {
35
+ const duration = Date.now() - start;
36
+ console.error(`[DB QUERY ERROR] ${duration}ms | ${text.substring(0, 120)} | ${error.message}`);
37
+ throw error;
38
+ } finally {
39
+ try {
40
+ await client.query('RESET search_path');
41
+ } catch (_) {}
42
+ client.release();
43
+ }
44
+ }
45
+
46
+ export async function withTransaction<T>(callback: (client: pg.PoolClient) => Promise<T>): Promise<T> {
47
+ if ((globalThis as any).dbMock) {
48
+ return (globalThis as any).dbMock.withTransaction(callback);
49
+ }
50
+ const client = await pool.connect();
51
+ try {
52
+ const store = requestContext.getStore();
53
+ const tenantId = store?.tenantId || 'public';
54
+ await client.query('BEGIN');
55
+ await client.query(`SET search_path TO ${tenantId}, public`);
56
+ await client.query(`SET LOCAL statement_timeout = '${config.dbStatementTimeout}'`);
57
+ const result = await callback(client);
58
+ await client.query('COMMIT');
59
+ return result;
60
+ } catch (error) {
61
+ await client.query('ROLLBACK');
62
+ throw error;
63
+ } finally {
64
+ try {
65
+ await client.query('RESET search_path');
66
+ } catch (_) {}
67
+ client.release();
68
+ }
69
+ }
70
+
71
+ export async function closePool(): Promise<void> {
72
+ await pool.end();
73
+ }
package/src/index.ts ADDED
@@ -0,0 +1,178 @@
1
+ import { Hono } from 'hono';
2
+ import { cors } from 'hono/cors';
3
+ import { config } from './config.js';
4
+ import { metaRouter } from './routes/meta.js';
5
+ import { crudRouter } from './routes/crud.js';
6
+ import { authRouter } from './routes/auth.js';
7
+ import { authMiddleware } from './middleware/auth.js';
8
+ import { rbacMiddleware } from './middleware/rbac.js';
9
+ import { rateLimitMiddleware } from './middleware/rate-limit.js';
10
+ import { bodyLimitMiddleware } from './middleware/body-limit.js';
11
+ import { query, closePool } from './db.js';
12
+ import { filesRouter } from './routes/files.js';
13
+ import { tenantMiddleware } from './middleware/tenant.js';
14
+ import crypto from 'crypto';
15
+
16
+ const app = new Hono<{ Variables: { request_id: string; user?: any } }>();
17
+
18
+ // Global Middleware Stack
19
+
20
+ // CORS
21
+ app.use('*', cors({
22
+ origin: config.corsOrigins.includes('*') ? '*' : config.corsOrigins,
23
+ allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
24
+ allowHeaders: ['Content-Type', 'Authorization'],
25
+ exposeHeaders: ['X-Request-ID', 'X-RateLimit-Limit', 'X-RateLimit-Remaining', 'X-RateLimit-Reset'],
26
+ maxAge: 86400
27
+ }));
28
+
29
+ // Request ID + structured logging
30
+ app.use('*', async (c, next) => {
31
+ const requestId = crypto.randomUUID();
32
+ c.set('request_id', requestId);
33
+ c.header('X-Request-ID', requestId);
34
+ const start = Date.now();
35
+ await next();
36
+ console.log(JSON.stringify({
37
+ level: 'info', type: 'http', method: c.req.method, url: c.req.url,
38
+ status: c.res.status, duration_ms: Date.now() - start, request_id: requestId
39
+ }));
40
+ });
41
+
42
+ // Tenant Isolation Context
43
+ app.use('*', tenantMiddleware);
44
+
45
+ // Body size limit
46
+ app.use('*', bodyLimitMiddleware);
47
+
48
+ // Rate limiting
49
+ app.use('*', rateLimitMiddleware);
50
+
51
+ // Error handler - hide details in production
52
+ app.onError((err, c) => {
53
+ const requestId = c.get('request_id') || crypto.randomUUID();
54
+ console.error(JSON.stringify({
55
+ level: 'error', type: 'exception', request_id: requestId,
56
+ message: err.message, stack: config.isProduction ? undefined : err.stack
57
+ }));
58
+ return c.json({
59
+ error: 'Internal Server Error', request_id: requestId,
60
+ ...(config.isProduction ? {} : { message: err.message })
61
+ }, 500);
62
+ });
63
+
64
+ // === Public Routes (no auth required) ===
65
+ app.get('/api/health', (c) => c.json({ status: 'ok', timestamp: new Date().toISOString() }));
66
+ app.route('/api/auth', authRouter);
67
+ app.route('/api/meta', metaRouter);
68
+
69
+ // === Protected Routes ===
70
+ app.use('/api/files/*', authMiddleware);
71
+ app.route('/api/files', filesRouter);
72
+
73
+ app.use('/api/doctype/*', authMiddleware, rbacMiddleware);
74
+ app.route('/api/doctype/:doctype', crudRouter);
75
+
76
+ // === Bootstrap ===
77
+ async function bootstrapDatabase() {
78
+ console.log('[BOOT] Initializing system database schemas...');
79
+ try {
80
+ // System tables
81
+ await query(`CREATE TABLE IF NOT EXISTS _tenants (
82
+ id VARCHAR(100) PRIMARY KEY,
83
+ name VARCHAR(255) NOT NULL,
84
+ domain VARCHAR(255),
85
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
86
+ );`);
87
+
88
+ await query(`CREATE TABLE IF NOT EXISTS _doctype_meta (
89
+ name VARCHAR(255) PRIMARY KEY, uuid UUID DEFAULT gen_random_uuid() UNIQUE NOT NULL,
90
+ definition JSONB NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
91
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW());`);
92
+
93
+ await query(`CREATE TABLE IF NOT EXISTS _doctype_migration_history (
94
+ id SERIAL PRIMARY KEY, uuid UUID DEFAULT gen_random_uuid() UNIQUE NOT NULL,
95
+ doctype_name VARCHAR(255) NOT NULL, version INTEGER NOT NULL,
96
+ ddl_executed TEXT NOT NULL, executed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
97
+ executed_by VARCHAR(255));`);
98
+
99
+ await query(`CREATE TABLE IF NOT EXISTS _doctype_definition_history (
100
+ id SERIAL PRIMARY KEY, uuid UUID DEFAULT gen_random_uuid() UNIQUE NOT NULL,
101
+ doctype_name VARCHAR(255) REFERENCES _doctype_meta(name) ON DELETE CASCADE,
102
+ version INTEGER NOT NULL,
103
+ definition JSONB NOT NULL,
104
+ changed_by VARCHAR(255) NOT NULL,
105
+ changed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
106
+ ddl_applied TEXT,
107
+ notes TEXT);`);
108
+
109
+ await query(`CREATE TABLE IF NOT EXISTS _naming_series (
110
+ prefix VARCHAR(100) PRIMARY KEY, uuid UUID DEFAULT gen_random_uuid() UNIQUE NOT NULL,
111
+ current_value INTEGER NOT NULL DEFAULT 0);`);
112
+
113
+ await query(`CREATE TABLE IF NOT EXISTS _audit_log (
114
+ id BIGSERIAL PRIMARY KEY, uuid UUID DEFAULT gen_random_uuid() UNIQUE NOT NULL,
115
+ doctype_name VARCHAR(255) NOT NULL, docname VARCHAR(255) NOT NULL,
116
+ action VARCHAR(50) NOT NULL, changed_by VARCHAR(255) NOT NULL,
117
+ changed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), diff JSONB NOT NULL);`);
118
+
119
+ // User management table
120
+ await query(`CREATE TABLE IF NOT EXISTS _users (
121
+ name VARCHAR(255) PRIMARY KEY, uuid UUID DEFAULT gen_random_uuid() UNIQUE NOT NULL,
122
+ email VARCHAR(255) UNIQUE NOT NULL, full_name VARCHAR(255) DEFAULT '',
123
+ hashed_password TEXT NOT NULL, roles JSONB DEFAULT '["Guest"]',
124
+ enabled BOOLEAN DEFAULT true, last_login TIMESTAMP WITH TIME ZONE,
125
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
126
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW());`);
127
+
128
+ // Files attachment tracking table
129
+ await query(`CREATE TABLE IF NOT EXISTS _files (
130
+ name VARCHAR(255) PRIMARY KEY, uuid UUID DEFAULT gen_random_uuid() UNIQUE NOT NULL,
131
+ file_name VARCHAR(255) NOT NULL, file_url VARCHAR(255) NOT NULL,
132
+ file_size INTEGER NOT NULL, mime_type VARCHAR(100) NOT NULL,
133
+ attached_to_doctype VARCHAR(255), attached_to_name VARCHAR(255),
134
+ attached_to_field VARCHAR(255), created_by VARCHAR(255),
135
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW());`);
136
+
137
+ // System indexes
138
+ await query(`CREATE INDEX IF NOT EXISTS idx_audit_log_doctype ON _audit_log (doctype_name, docname);`);
139
+ await query(`CREATE INDEX IF NOT EXISTS idx_audit_log_changed_at ON _audit_log (changed_at);`);
140
+ await query(`CREATE INDEX IF NOT EXISTS idx_users_email ON _users (email);`);
141
+
142
+ console.log('[BOOT] System database schemas initialized successfully.');
143
+ } catch (err) {
144
+ console.error('[BOOT ERROR] Failed to initialize system schemas:', err);
145
+ process.exit(1);
146
+ }
147
+ }
148
+
149
+ bootstrapDatabase().then(() => {
150
+ console.log(`[BOOT] Server launching on http://localhost:${config.port}`);
151
+ if (!config.isProduction) {
152
+ console.log('[BOOT] Running in DEVELOPMENT mode. Set NODE_ENV=production for production.');
153
+ }
154
+ });
155
+
156
+ // Graceful shutdown
157
+ const shutdown = async () => {
158
+ console.log('[SHUTDOWN] Graceful shutdown initiated...');
159
+ await closePool();
160
+ process.exit(0);
161
+ };
162
+ process.on('SIGTERM', shutdown);
163
+ process.on('SIGINT', shutdown);
164
+
165
+ export default { port: config.port, fetch: app.fetch };
166
+
167
+ export { metaRouter } from './routes/meta.js';
168
+ export { crudRouter } from './routes/crud.js';
169
+ export { authRouter } from './routes/auth.js';
170
+ export { filesRouter } from './routes/files.js';
171
+ export { tenantMiddleware } from './middleware/tenant.js';
172
+ export { bodyLimitMiddleware } from './middleware/body-limit.js';
173
+ export { rateLimitMiddleware } from './middleware/rate-limit.js';
174
+ export { authMiddleware } from './middleware/auth.js';
175
+ export { rbacMiddleware } from './middleware/rbac.js';
176
+ export { query, withTransaction } from './db.js';
177
+ export { config } from './config.js';
178
+