@sidekick-coder/zenith-kit 0.0.10 → 0.0.11
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/dist/server/index.d.mts +621 -4
- package/dist/server/index.mjs +1258 -4
- package/dist/shared/chunk-CfYAbeIz.mjs +13 -0
- package/dist/shared/index.d.mts +132 -6
- package/dist/shared/index.mjs +167 -9
- package/eslint.config.mts +1 -0
- package/package.json +15 -2
- package/tsconfig.server.json +1 -1
- package/tsdown.config.ts +15 -1
package/dist/server/index.mjs
CHANGED
|
@@ -1,10 +1,85 @@
|
|
|
1
|
-
import { debounce } from "lodash-es";
|
|
1
|
+
import { debounce, get, has, set, unset } from "lodash-es";
|
|
2
|
+
import fg from "fast-glob";
|
|
3
|
+
import path, { join } from "path";
|
|
4
|
+
import * as v from "valibot";
|
|
5
|
+
import { format } from "date-fns";
|
|
6
|
+
import qs from "qs";
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import { spawn } from "child_process";
|
|
2
9
|
import { exec } from "node:child_process";
|
|
3
10
|
import { promisify } from "node:util";
|
|
4
11
|
import { unlink, writeFile } from "node:fs/promises";
|
|
5
12
|
import { tmpdir } from "node:os";
|
|
6
|
-
import { join } from "node:path";
|
|
13
|
+
import { join as join$1 } from "node:path";
|
|
7
14
|
import { randomUUID } from "node:crypto";
|
|
15
|
+
import { pathToFileURL } from "url";
|
|
16
|
+
import { pathToFileURL as pathToFileURL$1 } from "node:url";
|
|
17
|
+
import fs$1 from "node:fs";
|
|
18
|
+
//#region \0rolldown/runtime.js
|
|
19
|
+
var __defProp = Object.defineProperty;
|
|
20
|
+
var __exportAll = (all, no_symbols) => {
|
|
21
|
+
let target = {};
|
|
22
|
+
for (var name in all) __defProp(target, name, {
|
|
23
|
+
get: all[name],
|
|
24
|
+
enumerable: true
|
|
25
|
+
});
|
|
26
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
27
|
+
return target;
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/shared/utils/compose.ts
|
|
31
|
+
/**
|
|
32
|
+
* Composes multiple mixins into a single class that can be extended.
|
|
33
|
+
* Allows for multiple inheritance-like behavior by applying mixins sequentially.
|
|
34
|
+
*
|
|
35
|
+
* @param mixins - Array of mixin functions to compose
|
|
36
|
+
* @returns A class constructor that includes all mixin functionality
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* class User extends compose(Timestamp, SoftDelete) {
|
|
41
|
+
* constructor(public name: string) {
|
|
42
|
+
* super()
|
|
43
|
+
* }
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
function compose(...mixins) {
|
|
48
|
+
return mixins.reduce((base, mixin) => mixin(base), class {});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Alternative compose function that starts with a base class
|
|
52
|
+
*
|
|
53
|
+
* @param baseClass - The base class to start with
|
|
54
|
+
* @param mixins - Array of mixin functions to apply
|
|
55
|
+
* @returns A class constructor that extends the base class with all mixin functionality
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* class User extends composeWith(BaseEntity, Timestamp, SoftDelete) {
|
|
60
|
+
* constructor(public name: string) {
|
|
61
|
+
* super()
|
|
62
|
+
* }
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
function composeWith(baseClass, ...mixins) {
|
|
67
|
+
return mixins.reduce((base, mixin) => mixin(base), baseClass);
|
|
68
|
+
}
|
|
69
|
+
function mixin(Source) {
|
|
70
|
+
return function(Target) {
|
|
71
|
+
class Mixed extends Target {
|
|
72
|
+
constructor(...args) {
|
|
73
|
+
super(...args);
|
|
74
|
+
const source = new Source(...args);
|
|
75
|
+
Object.assign(this, source);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const key of Reflect.ownKeys(Source.prototype)) if (key !== "constructor") Object.defineProperty(Mixed.prototype, key, Object.getOwnPropertyDescriptor(Source.prototype, key));
|
|
79
|
+
return Mixed;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
8
83
|
//#region src/shared/utils/tryCatch.ts
|
|
9
84
|
async function tryCatch(tryer) {
|
|
10
85
|
try {
|
|
@@ -397,6 +472,1011 @@ var PluginRouter = class {
|
|
|
397
472
|
}
|
|
398
473
|
};
|
|
399
474
|
//#endregion
|
|
475
|
+
//#region src/shared/exceptions/BaseException.ts
|
|
476
|
+
var BaseException = class BaseException extends Error {
|
|
477
|
+
statusCode = 500;
|
|
478
|
+
constructor(message, statusCode = 500) {
|
|
479
|
+
super(message);
|
|
480
|
+
this.name = this.constructor.name;
|
|
481
|
+
this.statusCode = statusCode;
|
|
482
|
+
}
|
|
483
|
+
static fromError(error) {
|
|
484
|
+
return new BaseException(error.message, 500);
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
//#endregion
|
|
488
|
+
//#region src/server/services/RouterFileBaseRoutingService.ts
|
|
489
|
+
var RouterFileBaseRoutingService = class RouterFileBaseRoutingService {
|
|
490
|
+
directory;
|
|
491
|
+
prefix;
|
|
492
|
+
module;
|
|
493
|
+
router;
|
|
494
|
+
logger = new LoggerService();
|
|
495
|
+
routes = [];
|
|
496
|
+
debug = false;
|
|
497
|
+
setDirectory(directory) {
|
|
498
|
+
this.directory = directory;
|
|
499
|
+
return this;
|
|
500
|
+
}
|
|
501
|
+
setPrefix(prefix) {
|
|
502
|
+
this.prefix = prefix;
|
|
503
|
+
return this;
|
|
504
|
+
}
|
|
505
|
+
setRouter(router) {
|
|
506
|
+
this.router = router;
|
|
507
|
+
return this;
|
|
508
|
+
}
|
|
509
|
+
setModule(module) {
|
|
510
|
+
this.module = module;
|
|
511
|
+
return this;
|
|
512
|
+
}
|
|
513
|
+
setLogger(logger) {
|
|
514
|
+
this.logger = logger;
|
|
515
|
+
return this;
|
|
516
|
+
}
|
|
517
|
+
setDebug(debug) {
|
|
518
|
+
this.debug = debug;
|
|
519
|
+
return this;
|
|
520
|
+
}
|
|
521
|
+
static create(directory) {
|
|
522
|
+
return new RouterFileBaseRoutingService().setDirectory(directory);
|
|
523
|
+
}
|
|
524
|
+
async loadFiles() {
|
|
525
|
+
const files = fg.sync("**/*.(ts|js)", { cwd: this.directory });
|
|
526
|
+
if (this.debug) this.logger.debug(`Found ${files.length} route files in ${this.directory}`, { files });
|
|
527
|
+
const routes = [];
|
|
528
|
+
for (const file of files) {
|
|
529
|
+
let routePath = file.replace(/\.(ts|js)$/, "");
|
|
530
|
+
let method = "get";
|
|
531
|
+
for (const suffix of [
|
|
532
|
+
".post",
|
|
533
|
+
".patch",
|
|
534
|
+
".delete",
|
|
535
|
+
".put"
|
|
536
|
+
]) if (routePath.endsWith(suffix)) {
|
|
537
|
+
method = suffix.substring(1);
|
|
538
|
+
routePath = routePath.replace(suffix, "");
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
routePath = routePath.replace(/\/index$/, "");
|
|
542
|
+
routePath = routePath.replace(/^index$/, "");
|
|
543
|
+
routePath = routePath.replace(/\[\.\.\.([^\]]+)]/g, "*$1");
|
|
544
|
+
routePath = routePath.replace(/\[([^\]]+)]/g, ":$1");
|
|
545
|
+
if (!routePath.startsWith("/")) routePath = "/" + routePath;
|
|
546
|
+
if (routePath === "/") routePath = "/";
|
|
547
|
+
if (this.prefix) routePath = (this.prefix.startsWith("/") ? this.prefix : "/" + this.prefix) + routePath;
|
|
548
|
+
let sortKey = 0;
|
|
549
|
+
if (routePath.includes("*")) sortKey += 1e6;
|
|
550
|
+
const paramCount = (routePath.match(/:/g) || []).length;
|
|
551
|
+
sortKey += paramCount * 1e4;
|
|
552
|
+
const segmentCount = routePath.split("/").filter((s) => s).length;
|
|
553
|
+
sortKey -= segmentCount * 100;
|
|
554
|
+
routes.push({
|
|
555
|
+
file,
|
|
556
|
+
routePath,
|
|
557
|
+
method,
|
|
558
|
+
sortKey,
|
|
559
|
+
module: this.module
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
routes.sort((a, b) => a.sortKey - b.sortKey);
|
|
563
|
+
this.routes = routes;
|
|
564
|
+
}
|
|
565
|
+
async registerRoutes() {
|
|
566
|
+
if (!this.router) throw new BaseException("Router instance not set");
|
|
567
|
+
const moduleName = this.module || "unknown";
|
|
568
|
+
const hook = function(route) {
|
|
569
|
+
route.metadata = {
|
|
570
|
+
...route.metadata,
|
|
571
|
+
module: moduleName
|
|
572
|
+
};
|
|
573
|
+
};
|
|
574
|
+
this.router.on("added", hook);
|
|
575
|
+
for (const route of this.routes) {
|
|
576
|
+
const handler = (await import(`${this.directory}/${route.file}`)).default;
|
|
577
|
+
if (typeof handler !== "function") {
|
|
578
|
+
this.logger.warn(`Route file ${route.file} does not export a default function`);
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
this.router[route.method](route.routePath, handler);
|
|
582
|
+
}
|
|
583
|
+
this.router.off("added", hook);
|
|
584
|
+
}
|
|
585
|
+
async load() {
|
|
586
|
+
await this.loadFiles();
|
|
587
|
+
await this.registerRoutes();
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
//#endregion
|
|
591
|
+
//#region src/shared/services/ConfigService.ts
|
|
592
|
+
var ConfigService = class {
|
|
593
|
+
entries;
|
|
594
|
+
constructor() {
|
|
595
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
596
|
+
}
|
|
597
|
+
list() {
|
|
598
|
+
return Array.from(this.entries.values());
|
|
599
|
+
}
|
|
600
|
+
parseValue(value) {
|
|
601
|
+
if (typeof value === "string" && value.endsWith(":boolean")) return value.replace(":boolean", "").trim() === "true";
|
|
602
|
+
return value;
|
|
603
|
+
}
|
|
604
|
+
loadFromRecord(record, source = "unknow") {
|
|
605
|
+
for (const [key, value] of Object.entries(record)) this.entries.set(key, {
|
|
606
|
+
key,
|
|
607
|
+
value: this.parseValue(value),
|
|
608
|
+
source
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
loadFromEntries(entries, source = "unknow") {
|
|
612
|
+
for (const [key, value] of entries) this.entries.set(key, {
|
|
613
|
+
key,
|
|
614
|
+
value: this.parseValue(value),
|
|
615
|
+
source
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
toRecord() {
|
|
619
|
+
const record = {};
|
|
620
|
+
for (const [key, entry] of this.entries.entries()) record[key] = entry.value;
|
|
621
|
+
return record;
|
|
622
|
+
}
|
|
623
|
+
has(key) {
|
|
624
|
+
if (this.entries.get(key)) return true;
|
|
625
|
+
if (!key.includes(".")) return false;
|
|
626
|
+
const primary = key.split(".")[0];
|
|
627
|
+
const primaryEntry = this.entries.get(primary);
|
|
628
|
+
if (!primaryEntry) return this.entries.get(key) ? true : false;
|
|
629
|
+
const value = primaryEntry.value;
|
|
630
|
+
if (typeof value !== "object" || Array.isArray(value)) return false;
|
|
631
|
+
return has(value, key.substring(primary.length + 1));
|
|
632
|
+
}
|
|
633
|
+
get(key, defaultValue) {
|
|
634
|
+
const entry = this.entries.get(key);
|
|
635
|
+
if (entry) return entry.value;
|
|
636
|
+
if (!key.includes(".")) return defaultValue;
|
|
637
|
+
const primary = key.split(".")[0];
|
|
638
|
+
const primaryEntry = this.entries.get(primary);
|
|
639
|
+
if (!primaryEntry) {
|
|
640
|
+
const entry = this.entries.get(key);
|
|
641
|
+
return entry ? entry.value : defaultValue;
|
|
642
|
+
}
|
|
643
|
+
const value = primaryEntry.value;
|
|
644
|
+
if (typeof value !== "object" || Array.isArray(value)) return defaultValue;
|
|
645
|
+
return get(value, key.substring(primary.length + 1), defaultValue);
|
|
646
|
+
}
|
|
647
|
+
getOne(keys, defaultValue) {
|
|
648
|
+
for (const key of keys) if (this.has(key)) return this.get(key);
|
|
649
|
+
return defaultValue;
|
|
650
|
+
}
|
|
651
|
+
set(key, value, source = "runtime") {
|
|
652
|
+
if (!key.includes(".")) {
|
|
653
|
+
this.entries.set(key, {
|
|
654
|
+
key,
|
|
655
|
+
source,
|
|
656
|
+
value
|
|
657
|
+
});
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
const primary = key.split(".")[0];
|
|
661
|
+
let primaryValue = this.get(primary, {});
|
|
662
|
+
if (typeof primaryValue !== "object" || Array.isArray(primaryValue)) primaryValue = {};
|
|
663
|
+
set(primaryValue, key.substring(primary.length + 1), value);
|
|
664
|
+
this.entries.set(primary, {
|
|
665
|
+
key: primary,
|
|
666
|
+
source,
|
|
667
|
+
value: primaryValue
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
unset(key) {
|
|
671
|
+
if (!key.includes(".")) {
|
|
672
|
+
this.entries.delete(key);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
const primary = key.split(".")[0];
|
|
676
|
+
const primaryValue = this.get(primary, {});
|
|
677
|
+
if (!primaryValue) return;
|
|
678
|
+
if (typeof primaryValue !== "object" || Array.isArray(primaryValue)) return;
|
|
679
|
+
unset(primaryValue, key.substring(primary.length + 1));
|
|
680
|
+
this.entries.set(primary, {
|
|
681
|
+
key: primary,
|
|
682
|
+
source: "runtime",
|
|
683
|
+
value: primaryValue
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
clear() {
|
|
687
|
+
this.entries.clear();
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
//#endregion
|
|
691
|
+
//#region src/shared/services/ContainerService.ts
|
|
692
|
+
var ContainerService = class {
|
|
693
|
+
entries = /* @__PURE__ */ new Map();
|
|
694
|
+
loadFromRecord(record) {
|
|
695
|
+
Object.entries(record).forEach(([key, value]) => {
|
|
696
|
+
this.set(key, value);
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
toRecord() {
|
|
700
|
+
const record = {};
|
|
701
|
+
for (const [key, value] of this.entries.entries()) record[String(key)] = value;
|
|
702
|
+
return record;
|
|
703
|
+
}
|
|
704
|
+
set(payload, value) {
|
|
705
|
+
let key = payload;
|
|
706
|
+
if (typeof payload === "function" || typeof payload === "object") key = payload.name;
|
|
707
|
+
this.entries.set(key, value);
|
|
708
|
+
}
|
|
709
|
+
has(payload) {
|
|
710
|
+
let key = payload;
|
|
711
|
+
if (typeof payload === "function" || typeof payload === "object") key = payload.name;
|
|
712
|
+
return this.entries.has(key);
|
|
713
|
+
}
|
|
714
|
+
get(payload) {
|
|
715
|
+
let key = payload;
|
|
716
|
+
if (typeof payload === "function" || typeof payload === "object") key = payload.name;
|
|
717
|
+
if (!this.has(key)) throw new Error(`entry not found: ${String(key)}`);
|
|
718
|
+
return this.entries.get(key);
|
|
719
|
+
}
|
|
720
|
+
singleton(classConstructor) {
|
|
721
|
+
const key = classConstructor.name;
|
|
722
|
+
const existingInstance = this.entries.get(key);
|
|
723
|
+
if (existingInstance) return existingInstance;
|
|
724
|
+
const newInstance = new classConstructor();
|
|
725
|
+
this.entries.set(key, newInstance);
|
|
726
|
+
return newInstance;
|
|
727
|
+
}
|
|
728
|
+
load(entries) {
|
|
729
|
+
Object.entries(entries).forEach(([key, value]) => {
|
|
730
|
+
this.set(key, value);
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
proxy(key) {
|
|
734
|
+
return new Proxy({}, {
|
|
735
|
+
get: (_target, prop) => {
|
|
736
|
+
const entry = this.get(key);
|
|
737
|
+
const value = entry[prop];
|
|
738
|
+
if (typeof value === "function") return value.bind(entry);
|
|
739
|
+
return entry[prop];
|
|
740
|
+
},
|
|
741
|
+
set: (_target, prop, value) => {
|
|
742
|
+
const entry = this.get(key);
|
|
743
|
+
entry[prop] = value;
|
|
744
|
+
return true;
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
keys() {
|
|
749
|
+
return Array.from(this.entries.keys());
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
//#endregion
|
|
753
|
+
//#region src/shared/validators/url.ts
|
|
754
|
+
var url_exports = /* @__PURE__ */ __exportAll({
|
|
755
|
+
array: () => array,
|
|
756
|
+
arrayNumber: () => arrayNumber,
|
|
757
|
+
boolean: () => boolean,
|
|
758
|
+
date: () => date,
|
|
759
|
+
datetime: () => datetime,
|
|
760
|
+
number: () => number,
|
|
761
|
+
object: () => object
|
|
762
|
+
});
|
|
763
|
+
const number = () => v.pipe(v.union([v.string(), v.number()]), v.transform(Number), v.integer());
|
|
764
|
+
const boolean = () => v.pipe(v.union([v.string(), v.boolean()]), v.transform((v) => v === true || v === "true"));
|
|
765
|
+
const date = () => v.pipe(v.union([v.string(), v.date()]), v.transform((v) => v instanceof Date ? v : new Date(v)), v.transform((v) => v ? format(v, "yyyy-MM-dd") : v));
|
|
766
|
+
const datetime = () => v.pipe(v.union([v.string(), v.date()]), v.transform((v) => {
|
|
767
|
+
if (!v) return v;
|
|
768
|
+
if (v === "null") return null;
|
|
769
|
+
if (typeof v === "string") v = new Date(v);
|
|
770
|
+
return format(v, "yyyy-MM-dd HH:mm");
|
|
771
|
+
}));
|
|
772
|
+
const array = (schema = v.any()) => v.pipe(v.union([v.string(), v.array(v.string())]), v.transform((value) => Array.isArray(value) ? value : value.split(",")), v.array(schema));
|
|
773
|
+
const arrayNumber = () => v.pipe(array(), v.transform((value) => value.map(Number)));
|
|
774
|
+
const object = () => v.pipe(v.union([v.string(), v.record(v.string(), v.any())]), v.transform((value) => typeof value === "string" ? qs.parse(value) : value), v.transform((value) => {
|
|
775
|
+
const result = {};
|
|
776
|
+
for (const key in value) set(result, key, get(value, key));
|
|
777
|
+
return result;
|
|
778
|
+
}));
|
|
779
|
+
//#endregion
|
|
780
|
+
//#region src/shared/services/ValidatorService.ts
|
|
781
|
+
const extras = { url: url_exports };
|
|
782
|
+
var ValidatorService = class {
|
|
783
|
+
v = {
|
|
784
|
+
...v,
|
|
785
|
+
extras
|
|
786
|
+
};
|
|
787
|
+
create(cb) {
|
|
788
|
+
return cb(this.v);
|
|
789
|
+
}
|
|
790
|
+
validate(payload, cb) {
|
|
791
|
+
const schema = typeof cb === "function" ? cb(this.v) : cb;
|
|
792
|
+
const { output, issues, success } = v.safeParse(schema, payload);
|
|
793
|
+
if (!success) {
|
|
794
|
+
const flatten = v.flatten(issues);
|
|
795
|
+
const messages = [];
|
|
796
|
+
if (flatten.root) messages.push(...flatten.root);
|
|
797
|
+
if (flatten.nested) Object.entries(flatten.nested).forEach((entry) => {
|
|
798
|
+
const [key, value] = entry;
|
|
799
|
+
messages.push(...value.map((v) => `${key}: ${v}`));
|
|
800
|
+
});
|
|
801
|
+
const error = new BaseException(messages.length ? messages.join(", ") : "Validation failed", 422);
|
|
802
|
+
error.name = "ValidationError";
|
|
803
|
+
Object.assign(error, { messages });
|
|
804
|
+
throw error;
|
|
805
|
+
}
|
|
806
|
+
return output;
|
|
807
|
+
}
|
|
808
|
+
async validateAsync(payload, cb) {
|
|
809
|
+
const schema = typeof cb === "function" ? cb(this.v) : cb;
|
|
810
|
+
const { output, issues, success } = await v.safeParseAsync(schema, payload);
|
|
811
|
+
if (!success) {
|
|
812
|
+
const error = /* @__PURE__ */ new Error("Validation failed");
|
|
813
|
+
const flatten = v.flatten(issues);
|
|
814
|
+
const details = {
|
|
815
|
+
...flatten.root,
|
|
816
|
+
...flatten.nested
|
|
817
|
+
};
|
|
818
|
+
Object.assign(error, { details });
|
|
819
|
+
throw error;
|
|
820
|
+
}
|
|
821
|
+
return output;
|
|
822
|
+
}
|
|
823
|
+
isValid(payload, cb) {
|
|
824
|
+
const schema = typeof cb === "function" ? cb(this.v) : cb;
|
|
825
|
+
const { success } = v.safeParse(schema, payload);
|
|
826
|
+
return success;
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
v.object({
|
|
830
|
+
id: v.number(),
|
|
831
|
+
permission_id: v.number(),
|
|
832
|
+
assignable_type: v.string(),
|
|
833
|
+
assignable_id: v.string(),
|
|
834
|
+
created_at: v.string(),
|
|
835
|
+
updated_at: v.string()
|
|
836
|
+
});
|
|
837
|
+
v.object({
|
|
838
|
+
id: v.number(),
|
|
839
|
+
name: v.nullable(v.string()),
|
|
840
|
+
description: v.nullable(v.string()),
|
|
841
|
+
action: v.string(),
|
|
842
|
+
subject: v.string(),
|
|
843
|
+
conditions: v.nullable(v.string()),
|
|
844
|
+
created_at: v.string(),
|
|
845
|
+
updated_at: v.string(),
|
|
846
|
+
expires_at: v.string()
|
|
847
|
+
});
|
|
848
|
+
v.object({
|
|
849
|
+
id: v.number(),
|
|
850
|
+
name: v.nullable(v.string()),
|
|
851
|
+
type: v.string(),
|
|
852
|
+
user_id: v.number(),
|
|
853
|
+
token: v.string(),
|
|
854
|
+
created_at: v.string(),
|
|
855
|
+
updated_at: v.string(),
|
|
856
|
+
expires_at: v.string()
|
|
857
|
+
});
|
|
858
|
+
//#endregion
|
|
859
|
+
//#region src/shared/exceptions/ShellException.ts
|
|
860
|
+
var ShellException = class extends BaseException {
|
|
861
|
+
output;
|
|
862
|
+
bin;
|
|
863
|
+
args;
|
|
864
|
+
constructor(message, output, bin, args) {
|
|
865
|
+
super(message, 501);
|
|
866
|
+
this.output = output;
|
|
867
|
+
this.bin = bin;
|
|
868
|
+
this.args = args;
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
new ValidatorService();
|
|
872
|
+
//#endregion
|
|
873
|
+
//#region src/shared/entities/LifecycleHook.ts
|
|
874
|
+
var LifecycleHook = class {
|
|
875
|
+
hook_id;
|
|
876
|
+
order;
|
|
877
|
+
subhooks;
|
|
878
|
+
constructor() {
|
|
879
|
+
if (!this.hook_id) this.hook_id = this.constructor.name;
|
|
880
|
+
}
|
|
881
|
+
async onRegister() {}
|
|
882
|
+
async onLoad() {}
|
|
883
|
+
async onBoot() {}
|
|
884
|
+
async onShutdown() {}
|
|
885
|
+
};
|
|
886
|
+
//#endregion
|
|
887
|
+
//#region src/shared/mixins/BaseEntityMixin.ts
|
|
888
|
+
function BaseEntity(Base) {
|
|
889
|
+
return class extends Base {
|
|
890
|
+
static from(data) {
|
|
891
|
+
const contructor = typeof this === "function" ? this : Base;
|
|
892
|
+
const instance = new contructor();
|
|
893
|
+
let payload = { ...data };
|
|
894
|
+
if (typeof contructor?.parse === "function") payload = contructor.parse(data);
|
|
895
|
+
if (typeof this?.parse === "function") payload = this.parse(data);
|
|
896
|
+
Object.assign(instance, payload);
|
|
897
|
+
return instance;
|
|
898
|
+
}
|
|
899
|
+
merge(data) {
|
|
900
|
+
Object.assign(this, data);
|
|
901
|
+
return this;
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
//#endregion
|
|
906
|
+
//#region src/shared/entities/ModuleEntity.ts
|
|
907
|
+
var Module$1 = class extends compose(BaseEntity, mixin(LifecycleHook)) {
|
|
908
|
+
id;
|
|
909
|
+
name;
|
|
910
|
+
enabled = false;
|
|
911
|
+
dependencies = {};
|
|
912
|
+
build = {};
|
|
913
|
+
directory;
|
|
914
|
+
upgrade_info;
|
|
915
|
+
setData(data) {
|
|
916
|
+
const filtered = Object.fromEntries(Object.entries(data).filter(([, v]) => v !== void 0));
|
|
917
|
+
Object.assign(this, filtered);
|
|
918
|
+
this.hook_id = `module:${this.id}`;
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
compose(BaseEntity);
|
|
922
|
+
//#endregion
|
|
923
|
+
//#region src/server/facades/container.ts
|
|
924
|
+
const container = globalThis.container || new ContainerService();
|
|
925
|
+
//#endregion
|
|
926
|
+
//#region src/server/facades/logger.ts
|
|
927
|
+
const logger = container.proxy(LoggerService);
|
|
928
|
+
//#endregion
|
|
929
|
+
//#region src/server/entities/RouteEntity.ts
|
|
930
|
+
var Route = class {
|
|
931
|
+
path = "";
|
|
932
|
+
method = "get";
|
|
933
|
+
handler = null;
|
|
934
|
+
middlewares = [];
|
|
935
|
+
metadata = {};
|
|
936
|
+
constructor(data = {}) {
|
|
937
|
+
this.path = data.path || "";
|
|
938
|
+
this.method = data.method || "get";
|
|
939
|
+
this.handler = data.handler || null;
|
|
940
|
+
this.middlewares = data.middlewares || [];
|
|
941
|
+
this.metadata = data.metadata || {};
|
|
942
|
+
}
|
|
943
|
+
static params(routePath, requestPath) {
|
|
944
|
+
const params = {};
|
|
945
|
+
const routeSegments = routePath.split("/").filter(Boolean);
|
|
946
|
+
const requestSegments = requestPath.split("/").filter(Boolean);
|
|
947
|
+
for (let i = 0; i < routeSegments.length; i++) {
|
|
948
|
+
const routeSegment = routeSegments[i];
|
|
949
|
+
const requestSegment = requestSegments[i];
|
|
950
|
+
if (routeSegment.startsWith(":")) {
|
|
951
|
+
const paramName = routeSegment.slice(1);
|
|
952
|
+
params[paramName] = requestSegment;
|
|
953
|
+
}
|
|
954
|
+
if (routeSegment === "*") {
|
|
955
|
+
params["*"] = requestSegments.slice(i).join("/");
|
|
956
|
+
break;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return params;
|
|
960
|
+
}
|
|
961
|
+
static query(requestPath) {
|
|
962
|
+
const query = {};
|
|
963
|
+
const queryString = requestPath.split("?")[1];
|
|
964
|
+
if (!queryString) return query;
|
|
965
|
+
return qs.parse(queryString);
|
|
966
|
+
}
|
|
967
|
+
toJSON() {
|
|
968
|
+
return {
|
|
969
|
+
path: this.path,
|
|
970
|
+
method: this.method,
|
|
971
|
+
middlewares: this.middlewares.map((mw) => mw.constructor.name),
|
|
972
|
+
metadata: this.metadata
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
};
|
|
976
|
+
//#endregion
|
|
977
|
+
//#region src/shared/mixins/HooksMixin.ts
|
|
978
|
+
async function emitHook(constructor, event, ...args) {
|
|
979
|
+
const listeners = constructor.listeners || [];
|
|
980
|
+
for await (const l of listeners.filter((l) => l.event === event)) await l.listener(...args);
|
|
981
|
+
}
|
|
982
|
+
function onHook(constructor, event, listener) {
|
|
983
|
+
const listeners = constructor.listeners || [];
|
|
984
|
+
if (listeners.find((l) => l.event === event && l.listener === listener)) return;
|
|
985
|
+
listeners.push({
|
|
986
|
+
event,
|
|
987
|
+
listener
|
|
988
|
+
});
|
|
989
|
+
constructor.listeners = listeners;
|
|
990
|
+
}
|
|
991
|
+
function HooksStatic(Base) {
|
|
992
|
+
return class extends Base {
|
|
993
|
+
constructor(...args) {
|
|
994
|
+
super(...args);
|
|
995
|
+
if (typeof this.constructor.boot === "function") this.constructor.boot.apply(this.constructor);
|
|
996
|
+
}
|
|
997
|
+
static listeners = [];
|
|
998
|
+
static on(event, listener) {
|
|
999
|
+
return onHook(this, event, listener);
|
|
1000
|
+
}
|
|
1001
|
+
static async emit(event, ...args) {
|
|
1002
|
+
return emitHook(this, event, ...args);
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
function Hooks(Base) {
|
|
1007
|
+
return class extends Base {
|
|
1008
|
+
listeners;
|
|
1009
|
+
constructor(...args) {
|
|
1010
|
+
super(...args);
|
|
1011
|
+
this.listeners = [];
|
|
1012
|
+
}
|
|
1013
|
+
on(event, listener) {
|
|
1014
|
+
this.listeners.push({
|
|
1015
|
+
event,
|
|
1016
|
+
listener
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
off(event, listener) {
|
|
1020
|
+
const index = this.listeners.findIndex((l) => l.event === event && l.listener === listener);
|
|
1021
|
+
if (index === -1) return;
|
|
1022
|
+
this.listeners.splice(index, 1);
|
|
1023
|
+
}
|
|
1024
|
+
emit(event, ...args) {
|
|
1025
|
+
const listeners = this.listeners.filter((l) => l.event === event);
|
|
1026
|
+
for (const l of listeners) l.listener(...args);
|
|
1027
|
+
}
|
|
1028
|
+
async emitAsync(event, ...args) {
|
|
1029
|
+
const listeners = this.listeners.filter((l) => l.event === event);
|
|
1030
|
+
for await (const l of listeners) await l.listener(...args);
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
HooksStatic.emit = emitHook;
|
|
1035
|
+
HooksStatic.on = onHook;
|
|
1036
|
+
//#endregion
|
|
1037
|
+
//#region src/server/services/RouterService.ts
|
|
1038
|
+
var Router = class Router extends compose(Hooks) {
|
|
1039
|
+
routes = [];
|
|
1040
|
+
middlewares = [];
|
|
1041
|
+
prefixes = [];
|
|
1042
|
+
groups = [];
|
|
1043
|
+
groupPrefixes = [];
|
|
1044
|
+
debug = false;
|
|
1045
|
+
logger = logger.child({ label: "router" });
|
|
1046
|
+
metadata = {};
|
|
1047
|
+
constructor(data = {}) {
|
|
1048
|
+
super();
|
|
1049
|
+
this.routes = data.routes || [];
|
|
1050
|
+
this.middlewares = data.middlewares || [];
|
|
1051
|
+
this.prefixes = data.prefixes || [];
|
|
1052
|
+
this.groups = data.groups || [];
|
|
1053
|
+
this.groupPrefixes = data.groupPrefixes || [];
|
|
1054
|
+
this.debug = data.debug || false;
|
|
1055
|
+
this.metadata = data.metadata || {};
|
|
1056
|
+
this.listeners = data.listeners || [];
|
|
1057
|
+
}
|
|
1058
|
+
use(middleware, context = "route") {
|
|
1059
|
+
this.middlewares.push({
|
|
1060
|
+
middleware,
|
|
1061
|
+
context
|
|
1062
|
+
});
|
|
1063
|
+
return this;
|
|
1064
|
+
}
|
|
1065
|
+
prefix(prefix) {
|
|
1066
|
+
this.prefixes.push(prefix);
|
|
1067
|
+
return this;
|
|
1068
|
+
}
|
|
1069
|
+
makePath(args) {
|
|
1070
|
+
return join(...this.groupPrefixes, ...this.prefixes, args);
|
|
1071
|
+
}
|
|
1072
|
+
add(payload) {
|
|
1073
|
+
const route = new Route({
|
|
1074
|
+
method: payload.method,
|
|
1075
|
+
path: this.makePath(payload.path),
|
|
1076
|
+
handler: payload.handler,
|
|
1077
|
+
middlewares: this.middlewares.map((m) => m.middleware)
|
|
1078
|
+
});
|
|
1079
|
+
this.middlewares = this.middlewares.filter((m) => m.context !== "route");
|
|
1080
|
+
this.prefixes = [];
|
|
1081
|
+
this.routes.push(route);
|
|
1082
|
+
if (this.debug) this.logger.debug("added route", route);
|
|
1083
|
+
this.emit("added", route);
|
|
1084
|
+
}
|
|
1085
|
+
get(path, handler) {
|
|
1086
|
+
this.add({
|
|
1087
|
+
path,
|
|
1088
|
+
method: "GET",
|
|
1089
|
+
handler
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
post(path, handler) {
|
|
1093
|
+
this.add({
|
|
1094
|
+
path,
|
|
1095
|
+
method: "POST",
|
|
1096
|
+
handler
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
put(path, handler) {
|
|
1100
|
+
this.add({
|
|
1101
|
+
path,
|
|
1102
|
+
method: "PUT",
|
|
1103
|
+
handler
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
patch(path, handler) {
|
|
1107
|
+
this.add({
|
|
1108
|
+
path,
|
|
1109
|
+
method: "PATCH",
|
|
1110
|
+
handler
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
many(methods, path, handler) {
|
|
1114
|
+
methods.forEach((method) => {
|
|
1115
|
+
this.add({
|
|
1116
|
+
path,
|
|
1117
|
+
method,
|
|
1118
|
+
handler
|
|
1119
|
+
});
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
delete(path, handler) {
|
|
1123
|
+
this.add({
|
|
1124
|
+
path,
|
|
1125
|
+
method: "DELETE",
|
|
1126
|
+
handler
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
group() {
|
|
1130
|
+
const group = new Router({
|
|
1131
|
+
listeners: this.listeners,
|
|
1132
|
+
debug: this.debug
|
|
1133
|
+
});
|
|
1134
|
+
group.groupPrefixes = this.prefixes;
|
|
1135
|
+
group.middlewares = this.middlewares.map((r) => ({
|
|
1136
|
+
middleware: r.middleware,
|
|
1137
|
+
context: "group"
|
|
1138
|
+
}));
|
|
1139
|
+
this.groups.push(group);
|
|
1140
|
+
this.middlewares = this.middlewares.filter((m) => m.context !== "route");
|
|
1141
|
+
this.prefixes = [];
|
|
1142
|
+
return group;
|
|
1143
|
+
}
|
|
1144
|
+
resolve(method, path) {
|
|
1145
|
+
const route = this.list().find((r) => {
|
|
1146
|
+
if (r.method.toUpperCase() !== method.toUpperCase()) return false;
|
|
1147
|
+
return this.matchPath(r.path, path);
|
|
1148
|
+
});
|
|
1149
|
+
if (!route) return null;
|
|
1150
|
+
return route;
|
|
1151
|
+
}
|
|
1152
|
+
async execute(route, initialCtx) {
|
|
1153
|
+
if (!route.handler) throw new Error(`Route handler not found for ${route.method} ${route.path}`);
|
|
1154
|
+
const ctx = { ...initialCtx };
|
|
1155
|
+
for await (const middleware of route.middlewares) {
|
|
1156
|
+
const result = await middleware.handle(ctx);
|
|
1157
|
+
if (result && "redirect" in result) return result;
|
|
1158
|
+
Object.assign(ctx, result);
|
|
1159
|
+
}
|
|
1160
|
+
return route.handler(ctx);
|
|
1161
|
+
}
|
|
1162
|
+
matchPath(routePath, requestPath) {
|
|
1163
|
+
const routeSegments = routePath.split("/").filter(Boolean);
|
|
1164
|
+
const requestSegments = requestPath.split("/").filter(Boolean);
|
|
1165
|
+
for (let i = 0; i < routeSegments.length; i++) {
|
|
1166
|
+
const routeSegment = routeSegments[i];
|
|
1167
|
+
const requestSegment = requestSegments[i];
|
|
1168
|
+
if (routeSegment === "*") return true;
|
|
1169
|
+
if (i >= requestSegments.length) return false;
|
|
1170
|
+
if (routeSegment.startsWith(":")) continue;
|
|
1171
|
+
if (routeSegment !== requestSegment) return false;
|
|
1172
|
+
}
|
|
1173
|
+
if (requestSegments.length > routeSegments.length) return false;
|
|
1174
|
+
return true;
|
|
1175
|
+
}
|
|
1176
|
+
clear() {
|
|
1177
|
+
if (this.debug) this.logger.debug("clear", { count: this.routes.length });
|
|
1178
|
+
this.routes = [];
|
|
1179
|
+
this.groups = [];
|
|
1180
|
+
}
|
|
1181
|
+
list() {
|
|
1182
|
+
return this.routes.concat(...this.groups.map((g) => g.routes));
|
|
1183
|
+
}
|
|
1184
|
+
async load(options = {}) {
|
|
1185
|
+
this.debug = options.debug ?? this.debug;
|
|
1186
|
+
if (this.debug) this.logger.debug("service loaded in debug mode");
|
|
1187
|
+
}
|
|
1188
|
+
loadSync(options = {}) {
|
|
1189
|
+
this.debug = options.debug ?? this.debug;
|
|
1190
|
+
if (this.debug) this.logger.debug("service loaded in debug mode");
|
|
1191
|
+
}
|
|
1192
|
+
};
|
|
1193
|
+
//#endregion
|
|
1194
|
+
//#region src/server/services/ShellService.ts
|
|
1195
|
+
var ShellService = class {
|
|
1196
|
+
debug;
|
|
1197
|
+
logger;
|
|
1198
|
+
init(data = {}) {
|
|
1199
|
+
this.debug = data.debug || false;
|
|
1200
|
+
this.logger = data.logger || logger.child({ label: "shell" });
|
|
1201
|
+
if (this.debug) this.logger.debug("initialized in debug mode");
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Execute a shell command and return a promise
|
|
1205
|
+
*/
|
|
1206
|
+
async command(bin, args, options = {}) {
|
|
1207
|
+
return new Promise((resolve, reject) => {
|
|
1208
|
+
const child = spawn(bin, args, {
|
|
1209
|
+
cwd: options.cwd || process.cwd(),
|
|
1210
|
+
stdio: "pipe",
|
|
1211
|
+
shell: options.shell ?? true,
|
|
1212
|
+
env: options.env || process.env
|
|
1213
|
+
});
|
|
1214
|
+
let data = "";
|
|
1215
|
+
child.stdout?.on("data", (d) => {
|
|
1216
|
+
data += d.toString();
|
|
1217
|
+
});
|
|
1218
|
+
child.stderr?.on("data", (d) => {
|
|
1219
|
+
data += d.toString();
|
|
1220
|
+
});
|
|
1221
|
+
child.on("close", (code) => {
|
|
1222
|
+
if (this.debug) this.logger.debug("command executed", {
|
|
1223
|
+
bin,
|
|
1224
|
+
args,
|
|
1225
|
+
output: data
|
|
1226
|
+
});
|
|
1227
|
+
if (code === 0) return resolve();
|
|
1228
|
+
const errorMessage = `Command failed with exit code ${code}`;
|
|
1229
|
+
this.logger.error(errorMessage, {
|
|
1230
|
+
bin,
|
|
1231
|
+
args,
|
|
1232
|
+
code,
|
|
1233
|
+
output: data
|
|
1234
|
+
});
|
|
1235
|
+
reject(new ShellException(errorMessage, data, bin, args));
|
|
1236
|
+
});
|
|
1237
|
+
child.on("error", (error) => {
|
|
1238
|
+
this.logger.error("Command execution error", {
|
|
1239
|
+
bin,
|
|
1240
|
+
args,
|
|
1241
|
+
error: error.message
|
|
1242
|
+
});
|
|
1243
|
+
reject(error);
|
|
1244
|
+
});
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Execute a shell command and return the output as a string
|
|
1249
|
+
*/
|
|
1250
|
+
async executeCommandWithOutput(bin, args, options = {}) {
|
|
1251
|
+
if (this.debug) this.logger.debug("executing command", {
|
|
1252
|
+
bin,
|
|
1253
|
+
args
|
|
1254
|
+
});
|
|
1255
|
+
return new Promise((resolve, reject) => {
|
|
1256
|
+
const child = spawn(bin, args, {
|
|
1257
|
+
cwd: options.cwd || process.cwd(),
|
|
1258
|
+
stdio: "pipe",
|
|
1259
|
+
shell: true,
|
|
1260
|
+
env: options.env || process.env
|
|
1261
|
+
});
|
|
1262
|
+
let output = "";
|
|
1263
|
+
let errorOutput = "";
|
|
1264
|
+
child.stdout?.on("data", (data) => {
|
|
1265
|
+
output += data.toString();
|
|
1266
|
+
});
|
|
1267
|
+
child.stderr?.on("data", (data) => {
|
|
1268
|
+
errorOutput += data.toString();
|
|
1269
|
+
});
|
|
1270
|
+
child.on("close", (code) => {
|
|
1271
|
+
if (code === 0) resolve(output.trim());
|
|
1272
|
+
else {
|
|
1273
|
+
const errorMessage = `Command failed with exit code ${code}: ${errorOutput}`;
|
|
1274
|
+
this.logger.error(errorMessage, {
|
|
1275
|
+
bin,
|
|
1276
|
+
args,
|
|
1277
|
+
code,
|
|
1278
|
+
errorOutput
|
|
1279
|
+
});
|
|
1280
|
+
reject(new ShellException(errorMessage, output, bin, args));
|
|
1281
|
+
}
|
|
1282
|
+
});
|
|
1283
|
+
child.on("error", (error) => {
|
|
1284
|
+
this.logger.error("Command execution error", {
|
|
1285
|
+
bin,
|
|
1286
|
+
args,
|
|
1287
|
+
error: error.message
|
|
1288
|
+
});
|
|
1289
|
+
reject(error);
|
|
1290
|
+
});
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
//#endregion
|
|
1295
|
+
//#region src/server/repositories/DatabaseRepository.ts
|
|
1296
|
+
var DatabaseRepository = class {
|
|
1297
|
+
db = null;
|
|
1298
|
+
table = "";
|
|
1299
|
+
primaryKey = "id";
|
|
1300
|
+
constructor(db, table, primaryKey) {
|
|
1301
|
+
if (db) this.db = db;
|
|
1302
|
+
if (table) this.table = table;
|
|
1303
|
+
if (primaryKey) this.primaryKey = primaryKey;
|
|
1304
|
+
}
|
|
1305
|
+
setDatabase(db) {
|
|
1306
|
+
this.db = db;
|
|
1307
|
+
return this;
|
|
1308
|
+
}
|
|
1309
|
+
setTable(table) {
|
|
1310
|
+
this.table = table;
|
|
1311
|
+
return this;
|
|
1312
|
+
}
|
|
1313
|
+
setPrimaryKey(primaryKey) {
|
|
1314
|
+
this.primaryKey = primaryKey;
|
|
1315
|
+
return this;
|
|
1316
|
+
}
|
|
1317
|
+
query(options) {
|
|
1318
|
+
return options?.qb || this.db.selectFrom(this.table);
|
|
1319
|
+
}
|
|
1320
|
+
async count(options) {
|
|
1321
|
+
let qb = this.query(options);
|
|
1322
|
+
qb = qb.select((eb) => eb.fn.countAll().as("count"));
|
|
1323
|
+
const result = await qb.executeTakeFirstOrThrow();
|
|
1324
|
+
return Number(result.count);
|
|
1325
|
+
}
|
|
1326
|
+
async findMany(options) {
|
|
1327
|
+
let qb = this.query(options);
|
|
1328
|
+
qb = qb.selectAll();
|
|
1329
|
+
if (options?.limit) qb = qb.limit(options.limit);
|
|
1330
|
+
if (options?.offset) qb = qb.offset(options.offset);
|
|
1331
|
+
if (options?.orderBy) {
|
|
1332
|
+
const orderBy = Array.isArray(options.orderBy) ? options.orderBy : [options.orderBy];
|
|
1333
|
+
const orderDirection = Array.isArray(options.orderDirection) ? options.orderDirection : [options.orderDirection ?? "asc"];
|
|
1334
|
+
orderBy.forEach((ob, index) => {
|
|
1335
|
+
qb = qb.orderBy(ob, orderDirection[index] || "asc");
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
return await qb.execute();
|
|
1339
|
+
}
|
|
1340
|
+
async findById(id, options) {
|
|
1341
|
+
let qb = this.query(options);
|
|
1342
|
+
qb = qb.selectAll();
|
|
1343
|
+
qb = qb.where(this.primaryKey, "=", id);
|
|
1344
|
+
return await qb.executeTakeFirst();
|
|
1345
|
+
}
|
|
1346
|
+
async findByIdOrFail(id, options) {
|
|
1347
|
+
const item = await this.findById(id, options);
|
|
1348
|
+
if (!item) throw new BaseException("Item not found", 404);
|
|
1349
|
+
return item;
|
|
1350
|
+
}
|
|
1351
|
+
async paginate(options) {
|
|
1352
|
+
const page = options?.page ?? 1;
|
|
1353
|
+
const offset = (page - 1) * (options?.limit ?? 10);
|
|
1354
|
+
const limit = options?.limit ?? 10;
|
|
1355
|
+
const findAllOptions = {
|
|
1356
|
+
...options,
|
|
1357
|
+
limit,
|
|
1358
|
+
offset
|
|
1359
|
+
};
|
|
1360
|
+
const countOptions = { ...options };
|
|
1361
|
+
const [items, totalItems] = await Promise.all([this.findMany(findAllOptions), this.count(countOptions)]);
|
|
1362
|
+
return {
|
|
1363
|
+
items,
|
|
1364
|
+
page,
|
|
1365
|
+
per_page: limit,
|
|
1366
|
+
total: totalItems,
|
|
1367
|
+
total_pages: Math.ceil(totalItems / limit)
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
async create(data) {
|
|
1371
|
+
let qb = this.db.insertInto(this.table);
|
|
1372
|
+
qb = qb.values(data).returningAll();
|
|
1373
|
+
return await qb.executeTakeFirst();
|
|
1374
|
+
}
|
|
1375
|
+
async createMany(data) {
|
|
1376
|
+
let qb = this.db.insertInto(this.table);
|
|
1377
|
+
qb = qb.values(data).returningAll();
|
|
1378
|
+
return await qb.execute();
|
|
1379
|
+
}
|
|
1380
|
+
async updateById(id, data) {
|
|
1381
|
+
const row = await this.findByIdOrFail(id);
|
|
1382
|
+
let qb = this.db.updateTable(this.table);
|
|
1383
|
+
qb = qb.set(data).where(this.primaryKey, "=", row[this.primaryKey]).returningAll();
|
|
1384
|
+
await qb.executeTakeFirst();
|
|
1385
|
+
}
|
|
1386
|
+
async deleteById(id) {
|
|
1387
|
+
const row = await this.findByIdOrFail(id);
|
|
1388
|
+
let qb = this.db.deleteFrom(this.table);
|
|
1389
|
+
qb = qb.where(this.primaryKey, "=", row[this.primaryKey]);
|
|
1390
|
+
await qb.executeTakeFirst();
|
|
1391
|
+
}
|
|
1392
|
+
async deleteMany(options) {
|
|
1393
|
+
const deleteOptions = {
|
|
1394
|
+
...options,
|
|
1395
|
+
qb: this.db.deleteFrom(this.table)
|
|
1396
|
+
};
|
|
1397
|
+
let qb = this.query(deleteOptions);
|
|
1398
|
+
if (options?.limit) qb = qb.limit(options.limit);
|
|
1399
|
+
await qb.execute();
|
|
1400
|
+
}
|
|
1401
|
+
};
|
|
1402
|
+
//#endregion
|
|
1403
|
+
//#region src/server/mixins/DatabaseRepositoryInferMixin.ts
|
|
1404
|
+
function DatabaseRepositoryInferMixin() {
|
|
1405
|
+
return function(Base) {
|
|
1406
|
+
class OptionsMixin extends Base {}
|
|
1407
|
+
return OptionsMixin;
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
//#endregion
|
|
1411
|
+
//#region src/server/repositories/PermissionAssignmentRepository.ts
|
|
1412
|
+
var PermissionAssignmentRepository = class extends compose(mixin(DatabaseRepository), DatabaseRepositoryInferMixin()) {
|
|
1413
|
+
constructor(db) {
|
|
1414
|
+
super(db, "permissions_assignments", "id");
|
|
1415
|
+
}
|
|
1416
|
+
query(options) {
|
|
1417
|
+
let qb = super.query(options);
|
|
1418
|
+
if (options?.id) {
|
|
1419
|
+
const ids = Array.isArray(options.id) ? options.id : [options.id];
|
|
1420
|
+
qb = qb.where("id", "in", ids);
|
|
1421
|
+
}
|
|
1422
|
+
if (options?.permissionId) {
|
|
1423
|
+
const permissionIds = Array.isArray(options.permissionId) ? options.permissionId : [options.permissionId];
|
|
1424
|
+
qb = qb.where("permission_id", "in", permissionIds);
|
|
1425
|
+
}
|
|
1426
|
+
if (options?.assignableId) {
|
|
1427
|
+
const assignableIds = Array.isArray(options.assignableId) ? options.assignableId : [options.assignableId];
|
|
1428
|
+
qb = qb.where("assignable_id", "in", assignableIds);
|
|
1429
|
+
}
|
|
1430
|
+
if (options?.assignableType) {
|
|
1431
|
+
const assignableTypes = Array.isArray(options.assignableType) ? options.assignableType : [options.assignableType];
|
|
1432
|
+
qb = qb.where("assignable_type", "in", assignableTypes);
|
|
1433
|
+
}
|
|
1434
|
+
return qb;
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
//#endregion
|
|
1438
|
+
//#region src/server/repositories/PermissionRepository.ts
|
|
1439
|
+
var PermissionRepository = class extends compose(mixin(DatabaseRepository), DatabaseRepositoryInferMixin()) {
|
|
1440
|
+
constructor(db) {
|
|
1441
|
+
super(db, "permissions", "id");
|
|
1442
|
+
}
|
|
1443
|
+
query(options) {
|
|
1444
|
+
let qb = super.query(options);
|
|
1445
|
+
if (options?.id) {
|
|
1446
|
+
const ids = Array.isArray(options.id) ? options.id : [options.id];
|
|
1447
|
+
qb = qb.where("id", "in", ids);
|
|
1448
|
+
}
|
|
1449
|
+
if (options?.search) qb = qb.where("name", "like", `%${options.search}%`);
|
|
1450
|
+
return qb;
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
//#endregion
|
|
1454
|
+
//#region src/server/repositories/TokenRepository.ts
|
|
1455
|
+
var TokenRepository = class extends compose(mixin(DatabaseRepository), DatabaseRepositoryInferMixin()) {
|
|
1456
|
+
constructor(db) {
|
|
1457
|
+
super(db, "tokens", "id");
|
|
1458
|
+
}
|
|
1459
|
+
query(options) {
|
|
1460
|
+
let qb = super.query(options);
|
|
1461
|
+
if (options?.id) {
|
|
1462
|
+
const ids = Array.isArray(options.id) ? options.id : [options.id];
|
|
1463
|
+
qb = qb.where("id", "in", ids);
|
|
1464
|
+
}
|
|
1465
|
+
if (options?.search) qb = qb.where("name", "like", `%${options.search}%`);
|
|
1466
|
+
if (options?.type) {
|
|
1467
|
+
const types = Array.isArray(options.type) ? options.type : [options.type];
|
|
1468
|
+
qb = qb.where("type", "in", types);
|
|
1469
|
+
}
|
|
1470
|
+
return qb;
|
|
1471
|
+
}
|
|
1472
|
+
async findByToken(token) {
|
|
1473
|
+
let qb = this.query();
|
|
1474
|
+
qb = qb.selectAll();
|
|
1475
|
+
qb = qb.where("token", "=", token);
|
|
1476
|
+
return await qb.executeTakeFirst() || null;
|
|
1477
|
+
}
|
|
1478
|
+
};
|
|
1479
|
+
//#endregion
|
|
400
1480
|
//#region src/server/gateways/GitGateway.ts
|
|
401
1481
|
const execAsync = promisify(exec);
|
|
402
1482
|
function escapeShellArgument(value) {
|
|
@@ -423,7 +1503,7 @@ var GitGateway = class {
|
|
|
423
1503
|
result.sshKeyFile = this.sshKeyFile;
|
|
424
1504
|
}
|
|
425
1505
|
if (this.sshKey) {
|
|
426
|
-
const tempFile = this.sshKeyTmpFileName ?? join(tmpdir(), `git-ssh-key-${randomUUID()}`);
|
|
1506
|
+
const tempFile = this.sshKeyTmpFileName ?? join$1(tmpdir(), `git-ssh-key-${randomUUID()}`);
|
|
427
1507
|
await writeFile(tempFile, this.sshKey, { mode: 384 });
|
|
428
1508
|
result.env.GIT_SSH_COMMAND = `ssh -i ${escapeShellArgument(tempFile)} -o StrictHostKeyChecking=no`;
|
|
429
1509
|
result.sshKeyFile = tempFile;
|
|
@@ -486,4 +1566,178 @@ var GitGateway = class {
|
|
|
486
1566
|
}
|
|
487
1567
|
};
|
|
488
1568
|
//#endregion
|
|
489
|
-
|
|
1569
|
+
//#region src/server/facades/config.ts
|
|
1570
|
+
const config = container.proxy(ConfigService);
|
|
1571
|
+
//#endregion
|
|
1572
|
+
//#region src/server/facades/database.ts
|
|
1573
|
+
const key = "database";
|
|
1574
|
+
const database = container.proxy(key);
|
|
1575
|
+
//#endregion
|
|
1576
|
+
//#region src/server/facades/router.ts
|
|
1577
|
+
const router = container.proxy(Router);
|
|
1578
|
+
//#endregion
|
|
1579
|
+
//#region src/server/facades/shell.ts
|
|
1580
|
+
const shell = new ShellService();
|
|
1581
|
+
//#endregion
|
|
1582
|
+
//#region src/server/utils/defineLoader.ts
|
|
1583
|
+
function defineLoader(loader) {
|
|
1584
|
+
return loader;
|
|
1585
|
+
}
|
|
1586
|
+
//#endregion
|
|
1587
|
+
//#region src/server/loaders/createHasManyThroughLoader.ts
|
|
1588
|
+
async function loadHasManyThrough(payload, options) {
|
|
1589
|
+
const entities = Array.isArray(payload) ? payload : [payload];
|
|
1590
|
+
const target = options.target;
|
|
1591
|
+
const pivot = options.pivot;
|
|
1592
|
+
function findKey(entity, keyOrFn) {
|
|
1593
|
+
if (typeof keyOrFn === "function") return keyOrFn(entity);
|
|
1594
|
+
return get(entity, keyOrFn);
|
|
1595
|
+
}
|
|
1596
|
+
const pivoIds = entities.map((e) => findKey(e, pivot.sourceKey)).filter(Boolean);
|
|
1597
|
+
const pivotEntities = await pivot.findEntities(pivoIds);
|
|
1598
|
+
const targetIds = pivotEntities.map((p) => findKey(p, pivot.targetKey)).filter(Boolean);
|
|
1599
|
+
const targetEntities = await target.findEntities(targetIds);
|
|
1600
|
+
for (const entity of entities) {
|
|
1601
|
+
const pivots = pivotEntities.filter((p) => findKey(p, pivot.sourceKey) === findKey(entity, pivot.sourceKey));
|
|
1602
|
+
if (!pivots.length) {
|
|
1603
|
+
set(entity, options.key, []);
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1606
|
+
const targets = targetEntities.filter((t) => pivots.some((p) => findKey(p, target.sourceKey) === findKey(t, target.targetKey)));
|
|
1607
|
+
set(entity, options.key, targets);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
function createHasManyThroughLoader(options) {
|
|
1611
|
+
return defineLoader({ load: async (entities) => loadHasManyThrough(entities, options) });
|
|
1612
|
+
}
|
|
1613
|
+
//#endregion
|
|
1614
|
+
//#region src/server/utils/basePath.ts
|
|
1615
|
+
const BASE_PATH = process.env.ZENITH_BASE_PATH;
|
|
1616
|
+
function basePath(...args) {
|
|
1617
|
+
if (!BASE_PATH) throw new Error("ZENITH_BASE_PATH environment variable is not set");
|
|
1618
|
+
return path.resolve(BASE_PATH, ...args);
|
|
1619
|
+
}
|
|
1620
|
+
function tmpPath(...args) {
|
|
1621
|
+
return basePath("tmp", ...args);
|
|
1622
|
+
}
|
|
1623
|
+
//#endregion
|
|
1624
|
+
//#region src/server/entities/ModuleEntity.ts
|
|
1625
|
+
var Module = class extends composeWith(Module$1) {
|
|
1626
|
+
get git() {
|
|
1627
|
+
return new GitGateway({
|
|
1628
|
+
cwd: this.directory,
|
|
1629
|
+
sshKey: config.get(`modules.${this.id}.ssh_key`),
|
|
1630
|
+
sshKeyTmpFileName: tmpPath(`ssh_key_${this.id}.pem`),
|
|
1631
|
+
logger: logger.child({ label: "git" }),
|
|
1632
|
+
debug: config.get("git.debug", false)
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
makePath(...parts) {
|
|
1636
|
+
return join(this.directory, this.id, ...parts);
|
|
1637
|
+
}
|
|
1638
|
+
staticPath(...parts) {
|
|
1639
|
+
return join("/static", "modules", this.id, ...parts);
|
|
1640
|
+
}
|
|
1641
|
+
command = (bin, args, options) => {
|
|
1642
|
+
return shell.command(bin, args, {
|
|
1643
|
+
cwd: this.directory,
|
|
1644
|
+
...options
|
|
1645
|
+
});
|
|
1646
|
+
};
|
|
1647
|
+
load() {
|
|
1648
|
+
const manifestPath = this.makePath("module.json");
|
|
1649
|
+
if (!fs.existsSync(manifestPath)) return;
|
|
1650
|
+
const content = fs.readFileSync(manifestPath, "utf-8");
|
|
1651
|
+
const json = JSON.parse(content);
|
|
1652
|
+
this.dependencies = json.dependencies || {};
|
|
1653
|
+
this.build = json.build || {};
|
|
1654
|
+
}
|
|
1655
|
+
};
|
|
1656
|
+
//#endregion
|
|
1657
|
+
//#region src/server/utils/createLoaderFactory.ts
|
|
1658
|
+
function createLoaderFactory(loaders) {
|
|
1659
|
+
async function load(entities, names) {
|
|
1660
|
+
const items = Array.isArray(entities) ? entities : [entities];
|
|
1661
|
+
const relationNames = Array.isArray(names) ? names : [names];
|
|
1662
|
+
for (const name of relationNames) {
|
|
1663
|
+
const loader = loaders?.[name];
|
|
1664
|
+
if (!loader) throw new BaseException(`Loader ${String(name)} not declared`);
|
|
1665
|
+
await loader.load(items);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
return {
|
|
1669
|
+
keys: Object.keys(loaders),
|
|
1670
|
+
loaders,
|
|
1671
|
+
load
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
//#endregion
|
|
1675
|
+
//#region src/server/utils/importAll.ts
|
|
1676
|
+
async function importFiles(files, options = {}) {
|
|
1677
|
+
const modules = {};
|
|
1678
|
+
let onError = (ctx) => {
|
|
1679
|
+
Object.assign(ctx.error, { filename: ctx.filename });
|
|
1680
|
+
logger.error(`Failed to import ${ctx.filename}`, ctx.error);
|
|
1681
|
+
};
|
|
1682
|
+
if (options.onError) onError = options.onError;
|
|
1683
|
+
for (const filename of files) {
|
|
1684
|
+
if (options.exclude && options.exclude.some((pattern) => filename.includes(pattern))) continue;
|
|
1685
|
+
const ctx = { filename };
|
|
1686
|
+
if (options.onBeforeImport) await options.onBeforeImport(ctx);
|
|
1687
|
+
const cleanFilename = ctx.filename.split("?")[0];
|
|
1688
|
+
if (/\.(mts|ts|js)$/.test(cleanFilename)) {
|
|
1689
|
+
const fileUrl = pathToFileURL(path.resolve(ctx.filename));
|
|
1690
|
+
if (options.cache === false) fileUrl.searchParams.set("t", Date.now().toString());
|
|
1691
|
+
const [error, mod] = await tryCatch(() => import(fileUrl.toString()));
|
|
1692
|
+
if (error) {
|
|
1693
|
+
Object.assign(error, {
|
|
1694
|
+
filename: ctx.filename,
|
|
1695
|
+
url: fileUrl
|
|
1696
|
+
});
|
|
1697
|
+
onError({
|
|
1698
|
+
filename: ctx.filename,
|
|
1699
|
+
error
|
|
1700
|
+
});
|
|
1701
|
+
continue;
|
|
1702
|
+
}
|
|
1703
|
+
modules[filename] = mod;
|
|
1704
|
+
}
|
|
1705
|
+
if (/\.json$/.test(cleanFilename)) {
|
|
1706
|
+
const [error, json] = await tryCatch(async () => {
|
|
1707
|
+
const text = await fs.promises.readFile(ctx.filename, "utf8");
|
|
1708
|
+
return JSON.parse(text);
|
|
1709
|
+
});
|
|
1710
|
+
if (error) {
|
|
1711
|
+
onError({
|
|
1712
|
+
filename: ctx.filename,
|
|
1713
|
+
error
|
|
1714
|
+
});
|
|
1715
|
+
continue;
|
|
1716
|
+
}
|
|
1717
|
+
modules[filename] = json;
|
|
1718
|
+
}
|
|
1719
|
+
if (options.onAfterImport) await options.onAfterImport({
|
|
1720
|
+
...ctx,
|
|
1721
|
+
module: modules[filename]
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
return modules;
|
|
1725
|
+
}
|
|
1726
|
+
async function importGlob(pattern, options = {}) {
|
|
1727
|
+
return importFiles(await fg(pattern), options);
|
|
1728
|
+
}
|
|
1729
|
+
async function importAll(directory, options = {}) {
|
|
1730
|
+
return importFiles((await fs.promises.readdir(directory, { withFileTypes: true })).filter((dirent) => dirent.isFile()).map((dirent) => path.join(directory, dirent.name)), options);
|
|
1731
|
+
}
|
|
1732
|
+
//#endregion
|
|
1733
|
+
//#region src/server/utils/importOne.ts
|
|
1734
|
+
async function importOne(filenames) {
|
|
1735
|
+
for (const filename of filenames) {
|
|
1736
|
+
const fileUrl = pathToFileURL$1(filename);
|
|
1737
|
+
if (!fs$1.existsSync(filename)) continue;
|
|
1738
|
+
return await import(fileUrl.toString());
|
|
1739
|
+
}
|
|
1740
|
+
return null;
|
|
1741
|
+
}
|
|
1742
|
+
//#endregion
|
|
1743
|
+
export { DatabaseRepository, DatabaseRepositoryInferMixin, EmmitterService, GitBranchRepository, GitCommitRepository, GitGateway, Module as ModuleEntity, PermissionAssignmentRepository, PermissionRepository, PluginIpcClient, PluginIpcHost, PluginRouter, Route as RouteEntity, RouterFileBaseRoutingService, Router as RouterService, ShellService, TokenRepository, basePath, config, container, createHasManyThroughLoader, createLoaderFactory, database, defineLoader, importAll, importFiles, importGlob, importOne, key, loadHasManyThrough, logger, router, shell, tmpPath };
|