speexjs 0.2.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.
- package/README.md +555 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1017 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client/index.d.ts +73 -0
- package/dist/client/index.js +927 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/signals/index.d.ts +62 -0
- package/dist/client/signals/index.js +248 -0
- package/dist/client/signals/index.js.map +1 -0
- package/dist/client/vdom/index.d.ts +50 -0
- package/dist/client/vdom/index.js +540 -0
- package/dist/client/vdom/index.js.map +1 -0
- package/dist/client/vdom/jsx-runtime.d.ts +9 -0
- package/dist/client/vdom/jsx-runtime.js +203 -0
- package/dist/client/vdom/jsx-runtime.js.map +1 -0
- package/dist/index-CMkhSDh7.d.ts +97 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +6402 -0
- package/dist/index.js.map +1 -0
- package/dist/jsx-DGrnv8QB.d.ts +8 -0
- package/dist/response-Ca8KWK5_.d.ts +173 -0
- package/dist/rpc/index.d.ts +70 -0
- package/dist/rpc/index.js +136 -0
- package/dist/rpc/index.js.map +1 -0
- package/dist/schema/index.d.ts +231 -0
- package/dist/schema/index.js +1160 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/server/auth/index.d.ts +61 -0
- package/dist/server/auth/index.js +462 -0
- package/dist/server/auth/index.js.map +1 -0
- package/dist/server/cache/index.d.ts +45 -0
- package/dist/server/cache/index.js +238 -0
- package/dist/server/cache/index.js.map +1 -0
- package/dist/server/container/index.d.ts +20 -0
- package/dist/server/container/index.js +62 -0
- package/dist/server/container/index.js.map +1 -0
- package/dist/server/controller/index.d.ts +37 -0
- package/dist/server/controller/index.js +139 -0
- package/dist/server/controller/index.js.map +1 -0
- package/dist/server/database/index.d.ts +461 -0
- package/dist/server/database/index.js +1977 -0
- package/dist/server/database/index.js.map +1 -0
- package/dist/server/events/index.d.ts +29 -0
- package/dist/server/events/index.js +159 -0
- package/dist/server/events/index.js.map +1 -0
- package/dist/server/gate/index.d.ts +36 -0
- package/dist/server/gate/index.js +169 -0
- package/dist/server/gate/index.js.map +1 -0
- package/dist/server/http/index.d.ts +45 -0
- package/dist/server/http/index.js +871 -0
- package/dist/server/http/index.js.map +1 -0
- package/dist/server/index.d.ts +79 -0
- package/dist/server/index.js +4185 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/middleware/index.d.ts +5 -0
- package/dist/server/middleware/index.js +416 -0
- package/dist/server/middleware/index.js.map +1 -0
- package/dist/server/router/index.d.ts +5 -0
- package/dist/server/router/index.js +231 -0
- package/dist/server/router/index.js.map +1 -0
- package/dist/server/storage/index.d.ts +66 -0
- package/dist/server/storage/index.js +244 -0
- package/dist/server/storage/index.js.map +1 -0
- package/dist/session-guard-CZeN87L9.d.ts +48 -0
- package/dist/types-CXH8hPei.d.ts +38 -0
- package/package.json +138 -0
|
@@ -0,0 +1,4185 @@
|
|
|
1
|
+
// src/server/container/index.ts
|
|
2
|
+
var Container = class {
|
|
3
|
+
bindings = /* @__PURE__ */ new Map();
|
|
4
|
+
resolving = /* @__PURE__ */ new Set();
|
|
5
|
+
bind(name, factory) {
|
|
6
|
+
this.bindings.set(name, { factory, singleton: false });
|
|
7
|
+
return this;
|
|
8
|
+
}
|
|
9
|
+
singleton(name, factory) {
|
|
10
|
+
this.bindings.set(name, { factory, singleton: true });
|
|
11
|
+
return this;
|
|
12
|
+
}
|
|
13
|
+
instance(name, instance) {
|
|
14
|
+
this.bindings.set(name, {
|
|
15
|
+
factory: () => instance,
|
|
16
|
+
singleton: true,
|
|
17
|
+
instance
|
|
18
|
+
});
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
resolve(name) {
|
|
22
|
+
const binding = this.bindings.get(name);
|
|
23
|
+
if (binding === void 0) {
|
|
24
|
+
throw new Error(`Binding not found: ${name}`);
|
|
25
|
+
}
|
|
26
|
+
if (binding.singleton && binding.instance !== void 0) {
|
|
27
|
+
return binding.instance;
|
|
28
|
+
}
|
|
29
|
+
if (this.resolving.has(name)) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Circular dependency detected: ${name} is already being resolved`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
this.resolving.add(name);
|
|
35
|
+
try {
|
|
36
|
+
const instance = binding.factory();
|
|
37
|
+
if (binding.singleton) {
|
|
38
|
+
binding.instance = instance;
|
|
39
|
+
}
|
|
40
|
+
return instance;
|
|
41
|
+
} finally {
|
|
42
|
+
this.resolving.delete(name);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
has(name) {
|
|
46
|
+
return this.bindings.has(name);
|
|
47
|
+
}
|
|
48
|
+
remove(name) {
|
|
49
|
+
this.bindings.delete(name);
|
|
50
|
+
}
|
|
51
|
+
clear() {
|
|
52
|
+
this.bindings.clear();
|
|
53
|
+
this.resolving.clear();
|
|
54
|
+
}
|
|
55
|
+
getBindings() {
|
|
56
|
+
return new Map(this.bindings);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// src/server/http/status.ts
|
|
61
|
+
var HttpStatus = {
|
|
62
|
+
OK: 200,
|
|
63
|
+
CREATED: 201,
|
|
64
|
+
ACCEPTED: 202,
|
|
65
|
+
NO_CONTENT: 204,
|
|
66
|
+
RESET_CONTENT: 205,
|
|
67
|
+
PARTIAL_CONTENT: 206,
|
|
68
|
+
MOVED_PERMANENTLY: 301,
|
|
69
|
+
FOUND: 302,
|
|
70
|
+
SEE_OTHER: 303,
|
|
71
|
+
NOT_MODIFIED: 304,
|
|
72
|
+
TEMPORARY_REDIRECT: 307,
|
|
73
|
+
PERMANENT_REDIRECT: 308,
|
|
74
|
+
BAD_REQUEST: 400,
|
|
75
|
+
UNAUTHORIZED: 401,
|
|
76
|
+
PAYMENT_REQUIRED: 402,
|
|
77
|
+
FORBIDDEN: 403,
|
|
78
|
+
NOT_FOUND: 404,
|
|
79
|
+
METHOD_NOT_ALLOWED: 405,
|
|
80
|
+
NOT_ACCEPTABLE: 406,
|
|
81
|
+
REQUEST_TIMEOUT: 408,
|
|
82
|
+
CONFLICT: 409,
|
|
83
|
+
GONE: 410,
|
|
84
|
+
LENGTH_REQUIRED: 411,
|
|
85
|
+
PRECONDITION_FAILED: 412,
|
|
86
|
+
PAYLOAD_TOO_LARGE: 413,
|
|
87
|
+
URI_TOO_LONG: 414,
|
|
88
|
+
UNSUPPORTED_MEDIA_TYPE: 415,
|
|
89
|
+
UNPROCESSABLE_ENTITY: 422,
|
|
90
|
+
TOO_MANY_REQUESTS: 429,
|
|
91
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
92
|
+
NOT_IMPLEMENTED: 501,
|
|
93
|
+
BAD_GATEWAY: 502,
|
|
94
|
+
SERVICE_UNAVAILABLE: 503,
|
|
95
|
+
GATEWAY_TIMEOUT: 504,
|
|
96
|
+
HTTP_VERSION_NOT_SUPPORTED: 505
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// src/server/controller/index.ts
|
|
100
|
+
var controllerPrefixMap = /* @__PURE__ */ new WeakMap();
|
|
101
|
+
var controllerRoutesMap = /* @__PURE__ */ new WeakMap();
|
|
102
|
+
function createRouteDecorator(method) {
|
|
103
|
+
return (path3) => {
|
|
104
|
+
return (target, context) => {
|
|
105
|
+
const classTarget = context.static ? target : target.constructor;
|
|
106
|
+
const routes = controllerRoutesMap.get(classTarget) ?? [];
|
|
107
|
+
routes.push({ method, path: path3, handler: context.name });
|
|
108
|
+
controllerRoutesMap.set(classTarget, routes);
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
var get = createRouteDecorator("GET");
|
|
113
|
+
var post = createRouteDecorator("POST");
|
|
114
|
+
var put = createRouteDecorator("PUT");
|
|
115
|
+
var patch = createRouteDecorator("PATCH");
|
|
116
|
+
var del = createRouteDecorator("DELETE");
|
|
117
|
+
function getControllerPrefix(controllerClass) {
|
|
118
|
+
return controllerPrefixMap.get(controllerClass) ?? "";
|
|
119
|
+
}
|
|
120
|
+
function getControllerRoutes(controllerClass) {
|
|
121
|
+
return controllerRoutesMap.get(controllerClass) ?? [];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/server/engine/index.ts
|
|
125
|
+
import { createServer as createHttpServer } from "http";
|
|
126
|
+
|
|
127
|
+
// src/server/http/headers.ts
|
|
128
|
+
var HeadersMap = class {
|
|
129
|
+
data;
|
|
130
|
+
constructor(initial) {
|
|
131
|
+
this.data = /* @__PURE__ */ new Map();
|
|
132
|
+
if (initial) {
|
|
133
|
+
for (const key of Object.keys(initial)) {
|
|
134
|
+
const value = initial[key];
|
|
135
|
+
if (value !== void 0) {
|
|
136
|
+
this.set(key, Array.isArray(value) ? value.join(", ") : value);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
get(name) {
|
|
142
|
+
const values = this.data.get(name.toLowerCase());
|
|
143
|
+
if (values !== void 0 && values.length > 0) {
|
|
144
|
+
return values[0];
|
|
145
|
+
}
|
|
146
|
+
return void 0;
|
|
147
|
+
}
|
|
148
|
+
getAll(name) {
|
|
149
|
+
const values = this.data.get(name.toLowerCase());
|
|
150
|
+
return values ?? [];
|
|
151
|
+
}
|
|
152
|
+
set(name, value) {
|
|
153
|
+
this.data.set(name.toLowerCase(), [value]);
|
|
154
|
+
}
|
|
155
|
+
append(name, value) {
|
|
156
|
+
const key = name.toLowerCase();
|
|
157
|
+
const existing = this.data.get(key);
|
|
158
|
+
if (existing !== void 0) {
|
|
159
|
+
existing.push(value);
|
|
160
|
+
} else {
|
|
161
|
+
this.data.set(key, [value]);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
has(name) {
|
|
165
|
+
return this.data.has(name.toLowerCase());
|
|
166
|
+
}
|
|
167
|
+
delete(name) {
|
|
168
|
+
this.data.delete(name.toLowerCase());
|
|
169
|
+
}
|
|
170
|
+
*entries() {
|
|
171
|
+
for (const [key, values] of this.data) {
|
|
172
|
+
for (const value of values) {
|
|
173
|
+
yield [key, value];
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
*keys() {
|
|
178
|
+
for (const key of this.data.keys()) {
|
|
179
|
+
yield key;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
*values() {
|
|
183
|
+
for (const [, values] of this.data) {
|
|
184
|
+
for (const value of values) {
|
|
185
|
+
yield value;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
toJSON() {
|
|
190
|
+
const result = {};
|
|
191
|
+
for (const [key, values] of this.data) {
|
|
192
|
+
result[key] = values.length === 1 ? values[0] : values;
|
|
193
|
+
}
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
toNodeHeaders() {
|
|
197
|
+
const result = {};
|
|
198
|
+
for (const [key, values] of this.data) {
|
|
199
|
+
if (key === "set-cookie") {
|
|
200
|
+
result[key] = values;
|
|
201
|
+
} else {
|
|
202
|
+
result[key] = values.join(", ");
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
get size() {
|
|
208
|
+
return this.data.size;
|
|
209
|
+
}
|
|
210
|
+
[Symbol.iterator]() {
|
|
211
|
+
return this.entries();
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// src/server/http/cookie.ts
|
|
216
|
+
function parseCookies(header) {
|
|
217
|
+
const result = {};
|
|
218
|
+
if (!header) return result;
|
|
219
|
+
const pairs = header.split(";");
|
|
220
|
+
for (const pair of pairs) {
|
|
221
|
+
const trimmed = pair.trim();
|
|
222
|
+
if (!trimmed) continue;
|
|
223
|
+
const eqIndex = trimmed.indexOf("=");
|
|
224
|
+
if (eqIndex === -1) {
|
|
225
|
+
result[trimmed] = "";
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const name = trimmed.slice(0, eqIndex).trim();
|
|
229
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
230
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
231
|
+
value = value.slice(1, -1);
|
|
232
|
+
}
|
|
233
|
+
if (name) {
|
|
234
|
+
result[decodeURIComponent(name)] = decodeURIComponent(value);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
function serializeCookieValue(name, value) {
|
|
240
|
+
return `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
|
241
|
+
}
|
|
242
|
+
function serializeCookie(name, value, options) {
|
|
243
|
+
const parts = [serializeCookieValue(name, value)];
|
|
244
|
+
if (options) {
|
|
245
|
+
if (options.maxAge !== void 0) {
|
|
246
|
+
parts.push(`Max-Age=${Math.floor(options.maxAge)}`);
|
|
247
|
+
}
|
|
248
|
+
if (options.expires !== void 0) {
|
|
249
|
+
parts.push(`Expires=${options.expires.toUTCString()}`);
|
|
250
|
+
}
|
|
251
|
+
if (options.path !== void 0) {
|
|
252
|
+
parts.push(`Path=${options.path}`);
|
|
253
|
+
} else {
|
|
254
|
+
parts.push("Path=/");
|
|
255
|
+
}
|
|
256
|
+
if (options.domain !== void 0) {
|
|
257
|
+
parts.push(`Domain=${options.domain}`);
|
|
258
|
+
}
|
|
259
|
+
if (options.secure) {
|
|
260
|
+
parts.push("Secure");
|
|
261
|
+
}
|
|
262
|
+
if (options.httpOnly) {
|
|
263
|
+
parts.push("HttpOnly");
|
|
264
|
+
}
|
|
265
|
+
if (options.sameSite !== void 0) {
|
|
266
|
+
parts.push(`SameSite=${options.sameSite}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return parts.join("; ");
|
|
270
|
+
}
|
|
271
|
+
function clearCookie(name, options) {
|
|
272
|
+
return serializeCookie(name, "", {
|
|
273
|
+
...options,
|
|
274
|
+
maxAge: 0,
|
|
275
|
+
expires: /* @__PURE__ */ new Date(0)
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/server/http/upload.ts
|
|
280
|
+
import { randomUUID } from "crypto";
|
|
281
|
+
import { writeFile, readFile, unlink, mkdir } from "fs/promises";
|
|
282
|
+
import { join, extname, basename, dirname } from "path";
|
|
283
|
+
import { tmpdir } from "os";
|
|
284
|
+
var IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
285
|
+
"image/jpeg",
|
|
286
|
+
"image/png",
|
|
287
|
+
"image/gif",
|
|
288
|
+
"image/webp",
|
|
289
|
+
"image/svg+xml",
|
|
290
|
+
"image/bmp",
|
|
291
|
+
"image/tiff",
|
|
292
|
+
"image/avif"
|
|
293
|
+
]);
|
|
294
|
+
var VIDEO_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
295
|
+
"video/mp4",
|
|
296
|
+
"video/mpeg",
|
|
297
|
+
"video/webm",
|
|
298
|
+
"video/ogg",
|
|
299
|
+
"video/quicktime",
|
|
300
|
+
"video/x-msvideo",
|
|
301
|
+
"video/x-matroska"
|
|
302
|
+
]);
|
|
303
|
+
var SuperUploadedFile = class _SuperUploadedFile {
|
|
304
|
+
fieldName;
|
|
305
|
+
originalName;
|
|
306
|
+
mimeType;
|
|
307
|
+
size;
|
|
308
|
+
path;
|
|
309
|
+
extension;
|
|
310
|
+
buffer;
|
|
311
|
+
constructor(opts) {
|
|
312
|
+
this.fieldName = opts.fieldName;
|
|
313
|
+
this.originalName = opts.originalName;
|
|
314
|
+
this.mimeType = opts.mimeType;
|
|
315
|
+
this.size = opts.size;
|
|
316
|
+
this.path = opts.path;
|
|
317
|
+
this.extension = extname(opts.originalName).toLowerCase();
|
|
318
|
+
this.buffer = opts.buffer ?? null;
|
|
319
|
+
}
|
|
320
|
+
async move(destination, filename) {
|
|
321
|
+
const destName = filename ?? basename(this.path);
|
|
322
|
+
const destPath = join(destination, destName);
|
|
323
|
+
await mkdir(dirname(destPath), { recursive: true });
|
|
324
|
+
const buf = await this.toBuffer();
|
|
325
|
+
await writeFile(destPath, buf);
|
|
326
|
+
return destPath;
|
|
327
|
+
}
|
|
328
|
+
async toBuffer() {
|
|
329
|
+
if (this.buffer !== null) return this.buffer;
|
|
330
|
+
this.buffer = await readFile(this.path);
|
|
331
|
+
return this.buffer;
|
|
332
|
+
}
|
|
333
|
+
toBase64() {
|
|
334
|
+
if (this.buffer === null) {
|
|
335
|
+
throw new Error(
|
|
336
|
+
"Buffer not loaded. Call toBuffer() first or read the file into memory."
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
return this.buffer.toString("base64");
|
|
340
|
+
}
|
|
341
|
+
isImage() {
|
|
342
|
+
return IMAGE_MIME_TYPES.has(this.mimeType);
|
|
343
|
+
}
|
|
344
|
+
isVideo() {
|
|
345
|
+
return VIDEO_MIME_TYPES.has(this.mimeType);
|
|
346
|
+
}
|
|
347
|
+
static async createFromBuffer(fieldName, originalName, mimeType, buffer, tempDir) {
|
|
348
|
+
const tmp = tempDir ?? tmpdir();
|
|
349
|
+
const fileName = `${randomUUID()}${extname(originalName)}`;
|
|
350
|
+
const filePath = join(tmp, fileName);
|
|
351
|
+
await writeFile(filePath, buffer);
|
|
352
|
+
return new _SuperUploadedFile({
|
|
353
|
+
fieldName,
|
|
354
|
+
originalName,
|
|
355
|
+
mimeType: mimeType || "application/octet-stream",
|
|
356
|
+
size: buffer.length,
|
|
357
|
+
path: filePath,
|
|
358
|
+
buffer
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
async cleanup() {
|
|
362
|
+
try {
|
|
363
|
+
await unlink(this.path);
|
|
364
|
+
} catch {
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// src/server/http/request.ts
|
|
370
|
+
var SuperRequest = class {
|
|
371
|
+
raw;
|
|
372
|
+
_headers;
|
|
373
|
+
_query;
|
|
374
|
+
_cookies = null;
|
|
375
|
+
_ip;
|
|
376
|
+
_params = {};
|
|
377
|
+
bodyCache = null;
|
|
378
|
+
_bodyReadPromise = null;
|
|
379
|
+
constructor(raw) {
|
|
380
|
+
this.raw = raw;
|
|
381
|
+
this._headers = new HeadersMap(
|
|
382
|
+
raw.headers
|
|
383
|
+
);
|
|
384
|
+
const parsedUrl = new URL(raw.url ?? "/", "http://localhost");
|
|
385
|
+
this._query = parseQueryParams(parsedUrl.searchParams);
|
|
386
|
+
this._ip = parseIp(raw);
|
|
387
|
+
this.path = parsedUrl.pathname;
|
|
388
|
+
this.url = raw.url ?? "/";
|
|
389
|
+
this.method = (raw.method ?? "GET").toUpperCase();
|
|
390
|
+
}
|
|
391
|
+
method;
|
|
392
|
+
url;
|
|
393
|
+
path;
|
|
394
|
+
get headers() {
|
|
395
|
+
return this._headers;
|
|
396
|
+
}
|
|
397
|
+
get query() {
|
|
398
|
+
return this._query;
|
|
399
|
+
}
|
|
400
|
+
get params() {
|
|
401
|
+
return this._params;
|
|
402
|
+
}
|
|
403
|
+
set params(value) {
|
|
404
|
+
this._params = value;
|
|
405
|
+
}
|
|
406
|
+
get ip() {
|
|
407
|
+
return this._ip;
|
|
408
|
+
}
|
|
409
|
+
async ensureBody() {
|
|
410
|
+
if (this.bodyCache !== null) return this.bodyCache;
|
|
411
|
+
if (this._bodyReadPromise === null) {
|
|
412
|
+
this._bodyReadPromise = this.readBodyFromStream();
|
|
413
|
+
}
|
|
414
|
+
this.bodyCache = await this._bodyReadPromise;
|
|
415
|
+
return this.bodyCache;
|
|
416
|
+
}
|
|
417
|
+
async readBodyFromStream() {
|
|
418
|
+
const chunks = [];
|
|
419
|
+
for await (const chunk of this.raw) {
|
|
420
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
421
|
+
}
|
|
422
|
+
const raw = Buffer.concat(chunks);
|
|
423
|
+
const text = raw.toString("utf-8");
|
|
424
|
+
let json = void 0;
|
|
425
|
+
if (this.isContentType("application/json")) {
|
|
426
|
+
try {
|
|
427
|
+
json = JSON.parse(text);
|
|
428
|
+
} catch {
|
|
429
|
+
json = void 0;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
let formData = null;
|
|
433
|
+
let multipartParsed = null;
|
|
434
|
+
let files = null;
|
|
435
|
+
if (this.isContentType("application/x-www-form-urlencoded")) {
|
|
436
|
+
formData = parseUrlEncoded(text);
|
|
437
|
+
} else if (this.isContentType("multipart/form-data")) {
|
|
438
|
+
const boundary = this.getMultipartBoundary();
|
|
439
|
+
if (boundary !== void 0) {
|
|
440
|
+
multipartParsed = parseMultipartBody(raw, boundary);
|
|
441
|
+
formData = {};
|
|
442
|
+
files = {};
|
|
443
|
+
for (const part of multipartParsed) {
|
|
444
|
+
if (part.type === "field") {
|
|
445
|
+
formData[part.name] = part.value;
|
|
446
|
+
} else if (part.type === "file") {
|
|
447
|
+
const uploadedFile = await SuperUploadedFile.createFromBuffer(
|
|
448
|
+
part.name,
|
|
449
|
+
part.filename,
|
|
450
|
+
part.mimeType,
|
|
451
|
+
part.data
|
|
452
|
+
);
|
|
453
|
+
files[part.name] = uploadedFile;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
raw,
|
|
460
|
+
text,
|
|
461
|
+
json,
|
|
462
|
+
parsed: json ?? text,
|
|
463
|
+
formData,
|
|
464
|
+
files,
|
|
465
|
+
multipartParsed
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
isContentType(expected) {
|
|
469
|
+
const ct = this._headers.get("content-type");
|
|
470
|
+
if (ct === void 0) return false;
|
|
471
|
+
return ct.toLowerCase().startsWith(expected);
|
|
472
|
+
}
|
|
473
|
+
getMultipartBoundary() {
|
|
474
|
+
const ct = this._headers.get("content-type");
|
|
475
|
+
if (ct === void 0) return void 0;
|
|
476
|
+
const match = ct.match(/boundary=(?:"([^"]+)"|([^;]+))/i);
|
|
477
|
+
if (match !== null) {
|
|
478
|
+
return match[1] ?? match[2];
|
|
479
|
+
}
|
|
480
|
+
return void 0;
|
|
481
|
+
}
|
|
482
|
+
async body() {
|
|
483
|
+
const cache = await this.ensureBody();
|
|
484
|
+
return cache.parsed;
|
|
485
|
+
}
|
|
486
|
+
async json() {
|
|
487
|
+
const cache = await this.ensureBody();
|
|
488
|
+
if (cache.json !== void 0) return cache.json;
|
|
489
|
+
throw new Error("Request body is not valid JSON");
|
|
490
|
+
}
|
|
491
|
+
async text() {
|
|
492
|
+
const cache = await this.ensureBody();
|
|
493
|
+
return cache.text;
|
|
494
|
+
}
|
|
495
|
+
async formData() {
|
|
496
|
+
const cache = await this.ensureBody();
|
|
497
|
+
if (cache.formData !== null) return cache.formData;
|
|
498
|
+
if (cache.multipartParsed !== null) {
|
|
499
|
+
const result = {};
|
|
500
|
+
for (const part of cache.multipartParsed) {
|
|
501
|
+
if (part.type === "field") {
|
|
502
|
+
result[part.name] = part.value;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return result;
|
|
506
|
+
}
|
|
507
|
+
return {};
|
|
508
|
+
}
|
|
509
|
+
async file(name) {
|
|
510
|
+
const cache = await this.ensureBody();
|
|
511
|
+
if (cache.files !== null) {
|
|
512
|
+
return cache.files[name];
|
|
513
|
+
}
|
|
514
|
+
return void 0;
|
|
515
|
+
}
|
|
516
|
+
async files() {
|
|
517
|
+
const cache = await this.ensureBody();
|
|
518
|
+
return cache.files ?? {};
|
|
519
|
+
}
|
|
520
|
+
cookie(name) {
|
|
521
|
+
if (this._cookies === null) {
|
|
522
|
+
const cookieHeader = this._headers.get("cookie");
|
|
523
|
+
this._cookies = cookieHeader !== void 0 ? parseCookies(cookieHeader) : {};
|
|
524
|
+
}
|
|
525
|
+
return this._cookies[name];
|
|
526
|
+
}
|
|
527
|
+
async validate(schema) {
|
|
528
|
+
const data = await this.body();
|
|
529
|
+
const result = schema.validate(data);
|
|
530
|
+
if (!result.success) {
|
|
531
|
+
const messages = (result.errors ?? []).map((e) => {
|
|
532
|
+
const path3 = e.path ?? "";
|
|
533
|
+
return path3 ? `${path3}: ${e.message}` : e.message;
|
|
534
|
+
}).join("; ");
|
|
535
|
+
throw new ValidationError(messages, result.errors ?? []);
|
|
536
|
+
}
|
|
537
|
+
return result.data;
|
|
538
|
+
}
|
|
539
|
+
isAjax() {
|
|
540
|
+
const requestedWith = this._headers.get("x-requested-with");
|
|
541
|
+
return requestedWith?.toLowerCase() === "xmlhttprequest";
|
|
542
|
+
}
|
|
543
|
+
wantsJson() {
|
|
544
|
+
const accept = this._headers.get("accept");
|
|
545
|
+
if (accept !== void 0 && accept.includes("application/json")) {
|
|
546
|
+
return true;
|
|
547
|
+
}
|
|
548
|
+
return this.isAjax();
|
|
549
|
+
}
|
|
550
|
+
bearerToken() {
|
|
551
|
+
const auth = this._headers.get("authorization");
|
|
552
|
+
if (auth === void 0) return void 0;
|
|
553
|
+
const match = auth.match(/^Bearer\s+(.+)$/i);
|
|
554
|
+
return match?.[1];
|
|
555
|
+
}
|
|
556
|
+
get rawRequest() {
|
|
557
|
+
return this.raw;
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
var ValidationError = class extends Error {
|
|
561
|
+
constructor(message, errors) {
|
|
562
|
+
super(message);
|
|
563
|
+
this.errors = errors;
|
|
564
|
+
this.name = "ValidationError";
|
|
565
|
+
}
|
|
566
|
+
errors;
|
|
567
|
+
};
|
|
568
|
+
function parseQueryParams(searchParams) {
|
|
569
|
+
const result = {};
|
|
570
|
+
for (const key of searchParams.keys()) {
|
|
571
|
+
const values = searchParams.getAll(key);
|
|
572
|
+
result[key] = values.length === 1 ? values[0] : values;
|
|
573
|
+
}
|
|
574
|
+
return result;
|
|
575
|
+
}
|
|
576
|
+
function parseIp(req) {
|
|
577
|
+
const forwarded = req.headers["x-forwarded-for"];
|
|
578
|
+
if (forwarded !== void 0) {
|
|
579
|
+
const first = Array.isArray(forwarded) ? forwarded[0] : forwarded.split(",")[0];
|
|
580
|
+
if (first !== void 0) return first.trim();
|
|
581
|
+
}
|
|
582
|
+
const realIp = req.headers["x-real-ip"];
|
|
583
|
+
if (realIp !== void 0) {
|
|
584
|
+
return Array.isArray(realIp) ? realIp[0] : realIp;
|
|
585
|
+
}
|
|
586
|
+
const remote = req.socket.remoteAddress;
|
|
587
|
+
if (remote !== void 0) {
|
|
588
|
+
if (remote.startsWith("::ffff:")) return remote.slice(7);
|
|
589
|
+
return remote;
|
|
590
|
+
}
|
|
591
|
+
return "127.0.0.1";
|
|
592
|
+
}
|
|
593
|
+
function parseUrlEncoded(text) {
|
|
594
|
+
const result = {};
|
|
595
|
+
const pairs = text.split("&");
|
|
596
|
+
for (const pair of pairs) {
|
|
597
|
+
if (!pair) continue;
|
|
598
|
+
const eqIndex = pair.indexOf("=");
|
|
599
|
+
if (eqIndex === -1) {
|
|
600
|
+
result[decodeURIComponent(pair)] = "";
|
|
601
|
+
} else {
|
|
602
|
+
const key = decodeURIComponent(pair.slice(0, eqIndex));
|
|
603
|
+
const value = decodeURIComponent(pair.slice(eqIndex + 1));
|
|
604
|
+
result[key] = value;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return result;
|
|
608
|
+
}
|
|
609
|
+
function parseMultipartBody(body, boundary) {
|
|
610
|
+
const result = [];
|
|
611
|
+
const boundaryBuffer = Buffer.from(`--${boundary}`);
|
|
612
|
+
let start = 0;
|
|
613
|
+
let searchFrom = 0;
|
|
614
|
+
while (true) {
|
|
615
|
+
const boundaryIndex = body.indexOf(boundaryBuffer, searchFrom);
|
|
616
|
+
if (boundaryIndex === -1) break;
|
|
617
|
+
searchFrom = boundaryIndex + boundaryBuffer.length;
|
|
618
|
+
if (searchFrom < body.length && body[searchFrom] === 45 && body[searchFrom + 1] === 45) {
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
if (searchFrom >= body.length) break;
|
|
622
|
+
if (body[searchFrom] === 13) searchFrom++;
|
|
623
|
+
if (body[searchFrom] === 10) searchFrom++;
|
|
624
|
+
start = searchFrom;
|
|
625
|
+
const nextBoundaryIndex = body.indexOf(boundaryBuffer, searchFrom);
|
|
626
|
+
if (nextBoundaryIndex === -1) break;
|
|
627
|
+
let partEnd = nextBoundaryIndex;
|
|
628
|
+
if (partEnd >= 2 && body[partEnd - 2] === 13 && body[partEnd - 1] === 10) {
|
|
629
|
+
partEnd -= 2;
|
|
630
|
+
}
|
|
631
|
+
const partData = body.slice(start, partEnd);
|
|
632
|
+
const headerEndIndex = partData.indexOf(Buffer.from("\r\n\r\n"));
|
|
633
|
+
if (headerEndIndex === -1) {
|
|
634
|
+
searchFrom = nextBoundaryIndex;
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
const headerSection = partData.slice(0, headerEndIndex).toString("utf-8");
|
|
638
|
+
const contentData = partData.slice(headerEndIndex + 4);
|
|
639
|
+
const disposition = parseDisposition(headerSection);
|
|
640
|
+
if (disposition === void 0) {
|
|
641
|
+
searchFrom = nextBoundaryIndex;
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
if (disposition.filename !== void 0) {
|
|
645
|
+
const contentType = parseContentType(headerSection);
|
|
646
|
+
result.push({
|
|
647
|
+
type: "file",
|
|
648
|
+
name: disposition.name,
|
|
649
|
+
filename: disposition.filename,
|
|
650
|
+
mimeType: contentType,
|
|
651
|
+
data: Buffer.from(contentData)
|
|
652
|
+
});
|
|
653
|
+
} else {
|
|
654
|
+
result.push({
|
|
655
|
+
type: "field",
|
|
656
|
+
name: disposition.name,
|
|
657
|
+
value: contentData.toString("utf-8").trim()
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
searchFrom = nextBoundaryIndex;
|
|
661
|
+
}
|
|
662
|
+
return result;
|
|
663
|
+
}
|
|
664
|
+
function parseDisposition(headerSection) {
|
|
665
|
+
const match = headerSection.match(
|
|
666
|
+
/content-disposition:\s*form-data;\s*(.+)/i
|
|
667
|
+
);
|
|
668
|
+
if (match === null) return void 0;
|
|
669
|
+
const params = match[1];
|
|
670
|
+
const nameMatch = params.match(/name="([^"]*)"/i);
|
|
671
|
+
const filenameMatch = params.match(/filename="([^"]*)"/i);
|
|
672
|
+
if (nameMatch === null) return void 0;
|
|
673
|
+
return {
|
|
674
|
+
name: nameMatch[1],
|
|
675
|
+
filename: filenameMatch?.[1]
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
function parseContentType(headerSection) {
|
|
679
|
+
const match = headerSection.match(/content-type:\s*([^\s;]+)/i);
|
|
680
|
+
return match?.[1] ?? "application/octet-stream";
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// src/server/http/response.ts
|
|
684
|
+
import { createReadStream, stat } from "fs";
|
|
685
|
+
import { basename as basename2, extname as extname2 } from "path";
|
|
686
|
+
import { promisify } from "util";
|
|
687
|
+
var statAsync = promisify(stat);
|
|
688
|
+
var MIME_TYPES = {
|
|
689
|
+
".html": "text/html",
|
|
690
|
+
".css": "text/css",
|
|
691
|
+
".js": "application/javascript",
|
|
692
|
+
".mjs": "application/javascript",
|
|
693
|
+
".json": "application/json",
|
|
694
|
+
".png": "image/png",
|
|
695
|
+
".jpg": "image/jpeg",
|
|
696
|
+
".jpeg": "image/jpeg",
|
|
697
|
+
".gif": "image/gif",
|
|
698
|
+
".svg": "image/svg+xml",
|
|
699
|
+
".webp": "image/webp",
|
|
700
|
+
".ico": "image/x-icon",
|
|
701
|
+
".pdf": "application/pdf",
|
|
702
|
+
".txt": "text/plain",
|
|
703
|
+
".xml": "application/xml",
|
|
704
|
+
".zip": "application/zip",
|
|
705
|
+
".woff": "font/woff",
|
|
706
|
+
".woff2": "font/woff2",
|
|
707
|
+
".ttf": "font/ttf",
|
|
708
|
+
".eot": "application/vnd.ms-fontobject",
|
|
709
|
+
".mp4": "video/mp4",
|
|
710
|
+
".webm": "video/webm",
|
|
711
|
+
".mp3": "audio/mpeg",
|
|
712
|
+
".wav": "audio/wav"
|
|
713
|
+
};
|
|
714
|
+
var SuperResponse = class {
|
|
715
|
+
raw;
|
|
716
|
+
_statusCode = HttpStatus.OK;
|
|
717
|
+
_headers = new HeadersMap();
|
|
718
|
+
_cookies = [];
|
|
719
|
+
_body = null;
|
|
720
|
+
_sent = false;
|
|
721
|
+
_contentTypeSet = false;
|
|
722
|
+
constructor(raw) {
|
|
723
|
+
this.raw = raw;
|
|
724
|
+
}
|
|
725
|
+
status(code) {
|
|
726
|
+
this._statusCode = code;
|
|
727
|
+
return this;
|
|
728
|
+
}
|
|
729
|
+
header(name, value) {
|
|
730
|
+
this._headers.set(name, value);
|
|
731
|
+
return this;
|
|
732
|
+
}
|
|
733
|
+
setHeader(name, value) {
|
|
734
|
+
return this.header(name, value);
|
|
735
|
+
}
|
|
736
|
+
getHeader(name) {
|
|
737
|
+
return this._headers.get(name);
|
|
738
|
+
}
|
|
739
|
+
removeHeader(name) {
|
|
740
|
+
this._headers.delete(name);
|
|
741
|
+
return this;
|
|
742
|
+
}
|
|
743
|
+
hasHeader(name) {
|
|
744
|
+
return this._headers.has(name);
|
|
745
|
+
}
|
|
746
|
+
type(contentType) {
|
|
747
|
+
this._headers.set("content-type", contentType);
|
|
748
|
+
this._contentTypeSet = true;
|
|
749
|
+
return this;
|
|
750
|
+
}
|
|
751
|
+
json(data, status) {
|
|
752
|
+
if (status !== void 0) this._statusCode = status;
|
|
753
|
+
const body = JSON.stringify(data);
|
|
754
|
+
return this.send(body, void 0, "application/json");
|
|
755
|
+
}
|
|
756
|
+
send(body, status, contentType) {
|
|
757
|
+
if (status !== void 0) this._statusCode = status;
|
|
758
|
+
this._body = body;
|
|
759
|
+
if (contentType !== void 0 && !this._contentTypeSet) {
|
|
760
|
+
this._headers.set("content-type", contentType);
|
|
761
|
+
}
|
|
762
|
+
return this;
|
|
763
|
+
}
|
|
764
|
+
html(html, status) {
|
|
765
|
+
return this.send(html, status, "text/html; charset=utf-8");
|
|
766
|
+
}
|
|
767
|
+
redirect(url2, status = HttpStatus.FOUND) {
|
|
768
|
+
this._statusCode = status;
|
|
769
|
+
this._headers.set("location", url2);
|
|
770
|
+
this._body = null;
|
|
771
|
+
return this;
|
|
772
|
+
}
|
|
773
|
+
stream(stream, status) {
|
|
774
|
+
if (status !== void 0) this._statusCode = status;
|
|
775
|
+
this.flushHeaders();
|
|
776
|
+
this.raw.statusCode = this._statusCode;
|
|
777
|
+
stream.pipe(this.raw);
|
|
778
|
+
stream.on("end", () => {
|
|
779
|
+
this._sent = true;
|
|
780
|
+
});
|
|
781
|
+
stream.on("error", (_err) => {
|
|
782
|
+
if (!this._sent) {
|
|
783
|
+
this._sent = true;
|
|
784
|
+
this.raw.statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
|
|
785
|
+
this.raw.end();
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
return this;
|
|
789
|
+
}
|
|
790
|
+
async file(filePath, options) {
|
|
791
|
+
const fullPath = options?.root ? joinPath(options.root, filePath) : filePath;
|
|
792
|
+
try {
|
|
793
|
+
const stats = await statAsync(fullPath);
|
|
794
|
+
if (!stats.isFile()) {
|
|
795
|
+
this._statusCode = HttpStatus.NOT_FOUND;
|
|
796
|
+
this._body = null;
|
|
797
|
+
return this;
|
|
798
|
+
}
|
|
799
|
+
const ext = extname2(fullPath);
|
|
800
|
+
const mimeType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
801
|
+
this._headers.set("content-type", mimeType);
|
|
802
|
+
this._headers.set("content-length", String(stats.size));
|
|
803
|
+
if (options?.cacheControl !== false) {
|
|
804
|
+
const maxAge = options?.maxAge ?? 0;
|
|
805
|
+
this._headers.set("cache-control", `public, max-age=${maxAge}`);
|
|
806
|
+
}
|
|
807
|
+
if (options?.lastModified !== false) {
|
|
808
|
+
this._headers.set("last-modified", stats.mtime.toUTCString());
|
|
809
|
+
}
|
|
810
|
+
if (options?.headers !== void 0) {
|
|
811
|
+
for (const [key, value] of Object.entries(options.headers)) {
|
|
812
|
+
this._headers.set(key, value);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
this.flushHeaders();
|
|
816
|
+
this.raw.statusCode = this._statusCode;
|
|
817
|
+
const readStream = createReadStream(fullPath);
|
|
818
|
+
readStream.pipe(this.raw);
|
|
819
|
+
readStream.on("end", () => {
|
|
820
|
+
this._sent = true;
|
|
821
|
+
});
|
|
822
|
+
readStream.on("error", () => {
|
|
823
|
+
if (!this._sent) {
|
|
824
|
+
this._sent = true;
|
|
825
|
+
this.raw.statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
|
|
826
|
+
this.raw.end();
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
} catch {
|
|
830
|
+
this._statusCode = HttpStatus.NOT_FOUND;
|
|
831
|
+
this._body = null;
|
|
832
|
+
}
|
|
833
|
+
return this;
|
|
834
|
+
}
|
|
835
|
+
async download(filePath, filename) {
|
|
836
|
+
const downloadName = filename ?? basename2(filePath);
|
|
837
|
+
this._headers.set(
|
|
838
|
+
"content-disposition",
|
|
839
|
+
`attachment; filename="${downloadName}"`
|
|
840
|
+
);
|
|
841
|
+
await this.file(filePath);
|
|
842
|
+
return this;
|
|
843
|
+
}
|
|
844
|
+
attachment(filename) {
|
|
845
|
+
if (filename !== void 0) {
|
|
846
|
+
this._headers.set(
|
|
847
|
+
"content-disposition",
|
|
848
|
+
`attachment; filename="${filename}"`
|
|
849
|
+
);
|
|
850
|
+
} else {
|
|
851
|
+
this._headers.set("content-disposition", "attachment");
|
|
852
|
+
}
|
|
853
|
+
return this;
|
|
854
|
+
}
|
|
855
|
+
cookie(name, value, options) {
|
|
856
|
+
this._cookies.push(serializeCookie(name, value, options));
|
|
857
|
+
return this;
|
|
858
|
+
}
|
|
859
|
+
clearCookie(name, options) {
|
|
860
|
+
this._cookies.push(clearCookie(name, options));
|
|
861
|
+
return this;
|
|
862
|
+
}
|
|
863
|
+
get statusCode() {
|
|
864
|
+
return this._statusCode;
|
|
865
|
+
}
|
|
866
|
+
get headersSent() {
|
|
867
|
+
return this._sent;
|
|
868
|
+
}
|
|
869
|
+
get rawResponse() {
|
|
870
|
+
return this.raw;
|
|
871
|
+
}
|
|
872
|
+
async flush() {
|
|
873
|
+
if (this._sent) return;
|
|
874
|
+
this._sent = true;
|
|
875
|
+
this.flushHeaders();
|
|
876
|
+
this.raw.statusCode = this._statusCode;
|
|
877
|
+
if (this._body !== null) {
|
|
878
|
+
this.raw.end(this._body);
|
|
879
|
+
} else {
|
|
880
|
+
this.raw.end();
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
flushHeaders() {
|
|
884
|
+
if (this._cookies.length > 0) {
|
|
885
|
+
this.raw.setHeader("Set-Cookie", this._cookies);
|
|
886
|
+
}
|
|
887
|
+
for (const [name, value] of this._headers.entries()) {
|
|
888
|
+
if (name.toLowerCase() !== "set-cookie") {
|
|
889
|
+
this.raw.setHeader(name, value);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
function joinPath(root, filePath) {
|
|
895
|
+
const normalizedRoot = root.replace(/\\/g, "/").replace(/\/$/, "");
|
|
896
|
+
const normalizedPath = filePath.replace(/\\/g, "/").replace(/^\//, "");
|
|
897
|
+
return `${normalizedRoot}/${normalizedPath}`;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// src/server/engine/index.ts
|
|
901
|
+
var NodeEngine = class {
|
|
902
|
+
async createServer(handler) {
|
|
903
|
+
const server = createHttpServer(async (nodeReq, nodeRes) => {
|
|
904
|
+
const req = new SuperRequest(nodeReq);
|
|
905
|
+
const res = new SuperResponse(nodeRes);
|
|
906
|
+
try {
|
|
907
|
+
await handler(req, res);
|
|
908
|
+
} catch (_err) {
|
|
909
|
+
if (!res.headersSent) {
|
|
910
|
+
const message = _err instanceof Error ? _err.message : "Internal Server Error";
|
|
911
|
+
res.status(500).json({ error: message });
|
|
912
|
+
await res.flush();
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
if (!res.headersSent) {
|
|
916
|
+
await res.flush();
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
return {
|
|
920
|
+
raw: server,
|
|
921
|
+
close: () => {
|
|
922
|
+
return new Promise((resolve2, reject) => {
|
|
923
|
+
server.close((err) => {
|
|
924
|
+
if (err !== void 0 && err !== null) {
|
|
925
|
+
reject(err);
|
|
926
|
+
} else {
|
|
927
|
+
resolve2();
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
getPort(server) {
|
|
935
|
+
const addr = server.raw.address();
|
|
936
|
+
if (addr !== null && typeof addr === "object") {
|
|
937
|
+
return addr.port;
|
|
938
|
+
}
|
|
939
|
+
return 0;
|
|
940
|
+
}
|
|
941
|
+
async close(server) {
|
|
942
|
+
await server.close();
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
// src/server/middleware/index.ts
|
|
947
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
948
|
+
import { createReadStream as createReadStream2, existsSync, statSync } from "fs";
|
|
949
|
+
import { extname as extname3, join as join2 } from "path";
|
|
950
|
+
import { createGzip } from "zlib";
|
|
951
|
+
function staticFiles(root, options) {
|
|
952
|
+
const opts = {
|
|
953
|
+
maxAge: 0,
|
|
954
|
+
index: "index.html",
|
|
955
|
+
dotfiles: "deny",
|
|
956
|
+
extensions: [],
|
|
957
|
+
...options
|
|
958
|
+
};
|
|
959
|
+
const MIME_TYPES2 = {
|
|
960
|
+
".html": "text/html",
|
|
961
|
+
".css": "text/css",
|
|
962
|
+
".js": "application/javascript",
|
|
963
|
+
".json": "application/json",
|
|
964
|
+
".png": "image/png",
|
|
965
|
+
".jpg": "image/jpeg",
|
|
966
|
+
".jpeg": "image/jpeg",
|
|
967
|
+
".gif": "image/gif",
|
|
968
|
+
".svg": "image/svg+xml",
|
|
969
|
+
".webp": "image/webp",
|
|
970
|
+
".ico": "image/x-icon",
|
|
971
|
+
".txt": "text/plain",
|
|
972
|
+
".pdf": "application/pdf",
|
|
973
|
+
".woff": "font/woff",
|
|
974
|
+
".woff2": "font/woff2",
|
|
975
|
+
".ttf": "font/ttf"
|
|
976
|
+
};
|
|
977
|
+
return (ctx, next) => {
|
|
978
|
+
const { request, response } = ctx;
|
|
979
|
+
const filePath = request.path;
|
|
980
|
+
if (opts.dotfiles === "deny") {
|
|
981
|
+
const segments = filePath.split("/");
|
|
982
|
+
for (const segment of segments) {
|
|
983
|
+
if (segment.startsWith(".")) {
|
|
984
|
+
return next();
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
let fullPath = join2(root, filePath);
|
|
989
|
+
if (!existsSync(fullPath)) {
|
|
990
|
+
let found = false;
|
|
991
|
+
for (const ext2 of opts.extensions) {
|
|
992
|
+
const tryPath = fullPath + ext2;
|
|
993
|
+
if (existsSync(tryPath)) {
|
|
994
|
+
fullPath = tryPath;
|
|
995
|
+
found = true;
|
|
996
|
+
break;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
if (!found) return next();
|
|
1000
|
+
}
|
|
1001
|
+
const stats = statSync(fullPath);
|
|
1002
|
+
if (!stats.isFile()) {
|
|
1003
|
+
if (stats.isDirectory()) {
|
|
1004
|
+
const indexPath = join2(fullPath, opts.index);
|
|
1005
|
+
if (existsSync(indexPath) && statSync(indexPath).isFile()) {
|
|
1006
|
+
fullPath = indexPath;
|
|
1007
|
+
} else {
|
|
1008
|
+
return next();
|
|
1009
|
+
}
|
|
1010
|
+
} else {
|
|
1011
|
+
return next();
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
const ext = extname3(fullPath);
|
|
1015
|
+
const mimeType = MIME_TYPES2[ext] ?? "application/octet-stream";
|
|
1016
|
+
response.header("content-type", mimeType).header("content-length", String(stats.size)).header("cache-control", `public, max-age=${opts.maxAge}`).header("last-modified", stats.mtime.toUTCString());
|
|
1017
|
+
const readStream = createReadStream2(fullPath);
|
|
1018
|
+
readStream.pipe(response.rawResponse);
|
|
1019
|
+
response.rawResponse.statusCode = HttpStatus.OK;
|
|
1020
|
+
return new Promise((resolve2, reject) => {
|
|
1021
|
+
readStream.on("end", () => resolve2());
|
|
1022
|
+
readStream.on("error", (err) => reject(err));
|
|
1023
|
+
});
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
var MiddlewarePipeline = class {
|
|
1027
|
+
middlewares = [];
|
|
1028
|
+
use(middleware) {
|
|
1029
|
+
this.middlewares.push(middleware);
|
|
1030
|
+
return this;
|
|
1031
|
+
}
|
|
1032
|
+
prepend(middleware) {
|
|
1033
|
+
this.middlewares.unshift(middleware);
|
|
1034
|
+
return this;
|
|
1035
|
+
}
|
|
1036
|
+
remove(name) {
|
|
1037
|
+
this.middlewares = this.middlewares.filter((mw) => mw.name !== name);
|
|
1038
|
+
}
|
|
1039
|
+
async run(ctx, final) {
|
|
1040
|
+
let index = 0;
|
|
1041
|
+
const next = async () => {
|
|
1042
|
+
if (index >= this.middlewares.length) {
|
|
1043
|
+
await final();
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
const middleware = this.middlewares[index];
|
|
1047
|
+
index++;
|
|
1048
|
+
await middleware(ctx, next);
|
|
1049
|
+
};
|
|
1050
|
+
await next();
|
|
1051
|
+
}
|
|
1052
|
+
getMiddlewares() {
|
|
1053
|
+
return [...this.middlewares];
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1057
|
+
// src/server/router/index.ts
|
|
1058
|
+
var Router = class {
|
|
1059
|
+
routes = [];
|
|
1060
|
+
groupMiddleware = [];
|
|
1061
|
+
groupPrefix = "";
|
|
1062
|
+
namedRoutes = /* @__PURE__ */ new Map();
|
|
1063
|
+
get(path3, handler) {
|
|
1064
|
+
return this.match(["GET"], path3, handler);
|
|
1065
|
+
}
|
|
1066
|
+
post(path3, handler) {
|
|
1067
|
+
return this.match(["POST"], path3, handler);
|
|
1068
|
+
}
|
|
1069
|
+
put(path3, handler) {
|
|
1070
|
+
return this.match(["PUT"], path3, handler);
|
|
1071
|
+
}
|
|
1072
|
+
patch(path3, handler) {
|
|
1073
|
+
return this.match(["PATCH"], path3, handler);
|
|
1074
|
+
}
|
|
1075
|
+
delete(path3, handler) {
|
|
1076
|
+
return this.match(["DELETE"], path3, handler);
|
|
1077
|
+
}
|
|
1078
|
+
options(path3, handler) {
|
|
1079
|
+
return this.match(["OPTIONS"], path3, handler);
|
|
1080
|
+
}
|
|
1081
|
+
any(path3, handler) {
|
|
1082
|
+
return this.match(
|
|
1083
|
+
["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
|
1084
|
+
path3,
|
|
1085
|
+
handler
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
match(methods, path3, handler) {
|
|
1089
|
+
const fullPath = this.groupPrefix + normalizePath(path3);
|
|
1090
|
+
const { regexp, keys } = pathToRegexp(fullPath);
|
|
1091
|
+
this.routes.push({
|
|
1092
|
+
methods: methods.map((m) => m.toUpperCase()),
|
|
1093
|
+
path: fullPath,
|
|
1094
|
+
handler,
|
|
1095
|
+
middleware: [...this.groupMiddleware],
|
|
1096
|
+
regexp,
|
|
1097
|
+
paramKeys: keys
|
|
1098
|
+
});
|
|
1099
|
+
return this;
|
|
1100
|
+
}
|
|
1101
|
+
group(prefix, callback) {
|
|
1102
|
+
const previousPrefix = this.groupPrefix;
|
|
1103
|
+
const previousMiddleware = [...this.groupMiddleware];
|
|
1104
|
+
this.groupPrefix = previousPrefix + normalizePath(prefix);
|
|
1105
|
+
callback(this);
|
|
1106
|
+
this.groupPrefix = previousPrefix;
|
|
1107
|
+
this.groupMiddleware = previousMiddleware;
|
|
1108
|
+
return this;
|
|
1109
|
+
}
|
|
1110
|
+
resource(name, controller) {
|
|
1111
|
+
const actions = {
|
|
1112
|
+
index: "index",
|
|
1113
|
+
create: "create",
|
|
1114
|
+
store: "store",
|
|
1115
|
+
show: "show",
|
|
1116
|
+
edit: "edit",
|
|
1117
|
+
update: "update",
|
|
1118
|
+
destroy: "destroy"
|
|
1119
|
+
};
|
|
1120
|
+
return this.registerResourceRoutes(name, controller, actions);
|
|
1121
|
+
}
|
|
1122
|
+
apiResource(name, controller) {
|
|
1123
|
+
const actions = {
|
|
1124
|
+
index: "index",
|
|
1125
|
+
store: "store",
|
|
1126
|
+
show: "show",
|
|
1127
|
+
update: "update",
|
|
1128
|
+
destroy: "destroy"
|
|
1129
|
+
};
|
|
1130
|
+
return this.registerResourceRoutes(name, controller, actions);
|
|
1131
|
+
}
|
|
1132
|
+
registerResourceRoutes(name, controller, actions) {
|
|
1133
|
+
const basePath = normalizePath(name);
|
|
1134
|
+
const paramName = singularize(name);
|
|
1135
|
+
if (actions.index !== void 0) {
|
|
1136
|
+
this.get(
|
|
1137
|
+
basePath,
|
|
1138
|
+
this.createControllerHandler(controller, actions.index)
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
if (actions.create !== void 0) {
|
|
1142
|
+
this.get(
|
|
1143
|
+
`${basePath}/create`,
|
|
1144
|
+
this.createControllerHandler(controller, actions.create)
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
if (actions.store !== void 0) {
|
|
1148
|
+
this.post(
|
|
1149
|
+
basePath,
|
|
1150
|
+
this.createControllerHandler(controller, actions.store)
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
if (actions.show !== void 0) {
|
|
1154
|
+
this.get(
|
|
1155
|
+
`${basePath}/:${paramName}`,
|
|
1156
|
+
this.createControllerHandler(controller, actions.show)
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
if (actions.edit !== void 0) {
|
|
1160
|
+
this.get(
|
|
1161
|
+
`${basePath}/:${paramName}/edit`,
|
|
1162
|
+
this.createControllerHandler(controller, actions.edit)
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
if (actions.update !== void 0) {
|
|
1166
|
+
this.put(
|
|
1167
|
+
`${basePath}/:${paramName}`,
|
|
1168
|
+
this.createControllerHandler(controller, actions.update)
|
|
1169
|
+
);
|
|
1170
|
+
this.patch(
|
|
1171
|
+
`${basePath}/:${paramName}`,
|
|
1172
|
+
this.createControllerHandler(controller, actions.update)
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
if (actions.destroy !== void 0) {
|
|
1176
|
+
this.delete(
|
|
1177
|
+
`${basePath}/:${paramName}`,
|
|
1178
|
+
this.createControllerHandler(controller, actions.destroy)
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
return this;
|
|
1182
|
+
}
|
|
1183
|
+
createControllerHandler(controller, action) {
|
|
1184
|
+
return async (ctx) => {
|
|
1185
|
+
const instance = createControllerInstance(controller, ctx);
|
|
1186
|
+
const handler = instance[action];
|
|
1187
|
+
if (typeof handler === "function") {
|
|
1188
|
+
await handler.call(instance, ctx);
|
|
1189
|
+
} else {
|
|
1190
|
+
throw new Error(
|
|
1191
|
+
`Action ${action} not found on controller ${controller.name}`
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
middleware(middleware) {
|
|
1197
|
+
const mw = Array.isArray(middleware) ? middleware : [middleware];
|
|
1198
|
+
this.groupMiddleware.push(...mw);
|
|
1199
|
+
return this;
|
|
1200
|
+
}
|
|
1201
|
+
name(name) {
|
|
1202
|
+
if (this.routes.length > 0) {
|
|
1203
|
+
const lastRoute = this.routes[this.routes.length - 1];
|
|
1204
|
+
if (lastRoute !== void 0) {
|
|
1205
|
+
lastRoute.name = name;
|
|
1206
|
+
this.namedRoutes.set(name, lastRoute);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
return this;
|
|
1210
|
+
}
|
|
1211
|
+
route(name, params) {
|
|
1212
|
+
const entry = this.namedRoutes.get(name);
|
|
1213
|
+
if (entry === void 0) {
|
|
1214
|
+
throw new Error(`Route not found: ${name}`);
|
|
1215
|
+
}
|
|
1216
|
+
let url2 = entry.path;
|
|
1217
|
+
if (params !== void 0) {
|
|
1218
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1219
|
+
url2 = url2.replace(`:${key}`, encodeURIComponent(value));
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
return url2;
|
|
1223
|
+
}
|
|
1224
|
+
resolve(method, path3) {
|
|
1225
|
+
const normalizedPath = normalizePath(path3);
|
|
1226
|
+
const upperMethod = method.toUpperCase();
|
|
1227
|
+
for (const route of this.routes) {
|
|
1228
|
+
if (!route.methods.includes(upperMethod)) continue;
|
|
1229
|
+
const match = normalizedPath.match(route.regexp);
|
|
1230
|
+
if (match === null) continue;
|
|
1231
|
+
const params = {};
|
|
1232
|
+
for (let i = 0; i < route.paramKeys.length; i++) {
|
|
1233
|
+
const key = route.paramKeys[i];
|
|
1234
|
+
const value = match[i + 1];
|
|
1235
|
+
if (key !== void 0 && value !== void 0) {
|
|
1236
|
+
params[key] = decodeURIComponent(value);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
return {
|
|
1240
|
+
handler: route.handler,
|
|
1241
|
+
params,
|
|
1242
|
+
middleware: route.middleware
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
return null;
|
|
1246
|
+
}
|
|
1247
|
+
getRoutes() {
|
|
1248
|
+
return [...this.routes];
|
|
1249
|
+
}
|
|
1250
|
+
getNamedRoutes() {
|
|
1251
|
+
return new Map(this.namedRoutes);
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
function normalizePath(path3) {
|
|
1255
|
+
let normalized = path3.replace(/\\/g, "/");
|
|
1256
|
+
if (!normalized.startsWith("/")) {
|
|
1257
|
+
normalized = "/" + normalized;
|
|
1258
|
+
}
|
|
1259
|
+
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
1260
|
+
normalized = normalized.slice(0, -1);
|
|
1261
|
+
}
|
|
1262
|
+
return normalized;
|
|
1263
|
+
}
|
|
1264
|
+
function pathToRegexp(pattern) {
|
|
1265
|
+
const keys = [];
|
|
1266
|
+
const regexpStr = pattern.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_match, key) => {
|
|
1267
|
+
keys.push(key);
|
|
1268
|
+
return "([^/]+)";
|
|
1269
|
+
}).replace(/\*/g, ".*?");
|
|
1270
|
+
return { regexp: new RegExp(`^${regexpStr}$`), keys };
|
|
1271
|
+
}
|
|
1272
|
+
function singularize(word) {
|
|
1273
|
+
const lastChar = word[word.length - 1];
|
|
1274
|
+
if (lastChar === "s") return word.slice(0, -1);
|
|
1275
|
+
if (lastChar === "S") return word.slice(0, -1);
|
|
1276
|
+
return word;
|
|
1277
|
+
}
|
|
1278
|
+
function createControllerInstance(controller, ctx) {
|
|
1279
|
+
const instance = new controller();
|
|
1280
|
+
instance.__ctx = ctx;
|
|
1281
|
+
instance.__container = ctx.container;
|
|
1282
|
+
return instance;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// src/server/cache/index.ts
|
|
1286
|
+
import * as crypto from "crypto";
|
|
1287
|
+
import * as fs from "fs";
|
|
1288
|
+
import * as path from "path";
|
|
1289
|
+
var Cache = class {
|
|
1290
|
+
store;
|
|
1291
|
+
config;
|
|
1292
|
+
hits = 0;
|
|
1293
|
+
misses = 0;
|
|
1294
|
+
constructor(config) {
|
|
1295
|
+
this.config = {
|
|
1296
|
+
store: "memory",
|
|
1297
|
+
path: path.join(process.cwd(), "cache"),
|
|
1298
|
+
ttl: 3600,
|
|
1299
|
+
prefix: "",
|
|
1300
|
+
...config
|
|
1301
|
+
};
|
|
1302
|
+
this.store = /* @__PURE__ */ new Map();
|
|
1303
|
+
if (this.config.store === "file") {
|
|
1304
|
+
const dir = this.config.path;
|
|
1305
|
+
if (!fs.existsSync(dir)) {
|
|
1306
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
async get(key) {
|
|
1311
|
+
const fullKey = this.config.prefix + key;
|
|
1312
|
+
if (this.config.store === "file") {
|
|
1313
|
+
return this.fileGet(fullKey);
|
|
1314
|
+
}
|
|
1315
|
+
const entry = this.store.get(fullKey);
|
|
1316
|
+
if (entry === void 0) {
|
|
1317
|
+
this.misses++;
|
|
1318
|
+
return null;
|
|
1319
|
+
}
|
|
1320
|
+
if (this.isExpired(entry)) {
|
|
1321
|
+
this.store.delete(fullKey);
|
|
1322
|
+
this.misses++;
|
|
1323
|
+
return null;
|
|
1324
|
+
}
|
|
1325
|
+
this.hits++;
|
|
1326
|
+
return entry.value;
|
|
1327
|
+
}
|
|
1328
|
+
async remember(key, ttl, callback) {
|
|
1329
|
+
const cached = await this.get(key);
|
|
1330
|
+
if (cached !== null) {
|
|
1331
|
+
return cached;
|
|
1332
|
+
}
|
|
1333
|
+
const value = await callback();
|
|
1334
|
+
await this.set(key, value, ttl);
|
|
1335
|
+
return value;
|
|
1336
|
+
}
|
|
1337
|
+
async set(key, value, ttl) {
|
|
1338
|
+
const fullKey = this.config.prefix + key;
|
|
1339
|
+
const expiresAt = ttl !== void 0 ? Date.now() + ttl * 1e3 : Date.now() + this.config.ttl * 1e3;
|
|
1340
|
+
if (this.config.store === "file") {
|
|
1341
|
+
await this.fileSet(fullKey, { value, expiresAt });
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
this.store.set(fullKey, { value, expiresAt });
|
|
1345
|
+
}
|
|
1346
|
+
async add(key, value, ttl) {
|
|
1347
|
+
const exists = await this.has(key);
|
|
1348
|
+
if (exists) {
|
|
1349
|
+
return false;
|
|
1350
|
+
}
|
|
1351
|
+
await this.set(key, value, ttl);
|
|
1352
|
+
return true;
|
|
1353
|
+
}
|
|
1354
|
+
async delete(key) {
|
|
1355
|
+
const fullKey = this.config.prefix + key;
|
|
1356
|
+
if (this.config.store === "file") {
|
|
1357
|
+
return this.fileDelete(fullKey);
|
|
1358
|
+
}
|
|
1359
|
+
return this.store.delete(fullKey);
|
|
1360
|
+
}
|
|
1361
|
+
async clear() {
|
|
1362
|
+
if (this.config.store === "file") {
|
|
1363
|
+
const dir = this.config.path;
|
|
1364
|
+
if (fs.existsSync(dir)) {
|
|
1365
|
+
for (const file of fs.readdirSync(dir)) {
|
|
1366
|
+
fs.unlinkSync(path.join(dir, file));
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
this.store.clear();
|
|
1372
|
+
}
|
|
1373
|
+
async has(key) {
|
|
1374
|
+
const value = await this.get(key);
|
|
1375
|
+
return value !== null;
|
|
1376
|
+
}
|
|
1377
|
+
async getMultiple(keys) {
|
|
1378
|
+
const result = {};
|
|
1379
|
+
for (const key of keys) {
|
|
1380
|
+
const value = await this.get(key);
|
|
1381
|
+
if (value !== null) {
|
|
1382
|
+
result[key] = value;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
return result;
|
|
1386
|
+
}
|
|
1387
|
+
async setMultiple(items, ttl) {
|
|
1388
|
+
for (const [key, value] of Object.entries(items)) {
|
|
1389
|
+
await this.set(key, value, ttl);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
async increment(key, value = 1) {
|
|
1393
|
+
const current = await this.get(key);
|
|
1394
|
+
const newValue = (current ?? 0) + value;
|
|
1395
|
+
await this.set(key, newValue);
|
|
1396
|
+
return newValue;
|
|
1397
|
+
}
|
|
1398
|
+
async decrement(key, value = 1) {
|
|
1399
|
+
const current = await this.get(key);
|
|
1400
|
+
const newValue = (current ?? 0) - value;
|
|
1401
|
+
await this.set(key, newValue);
|
|
1402
|
+
return newValue;
|
|
1403
|
+
}
|
|
1404
|
+
async forever(key, value) {
|
|
1405
|
+
const fullKey = this.config.prefix + key;
|
|
1406
|
+
const expiresAt = Number.MAX_SAFE_INTEGER;
|
|
1407
|
+
if (this.config.store === "file") {
|
|
1408
|
+
await this.fileSet(fullKey, { value, expiresAt });
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
this.store.set(fullKey, { value, expiresAt });
|
|
1412
|
+
}
|
|
1413
|
+
stats() {
|
|
1414
|
+
let keyCount = 0;
|
|
1415
|
+
let totalSize = 0;
|
|
1416
|
+
if (this.config.store === "file") {
|
|
1417
|
+
const dir = this.config.path;
|
|
1418
|
+
if (fs.existsSync(dir)) {
|
|
1419
|
+
const files = fs.readdirSync(dir);
|
|
1420
|
+
keyCount = files.length;
|
|
1421
|
+
for (const file of files) {
|
|
1422
|
+
try {
|
|
1423
|
+
const st = fs.statSync(path.join(dir, file));
|
|
1424
|
+
totalSize += st.size;
|
|
1425
|
+
} catch {
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
} else {
|
|
1430
|
+
keyCount = this.store.size;
|
|
1431
|
+
for (const [, entry] of this.store) {
|
|
1432
|
+
totalSize += JSON.stringify(entry).length;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
const sizeStr = totalSize > 1024 * 1024 ? `${(totalSize / (1024 * 1024)).toFixed(2)} MB` : totalSize > 1024 ? `${(totalSize / 1024).toFixed(2)} KB` : `${totalSize} B`;
|
|
1436
|
+
return {
|
|
1437
|
+
hits: this.hits,
|
|
1438
|
+
misses: this.misses,
|
|
1439
|
+
keys: keyCount,
|
|
1440
|
+
size: sizeStr
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
getFilePath(key) {
|
|
1444
|
+
const hashedKey = crypto.createHash("md5").update(key).digest("hex");
|
|
1445
|
+
return path.join(this.config.path, `${hashedKey}.json`);
|
|
1446
|
+
}
|
|
1447
|
+
isExpired(entry) {
|
|
1448
|
+
return entry.expiresAt < Date.now();
|
|
1449
|
+
}
|
|
1450
|
+
fileGet(key) {
|
|
1451
|
+
const filePath = this.getFilePath(key);
|
|
1452
|
+
if (!fs.existsSync(filePath)) {
|
|
1453
|
+
this.misses++;
|
|
1454
|
+
return null;
|
|
1455
|
+
}
|
|
1456
|
+
try {
|
|
1457
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
1458
|
+
const entry = JSON.parse(raw);
|
|
1459
|
+
if (this.isExpired(entry)) {
|
|
1460
|
+
fs.unlinkSync(filePath);
|
|
1461
|
+
this.misses++;
|
|
1462
|
+
return null;
|
|
1463
|
+
}
|
|
1464
|
+
this.hits++;
|
|
1465
|
+
return entry.value;
|
|
1466
|
+
} catch {
|
|
1467
|
+
this.misses++;
|
|
1468
|
+
return null;
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
fileSet(key, entry) {
|
|
1472
|
+
const filePath = this.getFilePath(key);
|
|
1473
|
+
const dir = path.dirname(filePath);
|
|
1474
|
+
if (!fs.existsSync(dir)) {
|
|
1475
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1476
|
+
}
|
|
1477
|
+
fs.writeFileSync(filePath, JSON.stringify(entry), "utf-8");
|
|
1478
|
+
}
|
|
1479
|
+
fileDelete(key) {
|
|
1480
|
+
const filePath = this.getFilePath(key);
|
|
1481
|
+
if (!fs.existsSync(filePath)) {
|
|
1482
|
+
return false;
|
|
1483
|
+
}
|
|
1484
|
+
fs.unlinkSync(filePath);
|
|
1485
|
+
return true;
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1488
|
+
var responseCache = new Cache({ store: "memory" });
|
|
1489
|
+
function cacheResponse(ttl) {
|
|
1490
|
+
const cache = responseCache;
|
|
1491
|
+
return async (ctx, next) => {
|
|
1492
|
+
const key = `_cache:${ctx.request.method}:${ctx.request.path}`;
|
|
1493
|
+
if (ctx.request.method !== "GET") {
|
|
1494
|
+
return next();
|
|
1495
|
+
}
|
|
1496
|
+
const cached = await cache.get(key);
|
|
1497
|
+
if (cached !== null) {
|
|
1498
|
+
ctx.response.status(cached.status).type(cached.contentType).send(cached.body);
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1501
|
+
await next();
|
|
1502
|
+
const resp = ctx.response;
|
|
1503
|
+
const body = resp._body;
|
|
1504
|
+
if (body !== null && resp._statusCode >= 200 && resp._statusCode < 300) {
|
|
1505
|
+
const contentType = ctx.response.getHeader("content-type") ?? "text/plain";
|
|
1506
|
+
await cache.set(
|
|
1507
|
+
key,
|
|
1508
|
+
{
|
|
1509
|
+
body: typeof body === "string" ? body : body.toString(),
|
|
1510
|
+
status: resp._statusCode,
|
|
1511
|
+
contentType
|
|
1512
|
+
},
|
|
1513
|
+
ttl
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
// src/server/database/connection.ts
|
|
1520
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1521
|
+
|
|
1522
|
+
// src/server/database/dialect.ts
|
|
1523
|
+
var MysqlDialect = class {
|
|
1524
|
+
wrapIdentifier(identifier) {
|
|
1525
|
+
return `\`${identifier.replace(/`/g, "``")}\``;
|
|
1526
|
+
}
|
|
1527
|
+
makeParameter(_index) {
|
|
1528
|
+
return "?";
|
|
1529
|
+
}
|
|
1530
|
+
compileLimitOffset(bindings, limit, offset) {
|
|
1531
|
+
if (limit === null && offset === null) return "";
|
|
1532
|
+
if (offset !== null) {
|
|
1533
|
+
bindings.push(limit ?? 0, offset);
|
|
1534
|
+
return " LIMIT ? OFFSET ?";
|
|
1535
|
+
}
|
|
1536
|
+
bindings.push(limit);
|
|
1537
|
+
return " LIMIT ?";
|
|
1538
|
+
}
|
|
1539
|
+
compileInsertReturning(sql, _bindings, _idColumn) {
|
|
1540
|
+
return `${sql}; SELECT LAST_INSERT_ID() as id`;
|
|
1541
|
+
}
|
|
1542
|
+
compileTruncate(tableName) {
|
|
1543
|
+
return `TRUNCATE TABLE ${this.wrapIdentifier(tableName)}`;
|
|
1544
|
+
}
|
|
1545
|
+
compileCreateMigrationsTable() {
|
|
1546
|
+
return `CREATE TABLE IF NOT EXISTS \`migrations\` (
|
|
1547
|
+
\`id\` INT AUTO_INCREMENT PRIMARY KEY,
|
|
1548
|
+
\`name\` VARCHAR(255) NOT NULL,
|
|
1549
|
+
\`batch\` INT NOT NULL,
|
|
1550
|
+
\`executed_at\` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
1551
|
+
)`;
|
|
1552
|
+
}
|
|
1553
|
+
mapType(type, options) {
|
|
1554
|
+
switch (type) {
|
|
1555
|
+
case "id":
|
|
1556
|
+
case "increments":
|
|
1557
|
+
return "INT AUTO_INCREMENT PRIMARY KEY";
|
|
1558
|
+
case "bigIncrements":
|
|
1559
|
+
return "BIGINT AUTO_INCREMENT PRIMARY KEY";
|
|
1560
|
+
case "string":
|
|
1561
|
+
return `VARCHAR(${options.length ?? 255})`;
|
|
1562
|
+
case "text":
|
|
1563
|
+
return "TEXT";
|
|
1564
|
+
case "integer":
|
|
1565
|
+
return "INT";
|
|
1566
|
+
case "bigInteger":
|
|
1567
|
+
return "BIGINT";
|
|
1568
|
+
case "tinyInteger":
|
|
1569
|
+
return "TINYINT";
|
|
1570
|
+
case "smallInteger":
|
|
1571
|
+
return "SMALLINT";
|
|
1572
|
+
case "boolean":
|
|
1573
|
+
return "TINYINT(1)";
|
|
1574
|
+
case "float":
|
|
1575
|
+
return "FLOAT";
|
|
1576
|
+
case "double":
|
|
1577
|
+
return "DOUBLE";
|
|
1578
|
+
case "decimal":
|
|
1579
|
+
return `DECIMAL(${options.precision ?? 10},${options.scale ?? 0})`;
|
|
1580
|
+
case "date":
|
|
1581
|
+
return "DATE";
|
|
1582
|
+
case "datetime":
|
|
1583
|
+
return "DATETIME";
|
|
1584
|
+
case "timestamp":
|
|
1585
|
+
return "TIMESTAMP";
|
|
1586
|
+
case "time":
|
|
1587
|
+
return "TIME";
|
|
1588
|
+
case "year":
|
|
1589
|
+
return "YEAR";
|
|
1590
|
+
case "json":
|
|
1591
|
+
case "jsonb":
|
|
1592
|
+
return "JSON";
|
|
1593
|
+
case "binary":
|
|
1594
|
+
return "BLOB";
|
|
1595
|
+
case "uuid":
|
|
1596
|
+
return "CHAR(36)";
|
|
1597
|
+
case "foreignId":
|
|
1598
|
+
return "INT UNSIGNED";
|
|
1599
|
+
default:
|
|
1600
|
+
return type;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
compileColumn(options) {
|
|
1604
|
+
let sql = `${this.wrapIdentifier(options.name)} ${this.mapType(options.type, options)}`;
|
|
1605
|
+
if (options.unsigned && options.type !== "id" && options.type !== "increments" && options.type !== "bigIncrements" && options.type !== "foreignId")
|
|
1606
|
+
sql += " UNSIGNED";
|
|
1607
|
+
if (options.autoIncrement && options.type !== "id" && options.type !== "increments" && options.type !== "bigIncrements")
|
|
1608
|
+
sql += " AUTO_INCREMENT";
|
|
1609
|
+
if (options.defaultValue !== void 0 && options.defaultValue !== null) {
|
|
1610
|
+
sql += ` DEFAULT ${this.formatDefault(options.defaultValue)}`;
|
|
1611
|
+
} else if (!options.nullable && !options.autoIncrement && options.type !== "id" && options.type !== "increments" && options.type !== "bigIncrements") {
|
|
1612
|
+
sql += " NOT NULL";
|
|
1613
|
+
}
|
|
1614
|
+
if (options.nullable) sql += " NULL";
|
|
1615
|
+
if (options.primary) sql += " PRIMARY KEY";
|
|
1616
|
+
if (options.unique) sql += " UNIQUE";
|
|
1617
|
+
if (options.comment !== null)
|
|
1618
|
+
sql += ` COMMENT '${options.comment.replace(/'/g, "\\'")}'`;
|
|
1619
|
+
if (options.after !== null)
|
|
1620
|
+
sql += ` AFTER ${this.wrapIdentifier(options.after)}`;
|
|
1621
|
+
return sql;
|
|
1622
|
+
}
|
|
1623
|
+
compileModifyColumn(options) {
|
|
1624
|
+
return `MODIFY COLUMN ${this.compileColumn(options)}`;
|
|
1625
|
+
}
|
|
1626
|
+
compileTableBlueprint(_tableName, columns, constraints) {
|
|
1627
|
+
const parts = [
|
|
1628
|
+
...columns.map((c) => this.compileColumn(c)),
|
|
1629
|
+
...constraints
|
|
1630
|
+
];
|
|
1631
|
+
return parts.join(",\n ");
|
|
1632
|
+
}
|
|
1633
|
+
compileAddColumns(_tableName, columns) {
|
|
1634
|
+
return `ADD ${columns.map((c) => this.compileColumn(c)).join(", ADD ")}`;
|
|
1635
|
+
}
|
|
1636
|
+
compileDropColumns(_tableName, columns) {
|
|
1637
|
+
return `DROP COLUMN ${columns.map((c) => this.wrapIdentifier(c)).join(", DROP COLUMN ")}`;
|
|
1638
|
+
}
|
|
1639
|
+
compileRenameColumn(_tableName, from, to) {
|
|
1640
|
+
return `RENAME COLUMN ${this.wrapIdentifier(from)} TO ${this.wrapIdentifier(to)}`;
|
|
1641
|
+
}
|
|
1642
|
+
compileCreateTable(tableName, columns, constraints) {
|
|
1643
|
+
const body = this.compileTableBlueprint(tableName, columns, constraints);
|
|
1644
|
+
return `CREATE TABLE ${this.wrapIdentifier(tableName)} (
|
|
1645
|
+
${body}
|
|
1646
|
+
)`;
|
|
1647
|
+
}
|
|
1648
|
+
compileRenameTable(from, to) {
|
|
1649
|
+
return `RENAME TABLE ${this.wrapIdentifier(from)} TO ${this.wrapIdentifier(to)}`;
|
|
1650
|
+
}
|
|
1651
|
+
compileDropTable(tableName) {
|
|
1652
|
+
return `DROP TABLE ${this.wrapIdentifier(tableName)}`;
|
|
1653
|
+
}
|
|
1654
|
+
compileDropTableIfExists(tableName) {
|
|
1655
|
+
return `DROP TABLE IF EXISTS ${this.wrapIdentifier(tableName)}`;
|
|
1656
|
+
}
|
|
1657
|
+
compileHasTable(_tableName) {
|
|
1658
|
+
return `SELECT COUNT(*) as \`count\` FROM \`information_schema\`.\`tables\` WHERE \`table_schema\` = DATABASE() AND \`table_name\` = ?`;
|
|
1659
|
+
}
|
|
1660
|
+
compileHasColumn(_tableName, _columnName) {
|
|
1661
|
+
return `SELECT COUNT(*) as \`count\` FROM \`information_schema\`.\`columns\` WHERE \`table_schema\` = DATABASE() AND \`table_name\` = ? AND \`column_name\` = ?`;
|
|
1662
|
+
}
|
|
1663
|
+
compileInsert(sql) {
|
|
1664
|
+
return sql;
|
|
1665
|
+
}
|
|
1666
|
+
formatDefault(value) {
|
|
1667
|
+
if (typeof value === "string") {
|
|
1668
|
+
if (value === "CURRENT_TIMESTAMP") return value;
|
|
1669
|
+
return `'${value.replace(/'/g, "\\'")}'`;
|
|
1670
|
+
}
|
|
1671
|
+
if (value === null) return "NULL";
|
|
1672
|
+
if (typeof value === "boolean") return value ? "1" : "0";
|
|
1673
|
+
return String(value);
|
|
1674
|
+
}
|
|
1675
|
+
};
|
|
1676
|
+
var SqliteDialect = class {
|
|
1677
|
+
wrapIdentifier(identifier) {
|
|
1678
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
1679
|
+
}
|
|
1680
|
+
makeParameter(_index) {
|
|
1681
|
+
return "?";
|
|
1682
|
+
}
|
|
1683
|
+
compileLimitOffset(bindings, limit, offset) {
|
|
1684
|
+
if (limit === null && offset === null) return "";
|
|
1685
|
+
if (offset !== null) {
|
|
1686
|
+
bindings.push(limit ?? 0, offset);
|
|
1687
|
+
return " LIMIT ? OFFSET ?";
|
|
1688
|
+
}
|
|
1689
|
+
bindings.push(limit);
|
|
1690
|
+
return " LIMIT ?";
|
|
1691
|
+
}
|
|
1692
|
+
compileInsertReturning(sql, _bindings, _idColumn) {
|
|
1693
|
+
return `${sql}; SELECT last_insert_rowid() as id`;
|
|
1694
|
+
}
|
|
1695
|
+
compileTruncate(tableName) {
|
|
1696
|
+
return `DELETE FROM ${this.wrapIdentifier(tableName)}`;
|
|
1697
|
+
}
|
|
1698
|
+
compileCreateMigrationsTable() {
|
|
1699
|
+
return `CREATE TABLE IF NOT EXISTS "migrations" (
|
|
1700
|
+
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1701
|
+
"name" TEXT NOT NULL,
|
|
1702
|
+
"batch" INTEGER NOT NULL,
|
|
1703
|
+
"executed_at" TEXT DEFAULT CURRENT_TIMESTAMP
|
|
1704
|
+
)`;
|
|
1705
|
+
}
|
|
1706
|
+
mapType(type, options) {
|
|
1707
|
+
switch (type) {
|
|
1708
|
+
case "id":
|
|
1709
|
+
case "increments":
|
|
1710
|
+
return "INTEGER PRIMARY KEY AUTOINCREMENT";
|
|
1711
|
+
case "bigIncrements":
|
|
1712
|
+
return "INTEGER PRIMARY KEY AUTOINCREMENT";
|
|
1713
|
+
case "string":
|
|
1714
|
+
return `VARCHAR(${options.length ?? 255})`;
|
|
1715
|
+
case "text":
|
|
1716
|
+
return "TEXT";
|
|
1717
|
+
case "integer":
|
|
1718
|
+
return "INTEGER";
|
|
1719
|
+
case "bigInteger":
|
|
1720
|
+
return "INTEGER";
|
|
1721
|
+
case "tinyInteger":
|
|
1722
|
+
return "INTEGER";
|
|
1723
|
+
case "smallInteger":
|
|
1724
|
+
return "INTEGER";
|
|
1725
|
+
case "boolean":
|
|
1726
|
+
return "INTEGER";
|
|
1727
|
+
case "float":
|
|
1728
|
+
return "REAL";
|
|
1729
|
+
case "double":
|
|
1730
|
+
return "REAL";
|
|
1731
|
+
case "decimal":
|
|
1732
|
+
return "REAL";
|
|
1733
|
+
case "date":
|
|
1734
|
+
return "TEXT";
|
|
1735
|
+
case "datetime":
|
|
1736
|
+
return "TEXT";
|
|
1737
|
+
case "timestamp":
|
|
1738
|
+
return "TEXT";
|
|
1739
|
+
case "time":
|
|
1740
|
+
return "TEXT";
|
|
1741
|
+
case "year":
|
|
1742
|
+
return "TEXT";
|
|
1743
|
+
case "json":
|
|
1744
|
+
case "jsonb":
|
|
1745
|
+
return "TEXT";
|
|
1746
|
+
case "binary":
|
|
1747
|
+
return "BLOB";
|
|
1748
|
+
case "uuid":
|
|
1749
|
+
return "TEXT";
|
|
1750
|
+
case "foreignId":
|
|
1751
|
+
return "INTEGER";
|
|
1752
|
+
default:
|
|
1753
|
+
return type;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
compileColumn(options) {
|
|
1757
|
+
let sql = `${this.wrapIdentifier(options.name)} ${this.mapType(options.type, options)}`;
|
|
1758
|
+
if (options.autoIncrement && options.type !== "id" && options.type !== "increments" && options.type !== "bigIncrements")
|
|
1759
|
+
sql += " AUTOINCREMENT";
|
|
1760
|
+
if (!options.nullable && options.defaultValue === void 0 && !options.autoIncrement && options.type !== "id" && options.type !== "increments" && options.type !== "bigIncrements")
|
|
1761
|
+
sql += " NOT NULL";
|
|
1762
|
+
if (options.nullable) sql += " NULL";
|
|
1763
|
+
if (options.defaultValue !== void 0 && options.defaultValue !== null) {
|
|
1764
|
+
sql += ` DEFAULT ${this.formatDefault(options.defaultValue)}`;
|
|
1765
|
+
}
|
|
1766
|
+
if (options.primary) sql += " PRIMARY KEY";
|
|
1767
|
+
if (options.unique) sql += " UNIQUE";
|
|
1768
|
+
return sql;
|
|
1769
|
+
}
|
|
1770
|
+
compileModifyColumn(options) {
|
|
1771
|
+
return this.compileColumn(options);
|
|
1772
|
+
}
|
|
1773
|
+
compileTableBlueprint(_tableName, columns, constraints) {
|
|
1774
|
+
const parts = [
|
|
1775
|
+
...columns.map((c) => this.compileColumn(c)),
|
|
1776
|
+
...constraints
|
|
1777
|
+
];
|
|
1778
|
+
return parts.join(",\n ");
|
|
1779
|
+
}
|
|
1780
|
+
compileAddColumns(_tableName, columns) {
|
|
1781
|
+
return `ADD COLUMN ${columns.map((c) => this.compileColumn(c)).join(", ADD COLUMN ")}`;
|
|
1782
|
+
}
|
|
1783
|
+
compileDropColumns(_tableName, columns) {
|
|
1784
|
+
return `DROP COLUMN ${columns.map((c) => this.wrapIdentifier(c)).join(", DROP COLUMN ")}`;
|
|
1785
|
+
}
|
|
1786
|
+
compileRenameColumn(_tableName, from, to) {
|
|
1787
|
+
return `RENAME COLUMN ${this.wrapIdentifier(from)} TO ${this.wrapIdentifier(to)}`;
|
|
1788
|
+
}
|
|
1789
|
+
compileCreateTable(tableName, columns, constraints) {
|
|
1790
|
+
const body = this.compileTableBlueprint(tableName, columns, constraints);
|
|
1791
|
+
return `CREATE TABLE ${this.wrapIdentifier(tableName)} (
|
|
1792
|
+
${body}
|
|
1793
|
+
)`;
|
|
1794
|
+
}
|
|
1795
|
+
compileRenameTable(from, to) {
|
|
1796
|
+
return `ALTER TABLE ${this.wrapIdentifier(from)} RENAME TO ${this.wrapIdentifier(to)}`;
|
|
1797
|
+
}
|
|
1798
|
+
compileDropTable(tableName) {
|
|
1799
|
+
return `DROP TABLE ${this.wrapIdentifier(tableName)}`;
|
|
1800
|
+
}
|
|
1801
|
+
compileDropTableIfExists(tableName) {
|
|
1802
|
+
return `DROP TABLE IF EXISTS ${this.wrapIdentifier(tableName)}`;
|
|
1803
|
+
}
|
|
1804
|
+
compileHasTable(_tableName) {
|
|
1805
|
+
return `SELECT COUNT(*) as "count" FROM "sqlite_master" WHERE "type" = 'table' AND "name" = ?`;
|
|
1806
|
+
}
|
|
1807
|
+
compileHasColumn(_tableName, _columnName) {
|
|
1808
|
+
return `PRAGMA table_info(${this.wrapIdentifier(_tableName)})`;
|
|
1809
|
+
}
|
|
1810
|
+
compileInsert(sql) {
|
|
1811
|
+
return sql;
|
|
1812
|
+
}
|
|
1813
|
+
formatDefault(value) {
|
|
1814
|
+
if (typeof value === "string") {
|
|
1815
|
+
if (value === "CURRENT_TIMESTAMP") return value;
|
|
1816
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
1817
|
+
}
|
|
1818
|
+
if (value === null) return "NULL";
|
|
1819
|
+
if (typeof value === "boolean") return value ? "1" : "0";
|
|
1820
|
+
return String(value);
|
|
1821
|
+
}
|
|
1822
|
+
};
|
|
1823
|
+
var PostgresqlDialect = class {
|
|
1824
|
+
wrapIdentifier(identifier) {
|
|
1825
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
1826
|
+
}
|
|
1827
|
+
makeParameter(index) {
|
|
1828
|
+
return `$${index + 1}`;
|
|
1829
|
+
}
|
|
1830
|
+
compileLimitOffset(bindings, limit, offset) {
|
|
1831
|
+
if (limit === null && offset === null) return "";
|
|
1832
|
+
const start = bindings.length;
|
|
1833
|
+
if (offset !== null) {
|
|
1834
|
+
bindings.push(limit ?? 0, offset);
|
|
1835
|
+
return ` LIMIT $${start + 1} OFFSET $${start + 2}`;
|
|
1836
|
+
}
|
|
1837
|
+
bindings.push(limit);
|
|
1838
|
+
return ` LIMIT $${start + 1}`;
|
|
1839
|
+
}
|
|
1840
|
+
compileInsertReturning(sql, _bindings, idColumn = "id") {
|
|
1841
|
+
return `${sql} RETURNING ${this.wrapIdentifier(idColumn)}`;
|
|
1842
|
+
}
|
|
1843
|
+
compileTruncate(tableName) {
|
|
1844
|
+
return `TRUNCATE TABLE ${this.wrapIdentifier(tableName)} RESTART IDENTITY CASCADE`;
|
|
1845
|
+
}
|
|
1846
|
+
compileCreateMigrationsTable() {
|
|
1847
|
+
return `CREATE TABLE IF NOT EXISTS "migrations" (
|
|
1848
|
+
"id" SERIAL PRIMARY KEY,
|
|
1849
|
+
"name" VARCHAR(255) NOT NULL,
|
|
1850
|
+
"batch" INTEGER NOT NULL,
|
|
1851
|
+
"executed_at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
1852
|
+
)`;
|
|
1853
|
+
}
|
|
1854
|
+
mapType(type, options) {
|
|
1855
|
+
switch (type) {
|
|
1856
|
+
case "id":
|
|
1857
|
+
case "increments":
|
|
1858
|
+
return "SERIAL PRIMARY KEY";
|
|
1859
|
+
case "bigIncrements":
|
|
1860
|
+
return "BIGSERIAL PRIMARY KEY";
|
|
1861
|
+
case "string":
|
|
1862
|
+
return `VARCHAR(${options.length ?? 255})`;
|
|
1863
|
+
case "text":
|
|
1864
|
+
return "TEXT";
|
|
1865
|
+
case "integer":
|
|
1866
|
+
return "INTEGER";
|
|
1867
|
+
case "bigInteger":
|
|
1868
|
+
return "BIGINT";
|
|
1869
|
+
case "tinyInteger":
|
|
1870
|
+
return "SMALLINT";
|
|
1871
|
+
case "smallInteger":
|
|
1872
|
+
return "SMALLINT";
|
|
1873
|
+
case "boolean":
|
|
1874
|
+
return "BOOLEAN";
|
|
1875
|
+
case "float":
|
|
1876
|
+
return "REAL";
|
|
1877
|
+
case "double":
|
|
1878
|
+
return "DOUBLE PRECISION";
|
|
1879
|
+
case "decimal":
|
|
1880
|
+
return `DECIMAL(${options.precision ?? 10},${options.scale ?? 0})`;
|
|
1881
|
+
case "date":
|
|
1882
|
+
return "DATE";
|
|
1883
|
+
case "datetime":
|
|
1884
|
+
return "TIMESTAMP";
|
|
1885
|
+
case "timestamp":
|
|
1886
|
+
return "TIMESTAMP";
|
|
1887
|
+
case "time":
|
|
1888
|
+
return "TIME";
|
|
1889
|
+
case "year":
|
|
1890
|
+
return "INTEGER";
|
|
1891
|
+
case "json":
|
|
1892
|
+
return "JSON";
|
|
1893
|
+
case "jsonb":
|
|
1894
|
+
return "JSONB";
|
|
1895
|
+
case "binary":
|
|
1896
|
+
return "BYTEA";
|
|
1897
|
+
case "uuid":
|
|
1898
|
+
return "UUID";
|
|
1899
|
+
case "foreignId":
|
|
1900
|
+
return "INTEGER";
|
|
1901
|
+
default:
|
|
1902
|
+
return type;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
compileColumn(options) {
|
|
1906
|
+
let sql = `${this.wrapIdentifier(options.name)} ${this.mapType(options.type, options)}`;
|
|
1907
|
+
if (!options.nullable && options.type !== "id" && options.type !== "increments" && options.type !== "bigIncrements")
|
|
1908
|
+
sql += " NOT NULL";
|
|
1909
|
+
if (options.nullable) sql += " NULL";
|
|
1910
|
+
if (options.defaultValue !== void 0 && options.defaultValue !== null) {
|
|
1911
|
+
sql += ` DEFAULT ${this.formatDefault(options.defaultValue)}`;
|
|
1912
|
+
}
|
|
1913
|
+
if (options.primary && options.type !== "id" && options.type !== "increments" && options.type !== "bigIncrements")
|
|
1914
|
+
sql += " PRIMARY KEY";
|
|
1915
|
+
if (options.unique) sql += " UNIQUE";
|
|
1916
|
+
if (options.comment !== null)
|
|
1917
|
+
sql += ` -- ${options.comment.replace(/--/g, "")}`;
|
|
1918
|
+
return sql;
|
|
1919
|
+
}
|
|
1920
|
+
compileModifyColumn(options) {
|
|
1921
|
+
return `ALTER COLUMN ${this.wrapIdentifier(options.name)} TYPE ${this.mapType(options.type, options)}`;
|
|
1922
|
+
}
|
|
1923
|
+
compileTableBlueprint(_tableName, columns, constraints) {
|
|
1924
|
+
const parts = [
|
|
1925
|
+
...columns.map((c) => this.compileColumn(c)),
|
|
1926
|
+
...constraints
|
|
1927
|
+
];
|
|
1928
|
+
return parts.join(",\n ");
|
|
1929
|
+
}
|
|
1930
|
+
compileAddColumns(_tableName, columns) {
|
|
1931
|
+
return `ADD COLUMN ${columns.map((c) => this.compileColumn(c)).join(", ADD COLUMN ")}`;
|
|
1932
|
+
}
|
|
1933
|
+
compileDropColumns(_tableName, columns) {
|
|
1934
|
+
return `DROP COLUMN ${columns.map((c) => this.wrapIdentifier(c)).join(", DROP COLUMN ")}`;
|
|
1935
|
+
}
|
|
1936
|
+
compileRenameColumn(_tableName, from, to) {
|
|
1937
|
+
return `RENAME COLUMN ${this.wrapIdentifier(from)} TO ${this.wrapIdentifier(to)}`;
|
|
1938
|
+
}
|
|
1939
|
+
compileCreateTable(tableName, columns, constraints) {
|
|
1940
|
+
const body = this.compileTableBlueprint(tableName, columns, constraints);
|
|
1941
|
+
return `CREATE TABLE ${this.wrapIdentifier(tableName)} (
|
|
1942
|
+
${body}
|
|
1943
|
+
)`;
|
|
1944
|
+
}
|
|
1945
|
+
compileRenameTable(from, to) {
|
|
1946
|
+
return `ALTER TABLE ${this.wrapIdentifier(from)} RENAME TO ${this.wrapIdentifier(to)}`;
|
|
1947
|
+
}
|
|
1948
|
+
compileDropTable(_tableName) {
|
|
1949
|
+
return `DROP TABLE IF EXISTS ${this.wrapIdentifier(_tableName)}`;
|
|
1950
|
+
}
|
|
1951
|
+
compileDropTableIfExists(tableName) {
|
|
1952
|
+
return `DROP TABLE IF EXISTS ${this.wrapIdentifier(tableName)}`;
|
|
1953
|
+
}
|
|
1954
|
+
compileHasTable(_tableName) {
|
|
1955
|
+
return `SELECT COUNT(*)::int as "count" FROM "information_schema"."tables" WHERE "table_schema" = 'public' AND "table_name" = $1`;
|
|
1956
|
+
}
|
|
1957
|
+
compileHasColumn(_tableName, _columnName) {
|
|
1958
|
+
return `SELECT COUNT(*)::int as "count" FROM "information_schema"."columns" WHERE "table_schema" = 'public' AND "table_name" = $1 AND "column_name" = $2`;
|
|
1959
|
+
}
|
|
1960
|
+
compileInsert(sql) {
|
|
1961
|
+
return sql;
|
|
1962
|
+
}
|
|
1963
|
+
formatDefault(value) {
|
|
1964
|
+
if (typeof value === "string") {
|
|
1965
|
+
if (value === "CURRENT_TIMESTAMP") return value;
|
|
1966
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
1967
|
+
}
|
|
1968
|
+
if (value === null) return "NULL";
|
|
1969
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
1970
|
+
return String(value);
|
|
1971
|
+
}
|
|
1972
|
+
};
|
|
1973
|
+
function createDialect(driver) {
|
|
1974
|
+
switch (driver) {
|
|
1975
|
+
case "mysql":
|
|
1976
|
+
return new MysqlDialect();
|
|
1977
|
+
case "sqlite":
|
|
1978
|
+
return new SqliteDialect();
|
|
1979
|
+
case "postgresql":
|
|
1980
|
+
return new PostgresqlDialect();
|
|
1981
|
+
default:
|
|
1982
|
+
return new MysqlDialect();
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
// src/server/database/driver.ts
|
|
1987
|
+
async function createDriver(config) {
|
|
1988
|
+
const driverType = config.driver ?? "mysql";
|
|
1989
|
+
switch (driverType) {
|
|
1990
|
+
case "mysql":
|
|
1991
|
+
return createMysqlDriver(config);
|
|
1992
|
+
case "postgresql":
|
|
1993
|
+
return createPostgresqlDriver(config);
|
|
1994
|
+
case "sqlite":
|
|
1995
|
+
return createSqliteDriver(config);
|
|
1996
|
+
default:
|
|
1997
|
+
return createMysqlDriver(config);
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
async function createMysqlDriver(config) {
|
|
2001
|
+
let mysqlPackage;
|
|
2002
|
+
try {
|
|
2003
|
+
mysqlPackage = await import("mysql2/promise");
|
|
2004
|
+
} catch {
|
|
2005
|
+
throw new Error(
|
|
2006
|
+
"MySQL driver (mysql2) is not installed. Run: npm install mysql2"
|
|
2007
|
+
);
|
|
2008
|
+
}
|
|
2009
|
+
let connection = null;
|
|
2010
|
+
const dialect = new MysqlDialect();
|
|
2011
|
+
const driver = {
|
|
2012
|
+
async connect() {
|
|
2013
|
+
connection = await mysqlPackage.createConnection({
|
|
2014
|
+
host: config.host ?? "127.0.0.1",
|
|
2015
|
+
port: config.port ?? 3306,
|
|
2016
|
+
user: config.username,
|
|
2017
|
+
password: config.password,
|
|
2018
|
+
database: config.database,
|
|
2019
|
+
charset: config.charset ?? "utf8mb4"
|
|
2020
|
+
});
|
|
2021
|
+
},
|
|
2022
|
+
async disconnect() {
|
|
2023
|
+
if (connection !== null) {
|
|
2024
|
+
await connection.end();
|
|
2025
|
+
connection = null;
|
|
2026
|
+
}
|
|
2027
|
+
},
|
|
2028
|
+
isConnected() {
|
|
2029
|
+
return connection !== null;
|
|
2030
|
+
},
|
|
2031
|
+
async raw(sql, bindings) {
|
|
2032
|
+
if (connection === null) {
|
|
2033
|
+
throw new Error("Database not connected. Call connect() first.");
|
|
2034
|
+
}
|
|
2035
|
+
const [rows, fields] = await connection.execute(sql, bindings ?? []);
|
|
2036
|
+
return { rows, fields };
|
|
2037
|
+
},
|
|
2038
|
+
async transaction(callback) {
|
|
2039
|
+
if (connection === null) {
|
|
2040
|
+
throw new Error("Database not connected. Call connect() first.");
|
|
2041
|
+
}
|
|
2042
|
+
await connection.beginTransaction();
|
|
2043
|
+
try {
|
|
2044
|
+
const result = await callback(driver);
|
|
2045
|
+
await connection.commit();
|
|
2046
|
+
return result;
|
|
2047
|
+
} catch (err) {
|
|
2048
|
+
await connection.rollback();
|
|
2049
|
+
throw err;
|
|
2050
|
+
}
|
|
2051
|
+
},
|
|
2052
|
+
getDialect() {
|
|
2053
|
+
return dialect;
|
|
2054
|
+
},
|
|
2055
|
+
getDriver() {
|
|
2056
|
+
return "mysql";
|
|
2057
|
+
}
|
|
2058
|
+
};
|
|
2059
|
+
return driver;
|
|
2060
|
+
}
|
|
2061
|
+
async function createPostgresqlDriver(config) {
|
|
2062
|
+
let pgPackage;
|
|
2063
|
+
try {
|
|
2064
|
+
pgPackage = await import("pg");
|
|
2065
|
+
} catch {
|
|
2066
|
+
throw new Error(
|
|
2067
|
+
"PostgreSQL driver (pg) is not installed. Run: npm install pg"
|
|
2068
|
+
);
|
|
2069
|
+
}
|
|
2070
|
+
let pool = null;
|
|
2071
|
+
const dialect = new PostgresqlDialect();
|
|
2072
|
+
const driver = {
|
|
2073
|
+
async connect() {
|
|
2074
|
+
const { Pool } = pgPackage;
|
|
2075
|
+
pool = new Pool({
|
|
2076
|
+
host: config.host ?? "127.0.0.1",
|
|
2077
|
+
port: config.port ?? 5432,
|
|
2078
|
+
user: config.username,
|
|
2079
|
+
password: config.password,
|
|
2080
|
+
database: config.database
|
|
2081
|
+
});
|
|
2082
|
+
await pool.query("SELECT 1");
|
|
2083
|
+
},
|
|
2084
|
+
async disconnect() {
|
|
2085
|
+
if (pool !== null) {
|
|
2086
|
+
await pool.end();
|
|
2087
|
+
pool = null;
|
|
2088
|
+
}
|
|
2089
|
+
},
|
|
2090
|
+
isConnected() {
|
|
2091
|
+
return pool !== null;
|
|
2092
|
+
},
|
|
2093
|
+
async raw(sql, bindings) {
|
|
2094
|
+
if (pool === null) {
|
|
2095
|
+
throw new Error("Database not connected. Call connect() first.");
|
|
2096
|
+
}
|
|
2097
|
+
const result = await pool.query(sql, bindings ?? []);
|
|
2098
|
+
return { rows: result.rows };
|
|
2099
|
+
},
|
|
2100
|
+
async transaction(callback) {
|
|
2101
|
+
if (pool === null) {
|
|
2102
|
+
throw new Error("Database not connected. Call connect() first.");
|
|
2103
|
+
}
|
|
2104
|
+
const client = await pool.connect();
|
|
2105
|
+
try {
|
|
2106
|
+
await client.query("BEGIN");
|
|
2107
|
+
const trxDriver = {
|
|
2108
|
+
...driver,
|
|
2109
|
+
async raw(sql, bindings) {
|
|
2110
|
+
const result2 = await client.query(sql, bindings ?? []);
|
|
2111
|
+
return { rows: result2.rows };
|
|
2112
|
+
},
|
|
2113
|
+
getDialect() {
|
|
2114
|
+
return dialect;
|
|
2115
|
+
},
|
|
2116
|
+
getDriver() {
|
|
2117
|
+
return "postgresql";
|
|
2118
|
+
}
|
|
2119
|
+
};
|
|
2120
|
+
const result = await callback(trxDriver);
|
|
2121
|
+
await client.query("COMMIT");
|
|
2122
|
+
return result;
|
|
2123
|
+
} catch (err) {
|
|
2124
|
+
await client.query("ROLLBACK");
|
|
2125
|
+
throw err;
|
|
2126
|
+
} finally {
|
|
2127
|
+
client.release();
|
|
2128
|
+
}
|
|
2129
|
+
},
|
|
2130
|
+
getDialect() {
|
|
2131
|
+
return dialect;
|
|
2132
|
+
},
|
|
2133
|
+
getDriver() {
|
|
2134
|
+
return "postgresql";
|
|
2135
|
+
}
|
|
2136
|
+
};
|
|
2137
|
+
return driver;
|
|
2138
|
+
}
|
|
2139
|
+
async function createSqliteDriver(config) {
|
|
2140
|
+
let sqlitePackage;
|
|
2141
|
+
try {
|
|
2142
|
+
sqlitePackage = await import("better-sqlite3");
|
|
2143
|
+
} catch {
|
|
2144
|
+
throw new Error(
|
|
2145
|
+
"SQLite driver (better-sqlite3) is not installed. Run: npm install better-sqlite3"
|
|
2146
|
+
);
|
|
2147
|
+
}
|
|
2148
|
+
let db = null;
|
|
2149
|
+
let connected = false;
|
|
2150
|
+
const dialect = new SqliteDialect();
|
|
2151
|
+
const driver = {
|
|
2152
|
+
async connect() {
|
|
2153
|
+
db = new sqlitePackage(config.database);
|
|
2154
|
+
db.pragma("journal_mode = WAL");
|
|
2155
|
+
connected = true;
|
|
2156
|
+
},
|
|
2157
|
+
async disconnect() {
|
|
2158
|
+
if (db !== null) {
|
|
2159
|
+
db.close();
|
|
2160
|
+
db = null;
|
|
2161
|
+
connected = false;
|
|
2162
|
+
}
|
|
2163
|
+
},
|
|
2164
|
+
isConnected() {
|
|
2165
|
+
return connected;
|
|
2166
|
+
},
|
|
2167
|
+
async raw(sql, bindings) {
|
|
2168
|
+
if (db === null) {
|
|
2169
|
+
throw new Error("Database not connected. Call connect() first.");
|
|
2170
|
+
}
|
|
2171
|
+
const trimmedSQL = sql.trim().toUpperCase();
|
|
2172
|
+
const isQuery = trimmedSQL.startsWith("SELECT") || trimmedSQL.startsWith("WITH") || trimmedSQL.startsWith("PRAGMA");
|
|
2173
|
+
const stmt = db.prepare(sql);
|
|
2174
|
+
if (isQuery) {
|
|
2175
|
+
const rows = bindings !== void 0 && bindings.length > 0 ? stmt.all(...bindings) : stmt.all();
|
|
2176
|
+
return { rows };
|
|
2177
|
+
}
|
|
2178
|
+
if (bindings !== void 0 && bindings.length > 0) {
|
|
2179
|
+
stmt.run(...bindings);
|
|
2180
|
+
} else {
|
|
2181
|
+
stmt.run();
|
|
2182
|
+
}
|
|
2183
|
+
return { rows: [] };
|
|
2184
|
+
},
|
|
2185
|
+
async transaction(callback) {
|
|
2186
|
+
if (db === null) {
|
|
2187
|
+
throw new Error("Database not connected. Call connect() first.");
|
|
2188
|
+
}
|
|
2189
|
+
const txn = db.transaction(async () => {
|
|
2190
|
+
return await callback(driver);
|
|
2191
|
+
});
|
|
2192
|
+
return txn();
|
|
2193
|
+
},
|
|
2194
|
+
getDialect() {
|
|
2195
|
+
return dialect;
|
|
2196
|
+
},
|
|
2197
|
+
getDriver() {
|
|
2198
|
+
return "sqlite";
|
|
2199
|
+
}
|
|
2200
|
+
};
|
|
2201
|
+
return driver;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
// src/server/database/query.ts
|
|
2205
|
+
var QueryBuilder = class _QueryBuilder {
|
|
2206
|
+
connection;
|
|
2207
|
+
tableName;
|
|
2208
|
+
columns = ["*"];
|
|
2209
|
+
distinctEnabled = false;
|
|
2210
|
+
wheres = [];
|
|
2211
|
+
joins = [];
|
|
2212
|
+
orderBys = [];
|
|
2213
|
+
havings = [];
|
|
2214
|
+
groupBys = [];
|
|
2215
|
+
limitValue = null;
|
|
2216
|
+
offsetValue = null;
|
|
2217
|
+
fromSubquery = null;
|
|
2218
|
+
constructor(connection, tableName) {
|
|
2219
|
+
this.connection = connection;
|
|
2220
|
+
this.tableName = tableName;
|
|
2221
|
+
}
|
|
2222
|
+
select(...columns) {
|
|
2223
|
+
this.columns = columns.length > 0 ? columns : ["*"];
|
|
2224
|
+
return this;
|
|
2225
|
+
}
|
|
2226
|
+
addSelect(...columns) {
|
|
2227
|
+
if (this.columns[0] === "*") {
|
|
2228
|
+
this.columns = columns;
|
|
2229
|
+
} else {
|
|
2230
|
+
this.columns.push(...columns);
|
|
2231
|
+
}
|
|
2232
|
+
return this;
|
|
2233
|
+
}
|
|
2234
|
+
distinct() {
|
|
2235
|
+
this.distinctEnabled = true;
|
|
2236
|
+
return this;
|
|
2237
|
+
}
|
|
2238
|
+
from(table) {
|
|
2239
|
+
this.fromSubquery = table;
|
|
2240
|
+
return this;
|
|
2241
|
+
}
|
|
2242
|
+
where(column, operator, value) {
|
|
2243
|
+
if (value === void 0) {
|
|
2244
|
+
value = operator;
|
|
2245
|
+
operator = "=";
|
|
2246
|
+
}
|
|
2247
|
+
this.wheres.push({
|
|
2248
|
+
type: "basic",
|
|
2249
|
+
column,
|
|
2250
|
+
operator: String(operator),
|
|
2251
|
+
value,
|
|
2252
|
+
boolean: "and"
|
|
2253
|
+
});
|
|
2254
|
+
return this;
|
|
2255
|
+
}
|
|
2256
|
+
orWhere(column, operator, value) {
|
|
2257
|
+
if (value === void 0) {
|
|
2258
|
+
value = operator;
|
|
2259
|
+
operator = "=";
|
|
2260
|
+
}
|
|
2261
|
+
this.wheres.push({
|
|
2262
|
+
type: "basic",
|
|
2263
|
+
column,
|
|
2264
|
+
operator: String(operator),
|
|
2265
|
+
value,
|
|
2266
|
+
boolean: "or"
|
|
2267
|
+
});
|
|
2268
|
+
return this;
|
|
2269
|
+
}
|
|
2270
|
+
whereIn(column, values) {
|
|
2271
|
+
this.wheres.push({ type: "in", column, values, boolean: "and" });
|
|
2272
|
+
return this;
|
|
2273
|
+
}
|
|
2274
|
+
whereNotIn(column, values) {
|
|
2275
|
+
this.wheres.push({ type: "notIn", column, values, boolean: "and" });
|
|
2276
|
+
return this;
|
|
2277
|
+
}
|
|
2278
|
+
whereNull(column) {
|
|
2279
|
+
this.wheres.push({ type: "null", column, boolean: "and" });
|
|
2280
|
+
return this;
|
|
2281
|
+
}
|
|
2282
|
+
whereNotNull(column) {
|
|
2283
|
+
this.wheres.push({ type: "notNull", column, boolean: "and" });
|
|
2284
|
+
return this;
|
|
2285
|
+
}
|
|
2286
|
+
whereBetween(column, range) {
|
|
2287
|
+
this.wheres.push({
|
|
2288
|
+
type: "between",
|
|
2289
|
+
column,
|
|
2290
|
+
values: range,
|
|
2291
|
+
boolean: "and"
|
|
2292
|
+
});
|
|
2293
|
+
return this;
|
|
2294
|
+
}
|
|
2295
|
+
whereNotBetween(column, range) {
|
|
2296
|
+
this.wheres.push({
|
|
2297
|
+
type: "notBetween",
|
|
2298
|
+
column,
|
|
2299
|
+
values: range,
|
|
2300
|
+
boolean: "and"
|
|
2301
|
+
});
|
|
2302
|
+
return this;
|
|
2303
|
+
}
|
|
2304
|
+
whereLike(column, pattern) {
|
|
2305
|
+
this.wheres.push({ type: "like", column, value: pattern, boolean: "and" });
|
|
2306
|
+
return this;
|
|
2307
|
+
}
|
|
2308
|
+
orWhereLike(column, pattern) {
|
|
2309
|
+
this.wheres.push({ type: "like", column, value: pattern, boolean: "or" });
|
|
2310
|
+
return this;
|
|
2311
|
+
}
|
|
2312
|
+
whereGroup(callback) {
|
|
2313
|
+
const subQuery = new _QueryBuilder(this.connection, this.tableName);
|
|
2314
|
+
callback(subQuery);
|
|
2315
|
+
this.wheres.push({
|
|
2316
|
+
type: "nested",
|
|
2317
|
+
nested: subQuery.wheres,
|
|
2318
|
+
boolean: "and"
|
|
2319
|
+
});
|
|
2320
|
+
return this;
|
|
2321
|
+
}
|
|
2322
|
+
join(table, first, operator, second, type = "inner") {
|
|
2323
|
+
this.joins.push({ table, first, operator, second, type });
|
|
2324
|
+
return this;
|
|
2325
|
+
}
|
|
2326
|
+
leftJoin(table, first, operator, second) {
|
|
2327
|
+
return this.join(table, first, operator, second, "left");
|
|
2328
|
+
}
|
|
2329
|
+
rightJoin(table, first, operator, second) {
|
|
2330
|
+
return this.join(table, first, operator, second, "right");
|
|
2331
|
+
}
|
|
2332
|
+
crossJoin(table, first, operator, second) {
|
|
2333
|
+
return this.join(table, first, operator, second, "cross");
|
|
2334
|
+
}
|
|
2335
|
+
orderBy(column, direction = "asc") {
|
|
2336
|
+
this.orderBys.push({ column, direction });
|
|
2337
|
+
return this;
|
|
2338
|
+
}
|
|
2339
|
+
orderByDesc(column) {
|
|
2340
|
+
return this.orderBy(column, "desc");
|
|
2341
|
+
}
|
|
2342
|
+
latest(column = "created_at") {
|
|
2343
|
+
return this.orderBy(column, "desc");
|
|
2344
|
+
}
|
|
2345
|
+
oldest(column = "created_at") {
|
|
2346
|
+
return this.orderBy(column, "asc");
|
|
2347
|
+
}
|
|
2348
|
+
inRandomOrder() {
|
|
2349
|
+
this.orderBys.push({ column: "RANDOM()", direction: "asc" });
|
|
2350
|
+
return this;
|
|
2351
|
+
}
|
|
2352
|
+
limit(limit) {
|
|
2353
|
+
this.limitValue = limit;
|
|
2354
|
+
return this;
|
|
2355
|
+
}
|
|
2356
|
+
offset(offset) {
|
|
2357
|
+
this.offsetValue = offset;
|
|
2358
|
+
return this;
|
|
2359
|
+
}
|
|
2360
|
+
skip(skip) {
|
|
2361
|
+
return this.offset(skip);
|
|
2362
|
+
}
|
|
2363
|
+
take(take) {
|
|
2364
|
+
return this.limit(take);
|
|
2365
|
+
}
|
|
2366
|
+
groupBy(...columns) {
|
|
2367
|
+
this.groupBys.push(...columns);
|
|
2368
|
+
return this;
|
|
2369
|
+
}
|
|
2370
|
+
having(column, operator, value) {
|
|
2371
|
+
this.havings.push({ column, operator, value });
|
|
2372
|
+
return this;
|
|
2373
|
+
}
|
|
2374
|
+
async get() {
|
|
2375
|
+
const { sql, bindings } = this.toSQL();
|
|
2376
|
+
const result = await this.connection.raw(sql, bindings);
|
|
2377
|
+
return result.rows;
|
|
2378
|
+
}
|
|
2379
|
+
async first() {
|
|
2380
|
+
const qb = this.clone();
|
|
2381
|
+
qb.limitValue = 1;
|
|
2382
|
+
const { sql, bindings } = qb.toSQL();
|
|
2383
|
+
const result = await this.connection.raw(sql, bindings);
|
|
2384
|
+
return result.rows.length > 0 ? result.rows[0] : null;
|
|
2385
|
+
}
|
|
2386
|
+
async find(id) {
|
|
2387
|
+
return this.where("id", id).first();
|
|
2388
|
+
}
|
|
2389
|
+
async pluck(column) {
|
|
2390
|
+
const qb = this.clone();
|
|
2391
|
+
qb.columns = [column];
|
|
2392
|
+
const { sql, bindings } = qb.toSQL();
|
|
2393
|
+
const result = await this.connection.raw(sql, bindings);
|
|
2394
|
+
return result.rows.map((row) => row[column]);
|
|
2395
|
+
}
|
|
2396
|
+
async count(column = "*") {
|
|
2397
|
+
const qb = this.clone();
|
|
2398
|
+
qb.columns = [
|
|
2399
|
+
`COUNT(${column === "*" ? "*" : this.wrap(column)}) as aggregate`
|
|
2400
|
+
];
|
|
2401
|
+
qb.orderBys = [];
|
|
2402
|
+
qb.limitValue = null;
|
|
2403
|
+
qb.offsetValue = null;
|
|
2404
|
+
const { sql, bindings } = qb.toSQL();
|
|
2405
|
+
const result = await this.connection.raw(sql, bindings);
|
|
2406
|
+
const row = result.rows[0];
|
|
2407
|
+
if (!row) return 0;
|
|
2408
|
+
return Number(row.aggregate ?? row.count ?? row["COUNT(*)"] ?? 0);
|
|
2409
|
+
}
|
|
2410
|
+
async exists() {
|
|
2411
|
+
const count = await this.count();
|
|
2412
|
+
return count > 0;
|
|
2413
|
+
}
|
|
2414
|
+
async doesntExist() {
|
|
2415
|
+
return !await this.exists();
|
|
2416
|
+
}
|
|
2417
|
+
async max(column) {
|
|
2418
|
+
return this.aggregate("MAX", column);
|
|
2419
|
+
}
|
|
2420
|
+
async min(column) {
|
|
2421
|
+
return this.aggregate("MIN", column);
|
|
2422
|
+
}
|
|
2423
|
+
async sum(column) {
|
|
2424
|
+
const result = await this.aggregate("SUM", column);
|
|
2425
|
+
return result ?? 0;
|
|
2426
|
+
}
|
|
2427
|
+
async avg(column) {
|
|
2428
|
+
const result = await this.aggregate("AVG", column);
|
|
2429
|
+
return result ?? 0;
|
|
2430
|
+
}
|
|
2431
|
+
async paginate(perPage = 15, page = 1) {
|
|
2432
|
+
const countQb = this.clone();
|
|
2433
|
+
const total = await countQb.count();
|
|
2434
|
+
const lastPage = Math.max(1, Math.ceil(total / perPage));
|
|
2435
|
+
const currentPage = Math.max(1, Math.min(page, lastPage));
|
|
2436
|
+
const fromVal = (currentPage - 1) * perPage + 1;
|
|
2437
|
+
const toVal = Math.min(currentPage * perPage, total);
|
|
2438
|
+
const qb = this.clone();
|
|
2439
|
+
qb.limitValue = perPage;
|
|
2440
|
+
qb.offsetValue = (currentPage - 1) * perPage;
|
|
2441
|
+
const { sql, bindings } = qb.toSQL();
|
|
2442
|
+
const result = await this.connection.raw(sql, bindings);
|
|
2443
|
+
return {
|
|
2444
|
+
data: result.rows,
|
|
2445
|
+
currentPage,
|
|
2446
|
+
perPage,
|
|
2447
|
+
total,
|
|
2448
|
+
lastPage,
|
|
2449
|
+
from: total > 0 ? fromVal : 0,
|
|
2450
|
+
to: total > 0 ? toVal : 0,
|
|
2451
|
+
hasMore: currentPage < lastPage,
|
|
2452
|
+
hasPrev: currentPage > 1,
|
|
2453
|
+
isEmpty: total === 0
|
|
2454
|
+
};
|
|
2455
|
+
}
|
|
2456
|
+
async insert(data) {
|
|
2457
|
+
const { sql, bindings } = this.compileInsert(data);
|
|
2458
|
+
const driverType = this.connection.getDriver();
|
|
2459
|
+
if (driverType === "postgresql") {
|
|
2460
|
+
const dialect = this.connection.getDialect();
|
|
2461
|
+
const returningSQL = dialect.compileInsertReturning(sql, bindings);
|
|
2462
|
+
const result2 = await this.connection.raw(returningSQL, bindings);
|
|
2463
|
+
return result2.rows.length > 0 ? Number(result2.rows[0].id) ?? 0 : 0;
|
|
2464
|
+
}
|
|
2465
|
+
const result = await this.connection.raw(sql, bindings);
|
|
2466
|
+
if (result.rows && result.rows.length > 0) {
|
|
2467
|
+
return Number(result.rows[0].id) ?? result.rows[0].insertId ?? 0;
|
|
2468
|
+
}
|
|
2469
|
+
return 0;
|
|
2470
|
+
}
|
|
2471
|
+
async insertGetId(data) {
|
|
2472
|
+
return this.insert(data);
|
|
2473
|
+
}
|
|
2474
|
+
async insertReturning(data) {
|
|
2475
|
+
const { sql, bindings } = this.compileInsert(data);
|
|
2476
|
+
const dialect = this.connection.getDialect();
|
|
2477
|
+
const returningSQL = dialect.compileInsertReturning(sql, bindings);
|
|
2478
|
+
const result = await this.connection.raw(returningSQL, bindings);
|
|
2479
|
+
return result.rows.length > 0 ? result.rows[0] : null;
|
|
2480
|
+
}
|
|
2481
|
+
async update(data) {
|
|
2482
|
+
const { sql, bindings } = this.compileUpdate(data);
|
|
2483
|
+
const result = await this.connection.raw(sql, bindings);
|
|
2484
|
+
const rows = result.rows;
|
|
2485
|
+
if (rows.length > 0) {
|
|
2486
|
+
const info = rows[0];
|
|
2487
|
+
return info.affectedRows ?? info.changes ?? rows.length;
|
|
2488
|
+
}
|
|
2489
|
+
return 0;
|
|
2490
|
+
}
|
|
2491
|
+
async delete() {
|
|
2492
|
+
const { sql, bindings } = this.compileDelete();
|
|
2493
|
+
const result = await this.connection.raw(sql, bindings);
|
|
2494
|
+
const rows = result.rows;
|
|
2495
|
+
if (rows.length > 0) {
|
|
2496
|
+
const info = rows[0];
|
|
2497
|
+
return info.affectedRows ?? info.changes ?? rows.length;
|
|
2498
|
+
}
|
|
2499
|
+
return 0;
|
|
2500
|
+
}
|
|
2501
|
+
async truncate() {
|
|
2502
|
+
const dialect = this.connection.getDialect();
|
|
2503
|
+
const sql = dialect.compileTruncate(this.tableName);
|
|
2504
|
+
await this.connection.raw(sql);
|
|
2505
|
+
}
|
|
2506
|
+
async chunk(size, callback) {
|
|
2507
|
+
let page = 1;
|
|
2508
|
+
let hasMore = true;
|
|
2509
|
+
while (hasMore) {
|
|
2510
|
+
const qb = this.clone();
|
|
2511
|
+
qb.limitValue = size;
|
|
2512
|
+
qb.offsetValue = (page - 1) * size;
|
|
2513
|
+
const rows = await qb.get();
|
|
2514
|
+
if (rows.length === 0) {
|
|
2515
|
+
hasMore = false;
|
|
2516
|
+
break;
|
|
2517
|
+
}
|
|
2518
|
+
await callback(rows);
|
|
2519
|
+
if (rows.length < size) {
|
|
2520
|
+
hasMore = false;
|
|
2521
|
+
}
|
|
2522
|
+
page++;
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
clone() {
|
|
2526
|
+
const qb = new _QueryBuilder(this.connection, this.tableName);
|
|
2527
|
+
qb.columns = [...this.columns];
|
|
2528
|
+
qb.distinctEnabled = this.distinctEnabled;
|
|
2529
|
+
qb.wheres = this.cloneWheres(this.wheres);
|
|
2530
|
+
qb.joins = [...this.joins];
|
|
2531
|
+
qb.orderBys = [...this.orderBys];
|
|
2532
|
+
qb.havings = [...this.havings];
|
|
2533
|
+
qb.groupBys = [...this.groupBys];
|
|
2534
|
+
qb.limitValue = this.limitValue;
|
|
2535
|
+
qb.offsetValue = this.offsetValue;
|
|
2536
|
+
qb.fromSubquery = this.fromSubquery;
|
|
2537
|
+
return qb;
|
|
2538
|
+
}
|
|
2539
|
+
toSQL() {
|
|
2540
|
+
const bindings = [];
|
|
2541
|
+
const dialect = this.connection.getDialect();
|
|
2542
|
+
const wrappedColumns = this.columns.map(
|
|
2543
|
+
(c) => c.includes("(") || c === "*" || c.includes(" as ") || c.includes(" AS ") ? c : dialect.wrapIdentifier(c)
|
|
2544
|
+
).join(", ");
|
|
2545
|
+
const from = this.fromSubquery ?? this.tableName;
|
|
2546
|
+
const wrappedFrom = this.fromSubquery ?? dialect.wrapIdentifier(from);
|
|
2547
|
+
let sql = this.distinctEnabled ? `SELECT DISTINCT ${wrappedColumns} FROM ${wrappedFrom}` : `SELECT ${wrappedColumns} FROM ${wrappedFrom}`;
|
|
2548
|
+
const joinSQL = this.compileJoins(dialect);
|
|
2549
|
+
if (joinSQL) sql += joinSQL;
|
|
2550
|
+
const whereSQL = this.compileWheres(dialect, bindings);
|
|
2551
|
+
if (whereSQL) sql += whereSQL;
|
|
2552
|
+
if (this.groupBys.length > 0) {
|
|
2553
|
+
sql += ` GROUP BY ${this.groupBys.map((c) => dialect.wrapIdentifier(c)).join(", ")}`;
|
|
2554
|
+
}
|
|
2555
|
+
const havingSQL = this.compileHavings(dialect, bindings);
|
|
2556
|
+
if (havingSQL) sql += havingSQL;
|
|
2557
|
+
if (this.orderBys.length > 0) {
|
|
2558
|
+
sql += ` ORDER BY ${this.orderBys.map((o) => {
|
|
2559
|
+
const col = o.column === "RANDOM()" ? o.column : dialect.wrapIdentifier(o.column);
|
|
2560
|
+
return `${col} ${o.direction.toUpperCase()}`;
|
|
2561
|
+
}).join(", ")}`;
|
|
2562
|
+
}
|
|
2563
|
+
const limitOffsetSQL = dialect.compileLimitOffset(
|
|
2564
|
+
bindings,
|
|
2565
|
+
this.limitValue,
|
|
2566
|
+
this.offsetValue
|
|
2567
|
+
);
|
|
2568
|
+
if (limitOffsetSQL) sql += limitOffsetSQL;
|
|
2569
|
+
return { sql, bindings };
|
|
2570
|
+
}
|
|
2571
|
+
dd() {
|
|
2572
|
+
const { sql, bindings } = this.toSQL();
|
|
2573
|
+
const output = `SQL: ${sql}
|
|
2574
|
+
Bindings: ${JSON.stringify(bindings)}`;
|
|
2575
|
+
console.error(output);
|
|
2576
|
+
process.exit(1);
|
|
2577
|
+
}
|
|
2578
|
+
compileJoins(dialect) {
|
|
2579
|
+
if (this.joins.length === 0) return "";
|
|
2580
|
+
return " " + this.joins.map((j) => {
|
|
2581
|
+
const type = j.type.toUpperCase();
|
|
2582
|
+
return `${type} JOIN ${dialect.wrapIdentifier(j.table)} ON ${dialect.wrapIdentifier(j.first)} ${j.operator} ${this.wrap(j.second)}`;
|
|
2583
|
+
}).join(" ");
|
|
2584
|
+
}
|
|
2585
|
+
compileWheres(dialect, bindings) {
|
|
2586
|
+
if (this.wheres.length === 0) return "";
|
|
2587
|
+
const sql = this.compileWhereArray(this.wheres, dialect, bindings);
|
|
2588
|
+
return sql ? ` WHERE ${sql}` : "";
|
|
2589
|
+
}
|
|
2590
|
+
compileWhereArray(wheres, dialect, bindings) {
|
|
2591
|
+
if (wheres.length === 0) return "";
|
|
2592
|
+
const parts = [];
|
|
2593
|
+
for (const w of wheres) {
|
|
2594
|
+
const sql = this.compileSingleWhere(w, dialect, bindings);
|
|
2595
|
+
if (sql !== null) parts.push(sql);
|
|
2596
|
+
}
|
|
2597
|
+
if (parts.length === 0) return "";
|
|
2598
|
+
return parts.join(" AND ");
|
|
2599
|
+
}
|
|
2600
|
+
compileSingleWhere(w, dialect, bindings) {
|
|
2601
|
+
const col = w.column ? dialect.wrapIdentifier(w.column) : "";
|
|
2602
|
+
switch (w.type) {
|
|
2603
|
+
case "basic": {
|
|
2604
|
+
bindings.push(w.value);
|
|
2605
|
+
const operator = w.operator === "=" ? "=" : w.operator;
|
|
2606
|
+
return `${col} ${operator} ${dialect.makeParameter(bindings.length - 1)}`;
|
|
2607
|
+
}
|
|
2608
|
+
case "in": {
|
|
2609
|
+
const placeholders = w.values.map((v) => {
|
|
2610
|
+
bindings.push(v);
|
|
2611
|
+
return dialect.makeParameter(bindings.length - 1);
|
|
2612
|
+
}).join(", ");
|
|
2613
|
+
return `${col} IN (${placeholders})`;
|
|
2614
|
+
}
|
|
2615
|
+
case "notIn": {
|
|
2616
|
+
const placeholders = w.values.map((v) => {
|
|
2617
|
+
bindings.push(v);
|
|
2618
|
+
return dialect.makeParameter(bindings.length - 1);
|
|
2619
|
+
}).join(", ");
|
|
2620
|
+
return `${col} NOT IN (${placeholders})`;
|
|
2621
|
+
}
|
|
2622
|
+
case "null":
|
|
2623
|
+
return `${col} IS NULL`;
|
|
2624
|
+
case "notNull":
|
|
2625
|
+
return `${col} IS NOT NULL`;
|
|
2626
|
+
case "between": {
|
|
2627
|
+
bindings.push(w.values[0], w.values[1]);
|
|
2628
|
+
return `${col} BETWEEN ${dialect.makeParameter(bindings.length - 2)} AND ${dialect.makeParameter(bindings.length - 1)}`;
|
|
2629
|
+
}
|
|
2630
|
+
case "notBetween": {
|
|
2631
|
+
bindings.push(w.values[0], w.values[1]);
|
|
2632
|
+
return `${col} NOT BETWEEN ${dialect.makeParameter(bindings.length - 2)} AND ${dialect.makeParameter(bindings.length - 1)}`;
|
|
2633
|
+
}
|
|
2634
|
+
case "like": {
|
|
2635
|
+
bindings.push(w.value);
|
|
2636
|
+
return `${col} LIKE ${dialect.makeParameter(bindings.length - 1)}`;
|
|
2637
|
+
}
|
|
2638
|
+
case "nested": {
|
|
2639
|
+
const nestedSQL = this.compileNestedWhere(w.nested, dialect, bindings);
|
|
2640
|
+
return nestedSQL ? `(${nestedSQL})` : null;
|
|
2641
|
+
}
|
|
2642
|
+
default:
|
|
2643
|
+
return null;
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
compileNestedWhere(wheres, dialect, bindings) {
|
|
2647
|
+
const parts = [];
|
|
2648
|
+
for (const w of wheres) {
|
|
2649
|
+
const sql = this.compileSingleWhere(w, dialect, bindings);
|
|
2650
|
+
if (sql !== null) {
|
|
2651
|
+
parts.push(sql);
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
if (parts.length === 0) return "";
|
|
2655
|
+
return parts.join(" AND ");
|
|
2656
|
+
}
|
|
2657
|
+
compileHavings(dialect, bindings) {
|
|
2658
|
+
if (this.havings.length === 0) return "";
|
|
2659
|
+
const parts = this.havings.map((h) => {
|
|
2660
|
+
bindings.push(h.value);
|
|
2661
|
+
return `${dialect.wrapIdentifier(h.column)} ${h.operator} ${dialect.makeParameter(bindings.length - 1)}`;
|
|
2662
|
+
});
|
|
2663
|
+
return ` HAVING ${parts.join(" AND ")}`;
|
|
2664
|
+
}
|
|
2665
|
+
compileInsert(data) {
|
|
2666
|
+
const dialect = this.connection.getDialect();
|
|
2667
|
+
const columns = Object.keys(data);
|
|
2668
|
+
const values = Object.values(data);
|
|
2669
|
+
const bindings = [];
|
|
2670
|
+
const placeholders = values.map((v) => {
|
|
2671
|
+
bindings.push(v);
|
|
2672
|
+
return dialect.makeParameter(bindings.length - 1);
|
|
2673
|
+
}).join(", ");
|
|
2674
|
+
const sql = `INSERT INTO ${dialect.wrapIdentifier(this.tableName)} (${columns.map((c) => dialect.wrapIdentifier(c)).join(", ")}) VALUES (${placeholders})`;
|
|
2675
|
+
return { sql, bindings };
|
|
2676
|
+
}
|
|
2677
|
+
compileUpdate(data) {
|
|
2678
|
+
const dialect = this.connection.getDialect();
|
|
2679
|
+
const bindings = [];
|
|
2680
|
+
const sets = Object.entries(data).map(([key, value]) => {
|
|
2681
|
+
bindings.push(value);
|
|
2682
|
+
return `${dialect.wrapIdentifier(key)} = ${dialect.makeParameter(bindings.length - 1)}`;
|
|
2683
|
+
});
|
|
2684
|
+
let sql = `UPDATE ${dialect.wrapIdentifier(this.tableName)} SET ${sets.join(", ")}`;
|
|
2685
|
+
const whereSQL = this.compileWheres(dialect, bindings);
|
|
2686
|
+
if (whereSQL) sql += whereSQL;
|
|
2687
|
+
if (this.orderBys.length > 0) {
|
|
2688
|
+
sql += ` ORDER BY ${this.orderBys.map((o) => {
|
|
2689
|
+
const col = o.column === "RANDOM()" ? o.column : dialect.wrapIdentifier(o.column);
|
|
2690
|
+
return `${col} ${o.direction.toUpperCase()}`;
|
|
2691
|
+
}).join(", ")}`;
|
|
2692
|
+
}
|
|
2693
|
+
const limitOffsetSQL = dialect.compileLimitOffset(
|
|
2694
|
+
bindings,
|
|
2695
|
+
this.limitValue,
|
|
2696
|
+
this.offsetValue
|
|
2697
|
+
);
|
|
2698
|
+
if (limitOffsetSQL) sql += limitOffsetSQL;
|
|
2699
|
+
return { sql, bindings };
|
|
2700
|
+
}
|
|
2701
|
+
compileDelete() {
|
|
2702
|
+
const dialect = this.connection.getDialect();
|
|
2703
|
+
const bindings = [];
|
|
2704
|
+
let sql = `DELETE FROM ${dialect.wrapIdentifier(this.tableName)}`;
|
|
2705
|
+
const whereSQL = this.compileWheres(dialect, bindings);
|
|
2706
|
+
if (whereSQL) sql += whereSQL;
|
|
2707
|
+
if (this.orderBys.length > 0) {
|
|
2708
|
+
sql += ` ORDER BY ${this.orderBys.map((o) => {
|
|
2709
|
+
const col = o.column === "RANDOM()" ? o.column : dialect.wrapIdentifier(o.column);
|
|
2710
|
+
return `${col} ${o.direction.toUpperCase()}`;
|
|
2711
|
+
}).join(", ")}`;
|
|
2712
|
+
}
|
|
2713
|
+
const limitOffsetSQL = dialect.compileLimitOffset(
|
|
2714
|
+
bindings,
|
|
2715
|
+
this.limitValue,
|
|
2716
|
+
this.offsetValue
|
|
2717
|
+
);
|
|
2718
|
+
if (limitOffsetSQL) sql += limitOffsetSQL;
|
|
2719
|
+
return { sql, bindings };
|
|
2720
|
+
}
|
|
2721
|
+
async aggregate(fn, column) {
|
|
2722
|
+
const qb = this.clone();
|
|
2723
|
+
qb.columns = [`${fn}(${this.wrap(column)}) as aggregate`];
|
|
2724
|
+
qb.orderBys = [];
|
|
2725
|
+
qb.limitValue = null;
|
|
2726
|
+
qb.offsetValue = null;
|
|
2727
|
+
const { sql, bindings } = qb.toSQL();
|
|
2728
|
+
const result = await this.connection.raw(sql, bindings);
|
|
2729
|
+
const row = result.rows[0];
|
|
2730
|
+
if (!row) return null;
|
|
2731
|
+
const val = row.aggregate ?? row[`${fn}(${column})`];
|
|
2732
|
+
return val !== null && val !== void 0 ? Number(val) : null;
|
|
2733
|
+
}
|
|
2734
|
+
cloneWheres(wheres) {
|
|
2735
|
+
return wheres.map((w) => ({
|
|
2736
|
+
...w,
|
|
2737
|
+
values: w.values ? [...w.values] : void 0,
|
|
2738
|
+
nested: w.nested ? this.cloneWheres(w.nested) : void 0
|
|
2739
|
+
}));
|
|
2740
|
+
}
|
|
2741
|
+
wrap(identifier) {
|
|
2742
|
+
if (identifier === "*" || identifier.includes("(")) return identifier;
|
|
2743
|
+
const dialect = this.connection.getDialect();
|
|
2744
|
+
return dialect.wrapIdentifier(identifier);
|
|
2745
|
+
}
|
|
2746
|
+
};
|
|
2747
|
+
|
|
2748
|
+
// src/server/database/connection.ts
|
|
2749
|
+
var DatabaseConnection = class _DatabaseConnection {
|
|
2750
|
+
config;
|
|
2751
|
+
driver = null;
|
|
2752
|
+
constructor(config) {
|
|
2753
|
+
this.config = {
|
|
2754
|
+
driver: "mysql",
|
|
2755
|
+
host: "127.0.0.1",
|
|
2756
|
+
charset: "utf8mb4",
|
|
2757
|
+
prefix: "",
|
|
2758
|
+
...config
|
|
2759
|
+
};
|
|
2760
|
+
if (this.config.port === void 0) {
|
|
2761
|
+
if (this.config.driver === "mysql") this.config.port = 3306;
|
|
2762
|
+
else if (this.config.driver === "postgresql") this.config.port = 5432;
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
async connect() {
|
|
2766
|
+
if (this.driver !== null) return;
|
|
2767
|
+
this.driver = await createDriver(this.config);
|
|
2768
|
+
await this.driver.connect();
|
|
2769
|
+
}
|
|
2770
|
+
async disconnect() {
|
|
2771
|
+
if (this.driver !== null) {
|
|
2772
|
+
await this.driver.disconnect();
|
|
2773
|
+
this.driver = null;
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
async raw(sql, bindings) {
|
|
2777
|
+
this.ensureConnected();
|
|
2778
|
+
return this.driver.raw(sql, bindings);
|
|
2779
|
+
}
|
|
2780
|
+
table(tableName) {
|
|
2781
|
+
return new QueryBuilder(this, `${this.config.prefix ?? ""}${tableName}`);
|
|
2782
|
+
}
|
|
2783
|
+
isConnected() {
|
|
2784
|
+
return this.driver !== null && this.driver.isConnected();
|
|
2785
|
+
}
|
|
2786
|
+
getDriver() {
|
|
2787
|
+
return this.config.driver ?? "mysql";
|
|
2788
|
+
}
|
|
2789
|
+
getDialect() {
|
|
2790
|
+
this.ensureConnected();
|
|
2791
|
+
return this.driver.getDialect();
|
|
2792
|
+
}
|
|
2793
|
+
getPrefix() {
|
|
2794
|
+
return this.config.prefix ?? "";
|
|
2795
|
+
}
|
|
2796
|
+
getConfig() {
|
|
2797
|
+
return { ...this.config };
|
|
2798
|
+
}
|
|
2799
|
+
async transaction(callback) {
|
|
2800
|
+
this.ensureConnected();
|
|
2801
|
+
return this.driver.transaction(async (_driver) => {
|
|
2802
|
+
const trxConnection = new _DatabaseConnection(this.config);
|
|
2803
|
+
trxConnection["driver"] = this.driver;
|
|
2804
|
+
trxConnection.raw = async (sql, bindings) => {
|
|
2805
|
+
return this.driver.raw(sql, bindings);
|
|
2806
|
+
};
|
|
2807
|
+
return callback(trxConnection);
|
|
2808
|
+
});
|
|
2809
|
+
}
|
|
2810
|
+
static generateId() {
|
|
2811
|
+
return randomUUID3();
|
|
2812
|
+
}
|
|
2813
|
+
ensureConnected() {
|
|
2814
|
+
if (this.driver === null || !this.driver.isConnected()) {
|
|
2815
|
+
throw new Error(
|
|
2816
|
+
"Database not connected. Call connect() before performing operations."
|
|
2817
|
+
);
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
};
|
|
2821
|
+
|
|
2822
|
+
// src/server/database/migration.ts
|
|
2823
|
+
var SchemaBuilder = class {
|
|
2824
|
+
connection;
|
|
2825
|
+
constructor(connection) {
|
|
2826
|
+
this.connection = connection;
|
|
2827
|
+
}
|
|
2828
|
+
async createTable(tableName, callback) {
|
|
2829
|
+
const blueprint = new TableBlueprint(this.connection, "create");
|
|
2830
|
+
callback(blueprint);
|
|
2831
|
+
const dialect = this.connection.getDialect();
|
|
2832
|
+
const columns = blueprint.compileColumns();
|
|
2833
|
+
const constraints = blueprint.compileConstraints(dialect);
|
|
2834
|
+
const sql = dialect.compileCreateTable(tableName, columns, constraints);
|
|
2835
|
+
await this.connection.raw(sql);
|
|
2836
|
+
}
|
|
2837
|
+
async dropTable(tableName) {
|
|
2838
|
+
const dialect = this.connection.getDialect();
|
|
2839
|
+
const sql = dialect.compileDropTable(tableName);
|
|
2840
|
+
await this.connection.raw(sql);
|
|
2841
|
+
}
|
|
2842
|
+
async dropTableIfExists(tableName) {
|
|
2843
|
+
const dialect = this.connection.getDialect();
|
|
2844
|
+
const sql = dialect.compileDropTableIfExists(tableName);
|
|
2845
|
+
await this.connection.raw(sql);
|
|
2846
|
+
}
|
|
2847
|
+
async renameTable(from, to) {
|
|
2848
|
+
const dialect = this.connection.getDialect();
|
|
2849
|
+
const sql = dialect.compileRenameTable(from, to);
|
|
2850
|
+
await this.connection.raw(sql);
|
|
2851
|
+
}
|
|
2852
|
+
async alterTable(tableName, callback) {
|
|
2853
|
+
const blueprint = new TableBlueprint(this.connection, "alter");
|
|
2854
|
+
callback(blueprint);
|
|
2855
|
+
const dialect = this.connection.getDialect();
|
|
2856
|
+
if (blueprint.droppedColumns.length > 0) {
|
|
2857
|
+
const dropSQL = dialect.compileDropColumns(
|
|
2858
|
+
tableName,
|
|
2859
|
+
blueprint.droppedColumns
|
|
2860
|
+
);
|
|
2861
|
+
await this.connection.raw(
|
|
2862
|
+
`ALTER TABLE ${dialect.wrapIdentifier(tableName)} ${dropSQL}`
|
|
2863
|
+
);
|
|
2864
|
+
}
|
|
2865
|
+
if (blueprint.renamedColumns.length > 0) {
|
|
2866
|
+
for (const { from, to } of blueprint.renamedColumns) {
|
|
2867
|
+
const renameSQL = dialect.compileRenameColumn(tableName, from, to);
|
|
2868
|
+
await this.connection.raw(
|
|
2869
|
+
`ALTER TABLE ${dialect.wrapIdentifier(tableName)} ${renameSQL}`
|
|
2870
|
+
);
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
const compiledColumns = blueprint.compileColumnDefinitions(dialect);
|
|
2874
|
+
if (compiledColumns.length > 0) {
|
|
2875
|
+
for (const colSQL of compiledColumns) {
|
|
2876
|
+
await this.connection.raw(
|
|
2877
|
+
`ALTER TABLE ${dialect.wrapIdentifier(tableName)} ${colSQL}`
|
|
2878
|
+
);
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
async hasTable(tableName) {
|
|
2883
|
+
const dialect = this.connection.getDialect();
|
|
2884
|
+
const sql = dialect.compileHasTable(tableName);
|
|
2885
|
+
const result = await this.connection.raw(sql, [tableName]);
|
|
2886
|
+
return this.parseCount(result) > 0;
|
|
2887
|
+
}
|
|
2888
|
+
async hasColumn(tableName, columnName) {
|
|
2889
|
+
const dialect = this.connection.getDialect();
|
|
2890
|
+
const driver = this.connection.getDriver();
|
|
2891
|
+
if (driver === "sqlite") {
|
|
2892
|
+
const sql2 = dialect.compileHasColumn(tableName, columnName);
|
|
2893
|
+
const result2 = await this.connection.raw(sql2);
|
|
2894
|
+
return result2.rows.some((row) => row.name === columnName);
|
|
2895
|
+
}
|
|
2896
|
+
const sql = dialect.compileHasColumn(tableName, columnName);
|
|
2897
|
+
const result = await this.connection.raw(sql, [tableName, columnName]);
|
|
2898
|
+
return this.parseCount(result) > 0;
|
|
2899
|
+
}
|
|
2900
|
+
parseCount(result) {
|
|
2901
|
+
if (result.rows.length === 0) return 0;
|
|
2902
|
+
const row = result.rows[0];
|
|
2903
|
+
return Number(row.count ?? row.Count ?? 0);
|
|
2904
|
+
}
|
|
2905
|
+
};
|
|
2906
|
+
var TableBlueprint = class {
|
|
2907
|
+
mode;
|
|
2908
|
+
columns = [];
|
|
2909
|
+
primaryKeys = [];
|
|
2910
|
+
uniqueKeys = [];
|
|
2911
|
+
indexKeys = [];
|
|
2912
|
+
foreignKeys = [];
|
|
2913
|
+
droppedColumns = [];
|
|
2914
|
+
renamedColumns = [];
|
|
2915
|
+
constructor(_connection, mode) {
|
|
2916
|
+
this.mode = mode;
|
|
2917
|
+
}
|
|
2918
|
+
id(name = "id") {
|
|
2919
|
+
const col = this.addColumn("id", name);
|
|
2920
|
+
col.autoIncrement();
|
|
2921
|
+
col.unsigned();
|
|
2922
|
+
return col;
|
|
2923
|
+
}
|
|
2924
|
+
increments(name = "id") {
|
|
2925
|
+
const col = this.addColumn("increments", name);
|
|
2926
|
+
col.autoIncrement();
|
|
2927
|
+
col.unsigned();
|
|
2928
|
+
return col;
|
|
2929
|
+
}
|
|
2930
|
+
bigIncrements(name = "id") {
|
|
2931
|
+
const col = this.addColumn("bigIncrements", name);
|
|
2932
|
+
col.autoIncrement();
|
|
2933
|
+
col.unsigned();
|
|
2934
|
+
return col;
|
|
2935
|
+
}
|
|
2936
|
+
string(name, length = 255) {
|
|
2937
|
+
const col = this.addColumn("string", name);
|
|
2938
|
+
col.setLength(length);
|
|
2939
|
+
return col;
|
|
2940
|
+
}
|
|
2941
|
+
text(name) {
|
|
2942
|
+
return this.addColumn("text", name);
|
|
2943
|
+
}
|
|
2944
|
+
integer(name) {
|
|
2945
|
+
return this.addColumn("integer", name);
|
|
2946
|
+
}
|
|
2947
|
+
bigInteger(name) {
|
|
2948
|
+
return this.addColumn("bigInteger", name);
|
|
2949
|
+
}
|
|
2950
|
+
tinyInteger(name) {
|
|
2951
|
+
return this.addColumn("tinyInteger", name);
|
|
2952
|
+
}
|
|
2953
|
+
smallInteger(name) {
|
|
2954
|
+
return this.addColumn("smallInteger", name);
|
|
2955
|
+
}
|
|
2956
|
+
boolean(name) {
|
|
2957
|
+
return this.addColumn("boolean", name);
|
|
2958
|
+
}
|
|
2959
|
+
float(name, _precision) {
|
|
2960
|
+
const col = this.addColumn("float", name);
|
|
2961
|
+
if (_precision !== void 0) col.setPrecision(_precision);
|
|
2962
|
+
return col;
|
|
2963
|
+
}
|
|
2964
|
+
double(name) {
|
|
2965
|
+
return this.addColumn("double", name);
|
|
2966
|
+
}
|
|
2967
|
+
decimal(name, precision = 10, scale = 0) {
|
|
2968
|
+
const col = this.addColumn("decimal", name);
|
|
2969
|
+
col.setPrecision(precision);
|
|
2970
|
+
col.setScale(scale);
|
|
2971
|
+
return col;
|
|
2972
|
+
}
|
|
2973
|
+
date(name) {
|
|
2974
|
+
return this.addColumn("date", name);
|
|
2975
|
+
}
|
|
2976
|
+
datetime(name) {
|
|
2977
|
+
return this.addColumn("datetime", name);
|
|
2978
|
+
}
|
|
2979
|
+
timestamp(name) {
|
|
2980
|
+
return this.addColumn("timestamp", name);
|
|
2981
|
+
}
|
|
2982
|
+
time(name) {
|
|
2983
|
+
return this.addColumn("time", name);
|
|
2984
|
+
}
|
|
2985
|
+
year(name) {
|
|
2986
|
+
return this.addColumn("year", name);
|
|
2987
|
+
}
|
|
2988
|
+
json(name) {
|
|
2989
|
+
return this.addColumn("json", name);
|
|
2990
|
+
}
|
|
2991
|
+
jsonb(name) {
|
|
2992
|
+
return this.addColumn("jsonb", name);
|
|
2993
|
+
}
|
|
2994
|
+
binary(name) {
|
|
2995
|
+
return this.addColumn("binary", name);
|
|
2996
|
+
}
|
|
2997
|
+
uuid(name = "uuid") {
|
|
2998
|
+
return this.addColumn("uuid", name);
|
|
2999
|
+
}
|
|
3000
|
+
enum(name, values) {
|
|
3001
|
+
const col = this.addColumn("enum", name);
|
|
3002
|
+
col.setValues(values);
|
|
3003
|
+
return col;
|
|
3004
|
+
}
|
|
3005
|
+
foreignId(name) {
|
|
3006
|
+
const col = this.addColumn("foreignId", name);
|
|
3007
|
+
col.unsigned();
|
|
3008
|
+
return col;
|
|
3009
|
+
}
|
|
3010
|
+
primary(...columns) {
|
|
3011
|
+
this.primaryKeys.push(...columns);
|
|
3012
|
+
}
|
|
3013
|
+
unique(...columns) {
|
|
3014
|
+
this.uniqueKeys.push(columns);
|
|
3015
|
+
}
|
|
3016
|
+
index(...columns) {
|
|
3017
|
+
this.indexKeys.push(columns);
|
|
3018
|
+
}
|
|
3019
|
+
foreign(column) {
|
|
3020
|
+
const fk = {
|
|
3021
|
+
column,
|
|
3022
|
+
references: "",
|
|
3023
|
+
on: "",
|
|
3024
|
+
onDelete: null,
|
|
3025
|
+
onUpdate: null
|
|
3026
|
+
};
|
|
3027
|
+
this.foreignKeys.push(fk);
|
|
3028
|
+
return new ForeignKeyDefinition(fk);
|
|
3029
|
+
}
|
|
3030
|
+
timestamps() {
|
|
3031
|
+
this.timestamp("created_at").nullable();
|
|
3032
|
+
this.timestamp("updated_at").nullable();
|
|
3033
|
+
}
|
|
3034
|
+
softDeletes() {
|
|
3035
|
+
this.timestamp("deleted_at").nullable();
|
|
3036
|
+
}
|
|
3037
|
+
rememberToken() {
|
|
3038
|
+
this.string("remember_token", 100).nullable();
|
|
3039
|
+
}
|
|
3040
|
+
dropColumn(column) {
|
|
3041
|
+
this.droppedColumns.push(column);
|
|
3042
|
+
}
|
|
3043
|
+
renameColumn(from, to) {
|
|
3044
|
+
this.renamedColumns.push({ from, to });
|
|
3045
|
+
}
|
|
3046
|
+
dropPrimary() {
|
|
3047
|
+
this.primaryKeys = [];
|
|
3048
|
+
}
|
|
3049
|
+
dropUnique(_indexName) {
|
|
3050
|
+
this.uniqueKeys = [];
|
|
3051
|
+
}
|
|
3052
|
+
dropIndex(_indexName) {
|
|
3053
|
+
this.indexKeys = [];
|
|
3054
|
+
}
|
|
3055
|
+
dropForeign(_indexName) {
|
|
3056
|
+
this.foreignKeys = [];
|
|
3057
|
+
}
|
|
3058
|
+
dropTimestamps() {
|
|
3059
|
+
this.droppedColumns.push("created_at", "updated_at");
|
|
3060
|
+
}
|
|
3061
|
+
dropSoftDeletes() {
|
|
3062
|
+
this.droppedColumns.push("deleted_at");
|
|
3063
|
+
}
|
|
3064
|
+
dropRememberToken() {
|
|
3065
|
+
this.droppedColumns.push("remember_token");
|
|
3066
|
+
}
|
|
3067
|
+
compileColumns() {
|
|
3068
|
+
return this.columns.filter((c) => this.mode === "create" || c.isNew).map((c) => c.compile());
|
|
3069
|
+
}
|
|
3070
|
+
compileConstraints(dialect) {
|
|
3071
|
+
const constraints = [];
|
|
3072
|
+
const wrap = (name) => dialect.wrapIdentifier(name);
|
|
3073
|
+
for (const pk of this.primaryKeys) {
|
|
3074
|
+
constraints.push(`PRIMARY KEY (${wrap(pk)})`);
|
|
3075
|
+
}
|
|
3076
|
+
for (const uk of this.uniqueKeys) {
|
|
3077
|
+
constraints.push(`UNIQUE (${uk.map((c) => wrap(c)).join(", ")})`);
|
|
3078
|
+
}
|
|
3079
|
+
for (const ik of this.indexKeys) {
|
|
3080
|
+
constraints.push(`INDEX (${ik.map((c) => wrap(c)).join(", ")})`);
|
|
3081
|
+
}
|
|
3082
|
+
for (const fk of this.foreignKeys) {
|
|
3083
|
+
let fkSQL = `FOREIGN KEY (${wrap(fk.column)}) REFERENCES ${wrap(fk.on)} (${wrap(fk.references)})`;
|
|
3084
|
+
if (fk.onDelete) fkSQL += ` ON DELETE ${fk.onDelete}`;
|
|
3085
|
+
if (fk.onUpdate) fkSQL += ` ON UPDATE ${fk.onUpdate}`;
|
|
3086
|
+
constraints.push(fkSQL);
|
|
3087
|
+
}
|
|
3088
|
+
return constraints;
|
|
3089
|
+
}
|
|
3090
|
+
compileColumnDefinitions(dialect) {
|
|
3091
|
+
return this.columns.filter((c) => c.isNew && this.mode === "alter").map((c) => dialect.compileAddColumns("", [c.compile()]));
|
|
3092
|
+
}
|
|
3093
|
+
addColumn(type, name) {
|
|
3094
|
+
const col = new ColumnDefinition(type, name);
|
|
3095
|
+
this.columns.push(col);
|
|
3096
|
+
return col;
|
|
3097
|
+
}
|
|
3098
|
+
};
|
|
3099
|
+
var ColumnDefinition = class {
|
|
3100
|
+
type;
|
|
3101
|
+
name;
|
|
3102
|
+
isNullable = false;
|
|
3103
|
+
defaultValue = void 0;
|
|
3104
|
+
isUnsigned = false;
|
|
3105
|
+
isUnique = false;
|
|
3106
|
+
isPrimary = false;
|
|
3107
|
+
isIndex = false;
|
|
3108
|
+
commentText = null;
|
|
3109
|
+
afterColumn = null;
|
|
3110
|
+
isFirst = false;
|
|
3111
|
+
isAutoIncrement = false;
|
|
3112
|
+
precisionValue = null;
|
|
3113
|
+
scaleValue = null;
|
|
3114
|
+
lengthValue = null;
|
|
3115
|
+
enumValues = null;
|
|
3116
|
+
isForeignId = false;
|
|
3117
|
+
isNew = true;
|
|
3118
|
+
constructor(type, name) {
|
|
3119
|
+
this.type = type;
|
|
3120
|
+
this.name = name;
|
|
3121
|
+
}
|
|
3122
|
+
nullable() {
|
|
3123
|
+
this.isNullable = true;
|
|
3124
|
+
return this;
|
|
3125
|
+
}
|
|
3126
|
+
default(value) {
|
|
3127
|
+
this.defaultValue = value;
|
|
3128
|
+
return this;
|
|
3129
|
+
}
|
|
3130
|
+
unsigned() {
|
|
3131
|
+
this.isUnsigned = true;
|
|
3132
|
+
return this;
|
|
3133
|
+
}
|
|
3134
|
+
unique() {
|
|
3135
|
+
this.isUnique = true;
|
|
3136
|
+
return this;
|
|
3137
|
+
}
|
|
3138
|
+
primary() {
|
|
3139
|
+
this.isPrimary = true;
|
|
3140
|
+
return this;
|
|
3141
|
+
}
|
|
3142
|
+
index() {
|
|
3143
|
+
this.isIndex = true;
|
|
3144
|
+
return this;
|
|
3145
|
+
}
|
|
3146
|
+
comment(text) {
|
|
3147
|
+
this.commentText = text;
|
|
3148
|
+
return this;
|
|
3149
|
+
}
|
|
3150
|
+
after(column) {
|
|
3151
|
+
this.afterColumn = column;
|
|
3152
|
+
return this;
|
|
3153
|
+
}
|
|
3154
|
+
first() {
|
|
3155
|
+
this.isFirst = true;
|
|
3156
|
+
return this;
|
|
3157
|
+
}
|
|
3158
|
+
autoIncrement() {
|
|
3159
|
+
this.isAutoIncrement = true;
|
|
3160
|
+
return this;
|
|
3161
|
+
}
|
|
3162
|
+
setValues(vals) {
|
|
3163
|
+
this.enumValues = vals;
|
|
3164
|
+
return this;
|
|
3165
|
+
}
|
|
3166
|
+
setLength(len) {
|
|
3167
|
+
this.lengthValue = len;
|
|
3168
|
+
return this;
|
|
3169
|
+
}
|
|
3170
|
+
setPrecision(precision) {
|
|
3171
|
+
this.precisionValue = precision;
|
|
3172
|
+
return this;
|
|
3173
|
+
}
|
|
3174
|
+
setScale(scale) {
|
|
3175
|
+
this.scaleValue = scale;
|
|
3176
|
+
return this;
|
|
3177
|
+
}
|
|
3178
|
+
compile() {
|
|
3179
|
+
return {
|
|
3180
|
+
name: this.name,
|
|
3181
|
+
type: this.type,
|
|
3182
|
+
nullable: this.isNullable,
|
|
3183
|
+
defaultValue: this.defaultValue,
|
|
3184
|
+
unsigned: this.isUnsigned,
|
|
3185
|
+
unique: this.isUnique,
|
|
3186
|
+
primary: this.isPrimary,
|
|
3187
|
+
index: this.isIndex,
|
|
3188
|
+
comment: this.commentText,
|
|
3189
|
+
after: this.afterColumn,
|
|
3190
|
+
first: this.isFirst,
|
|
3191
|
+
autoIncrement: this.isAutoIncrement,
|
|
3192
|
+
precision: this.precisionValue,
|
|
3193
|
+
scale: this.scaleValue,
|
|
3194
|
+
length: this.lengthValue,
|
|
3195
|
+
values: this.enumValues,
|
|
3196
|
+
isForeignId: this.isForeignId
|
|
3197
|
+
};
|
|
3198
|
+
}
|
|
3199
|
+
};
|
|
3200
|
+
var ForeignKeyDefinition = class {
|
|
3201
|
+
def;
|
|
3202
|
+
constructor(def) {
|
|
3203
|
+
this.def = def;
|
|
3204
|
+
}
|
|
3205
|
+
references(column) {
|
|
3206
|
+
this.def.references = column;
|
|
3207
|
+
return this;
|
|
3208
|
+
}
|
|
3209
|
+
on(table) {
|
|
3210
|
+
this.def.on = table;
|
|
3211
|
+
return this;
|
|
3212
|
+
}
|
|
3213
|
+
onDelete(action) {
|
|
3214
|
+
this.def.onDelete = action;
|
|
3215
|
+
return this;
|
|
3216
|
+
}
|
|
3217
|
+
onUpdate(action) {
|
|
3218
|
+
this.def.onUpdate = action;
|
|
3219
|
+
return this;
|
|
3220
|
+
}
|
|
3221
|
+
};
|
|
3222
|
+
var Migrator = class {
|
|
3223
|
+
connection;
|
|
3224
|
+
migrations = [];
|
|
3225
|
+
constructor(connection) {
|
|
3226
|
+
this.connection = connection;
|
|
3227
|
+
}
|
|
3228
|
+
addMigrations(migrations) {
|
|
3229
|
+
this.migrations.push(...migrations);
|
|
3230
|
+
}
|
|
3231
|
+
setMigrations(migrations) {
|
|
3232
|
+
this.migrations = migrations;
|
|
3233
|
+
}
|
|
3234
|
+
async run() {
|
|
3235
|
+
await this.ensureMigrationTable();
|
|
3236
|
+
const ran = await this.getRanMigrations();
|
|
3237
|
+
const ranNames = new Set(ran.map((r) => r.name));
|
|
3238
|
+
const pending = this.migrations.filter((m) => !ranNames.has(m.name));
|
|
3239
|
+
if (pending.length === 0) {
|
|
3240
|
+
console.log("Nothing to migrate.");
|
|
3241
|
+
return;
|
|
3242
|
+
}
|
|
3243
|
+
const nextBatch = await this.getNextBatchNumber();
|
|
3244
|
+
const schema = new SchemaBuilder(this.connection);
|
|
3245
|
+
for (const migration of pending) {
|
|
3246
|
+
console.log(`Migrating: ${migration.name}`);
|
|
3247
|
+
try {
|
|
3248
|
+
await migration.up(schema);
|
|
3249
|
+
await this.recordMigration(migration.name, nextBatch);
|
|
3250
|
+
console.log(`Migrated: ${migration.name}`);
|
|
3251
|
+
} catch (err) {
|
|
3252
|
+
console.error(`Migration failed: ${migration.name}`, err);
|
|
3253
|
+
throw err;
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
async rollback() {
|
|
3258
|
+
await this.ensureMigrationTable();
|
|
3259
|
+
const lastBatch = await this.getLastBatchNumber();
|
|
3260
|
+
if (lastBatch === 0) {
|
|
3261
|
+
console.log("Nothing to rollback.");
|
|
3262
|
+
return;
|
|
3263
|
+
}
|
|
3264
|
+
const lastBatchMigrations = await this.getMigrationsByBatch(lastBatch);
|
|
3265
|
+
const schema = new SchemaBuilder(this.connection);
|
|
3266
|
+
for (const migration of lastBatchMigrations.reverse()) {
|
|
3267
|
+
const def = this.migrations.find((m) => m.name === migration.name);
|
|
3268
|
+
if (def) {
|
|
3269
|
+
console.log(`Rolling back: ${migration.name}`);
|
|
3270
|
+
try {
|
|
3271
|
+
await def.down(schema);
|
|
3272
|
+
await this.removeMigration(migration.name);
|
|
3273
|
+
console.log(`Rolled back: ${migration.name}`);
|
|
3274
|
+
} catch (err) {
|
|
3275
|
+
console.error(`Rollback failed: ${migration.name}`, err);
|
|
3276
|
+
throw err;
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
async reset() {
|
|
3282
|
+
await this.ensureMigrationTable();
|
|
3283
|
+
const allRan = await this.getRanMigrations();
|
|
3284
|
+
const schema = new SchemaBuilder(this.connection);
|
|
3285
|
+
for (const migration of allRan.reverse()) {
|
|
3286
|
+
const def = this.migrations.find((m) => m.name === migration.name);
|
|
3287
|
+
if (def) {
|
|
3288
|
+
console.log(`Resetting: ${migration.name}`);
|
|
3289
|
+
try {
|
|
3290
|
+
await def.down(schema);
|
|
3291
|
+
await this.removeMigration(migration.name);
|
|
3292
|
+
console.log(`Reset: ${migration.name}`);
|
|
3293
|
+
} catch (err) {
|
|
3294
|
+
console.error(`Reset failed: ${migration.name}`, err);
|
|
3295
|
+
throw err;
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
async refresh() {
|
|
3301
|
+
await this.reset();
|
|
3302
|
+
await this.run();
|
|
3303
|
+
}
|
|
3304
|
+
async status() {
|
|
3305
|
+
await this.ensureMigrationTable();
|
|
3306
|
+
return this.getRanMigrations();
|
|
3307
|
+
}
|
|
3308
|
+
async ensureMigrationTable() {
|
|
3309
|
+
const dialect = this.connection.getDialect();
|
|
3310
|
+
const sql = dialect.compileCreateMigrationsTable();
|
|
3311
|
+
await this.connection.raw(sql);
|
|
3312
|
+
}
|
|
3313
|
+
async getRanMigrations() {
|
|
3314
|
+
const result = await this.connection.raw(
|
|
3315
|
+
"SELECT name, batch, executed_at as executedAt FROM migrations ORDER BY batch ASC, name ASC"
|
|
3316
|
+
);
|
|
3317
|
+
return result.rows.map((row) => ({
|
|
3318
|
+
name: String(row.name),
|
|
3319
|
+
batch: Number(row.batch),
|
|
3320
|
+
executedAt: String(row.executedAt ?? "")
|
|
3321
|
+
}));
|
|
3322
|
+
}
|
|
3323
|
+
async getNextBatchNumber() {
|
|
3324
|
+
const result = await this.connection.raw(
|
|
3325
|
+
"SELECT COALESCE(MAX(batch), 0) + 1 as next_batch FROM migrations"
|
|
3326
|
+
);
|
|
3327
|
+
return Number(result.rows[0]?.next_batch ?? 1);
|
|
3328
|
+
}
|
|
3329
|
+
async getLastBatchNumber() {
|
|
3330
|
+
const result = await this.connection.raw(
|
|
3331
|
+
"SELECT COALESCE(MAX(batch), 0) as last_batch FROM migrations"
|
|
3332
|
+
);
|
|
3333
|
+
return Number(result.rows[0]?.last_batch ?? 0);
|
|
3334
|
+
}
|
|
3335
|
+
async getMigrationsByBatch(batch) {
|
|
3336
|
+
const result = await this.connection.raw(
|
|
3337
|
+
"SELECT name, batch, executed_at as executedAt FROM migrations WHERE batch = ? ORDER BY name ASC",
|
|
3338
|
+
[batch]
|
|
3339
|
+
);
|
|
3340
|
+
return result.rows.map((row) => ({
|
|
3341
|
+
name: String(row.name),
|
|
3342
|
+
batch: Number(row.batch),
|
|
3343
|
+
executedAt: String(row.executedAt ?? "")
|
|
3344
|
+
}));
|
|
3345
|
+
}
|
|
3346
|
+
async recordMigration(name, batch) {
|
|
3347
|
+
await this.connection.raw(
|
|
3348
|
+
"INSERT INTO migrations (name, batch) VALUES (?, ?)",
|
|
3349
|
+
[name, batch]
|
|
3350
|
+
);
|
|
3351
|
+
}
|
|
3352
|
+
async removeMigration(name) {
|
|
3353
|
+
await this.connection.raw("DELETE FROM migrations WHERE name = ?", [name]);
|
|
3354
|
+
}
|
|
3355
|
+
};
|
|
3356
|
+
|
|
3357
|
+
// src/server/database/pagination.ts
|
|
3358
|
+
var Pagination = class _Pagination {
|
|
3359
|
+
data;
|
|
3360
|
+
currentPage;
|
|
3361
|
+
perPage;
|
|
3362
|
+
total;
|
|
3363
|
+
lastPage;
|
|
3364
|
+
from;
|
|
3365
|
+
to;
|
|
3366
|
+
constructor(result) {
|
|
3367
|
+
this.data = result.data;
|
|
3368
|
+
this.currentPage = result.currentPage;
|
|
3369
|
+
this.perPage = result.perPage;
|
|
3370
|
+
this.total = result.total;
|
|
3371
|
+
this.lastPage = result.lastPage;
|
|
3372
|
+
this.from = result.from;
|
|
3373
|
+
this.to = result.to;
|
|
3374
|
+
}
|
|
3375
|
+
get hasMore() {
|
|
3376
|
+
return this.currentPage < this.lastPage;
|
|
3377
|
+
}
|
|
3378
|
+
get hasPrev() {
|
|
3379
|
+
return this.currentPage > 1;
|
|
3380
|
+
}
|
|
3381
|
+
get isEmpty() {
|
|
3382
|
+
return this.data.length === 0;
|
|
3383
|
+
}
|
|
3384
|
+
nextPage() {
|
|
3385
|
+
if (!this.hasMore) return null;
|
|
3386
|
+
return {
|
|
3387
|
+
page: this.currentPage + 1,
|
|
3388
|
+
perPage: this.perPage,
|
|
3389
|
+
url: null
|
|
3390
|
+
};
|
|
3391
|
+
}
|
|
3392
|
+
prevPage() {
|
|
3393
|
+
if (!this.hasPrev) return null;
|
|
3394
|
+
return {
|
|
3395
|
+
page: this.currentPage - 1,
|
|
3396
|
+
perPage: this.perPage,
|
|
3397
|
+
url: null
|
|
3398
|
+
};
|
|
3399
|
+
}
|
|
3400
|
+
toJSON() {
|
|
3401
|
+
return {
|
|
3402
|
+
data: this.data,
|
|
3403
|
+
pagination: {
|
|
3404
|
+
currentPage: this.currentPage,
|
|
3405
|
+
perPage: this.perPage,
|
|
3406
|
+
total: this.total,
|
|
3407
|
+
lastPage: this.lastPage,
|
|
3408
|
+
from: this.from,
|
|
3409
|
+
to: this.to,
|
|
3410
|
+
hasMore: this.hasMore,
|
|
3411
|
+
hasPrev: this.hasPrev,
|
|
3412
|
+
isEmpty: this.isEmpty
|
|
3413
|
+
}
|
|
3414
|
+
};
|
|
3415
|
+
}
|
|
3416
|
+
map(fn) {
|
|
3417
|
+
return new _Pagination({
|
|
3418
|
+
...this,
|
|
3419
|
+
data: this.data.map(fn)
|
|
3420
|
+
});
|
|
3421
|
+
}
|
|
3422
|
+
items() {
|
|
3423
|
+
return this.data;
|
|
3424
|
+
}
|
|
3425
|
+
static from(result) {
|
|
3426
|
+
return new _Pagination(result);
|
|
3427
|
+
}
|
|
3428
|
+
};
|
|
3429
|
+
|
|
3430
|
+
// src/server/database/seeder.ts
|
|
3431
|
+
var Seeder = class {
|
|
3432
|
+
connection;
|
|
3433
|
+
constructor(connection) {
|
|
3434
|
+
this.connection = connection;
|
|
3435
|
+
}
|
|
3436
|
+
async call(seederClass) {
|
|
3437
|
+
await seederClass.run(this.connection);
|
|
3438
|
+
}
|
|
3439
|
+
async insert(table, data) {
|
|
3440
|
+
if (data.length === 0) return;
|
|
3441
|
+
const dialect = this.connection.getDialect();
|
|
3442
|
+
const firstRow = data[0];
|
|
3443
|
+
if (!firstRow) return;
|
|
3444
|
+
const columns = Object.keys(firstRow);
|
|
3445
|
+
const wrappedColumns = columns.map((c) => dialect.wrapIdentifier(c)).join(", ");
|
|
3446
|
+
const batchSize = 100;
|
|
3447
|
+
for (let i = 0; i < data.length; i += batchSize) {
|
|
3448
|
+
const batch = data.slice(i, i + batchSize);
|
|
3449
|
+
const placeholders = [];
|
|
3450
|
+
const bindings = [];
|
|
3451
|
+
for (const row of batch) {
|
|
3452
|
+
const rowPlaceholders = columns.map((col) => {
|
|
3453
|
+
bindings.push(row[col]);
|
|
3454
|
+
return dialect.makeParameter(bindings.length - 1);
|
|
3455
|
+
});
|
|
3456
|
+
placeholders.push(`(${rowPlaceholders.join(", ")})`);
|
|
3457
|
+
}
|
|
3458
|
+
const sql = `INSERT INTO ${dialect.wrapIdentifier(table)} (${wrappedColumns}) VALUES ${placeholders.join(", ")}`;
|
|
3459
|
+
await this.connection.raw(sql, bindings);
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
async truncate(table) {
|
|
3463
|
+
const dialect = this.connection.getDialect();
|
|
3464
|
+
const driver = this.connection.getDriver();
|
|
3465
|
+
if (driver === "sqlite") {
|
|
3466
|
+
await this.connection.raw(`DELETE FROM ${dialect.wrapIdentifier(table)}`);
|
|
3467
|
+
} else if (driver === "postgresql") {
|
|
3468
|
+
await this.connection.raw(dialect.compileTruncate(table));
|
|
3469
|
+
} else {
|
|
3470
|
+
await this.connection.raw("SET FOREIGN_KEY_CHECKS = 0");
|
|
3471
|
+
await this.connection.raw(dialect.compileTruncate(table));
|
|
3472
|
+
await this.connection.raw("SET FOREIGN_KEY_CHECKS = 1");
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
async run() {
|
|
3476
|
+
throw new Error("Seeder.run() must be overridden by subclasses");
|
|
3477
|
+
}
|
|
3478
|
+
};
|
|
3479
|
+
|
|
3480
|
+
// src/server/events/index.ts
|
|
3481
|
+
import { EventEmitter } from "events";
|
|
3482
|
+
var Event = class {
|
|
3483
|
+
emitter;
|
|
3484
|
+
wildcardMode;
|
|
3485
|
+
patterns = [];
|
|
3486
|
+
constructor(config) {
|
|
3487
|
+
this.emitter = new EventEmitter();
|
|
3488
|
+
this.wildcardMode = config?.wildcard ?? false;
|
|
3489
|
+
if (config?.maxListeners !== void 0) {
|
|
3490
|
+
this.emitter.setMaxListeners(config.maxListeners);
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
on(event2, handler) {
|
|
3494
|
+
this.emitter.on(event2, handler);
|
|
3495
|
+
return this;
|
|
3496
|
+
}
|
|
3497
|
+
addListener(event2, handler) {
|
|
3498
|
+
return this.on(event2, handler);
|
|
3499
|
+
}
|
|
3500
|
+
once(event2, handler) {
|
|
3501
|
+
this.emitter.once(event2, handler);
|
|
3502
|
+
return this;
|
|
3503
|
+
}
|
|
3504
|
+
async emit(event2, ...args) {
|
|
3505
|
+
const listeners = this.emitter.listeners(event2);
|
|
3506
|
+
for (const listener of listeners) {
|
|
3507
|
+
const result = listener(...args);
|
|
3508
|
+
if (result instanceof Promise) {
|
|
3509
|
+
await result;
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
if (this.wildcardMode) {
|
|
3513
|
+
for (const entry of this.patterns) {
|
|
3514
|
+
if (entry.regex.test(event2)) {
|
|
3515
|
+
const result = entry.handler(event2, ...args);
|
|
3516
|
+
if (result instanceof Promise) {
|
|
3517
|
+
await result;
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
off(event2, handler) {
|
|
3524
|
+
this.emitter.off(event2, handler);
|
|
3525
|
+
return this;
|
|
3526
|
+
}
|
|
3527
|
+
removeListener(event2, handler) {
|
|
3528
|
+
return this.off(event2, handler);
|
|
3529
|
+
}
|
|
3530
|
+
removeAllListeners(event2) {
|
|
3531
|
+
if (event2 !== void 0) {
|
|
3532
|
+
this.emitter.removeAllListeners(event2);
|
|
3533
|
+
this.patterns = this.patterns.filter((p) => p.pattern !== event2);
|
|
3534
|
+
} else {
|
|
3535
|
+
this.emitter.removeAllListeners();
|
|
3536
|
+
this.patterns = [];
|
|
3537
|
+
}
|
|
3538
|
+
return this;
|
|
3539
|
+
}
|
|
3540
|
+
listeners(event2) {
|
|
3541
|
+
return this.emitter.listeners(event2);
|
|
3542
|
+
}
|
|
3543
|
+
hasListeners(event2) {
|
|
3544
|
+
if (this.emitter.listenerCount(event2) > 0) {
|
|
3545
|
+
return true;
|
|
3546
|
+
}
|
|
3547
|
+
if (this.wildcardMode) {
|
|
3548
|
+
for (const entry of this.patterns) {
|
|
3549
|
+
if (entry.regex.test(event2)) {
|
|
3550
|
+
return true;
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
return false;
|
|
3555
|
+
}
|
|
3556
|
+
onPattern(pattern, handler) {
|
|
3557
|
+
if (!this.wildcardMode) {
|
|
3558
|
+
throw new Error(
|
|
3559
|
+
"Pattern matching is not enabled. Set wildcard: true in EventConfig."
|
|
3560
|
+
);
|
|
3561
|
+
}
|
|
3562
|
+
const regex = this.patternToRegex(pattern);
|
|
3563
|
+
this.patterns.push({ pattern, regex, handler });
|
|
3564
|
+
return this;
|
|
3565
|
+
}
|
|
3566
|
+
async ask(event2, ...args) {
|
|
3567
|
+
const handlers = this.emitter.listeners(event2);
|
|
3568
|
+
const results = [];
|
|
3569
|
+
for (const handler of handlers) {
|
|
3570
|
+
const result = handler(...args);
|
|
3571
|
+
if (result instanceof Promise) {
|
|
3572
|
+
const resolved = await result;
|
|
3573
|
+
if (resolved !== void 0) {
|
|
3574
|
+
results.push(resolved);
|
|
3575
|
+
}
|
|
3576
|
+
} else if (result !== void 0) {
|
|
3577
|
+
results.push(result);
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
if (this.wildcardMode) {
|
|
3581
|
+
for (const entry of this.patterns) {
|
|
3582
|
+
if (entry.regex.test(event2)) {
|
|
3583
|
+
const result = entry.handler(event2, ...args);
|
|
3584
|
+
if (result instanceof Promise) {
|
|
3585
|
+
const resolved = await result;
|
|
3586
|
+
if (resolved !== void 0) {
|
|
3587
|
+
results.push(resolved);
|
|
3588
|
+
}
|
|
3589
|
+
} else if (result !== void 0) {
|
|
3590
|
+
results.push(result);
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
return results;
|
|
3596
|
+
}
|
|
3597
|
+
listenerCount(event2) {
|
|
3598
|
+
if (event2 !== void 0) {
|
|
3599
|
+
let count = this.emitter.listenerCount(event2);
|
|
3600
|
+
if (this.wildcardMode) {
|
|
3601
|
+
for (const entry of this.patterns) {
|
|
3602
|
+
if (entry.regex.test(event2)) {
|
|
3603
|
+
count++;
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
return count;
|
|
3608
|
+
}
|
|
3609
|
+
return this.emitter.eventNames().reduce((sum, name) => sum + this.emitter.listenerCount(name), 0) + this.patterns.length;
|
|
3610
|
+
}
|
|
3611
|
+
eventNames() {
|
|
3612
|
+
const nativeNames = this.emitter.eventNames();
|
|
3613
|
+
const patternNames = this.patterns.map((p) => p.pattern);
|
|
3614
|
+
return [.../* @__PURE__ */ new Set([...nativeNames, ...patternNames])];
|
|
3615
|
+
}
|
|
3616
|
+
patternToRegex(pattern) {
|
|
3617
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
3618
|
+
const regexStr = escaped.replace(/\*/g, "[^.]*");
|
|
3619
|
+
return new RegExp(`^${regexStr}$`);
|
|
3620
|
+
}
|
|
3621
|
+
};
|
|
3622
|
+
var defaultEvent = null;
|
|
3623
|
+
function createEvent(config) {
|
|
3624
|
+
defaultEvent = new Event(config);
|
|
3625
|
+
return defaultEvent;
|
|
3626
|
+
}
|
|
3627
|
+
function event() {
|
|
3628
|
+
if (defaultEvent === null) {
|
|
3629
|
+
defaultEvent = new Event();
|
|
3630
|
+
}
|
|
3631
|
+
return defaultEvent;
|
|
3632
|
+
}
|
|
3633
|
+
|
|
3634
|
+
// src/server/helpers.ts
|
|
3635
|
+
var defaultBuilder = null;
|
|
3636
|
+
var URLBuilder = class {
|
|
3637
|
+
baseUrl;
|
|
3638
|
+
constructor(baseUrl) {
|
|
3639
|
+
this.baseUrl = baseUrl ?? "http://localhost:3000";
|
|
3640
|
+
}
|
|
3641
|
+
route(_name, _params) {
|
|
3642
|
+
throw new Error(
|
|
3643
|
+
"URLBuilder.route() requires a Router reference. Set the router via URLBuilder.setRouter() before calling route()."
|
|
3644
|
+
);
|
|
3645
|
+
}
|
|
3646
|
+
to(path3) {
|
|
3647
|
+
const normalized = path3.replace(/\\/g, "/");
|
|
3648
|
+
const base = this.baseUrl.replace(/\/$/, "");
|
|
3649
|
+
const cleanPath = normalized.startsWith("/") ? normalized : `/${normalized}`;
|
|
3650
|
+
return `${base}${cleanPath}`;
|
|
3651
|
+
}
|
|
3652
|
+
asset(path3) {
|
|
3653
|
+
return this.to(path3);
|
|
3654
|
+
}
|
|
3655
|
+
secure(path3) {
|
|
3656
|
+
const base = this.baseUrl.replace(/^http:/, "https:");
|
|
3657
|
+
const normalized = path3.replace(/\\/g, "/");
|
|
3658
|
+
const cleanPath = normalized.startsWith("/") ? normalized : `/${normalized}`;
|
|
3659
|
+
return `${base}${cleanPath}`;
|
|
3660
|
+
}
|
|
3661
|
+
setBaseUrl(url2) {
|
|
3662
|
+
this.baseUrl = url2;
|
|
3663
|
+
}
|
|
3664
|
+
getBaseUrl() {
|
|
3665
|
+
return this.baseUrl;
|
|
3666
|
+
}
|
|
3667
|
+
};
|
|
3668
|
+
function url() {
|
|
3669
|
+
if (defaultBuilder === null) {
|
|
3670
|
+
defaultBuilder = new URLBuilder();
|
|
3671
|
+
}
|
|
3672
|
+
return defaultBuilder;
|
|
3673
|
+
}
|
|
3674
|
+
var macroFns = {};
|
|
3675
|
+
function responseMacros(response) {
|
|
3676
|
+
for (const [name, fn] of Object.entries(macroFns)) {
|
|
3677
|
+
const bound = fn.bind(response);
|
|
3678
|
+
response[name] = bound;
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
function registerMacro(name, fn) {
|
|
3682
|
+
macroFns[name] = fn;
|
|
3683
|
+
}
|
|
3684
|
+
registerMacro("success", function(data, message) {
|
|
3685
|
+
return this.json({
|
|
3686
|
+
success: true,
|
|
3687
|
+
message: message ?? "Success",
|
|
3688
|
+
data
|
|
3689
|
+
});
|
|
3690
|
+
});
|
|
3691
|
+
registerMacro(
|
|
3692
|
+
"error",
|
|
3693
|
+
function(message, status = 400) {
|
|
3694
|
+
return this.json({ success: false, message }, status);
|
|
3695
|
+
}
|
|
3696
|
+
);
|
|
3697
|
+
registerMacro("created", function(data, message) {
|
|
3698
|
+
return this.status(201).json({
|
|
3699
|
+
success: true,
|
|
3700
|
+
message: message ?? "Created",
|
|
3701
|
+
data
|
|
3702
|
+
});
|
|
3703
|
+
});
|
|
3704
|
+
registerMacro("noContent", function() {
|
|
3705
|
+
return this.status(204).send("");
|
|
3706
|
+
});
|
|
3707
|
+
registerMacro(
|
|
3708
|
+
"accepted",
|
|
3709
|
+
function(data, message) {
|
|
3710
|
+
return this.status(202).json({
|
|
3711
|
+
success: true,
|
|
3712
|
+
message: message ?? "Accepted",
|
|
3713
|
+
data
|
|
3714
|
+
});
|
|
3715
|
+
}
|
|
3716
|
+
);
|
|
3717
|
+
registerMacro(
|
|
3718
|
+
"paginated",
|
|
3719
|
+
function(data, meta) {
|
|
3720
|
+
return this.json({
|
|
3721
|
+
success: true,
|
|
3722
|
+
data,
|
|
3723
|
+
meta
|
|
3724
|
+
});
|
|
3725
|
+
}
|
|
3726
|
+
);
|
|
3727
|
+
|
|
3728
|
+
// src/server/storage/index.ts
|
|
3729
|
+
import * as fs2 from "fs";
|
|
3730
|
+
import * as path2 from "path";
|
|
3731
|
+
var LocalDisk = class {
|
|
3732
|
+
root;
|
|
3733
|
+
baseUrl;
|
|
3734
|
+
constructor(root, baseUrl) {
|
|
3735
|
+
this.root = path2.resolve(root);
|
|
3736
|
+
this.baseUrl = baseUrl;
|
|
3737
|
+
}
|
|
3738
|
+
async put(filePath, content) {
|
|
3739
|
+
const fullPath = this.resolvePath(filePath);
|
|
3740
|
+
const dir = path2.dirname(fullPath);
|
|
3741
|
+
if (!fs2.existsSync(dir)) {
|
|
3742
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
3743
|
+
}
|
|
3744
|
+
fs2.writeFileSync(fullPath, content);
|
|
3745
|
+
return filePath;
|
|
3746
|
+
}
|
|
3747
|
+
async get(filePath) {
|
|
3748
|
+
const fullPath = this.resolvePath(filePath);
|
|
3749
|
+
if (!fs2.existsSync(fullPath)) {
|
|
3750
|
+
throw new Error(`File not found: ${filePath}`);
|
|
3751
|
+
}
|
|
3752
|
+
return fs2.readFileSync(fullPath);
|
|
3753
|
+
}
|
|
3754
|
+
async exists(filePath) {
|
|
3755
|
+
const fullPath = this.resolvePath(filePath);
|
|
3756
|
+
return fs2.existsSync(fullPath);
|
|
3757
|
+
}
|
|
3758
|
+
async delete(filePath) {
|
|
3759
|
+
const fullPath = this.resolvePath(filePath);
|
|
3760
|
+
if (!fs2.existsSync(fullPath)) {
|
|
3761
|
+
return false;
|
|
3762
|
+
}
|
|
3763
|
+
fs2.unlinkSync(fullPath);
|
|
3764
|
+
return true;
|
|
3765
|
+
}
|
|
3766
|
+
async copy(from, to) {
|
|
3767
|
+
const fromPath = this.resolvePath(from);
|
|
3768
|
+
const toPath = this.resolvePath(to);
|
|
3769
|
+
if (!fs2.existsSync(fromPath)) {
|
|
3770
|
+
return false;
|
|
3771
|
+
}
|
|
3772
|
+
const dir = path2.dirname(toPath);
|
|
3773
|
+
if (!fs2.existsSync(dir)) {
|
|
3774
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
3775
|
+
}
|
|
3776
|
+
fs2.copyFileSync(fromPath, toPath);
|
|
3777
|
+
return true;
|
|
3778
|
+
}
|
|
3779
|
+
async move(from, to) {
|
|
3780
|
+
const fromPath = this.resolvePath(from);
|
|
3781
|
+
const toPath = this.resolvePath(to);
|
|
3782
|
+
if (!fs2.existsSync(fromPath)) {
|
|
3783
|
+
return false;
|
|
3784
|
+
}
|
|
3785
|
+
const dir = path2.dirname(toPath);
|
|
3786
|
+
if (!fs2.existsSync(dir)) {
|
|
3787
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
3788
|
+
}
|
|
3789
|
+
fs2.renameSync(fromPath, toPath);
|
|
3790
|
+
return true;
|
|
3791
|
+
}
|
|
3792
|
+
url(filePath) {
|
|
3793
|
+
if (this.baseUrl === void 0) {
|
|
3794
|
+
throw new Error("Base URL not configured for this disk");
|
|
3795
|
+
}
|
|
3796
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
3797
|
+
return `${this.baseUrl.replace(/\/$/, "")}/${normalized.replace(/^\//, "")}`;
|
|
3798
|
+
}
|
|
3799
|
+
async size(filePath) {
|
|
3800
|
+
const fullPath = this.resolvePath(filePath);
|
|
3801
|
+
if (!fs2.existsSync(fullPath)) {
|
|
3802
|
+
throw new Error(`File not found: ${filePath}`);
|
|
3803
|
+
}
|
|
3804
|
+
return fs2.statSync(fullPath).size;
|
|
3805
|
+
}
|
|
3806
|
+
async lastModified(filePath) {
|
|
3807
|
+
const fullPath = this.resolvePath(filePath);
|
|
3808
|
+
if (!fs2.existsSync(fullPath)) {
|
|
3809
|
+
throw new Error(`File not found: ${filePath}`);
|
|
3810
|
+
}
|
|
3811
|
+
return fs2.statSync(fullPath).mtime;
|
|
3812
|
+
}
|
|
3813
|
+
async files(directory = "") {
|
|
3814
|
+
const fullPath = this.resolvePath(directory);
|
|
3815
|
+
if (!fs2.existsSync(fullPath)) {
|
|
3816
|
+
return [];
|
|
3817
|
+
}
|
|
3818
|
+
return fs2.readdirSync(fullPath).filter((name) => {
|
|
3819
|
+
const stat2 = fs2.statSync(path2.join(fullPath, name));
|
|
3820
|
+
return stat2.isFile();
|
|
3821
|
+
}).map((name) => path2.join(directory, name).replace(/\\/g, "/"));
|
|
3822
|
+
}
|
|
3823
|
+
async directories(directory = "") {
|
|
3824
|
+
const fullPath = this.resolvePath(directory);
|
|
3825
|
+
if (!fs2.existsSync(fullPath)) {
|
|
3826
|
+
return [];
|
|
3827
|
+
}
|
|
3828
|
+
return fs2.readdirSync(fullPath).filter((name) => {
|
|
3829
|
+
const stat2 = fs2.statSync(path2.join(fullPath, name));
|
|
3830
|
+
return stat2.isDirectory();
|
|
3831
|
+
}).map((name) => path2.join(directory, name).replace(/\\/g, "/"));
|
|
3832
|
+
}
|
|
3833
|
+
async makeDirectory(dirPath) {
|
|
3834
|
+
const fullPath = this.resolvePath(dirPath);
|
|
3835
|
+
fs2.mkdirSync(fullPath, { recursive: true });
|
|
3836
|
+
}
|
|
3837
|
+
async deleteDirectory(dirPath) {
|
|
3838
|
+
const fullPath = this.resolvePath(dirPath);
|
|
3839
|
+
if (fs2.existsSync(fullPath)) {
|
|
3840
|
+
fs2.rmSync(fullPath, { recursive: true, force: true });
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
async append(filePath, content) {
|
|
3844
|
+
const fullPath = this.resolvePath(filePath);
|
|
3845
|
+
const dir = path2.dirname(fullPath);
|
|
3846
|
+
if (!fs2.existsSync(dir)) {
|
|
3847
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
3848
|
+
}
|
|
3849
|
+
fs2.appendFileSync(fullPath, content, "utf-8");
|
|
3850
|
+
}
|
|
3851
|
+
async prepend(filePath, content) {
|
|
3852
|
+
const fullPath = this.resolvePath(filePath);
|
|
3853
|
+
const dir = path2.dirname(fullPath);
|
|
3854
|
+
if (!fs2.existsSync(dir)) {
|
|
3855
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
3856
|
+
}
|
|
3857
|
+
const existing = fs2.existsSync(fullPath) ? fs2.readFileSync(fullPath, "utf-8") : "";
|
|
3858
|
+
fs2.writeFileSync(fullPath, content + existing, "utf-8");
|
|
3859
|
+
}
|
|
3860
|
+
readStream(filePath) {
|
|
3861
|
+
const fullPath = this.resolvePath(filePath);
|
|
3862
|
+
if (!fs2.existsSync(fullPath)) {
|
|
3863
|
+
throw new Error(`File not found: ${filePath}`);
|
|
3864
|
+
}
|
|
3865
|
+
return fs2.createReadStream(fullPath);
|
|
3866
|
+
}
|
|
3867
|
+
writeStream(filePath) {
|
|
3868
|
+
const fullPath = this.resolvePath(filePath);
|
|
3869
|
+
const dir = path2.dirname(fullPath);
|
|
3870
|
+
if (!fs2.existsSync(dir)) {
|
|
3871
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
3872
|
+
}
|
|
3873
|
+
return fs2.createWriteStream(fullPath);
|
|
3874
|
+
}
|
|
3875
|
+
getRoot() {
|
|
3876
|
+
return this.root;
|
|
3877
|
+
}
|
|
3878
|
+
getUrl() {
|
|
3879
|
+
return this.baseUrl;
|
|
3880
|
+
}
|
|
3881
|
+
resolvePath(filePath) {
|
|
3882
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
3883
|
+
const resolved = path2.resolve(this.root, normalized);
|
|
3884
|
+
const resolvedRoot = path2.resolve(this.root);
|
|
3885
|
+
if (!resolved.startsWith(resolvedRoot)) {
|
|
3886
|
+
throw new Error(`Path traversal detected: ${filePath}`);
|
|
3887
|
+
}
|
|
3888
|
+
return resolved;
|
|
3889
|
+
}
|
|
3890
|
+
};
|
|
3891
|
+
var Storage = class {
|
|
3892
|
+
config;
|
|
3893
|
+
diskInstances;
|
|
3894
|
+
constructor(config) {
|
|
3895
|
+
this.config = config;
|
|
3896
|
+
this.diskInstances = /* @__PURE__ */ new Map();
|
|
3897
|
+
}
|
|
3898
|
+
disk(name) {
|
|
3899
|
+
const diskName = name ?? this.config.defaultDisk ?? "local";
|
|
3900
|
+
const existing = this.diskInstances.get(diskName);
|
|
3901
|
+
if (existing !== void 0) {
|
|
3902
|
+
return existing;
|
|
3903
|
+
}
|
|
3904
|
+
const diskConfig = this.config.disks[diskName];
|
|
3905
|
+
if (diskConfig === void 0) {
|
|
3906
|
+
throw new Error(`Disk not configured: ${diskName}`);
|
|
3907
|
+
}
|
|
3908
|
+
const instance = new LocalDisk(diskConfig.root, diskConfig.url);
|
|
3909
|
+
this.diskInstances.set(diskName, instance);
|
|
3910
|
+
return instance;
|
|
3911
|
+
}
|
|
3912
|
+
async put(filePath, content) {
|
|
3913
|
+
return this.disk().put(filePath, content);
|
|
3914
|
+
}
|
|
3915
|
+
async get(filePath) {
|
|
3916
|
+
return this.disk().get(filePath);
|
|
3917
|
+
}
|
|
3918
|
+
async exists(filePath) {
|
|
3919
|
+
return this.disk().exists(filePath);
|
|
3920
|
+
}
|
|
3921
|
+
async delete(filePath) {
|
|
3922
|
+
return this.disk().delete(filePath);
|
|
3923
|
+
}
|
|
3924
|
+
async copy(from, to) {
|
|
3925
|
+
return this.disk().copy(from, to);
|
|
3926
|
+
}
|
|
3927
|
+
async move(from, to) {
|
|
3928
|
+
return this.disk().move(from, to);
|
|
3929
|
+
}
|
|
3930
|
+
async url(filePath) {
|
|
3931
|
+
return this.disk().url(filePath);
|
|
3932
|
+
}
|
|
3933
|
+
async size(filePath) {
|
|
3934
|
+
return this.disk().size(filePath);
|
|
3935
|
+
}
|
|
3936
|
+
async lastModified(filePath) {
|
|
3937
|
+
return this.disk().lastModified(filePath);
|
|
3938
|
+
}
|
|
3939
|
+
async files(directory) {
|
|
3940
|
+
return this.disk().files(directory);
|
|
3941
|
+
}
|
|
3942
|
+
async directories(directory) {
|
|
3943
|
+
return this.disk().directories(directory);
|
|
3944
|
+
}
|
|
3945
|
+
async makeDirectory(dirPath) {
|
|
3946
|
+
return this.disk().makeDirectory(dirPath);
|
|
3947
|
+
}
|
|
3948
|
+
async deleteDirectory(dirPath) {
|
|
3949
|
+
return this.disk().deleteDirectory(dirPath);
|
|
3950
|
+
}
|
|
3951
|
+
};
|
|
3952
|
+
var defaultInstance = null;
|
|
3953
|
+
function createStorage(config) {
|
|
3954
|
+
defaultInstance = new Storage(config);
|
|
3955
|
+
return defaultInstance;
|
|
3956
|
+
}
|
|
3957
|
+
function storage() {
|
|
3958
|
+
if (defaultInstance === null) {
|
|
3959
|
+
throw new Error(
|
|
3960
|
+
"Storage not initialized. Call createStorage(config) first."
|
|
3961
|
+
);
|
|
3962
|
+
}
|
|
3963
|
+
return defaultInstance;
|
|
3964
|
+
}
|
|
3965
|
+
|
|
3966
|
+
// src/server/index.ts
|
|
3967
|
+
var SuperApp = class {
|
|
3968
|
+
router;
|
|
3969
|
+
container;
|
|
3970
|
+
engine;
|
|
3971
|
+
serverInstance;
|
|
3972
|
+
globalPipeline;
|
|
3973
|
+
started = false;
|
|
3974
|
+
serverPromise;
|
|
3975
|
+
constructor(options = {}) {
|
|
3976
|
+
this.container = options.container ?? new Container();
|
|
3977
|
+
this.router = new Router();
|
|
3978
|
+
this.engine = options.engine ?? new NodeEngine();
|
|
3979
|
+
this.globalPipeline = new MiddlewarePipeline();
|
|
3980
|
+
this.container.instance("app", this);
|
|
3981
|
+
this.container.instance("router", this.router);
|
|
3982
|
+
this.container.instance("container", this.container);
|
|
3983
|
+
}
|
|
3984
|
+
use(middleware) {
|
|
3985
|
+
this.globalPipeline.use(middleware);
|
|
3986
|
+
return this;
|
|
3987
|
+
}
|
|
3988
|
+
get(path3, handler) {
|
|
3989
|
+
this.router.get(path3, handler);
|
|
3990
|
+
return this;
|
|
3991
|
+
}
|
|
3992
|
+
post(path3, handler) {
|
|
3993
|
+
this.router.post(path3, handler);
|
|
3994
|
+
return this;
|
|
3995
|
+
}
|
|
3996
|
+
put(path3, handler) {
|
|
3997
|
+
this.router.put(path3, handler);
|
|
3998
|
+
return this;
|
|
3999
|
+
}
|
|
4000
|
+
patch(path3, handler) {
|
|
4001
|
+
this.router.patch(path3, handler);
|
|
4002
|
+
return this;
|
|
4003
|
+
}
|
|
4004
|
+
delete(path3, handler) {
|
|
4005
|
+
this.router.delete(path3, handler);
|
|
4006
|
+
return this;
|
|
4007
|
+
}
|
|
4008
|
+
options(path3, handler) {
|
|
4009
|
+
this.router.options(path3, handler);
|
|
4010
|
+
return this;
|
|
4011
|
+
}
|
|
4012
|
+
any(path3, handler) {
|
|
4013
|
+
this.router.any(path3, handler);
|
|
4014
|
+
return this;
|
|
4015
|
+
}
|
|
4016
|
+
match(methods, path3, handler) {
|
|
4017
|
+
this.router.match(methods, path3, handler);
|
|
4018
|
+
return this;
|
|
4019
|
+
}
|
|
4020
|
+
group(prefix, callback) {
|
|
4021
|
+
this.router.group(prefix, callback);
|
|
4022
|
+
return this;
|
|
4023
|
+
}
|
|
4024
|
+
resource(name, controller) {
|
|
4025
|
+
this.router.resource(name, controller);
|
|
4026
|
+
return this;
|
|
4027
|
+
}
|
|
4028
|
+
apiResource(name, controller) {
|
|
4029
|
+
this.router.apiResource(name, controller);
|
|
4030
|
+
return this;
|
|
4031
|
+
}
|
|
4032
|
+
controller(ctrl) {
|
|
4033
|
+
const prefix = getControllerPrefix(ctrl);
|
|
4034
|
+
const routes = getControllerRoutes(ctrl.prototype);
|
|
4035
|
+
for (const route of routes) {
|
|
4036
|
+
const handlerPath = `${prefix}${route.path}`;
|
|
4037
|
+
const handlerName = String(route.handler);
|
|
4038
|
+
this.router.match(
|
|
4039
|
+
[route.method],
|
|
4040
|
+
handlerPath,
|
|
4041
|
+
async (ctx) => {
|
|
4042
|
+
const instance = createControllerInstance2(ctrl, ctx);
|
|
4043
|
+
const handlerFn = instance[handlerName];
|
|
4044
|
+
if (typeof handlerFn === "function") {
|
|
4045
|
+
await handlerFn.call(instance, ctx);
|
|
4046
|
+
} else {
|
|
4047
|
+
throw new Error(
|
|
4048
|
+
`Handler ${handlerName} not found on controller ${ctrl.name}`
|
|
4049
|
+
);
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
);
|
|
4053
|
+
}
|
|
4054
|
+
return this;
|
|
4055
|
+
}
|
|
4056
|
+
middleware(middleware) {
|
|
4057
|
+
this.router.middleware(middleware);
|
|
4058
|
+
return this;
|
|
4059
|
+
}
|
|
4060
|
+
setEngine(engine) {
|
|
4061
|
+
if (this.started) {
|
|
4062
|
+
throw new Error("Cannot change engine after server has started");
|
|
4063
|
+
}
|
|
4064
|
+
this.engine = engine;
|
|
4065
|
+
return this;
|
|
4066
|
+
}
|
|
4067
|
+
static(path3, options) {
|
|
4068
|
+
this.use(staticFiles(path3, options));
|
|
4069
|
+
return this;
|
|
4070
|
+
}
|
|
4071
|
+
view(_engine) {
|
|
4072
|
+
return this;
|
|
4073
|
+
}
|
|
4074
|
+
getServer() {
|
|
4075
|
+
return this.serverInstance;
|
|
4076
|
+
}
|
|
4077
|
+
async start(port, host) {
|
|
4078
|
+
if (this.started) {
|
|
4079
|
+
throw new Error("Server has already been started");
|
|
4080
|
+
}
|
|
4081
|
+
this.started = true;
|
|
4082
|
+
this.serverInstance = await this.engine.createServer(
|
|
4083
|
+
async (req, res) => {
|
|
4084
|
+
await this.handleRequest(req, res);
|
|
4085
|
+
}
|
|
4086
|
+
);
|
|
4087
|
+
const listenPort = port ?? 3e3;
|
|
4088
|
+
const listenHost = host ?? "0.0.0.0";
|
|
4089
|
+
return new Promise((resolve2) => {
|
|
4090
|
+
const raw = this.serverInstance.raw;
|
|
4091
|
+
raw.listen(listenPort, listenHost, () => {
|
|
4092
|
+
resolve2();
|
|
4093
|
+
});
|
|
4094
|
+
});
|
|
4095
|
+
}
|
|
4096
|
+
listen(port, callback) {
|
|
4097
|
+
this.serverPromise = this.start(port).then(() => {
|
|
4098
|
+
callback?.();
|
|
4099
|
+
});
|
|
4100
|
+
}
|
|
4101
|
+
async close() {
|
|
4102
|
+
if (this.serverInstance !== void 0) {
|
|
4103
|
+
await this.engine.close(this.serverInstance);
|
|
4104
|
+
this.serverInstance = void 0;
|
|
4105
|
+
this.started = false;
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
async ready() {
|
|
4109
|
+
if (this.serverPromise !== void 0) {
|
|
4110
|
+
await this.serverPromise;
|
|
4111
|
+
}
|
|
4112
|
+
}
|
|
4113
|
+
async handleRequest(req, res) {
|
|
4114
|
+
const resolvedRoute = this.router.resolve(req.method, req.path);
|
|
4115
|
+
if (resolvedRoute === null) {
|
|
4116
|
+
res.status(HttpStatus.NOT_FOUND).json({
|
|
4117
|
+
error: "Not Found",
|
|
4118
|
+
message: `Route ${req.method} ${req.path} not found`
|
|
4119
|
+
});
|
|
4120
|
+
await res.flush();
|
|
4121
|
+
return;
|
|
4122
|
+
}
|
|
4123
|
+
req.params = resolvedRoute.params;
|
|
4124
|
+
const ctx = {
|
|
4125
|
+
request: req,
|
|
4126
|
+
response: res,
|
|
4127
|
+
params: resolvedRoute.params,
|
|
4128
|
+
query: req.query,
|
|
4129
|
+
container: this.container
|
|
4130
|
+
};
|
|
4131
|
+
await this.globalPipeline.run(ctx, async () => {
|
|
4132
|
+
const routePipeline = new MiddlewarePipeline();
|
|
4133
|
+
for (const mw of resolvedRoute.middleware) {
|
|
4134
|
+
routePipeline.use(mw);
|
|
4135
|
+
}
|
|
4136
|
+
await routePipeline.run(ctx, async () => {
|
|
4137
|
+
await resolvedRoute.handler(ctx);
|
|
4138
|
+
});
|
|
4139
|
+
});
|
|
4140
|
+
if (!res.headersSent) {
|
|
4141
|
+
await res.flush();
|
|
4142
|
+
}
|
|
4143
|
+
}
|
|
4144
|
+
};
|
|
4145
|
+
function constx(options) {
|
|
4146
|
+
return new SuperApp(options);
|
|
4147
|
+
}
|
|
4148
|
+
function createControllerInstance2(controller, ctx) {
|
|
4149
|
+
const instance = new controller();
|
|
4150
|
+
instance.__ctx = ctx;
|
|
4151
|
+
instance.__container = ctx.container;
|
|
4152
|
+
return instance;
|
|
4153
|
+
}
|
|
4154
|
+
export {
|
|
4155
|
+
Cache,
|
|
4156
|
+
ColumnDefinition,
|
|
4157
|
+
DatabaseConnection,
|
|
4158
|
+
Event,
|
|
4159
|
+
ForeignKeyDefinition,
|
|
4160
|
+
LocalDisk,
|
|
4161
|
+
Migrator,
|
|
4162
|
+
MysqlDialect,
|
|
4163
|
+
Pagination,
|
|
4164
|
+
PostgresqlDialect,
|
|
4165
|
+
QueryBuilder,
|
|
4166
|
+
SchemaBuilder,
|
|
4167
|
+
Seeder,
|
|
4168
|
+
SqliteDialect,
|
|
4169
|
+
Storage,
|
|
4170
|
+
SuperApp,
|
|
4171
|
+
TableBlueprint,
|
|
4172
|
+
URLBuilder,
|
|
4173
|
+
cacheResponse,
|
|
4174
|
+
constx,
|
|
4175
|
+
createDialect,
|
|
4176
|
+
createDriver,
|
|
4177
|
+
createEvent,
|
|
4178
|
+
createStorage,
|
|
4179
|
+
event,
|
|
4180
|
+
registerMacro,
|
|
4181
|
+
responseMacros,
|
|
4182
|
+
storage,
|
|
4183
|
+
url
|
|
4184
|
+
};
|
|
4185
|
+
//# sourceMappingURL=index.js.map
|