qhttpx 1.8.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 (197) hide show
  1. package/.eslintrc.json +22 -0
  2. package/.github/workflows/ci.yml +32 -0
  3. package/.github/workflows/npm-publish.yml +37 -0
  4. package/.github/workflows/release.yml +21 -0
  5. package/.prettierrc +7 -0
  6. package/CHANGELOG.md +145 -0
  7. package/LICENSE +21 -0
  8. package/README.md +343 -0
  9. package/dist/package.json +61 -0
  10. package/dist/src/benchmarks/compare-frameworks.js +119 -0
  11. package/dist/src/benchmarks/quantam-users.js +56 -0
  12. package/dist/src/benchmarks/simple-json.js +58 -0
  13. package/dist/src/benchmarks/ultra-mode.js +122 -0
  14. package/dist/src/cli/index.js +200 -0
  15. package/dist/src/client/index.js +72 -0
  16. package/dist/src/core/batch.js +97 -0
  17. package/dist/src/core/body-parser.js +121 -0
  18. package/dist/src/core/buffer-pool.js +70 -0
  19. package/dist/src/core/config.js +50 -0
  20. package/dist/src/core/fusion.js +183 -0
  21. package/dist/src/core/logger.js +49 -0
  22. package/dist/src/core/metrics.js +111 -0
  23. package/dist/src/core/resources.js +25 -0
  24. package/dist/src/core/scheduler.js +85 -0
  25. package/dist/src/core/scope.js +68 -0
  26. package/dist/src/core/serializer.js +44 -0
  27. package/dist/src/core/server.js +905 -0
  28. package/dist/src/core/stream.js +71 -0
  29. package/dist/src/core/tasks.js +87 -0
  30. package/dist/src/core/types.js +19 -0
  31. package/dist/src/core/websocket.js +86 -0
  32. package/dist/src/core/worker-queue.js +73 -0
  33. package/dist/src/database/adapters/memory.js +90 -0
  34. package/dist/src/database/adapters/mongo.js +141 -0
  35. package/dist/src/database/adapters/postgres.js +111 -0
  36. package/dist/src/database/adapters/sqlite.js +42 -0
  37. package/dist/src/database/coalescer.js +134 -0
  38. package/dist/src/database/manager.js +87 -0
  39. package/dist/src/database/types.js +2 -0
  40. package/dist/src/index.js +61 -0
  41. package/dist/src/middleware/compression.js +133 -0
  42. package/dist/src/middleware/cors.js +66 -0
  43. package/dist/src/middleware/presets.js +33 -0
  44. package/dist/src/middleware/rate-limit.js +77 -0
  45. package/dist/src/middleware/security.js +69 -0
  46. package/dist/src/middleware/static.js +191 -0
  47. package/dist/src/openapi/generator.js +149 -0
  48. package/dist/src/router/radix-router.js +89 -0
  49. package/dist/src/router/radix-tree.js +81 -0
  50. package/dist/src/router/router.js +146 -0
  51. package/dist/src/testing/index.js +84 -0
  52. package/dist/src/utils/cookies.js +59 -0
  53. package/dist/src/utils/logger.js +45 -0
  54. package/dist/src/utils/signals.js +31 -0
  55. package/dist/src/utils/sse.js +32 -0
  56. package/dist/src/validation/index.js +19 -0
  57. package/dist/src/validation/simple.js +102 -0
  58. package/dist/src/validation/types.js +12 -0
  59. package/dist/src/validation/zod.js +18 -0
  60. package/dist/src/views/index.js +17 -0
  61. package/dist/src/views/types.js +2 -0
  62. package/dist/tests/adapters.test.js +106 -0
  63. package/dist/tests/batch.test.js +117 -0
  64. package/dist/tests/body-parser.test.js +52 -0
  65. package/dist/tests/compression-sse.test.js +87 -0
  66. package/dist/tests/cookies.test.js +63 -0
  67. package/dist/tests/cors.test.js +55 -0
  68. package/dist/tests/database.test.js +80 -0
  69. package/dist/tests/dx.test.js +64 -0
  70. package/dist/tests/ecosystem.test.js +133 -0
  71. package/dist/tests/features.test.js +47 -0
  72. package/dist/tests/fusion.test.js +92 -0
  73. package/dist/tests/http-basic.test.js +124 -0
  74. package/dist/tests/logger.test.js +33 -0
  75. package/dist/tests/middleware.test.js +109 -0
  76. package/dist/tests/observability.test.js +59 -0
  77. package/dist/tests/openapi.test.js +64 -0
  78. package/dist/tests/plugin.test.js +65 -0
  79. package/dist/tests/plugins.test.js +71 -0
  80. package/dist/tests/rate-limit.test.js +77 -0
  81. package/dist/tests/resources.test.js +44 -0
  82. package/dist/tests/scheduler.test.js +46 -0
  83. package/dist/tests/schema-routes.test.js +77 -0
  84. package/dist/tests/security.test.js +83 -0
  85. package/dist/tests/server-db.test.js +72 -0
  86. package/dist/tests/smoke.test.js +10 -0
  87. package/dist/tests/sqlite-fusion.test.js +92 -0
  88. package/dist/tests/static.test.js +102 -0
  89. package/dist/tests/stream.test.js +44 -0
  90. package/dist/tests/task-metrics.test.js +53 -0
  91. package/dist/tests/tasks.test.js +62 -0
  92. package/dist/tests/testing.test.js +47 -0
  93. package/dist/tests/validation.test.js +107 -0
  94. package/dist/tests/websocket.test.js +146 -0
  95. package/dist/vitest.config.js +9 -0
  96. package/docs/AEGIS.md +76 -0
  97. package/docs/BENCHMARKS.md +36 -0
  98. package/docs/CAPABILITIES.md +70 -0
  99. package/docs/CLI.md +43 -0
  100. package/docs/DATABASE.md +142 -0
  101. package/docs/ECOSYSTEM.md +146 -0
  102. package/docs/NEXT_STEPS.md +99 -0
  103. package/docs/OPENAPI.md +99 -0
  104. package/docs/PLUGINS.md +59 -0
  105. package/docs/REAL_WORLD_EXAMPLES.md +109 -0
  106. package/docs/ROADMAP.md +366 -0
  107. package/docs/VALIDATION.md +136 -0
  108. package/eslint.config.cjs +26 -0
  109. package/examples/api-server.ts +254 -0
  110. package/package.json +61 -0
  111. package/src/benchmarks/compare-frameworks.ts +149 -0
  112. package/src/benchmarks/quantam-users.ts +70 -0
  113. package/src/benchmarks/simple-json.ts +71 -0
  114. package/src/benchmarks/ultra-mode.ts +159 -0
  115. package/src/cli/index.ts +214 -0
  116. package/src/client/index.ts +93 -0
  117. package/src/core/batch.ts +110 -0
  118. package/src/core/body-parser.ts +151 -0
  119. package/src/core/buffer-pool.ts +96 -0
  120. package/src/core/config.ts +60 -0
  121. package/src/core/fusion.ts +210 -0
  122. package/src/core/logger.ts +70 -0
  123. package/src/core/metrics.ts +166 -0
  124. package/src/core/resources.ts +38 -0
  125. package/src/core/scheduler.ts +126 -0
  126. package/src/core/scope.ts +87 -0
  127. package/src/core/serializer.ts +41 -0
  128. package/src/core/server.ts +1113 -0
  129. package/src/core/stream.ts +111 -0
  130. package/src/core/tasks.ts +138 -0
  131. package/src/core/types.ts +178 -0
  132. package/src/core/websocket.ts +112 -0
  133. package/src/core/worker-queue.ts +90 -0
  134. package/src/database/adapters/memory.ts +99 -0
  135. package/src/database/adapters/mongo.ts +116 -0
  136. package/src/database/adapters/postgres.ts +86 -0
  137. package/src/database/adapters/sqlite.ts +44 -0
  138. package/src/database/coalescer.ts +153 -0
  139. package/src/database/manager.ts +97 -0
  140. package/src/database/types.ts +24 -0
  141. package/src/index.ts +42 -0
  142. package/src/middleware/compression.ts +147 -0
  143. package/src/middleware/cors.ts +98 -0
  144. package/src/middleware/presets.ts +50 -0
  145. package/src/middleware/rate-limit.ts +106 -0
  146. package/src/middleware/security.ts +109 -0
  147. package/src/middleware/static.ts +216 -0
  148. package/src/openapi/generator.ts +167 -0
  149. package/src/router/radix-router.ts +119 -0
  150. package/src/router/radix-tree.ts +106 -0
  151. package/src/router/router.ts +190 -0
  152. package/src/testing/index.ts +104 -0
  153. package/src/utils/cookies.ts +67 -0
  154. package/src/utils/logger.ts +59 -0
  155. package/src/utils/signals.ts +45 -0
  156. package/src/utils/sse.ts +41 -0
  157. package/src/validation/index.ts +3 -0
  158. package/src/validation/simple.ts +93 -0
  159. package/src/validation/types.ts +38 -0
  160. package/src/validation/zod.ts +14 -0
  161. package/src/views/index.ts +1 -0
  162. package/src/views/types.ts +4 -0
  163. package/tests/adapters.test.ts +120 -0
  164. package/tests/batch.test.ts +139 -0
  165. package/tests/body-parser.test.ts +83 -0
  166. package/tests/compression-sse.test.ts +98 -0
  167. package/tests/cookies.test.ts +74 -0
  168. package/tests/cors.test.ts +79 -0
  169. package/tests/database.test.ts +90 -0
  170. package/tests/dx.test.ts +78 -0
  171. package/tests/ecosystem.test.ts +156 -0
  172. package/tests/features.test.ts +51 -0
  173. package/tests/fusion.test.ts +121 -0
  174. package/tests/http-basic.test.ts +161 -0
  175. package/tests/logger.test.ts +48 -0
  176. package/tests/middleware.test.ts +137 -0
  177. package/tests/observability.test.ts +91 -0
  178. package/tests/openapi.test.ts +74 -0
  179. package/tests/plugin.test.ts +85 -0
  180. package/tests/plugins.test.ts +93 -0
  181. package/tests/rate-limit.test.ts +97 -0
  182. package/tests/resources.test.ts +64 -0
  183. package/tests/scheduler.test.ts +71 -0
  184. package/tests/schema-routes.test.ts +89 -0
  185. package/tests/security.test.ts +128 -0
  186. package/tests/server-db.test.ts +72 -0
  187. package/tests/smoke.test.ts +9 -0
  188. package/tests/sqlite-fusion.test.ts +106 -0
  189. package/tests/static.test.ts +111 -0
  190. package/tests/stream.test.ts +58 -0
  191. package/tests/task-metrics.test.ts +78 -0
  192. package/tests/tasks.test.ts +90 -0
  193. package/tests/testing.test.ts +53 -0
  194. package/tests/validation.test.ts +126 -0
  195. package/tests/websocket.test.ts +132 -0
  196. package/tsconfig.json +16 -0
  197. package/vitest.config.ts +9 -0
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Router = void 0;
4
+ const radix_tree_1 = require("./radix-tree");
5
+ const types_1 = require("../core/types");
6
+ class Router {
7
+ constructor() {
8
+ // Per-method route buckets
9
+ this.methodBuckets = new Map([
10
+ ['GET', []],
11
+ ['POST', []],
12
+ ['PUT', []],
13
+ ['DELETE', []],
14
+ ['PATCH', []],
15
+ ['HEAD', []],
16
+ ['OPTIONS', []],
17
+ ]);
18
+ // Derived structures (built at freeze time)
19
+ this.radixTrees = new Map();
20
+ // Freeze state
21
+ this.isFrozen = false;
22
+ }
23
+ register(method, path, handler, options) {
24
+ if (this.isFrozen) {
25
+ console.warn(`Router is frozen. Late route registration (${method} ${path}) may not be optimized.`);
26
+ }
27
+ const segments = this.normalize(path);
28
+ const bucket = this.methodBuckets.get(method);
29
+ if (bucket) {
30
+ bucket.push({
31
+ path,
32
+ segments,
33
+ handler,
34
+ priority: options?.priority ?? types_1.RoutePriority.STANDARD,
35
+ schema: options?.schema,
36
+ });
37
+ }
38
+ }
39
+ getRoutes() {
40
+ return this.methodBuckets;
41
+ }
42
+ match(method, path) {
43
+ // Fast path for frozen router
44
+ if (this.isFrozen) {
45
+ const tree = this.radixTrees.get(method);
46
+ if (tree) {
47
+ const segments = this.normalize(path);
48
+ const match = tree.lookup(segments);
49
+ if (match) {
50
+ return match;
51
+ }
52
+ }
53
+ return undefined;
54
+ }
55
+ // Slow path for unfrozen router (legacy behavior)
56
+ const segments = this.normalize(path);
57
+ const bucket = this.methodBuckets.get(method);
58
+ if (!bucket || bucket.length === 0) {
59
+ return undefined;
60
+ }
61
+ // Try to match against routes
62
+ for (const route of bucket) {
63
+ if (route.segments.length !== segments.length) {
64
+ continue;
65
+ }
66
+ const params = {};
67
+ let matched = true;
68
+ for (let i = 0; i < route.segments.length; i += 1) {
69
+ const pattern = route.segments[i];
70
+ const value = segments[i];
71
+ if (pattern.startsWith(':')) {
72
+ const key = pattern.slice(1);
73
+ params[key] = value;
74
+ }
75
+ else if (pattern !== value) {
76
+ matched = false;
77
+ break;
78
+ }
79
+ }
80
+ if (matched) {
81
+ return {
82
+ handler: route.handler,
83
+ params,
84
+ priority: route.priority,
85
+ };
86
+ }
87
+ }
88
+ return undefined;
89
+ }
90
+ getAllowedMethods(path) {
91
+ const segments = this.normalize(path);
92
+ const methods = [];
93
+ for (const [method, routes] of this.methodBuckets.entries()) {
94
+ for (const route of routes) {
95
+ if (route.segments.length !== segments.length) {
96
+ continue;
97
+ }
98
+ let matched = true;
99
+ for (let i = 0; i < route.segments.length; i += 1) {
100
+ const pattern = route.segments[i];
101
+ const value = segments[i];
102
+ if (pattern.startsWith(':')) {
103
+ continue;
104
+ }
105
+ if (pattern !== value) {
106
+ matched = false;
107
+ break;
108
+ }
109
+ }
110
+ if (matched) {
111
+ methods.push(method);
112
+ break;
113
+ }
114
+ }
115
+ }
116
+ return methods;
117
+ }
118
+ /**
119
+ * Freeze the router after server starts.
120
+ * Prevents further route registration and builds derived structures for optimized matching.
121
+ */
122
+ freeze() {
123
+ if (this.isFrozen) {
124
+ return;
125
+ }
126
+ this.isFrozen = true;
127
+ // Build derived structures for faster matching
128
+ for (const [method, routes] of this.methodBuckets.entries()) {
129
+ const tree = new radix_tree_1.RadixTree();
130
+ for (const route of routes) {
131
+ tree.insert(route.segments, route.handler, route.priority);
132
+ }
133
+ this.radixTrees.set(method, tree);
134
+ }
135
+ }
136
+ isFrozenRouter() {
137
+ return this.isFrozen;
138
+ }
139
+ normalize(path) {
140
+ if (!path || path === '/') {
141
+ return [];
142
+ }
143
+ return path.split('/').filter((segment) => segment.length > 0);
144
+ }
145
+ }
146
+ exports.Router = Router;
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TestClient = void 0;
4
+ exports.createTestClient = createTestClient;
5
+ class TestClient {
6
+ constructor(app) {
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ this.server = null;
9
+ this.baseURL = '';
10
+ this.app = app;
11
+ }
12
+ /**
13
+ * Starts the server on a random port.
14
+ * Automatically called by request methods if not started.
15
+ */
16
+ async start() {
17
+ if (this.server)
18
+ return;
19
+ const { port } = await this.app.listen(0);
20
+ this.server = this.app.serverInstance;
21
+ this.baseURL = `http://127.0.0.1:${port}`;
22
+ }
23
+ async stop() {
24
+ if (this.server) {
25
+ await this.app.close();
26
+ this.server = null;
27
+ }
28
+ }
29
+ async request(method, path, options = {}) {
30
+ if (!this.server) {
31
+ await this.start();
32
+ }
33
+ const url = new URL(path, this.baseURL);
34
+ if (options.query) {
35
+ Object.entries(options.query).forEach(([k, v]) => {
36
+ url.searchParams.append(k, String(v));
37
+ });
38
+ }
39
+ const headers = options.headers || {};
40
+ let bodyPayload;
41
+ if (options.body) {
42
+ if (typeof options.body === 'object' &&
43
+ !(options.body instanceof Uint8Array) &&
44
+ !(options.body instanceof ArrayBuffer) &&
45
+ !(options.body instanceof FormData) &&
46
+ !(options.body instanceof URLSearchParams)) {
47
+ bodyPayload = JSON.stringify(options.body);
48
+ if (!headers['content-type']) {
49
+ headers['content-type'] = 'application/json';
50
+ }
51
+ }
52
+ else {
53
+ bodyPayload = options.body;
54
+ }
55
+ }
56
+ return fetch(url, {
57
+ method,
58
+ headers,
59
+ body: bodyPayload,
60
+ });
61
+ }
62
+ get(path, options) {
63
+ return this.request('GET', path, options);
64
+ }
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ post(path, body, options) {
67
+ return this.request('POST', path, { ...options, body });
68
+ }
69
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
+ put(path, body, options) {
71
+ return this.request('PUT', path, { ...options, body });
72
+ }
73
+ delete(path, options) {
74
+ return this.request('DELETE', path, options);
75
+ }
76
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
+ patch(path, body, options) {
78
+ return this.request('PATCH', path, { ...options, body });
79
+ }
80
+ }
81
+ exports.TestClient = TestClient;
82
+ function createTestClient(app) {
83
+ return new TestClient(app);
84
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseCookies = parseCookies;
4
+ exports.serializeCookie = serializeCookie;
5
+ function parseCookies(header) {
6
+ const list = {};
7
+ if (!header) {
8
+ return list;
9
+ }
10
+ header.split(';').forEach((cookie) => {
11
+ const parts = cookie.split('=');
12
+ const name = parts.shift()?.trim();
13
+ if (name) {
14
+ const value = parts.join('=');
15
+ list[name] = decodeURIComponent(value);
16
+ }
17
+ });
18
+ return list;
19
+ }
20
+ function serializeCookie(name, value, options = {}) {
21
+ let str = `${name}=${encodeURIComponent(value)}`;
22
+ if (options.maxAge) {
23
+ str += `; Max-Age=${Math.floor(options.maxAge)}`;
24
+ }
25
+ if (options.domain) {
26
+ str += `; Domain=${options.domain}`;
27
+ }
28
+ if (options.path) {
29
+ str += `; Path=${options.path}`;
30
+ }
31
+ else {
32
+ str += '; Path=/';
33
+ }
34
+ if (options.expires) {
35
+ str += `; Expires=${options.expires.toUTCString()}`;
36
+ }
37
+ if (options.httpOnly) {
38
+ str += '; HttpOnly';
39
+ }
40
+ if (options.secure) {
41
+ str += '; Secure';
42
+ }
43
+ if (options.sameSite) {
44
+ switch (options.sameSite) {
45
+ case 'lax':
46
+ str += '; SameSite=Lax';
47
+ break;
48
+ case 'strict':
49
+ str += '; SameSite=Strict';
50
+ break;
51
+ case 'none':
52
+ str += '; SameSite=None';
53
+ break;
54
+ default:
55
+ break;
56
+ }
57
+ }
58
+ return str;
59
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createLoggerMiddleware = createLoggerMiddleware;
4
+ function createLoggerMiddleware(options = {}) {
5
+ const sink = options.sink ??
6
+ ((entry) => {
7
+ const status = entry.status;
8
+ let color = '\x1b[37m';
9
+ if (status >= 200 && status < 300) {
10
+ color = '\x1b[32m';
11
+ }
12
+ else if (status === 404) {
13
+ color = '\x1b[33m';
14
+ }
15
+ else if (status >= 500) {
16
+ color = '\x1b[34m';
17
+ }
18
+ else if (status >= 400) {
19
+ color = '\x1b[35m';
20
+ }
21
+ const reset = '\x1b[0m';
22
+ const prefix = entry.requestId ? `${entry.requestId} ` : '';
23
+ const line = `${prefix}${entry.method} ${entry.path} ${status} ${entry.durationMs}ms`;
24
+ console.log(`${color}${line}${reset}`);
25
+ });
26
+ return async (ctx, next) => {
27
+ const start = ctx.requestStart ?? Date.now();
28
+ try {
29
+ await next();
30
+ }
31
+ finally {
32
+ const durationMs = Math.max(1, Date.now() - start);
33
+ const method = ctx.req.method || 'GET';
34
+ const path = ctx.url.pathname;
35
+ const status = ctx.res.statusCode || 200;
36
+ sink({
37
+ method,
38
+ path,
39
+ status,
40
+ durationMs,
41
+ requestId: ctx.requestId,
42
+ }, ctx);
43
+ }
44
+ };
45
+ }
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.attachSignalHandlers = attachSignalHandlers;
4
+ function attachSignalHandlers(app, options = {}) {
5
+ const signals = options.signals ?? ['SIGINT', 'SIGTERM'];
6
+ const timeoutMs = options.timeoutMs ?? 10000;
7
+ let shuttingDown = false;
8
+ const handler = async (signal) => {
9
+ if (shuttingDown) {
10
+ return;
11
+ }
12
+ shuttingDown = true;
13
+ console.log(`Received ${signal}, shutting down...`);
14
+ const timer = setTimeout(() => {
15
+ console.error('Shutdown timed out, forcing exit. (some connections might be lost)');
16
+ process.exit(1);
17
+ }, timeoutMs);
18
+ try {
19
+ await app.shutdown();
20
+ clearTimeout(timer);
21
+ process.exit(0);
22
+ }
23
+ catch (err) {
24
+ console.error('Error during shutdown:', err);
25
+ process.exit(1);
26
+ }
27
+ };
28
+ for (const signal of signals) {
29
+ process.on(signal, handler);
30
+ }
31
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSSE = createSSE;
4
+ function createSSE(ctx) {
5
+ const res = ctx.res;
6
+ // Disable auto-end so we can keep the connection open
7
+ ctx.disableAutoEnd = true;
8
+ // Only write headers if not already sent
9
+ if (!res.headersSent) {
10
+ res.setHeader('Content-Type', 'text/event-stream');
11
+ res.setHeader('Cache-Control', 'no-cache');
12
+ res.setHeader('Connection', 'keep-alive');
13
+ res.setHeader('X-Accel-Buffering', 'no'); // For Nginx
14
+ // Send initial ping/comment to flush headers and establish connection
15
+ res.write(': connected\n\n');
16
+ }
17
+ const send = (data, event, id) => {
18
+ if (id)
19
+ res.write(`id: ${id}\n`);
20
+ if (event)
21
+ res.write(`event: ${event}\n`);
22
+ const payload = typeof data === 'string' ? data : JSON.stringify(data);
23
+ res.write(`data: ${payload}\n\n`);
24
+ };
25
+ const close = () => {
26
+ res.end();
27
+ };
28
+ ctx.req.on('close', () => {
29
+ // console.log('SSE client disconnected');
30
+ });
31
+ return { send, close };
32
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types"), exports);
18
+ __exportStar(require("./simple"), exports);
19
+ __exportStar(require("./zod"), exports);
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SimpleValidator = void 0;
4
+ const types_1 = require("./types");
5
+ class SimpleValidator {
6
+ validate(schema, data) {
7
+ try {
8
+ // If schema is not a SimpleSchema object, we can't validate it with this validator
9
+ // But for simplicity, we assume the user passes a valid SimpleSchema if they use this validator.
10
+ const validData = this.check(schema, data);
11
+ return { success: true, data: validData };
12
+ }
13
+ catch (err) {
14
+ return {
15
+ success: false,
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
+ error: new types_1.ValidationError(err.message || 'Validation failed')
18
+ };
19
+ }
20
+ }
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ check(schema, data, path = '') {
23
+ if (schema.required !== false && (data === undefined || data === null)) {
24
+ throw new Error(`Field '${path}' is required`);
25
+ }
26
+ if (data === undefined || data === null) {
27
+ return data;
28
+ }
29
+ switch (schema.type) {
30
+ case 'string':
31
+ if (typeof data !== 'string')
32
+ throw new Error(`Field '${path}' must be a string`);
33
+ if (schema.min !== undefined && data.length < schema.min)
34
+ throw new Error(`Field '${path}' too short (min ${schema.min})`);
35
+ if (schema.max !== undefined && data.length > schema.max)
36
+ throw new Error(`Field '${path}' too long (max ${schema.max})`);
37
+ if (schema.pattern && !new RegExp(schema.pattern).test(data))
38
+ throw new Error(`Field '${path}' format invalid`);
39
+ if (schema.enum && !schema.enum.includes(data))
40
+ throw new Error(`Field '${path}' must be one of [${schema.enum.join(', ')}]`);
41
+ return data;
42
+ case 'number':
43
+ // Try to coerce if it's a string looking like a number (useful for query params)
44
+ let num = data;
45
+ if (typeof data === 'string' && !isNaN(Number(data))) {
46
+ num = Number(data);
47
+ }
48
+ if (typeof num !== 'number' || isNaN(num))
49
+ throw new Error(`Field '${path}' must be a number`);
50
+ if (schema.min !== undefined && num < schema.min)
51
+ throw new Error(`Field '${path}' too small (min ${schema.min})`);
52
+ if (schema.max !== undefined && num > schema.max)
53
+ throw new Error(`Field '${path}' too large (max ${schema.max})`);
54
+ if (schema.enum && !schema.enum.includes(num))
55
+ throw new Error(`Field '${path}' must be one of [${schema.enum.join(', ')}]`);
56
+ return num;
57
+ case 'boolean':
58
+ if (typeof data === 'boolean')
59
+ return data;
60
+ if (data === 'true')
61
+ return true;
62
+ if (data === 'false')
63
+ return false;
64
+ throw new Error(`Field '${path}' must be a boolean`);
65
+ case 'array':
66
+ if (!Array.isArray(data))
67
+ throw new Error(`Field '${path}' must be an array`);
68
+ if (schema.min !== undefined && data.length < schema.min)
69
+ throw new Error(`Field '${path}' too few items (min ${schema.min})`);
70
+ if (schema.max !== undefined && data.length > schema.max)
71
+ throw new Error(`Field '${path}' too many items (max ${schema.max})`);
72
+ if (schema.items) {
73
+ return data.map((item, i) => this.check(schema.items, item, `${path}[${i}]`));
74
+ }
75
+ return data;
76
+ case 'object':
77
+ if (typeof data !== 'object' || Array.isArray(data))
78
+ throw new Error(`Field '${path}' must be an object`);
79
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
80
+ const result = {};
81
+ const props = schema.properties || {};
82
+ // Check known properties
83
+ for (const key in props) {
84
+ const propSchema = props[key];
85
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
+ const propValue = data[key];
87
+ result[key] = this.check(propSchema, propValue, path ? `${path}.${key}` : key);
88
+ }
89
+ // Pass through unknown properties?
90
+ // For strictness, maybe we should strip them?
91
+ // Let's keep unknown properties for now to be safe, or make it configurable.
92
+ // For now: Only keep validated properties if properties are defined.
93
+ if (Object.keys(props).length > 0) {
94
+ return result;
95
+ }
96
+ return data;
97
+ default:
98
+ return data;
99
+ }
100
+ }
101
+ }
102
+ exports.SimpleValidator = SimpleValidator;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ValidationError = void 0;
4
+ class ValidationError extends Error {
5
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
+ constructor(details) {
7
+ super('Validation Error');
8
+ this.details = details;
9
+ this.name = 'ValidationError';
10
+ }
11
+ }
12
+ exports.ValidationError = ValidationError;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ZodValidator = void 0;
4
+ const types_1 = require("./types");
5
+ class ZodValidator {
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ async validate(schema, data) {
8
+ try {
9
+ const zodSchema = schema;
10
+ const result = await zodSchema.parseAsync(data);
11
+ return { success: true, data: result };
12
+ }
13
+ catch (error) {
14
+ return { success: false, error: new types_1.ValidationError(error) };
15
+ }
16
+ }
17
+ }
18
+ exports.ZodValidator = ZodValidator;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const postgres_1 = require("../src/database/adapters/postgres");
5
+ const mongo_1 = require("../src/database/adapters/mongo");
6
+ // Define mocks
7
+ const pgPoolQuery = vitest_1.vi.fn(() => Promise.resolve({ rows: [], command: 'SELECT' }));
8
+ const pgConnect = vitest_1.vi.fn(() => Promise.resolve({ release: vitest_1.vi.fn() }));
9
+ const pgEnd = vitest_1.vi.fn();
10
+ vitest_1.vi.mock('pg', () => {
11
+ const Pool = function () {
12
+ return {
13
+ connect: pgConnect,
14
+ query: pgPoolQuery,
15
+ end: pgEnd,
16
+ on: vitest_1.vi.fn(),
17
+ ended: false
18
+ };
19
+ };
20
+ return {
21
+ default: { Pool },
22
+ Pool
23
+ };
24
+ });
25
+ const mongoFind = vitest_1.vi.fn(() => ({
26
+ toArray: vitest_1.vi.fn(() => Promise.resolve([])),
27
+ limit: vitest_1.vi.fn(),
28
+ skip: vitest_1.vi.fn()
29
+ }));
30
+ const mongoConnect = vitest_1.vi.fn(() => Promise.resolve());
31
+ const mongoClose = vitest_1.vi.fn();
32
+ vitest_1.vi.mock('mongodb', () => {
33
+ const MongoClient = function () {
34
+ return {
35
+ connect: mongoConnect,
36
+ db: vitest_1.vi.fn(() => ({
37
+ collection: vitest_1.vi.fn(() => ({
38
+ find: mongoFind,
39
+ findOne: vitest_1.vi.fn(),
40
+ insertOne: vitest_1.vi.fn(),
41
+ // add other methods as needed
42
+ updateOne: vitest_1.vi.fn(),
43
+ updateMany: vitest_1.vi.fn(),
44
+ deleteOne: vitest_1.vi.fn(),
45
+ deleteMany: vitest_1.vi.fn(),
46
+ aggregate: vitest_1.vi.fn(() => ({ toArray: vitest_1.vi.fn(() => Promise.resolve([])) }))
47
+ }))
48
+ })),
49
+ close: mongoClose
50
+ };
51
+ };
52
+ return {
53
+ default: { MongoClient },
54
+ MongoClient
55
+ };
56
+ });
57
+ (0, vitest_1.describe)('Database Adapters', () => {
58
+ (0, vitest_1.describe)('PostgresAdapter', () => {
59
+ let adapter;
60
+ (0, vitest_1.beforeEach)(() => {
61
+ adapter = new postgres_1.PostgresAdapter({ type: 'postgres', database: 'test' });
62
+ vitest_1.vi.clearAllMocks();
63
+ });
64
+ (0, vitest_1.it)('should connect and disconnect', async () => {
65
+ await adapter.connect();
66
+ (0, vitest_1.expect)(pgConnect).toHaveBeenCalled();
67
+ (0, vitest_1.expect)(adapter.isConnected()).toBe(true);
68
+ await adapter.disconnect();
69
+ (0, vitest_1.expect)(pgEnd).toHaveBeenCalled();
70
+ // Since our mock object is static in the factory, we can't easily check 'pool' null state
71
+ // via the mock, but we can check the adapter state if it relies on the pool property.
72
+ // The adapter sets this.pool = null.
73
+ (0, vitest_1.expect)(adapter.isConnected()).toBe(false);
74
+ });
75
+ (0, vitest_1.it)('should execute query', async () => {
76
+ await adapter.connect();
77
+ await adapter.query('SELECT * FROM users');
78
+ (0, vitest_1.expect)(pgPoolQuery).toHaveBeenCalledWith('SELECT * FROM users', undefined);
79
+ });
80
+ });
81
+ (0, vitest_1.describe)('MongoAdapter', () => {
82
+ let adapter;
83
+ (0, vitest_1.beforeEach)(() => {
84
+ adapter = new mongo_1.MongoAdapter({ type: 'mongo', url: 'mongodb://localhost' });
85
+ vitest_1.vi.clearAllMocks();
86
+ });
87
+ (0, vitest_1.it)('should connect and disconnect', async () => {
88
+ await adapter.connect();
89
+ (0, vitest_1.expect)(mongoConnect).toHaveBeenCalled();
90
+ (0, vitest_1.expect)(adapter.isConnected()).toBe(true);
91
+ await adapter.disconnect();
92
+ (0, vitest_1.expect)(mongoClose).toHaveBeenCalled();
93
+ (0, vitest_1.expect)(adapter.isConnected()).toBe(false);
94
+ });
95
+ (0, vitest_1.it)('should execute find query via object syntax', async () => {
96
+ await adapter.connect();
97
+ await adapter.query({ collection: 'users', action: 'find', filter: { id: 1 } });
98
+ (0, vitest_1.expect)(mongoFind).toHaveBeenCalledWith({ id: 1 });
99
+ });
100
+ (0, vitest_1.it)('should execute find query via string syntax', async () => {
101
+ await adapter.connect();
102
+ await adapter.query('users.find', [{ id: 1 }]);
103
+ (0, vitest_1.expect)(mongoFind).toHaveBeenCalledWith({ id: 1 });
104
+ });
105
+ });
106
+ });