@sidekick-coder/zenith-kit 0.0.1
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 +72 -0
- package/dist/server/index.mjs +185 -0
- package/dist/shared/index.d.mts +107 -0
- package/dist/shared/index.mjs +187 -0
- package/eslint.config.mts +22 -0
- package/indexes.js +37 -0
- package/package.json +52 -0
- package/tsconfig.client.json +26 -0
- package/tsconfig.json +8 -0
- package/tsconfig.server.json +30 -0
- package/tsconfig.shared.json +26 -0
- package/tsdown.config.ts +14 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ChildProcess } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
//#region src/server/services/PluginIpcClient.d.ts
|
|
4
|
+
type IpcMessage<T = any> = {
|
|
5
|
+
id: string;
|
|
6
|
+
event: string;
|
|
7
|
+
data: T;
|
|
8
|
+
};
|
|
9
|
+
type IpcListener$1<T = any> = (data: T) => void;
|
|
10
|
+
declare class PluginIpcClient {
|
|
11
|
+
private listeners;
|
|
12
|
+
private pending;
|
|
13
|
+
constructor();
|
|
14
|
+
on<T = any>(event: string, callback: IpcListener$1<T>): void;
|
|
15
|
+
off<T = any>(event: string, callback: IpcListener$1<T>): void;
|
|
16
|
+
emit<T = any>(event: string, data?: T): string;
|
|
17
|
+
emitAsync<TData = any, TReply = any>(event: string, data?: TData): Promise<TReply>;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/server/services/PluginIpcHost.d.ts
|
|
21
|
+
type IpcListener<T = any> = (data: T) => void;
|
|
22
|
+
declare class PluginIpcHost {
|
|
23
|
+
private readonly child;
|
|
24
|
+
private listeners;
|
|
25
|
+
private pending;
|
|
26
|
+
constructor(child: ChildProcess);
|
|
27
|
+
on<T = any>(event: string, callback: IpcListener<T>): void;
|
|
28
|
+
off<T = any>(event: string, callback: IpcListener<T>): void;
|
|
29
|
+
emit<T = any>(event: string, data?: T): string;
|
|
30
|
+
emitAsync<TData = any, TReply = any>(event: string, data?: TData): Promise<TReply>;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/server/services/PluginRouter.d.ts
|
|
34
|
+
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
35
|
+
interface RouteDefinition {
|
|
36
|
+
method: HttpMethod;
|
|
37
|
+
path: string;
|
|
38
|
+
}
|
|
39
|
+
interface RouteRequest {
|
|
40
|
+
requestId: string;
|
|
41
|
+
method: HttpMethod;
|
|
42
|
+
path: string;
|
|
43
|
+
body?: any;
|
|
44
|
+
headers?: Record<string, string>;
|
|
45
|
+
query?: Record<string, string>;
|
|
46
|
+
params?: Record<string, string>;
|
|
47
|
+
}
|
|
48
|
+
interface RouteResponsePayload {
|
|
49
|
+
requestId: string;
|
|
50
|
+
status: number;
|
|
51
|
+
body?: any;
|
|
52
|
+
headers?: Record<string, string>;
|
|
53
|
+
}
|
|
54
|
+
interface RouteReply {
|
|
55
|
+
status(code: number): RouteReply;
|
|
56
|
+
send(body?: any): void;
|
|
57
|
+
}
|
|
58
|
+
type RouteHandler = (req: RouteRequest, reply: RouteReply) => void | Promise<void>;
|
|
59
|
+
declare class PluginRouter {
|
|
60
|
+
private readonly client;
|
|
61
|
+
private routes;
|
|
62
|
+
constructor(client: PluginIpcClient);
|
|
63
|
+
private dispatch;
|
|
64
|
+
private register;
|
|
65
|
+
get(path: string, handler: RouteHandler): void;
|
|
66
|
+
post(path: string, handler: RouteHandler): void;
|
|
67
|
+
put(path: string, handler: RouteHandler): void;
|
|
68
|
+
patch(path: string, handler: RouteHandler): void;
|
|
69
|
+
delete(path: string, handler: RouteHandler): void;
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
export { HttpMethod, IpcMessage, PluginIpcClient, PluginIpcHost, PluginRouter, RouteDefinition, RouteHandler, RouteReply, RouteRequest, RouteResponsePayload };
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
//#region src/shared/utils/createId.ts
|
|
2
|
+
function uuid() {
|
|
3
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) return crypto.randomUUID();
|
|
4
|
+
if (typeof self !== "undefined" && self.crypto && self.crypto.randomUUID) return self.crypto.randomUUID();
|
|
5
|
+
if (typeof window !== "undefined" && window.crypto && window.crypto.randomUUID) return window.crypto.randomUUID();
|
|
6
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
|
|
7
|
+
const r = Math.random() * 16 | 0;
|
|
8
|
+
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function createId(prefix = "") {
|
|
12
|
+
return prefix + uuid();
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/server/services/PluginIpcClient.ts
|
|
16
|
+
var PluginIpcClient = class {
|
|
17
|
+
listeners = /* @__PURE__ */ new Map();
|
|
18
|
+
pending = /* @__PURE__ */ new Map();
|
|
19
|
+
constructor() {
|
|
20
|
+
process.on("message", (message) => {
|
|
21
|
+
if (this.pending.has(message.id)) {
|
|
22
|
+
this.pending.get(message.id)(message.data);
|
|
23
|
+
this.pending.delete(message.id);
|
|
24
|
+
}
|
|
25
|
+
const handlers = this.listeners.get(message.event);
|
|
26
|
+
if (!handlers) return;
|
|
27
|
+
for (const handler of handlers) handler(message.data);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
on(event, callback) {
|
|
31
|
+
if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
32
|
+
this.listeners.get(event).add(callback);
|
|
33
|
+
}
|
|
34
|
+
off(event, callback) {
|
|
35
|
+
this.listeners.get(event)?.delete(callback);
|
|
36
|
+
}
|
|
37
|
+
emit(event, data) {
|
|
38
|
+
if (!process.send) throw new Error("PluginIpcClient.emit: process.send is not available — process was not spawned with IPC channel");
|
|
39
|
+
const id = createId();
|
|
40
|
+
const message = {
|
|
41
|
+
id,
|
|
42
|
+
event,
|
|
43
|
+
data
|
|
44
|
+
};
|
|
45
|
+
process.send(message);
|
|
46
|
+
return id;
|
|
47
|
+
}
|
|
48
|
+
emitAsync(event, data) {
|
|
49
|
+
return new Promise((resolve) => {
|
|
50
|
+
const id = this.emit(event, data);
|
|
51
|
+
this.pending.set(id, resolve);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/server/services/PluginIpcHost.ts
|
|
57
|
+
var PluginIpcHost = class {
|
|
58
|
+
listeners = /* @__PURE__ */ new Map();
|
|
59
|
+
pending = /* @__PURE__ */ new Map();
|
|
60
|
+
constructor(child) {
|
|
61
|
+
this.child = child;
|
|
62
|
+
this.child.on("message", (message) => {
|
|
63
|
+
if (this.pending.has(message.id)) {
|
|
64
|
+
this.pending.get(message.id)(message.data);
|
|
65
|
+
this.pending.delete(message.id);
|
|
66
|
+
}
|
|
67
|
+
const handlers = this.listeners.get(message.event);
|
|
68
|
+
if (!handlers) return;
|
|
69
|
+
for (const handler of handlers) handler(message.data);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
on(event, callback) {
|
|
73
|
+
if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
74
|
+
this.listeners.get(event).add(callback);
|
|
75
|
+
}
|
|
76
|
+
off(event, callback) {
|
|
77
|
+
this.listeners.get(event)?.delete(callback);
|
|
78
|
+
}
|
|
79
|
+
emit(event, data) {
|
|
80
|
+
if (!this.child.send) throw new Error("PluginIpcHost.emit: child process was not spawned with an IPC channel");
|
|
81
|
+
const id = createId();
|
|
82
|
+
const message = {
|
|
83
|
+
id,
|
|
84
|
+
event,
|
|
85
|
+
data
|
|
86
|
+
};
|
|
87
|
+
this.child.send(message);
|
|
88
|
+
return id;
|
|
89
|
+
}
|
|
90
|
+
emitAsync(event, data) {
|
|
91
|
+
return new Promise((resolve) => {
|
|
92
|
+
const id = this.emit(event, data);
|
|
93
|
+
this.pending.set(id, resolve);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/server/services/PluginRouter.ts
|
|
99
|
+
function buildPattern(path) {
|
|
100
|
+
const paramNames = [];
|
|
101
|
+
const regexStr = path.replace(/:[a-zA-Z_][a-zA-Z0-9_]*/g, (match) => {
|
|
102
|
+
paramNames.push(match.slice(1));
|
|
103
|
+
return "([^/]+)";
|
|
104
|
+
}).replace(/\//g, "\\/");
|
|
105
|
+
return {
|
|
106
|
+
pattern: new RegExp(`^${regexStr}$`),
|
|
107
|
+
paramNames
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
var PluginRouter = class {
|
|
111
|
+
routes = [];
|
|
112
|
+
constructor(client) {
|
|
113
|
+
this.client = client;
|
|
114
|
+
this.client.on("router:request", (req) => this.dispatch(req));
|
|
115
|
+
}
|
|
116
|
+
async dispatch(req) {
|
|
117
|
+
for (const route of this.routes) {
|
|
118
|
+
if (route.method !== req.method) continue;
|
|
119
|
+
const match = route.pattern.exec(req.path);
|
|
120
|
+
if (!match) continue;
|
|
121
|
+
const params = {};
|
|
122
|
+
route.paramNames.forEach((name, i) => {
|
|
123
|
+
params[name] = match[i + 1];
|
|
124
|
+
});
|
|
125
|
+
let statusCode = 200;
|
|
126
|
+
const headers = {};
|
|
127
|
+
const reply = {
|
|
128
|
+
status(code) {
|
|
129
|
+
statusCode = code;
|
|
130
|
+
return this;
|
|
131
|
+
},
|
|
132
|
+
send: (body) => {
|
|
133
|
+
const response = {
|
|
134
|
+
requestId: req.requestId,
|
|
135
|
+
status: statusCode,
|
|
136
|
+
body,
|
|
137
|
+
headers
|
|
138
|
+
};
|
|
139
|
+
this.client.emit("router:response", response);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
await route.handler({
|
|
143
|
+
...req,
|
|
144
|
+
params
|
|
145
|
+
}, reply);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
this.client.emit("router:response", {
|
|
149
|
+
requestId: req.requestId,
|
|
150
|
+
status: 404,
|
|
151
|
+
body: { message: "Not Found" }
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
register(method, path, handler) {
|
|
155
|
+
const { pattern, paramNames } = buildPattern(path);
|
|
156
|
+
this.routes.push({
|
|
157
|
+
method,
|
|
158
|
+
path,
|
|
159
|
+
handler,
|
|
160
|
+
pattern,
|
|
161
|
+
paramNames
|
|
162
|
+
});
|
|
163
|
+
this.client.emit("router:register", {
|
|
164
|
+
method,
|
|
165
|
+
path
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
get(path, handler) {
|
|
169
|
+
this.register("GET", path, handler);
|
|
170
|
+
}
|
|
171
|
+
post(path, handler) {
|
|
172
|
+
this.register("POST", path, handler);
|
|
173
|
+
}
|
|
174
|
+
put(path, handler) {
|
|
175
|
+
this.register("PUT", path, handler);
|
|
176
|
+
}
|
|
177
|
+
patch(path, handler) {
|
|
178
|
+
this.register("PATCH", path, handler);
|
|
179
|
+
}
|
|
180
|
+
delete(path, handler) {
|
|
181
|
+
this.register("DELETE", path, handler);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
//#endregion
|
|
185
|
+
export { PluginIpcClient, PluginIpcHost, PluginRouter };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
|
|
3
|
+
//#region src/shared/services/LoggerService.d.ts
|
|
4
|
+
declare class LoggerService {
|
|
5
|
+
info(message: string, meta?: any): void;
|
|
6
|
+
debug(message: string, meta?: any): void;
|
|
7
|
+
warn(message: string, meta?: any): void;
|
|
8
|
+
error(message: string, meta?: any): void;
|
|
9
|
+
child(options: any): LoggerService;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/shared/services/EmmitterService.d.ts
|
|
13
|
+
interface EmmitterServiceOptions {
|
|
14
|
+
debug?: boolean;
|
|
15
|
+
logger?: LoggerService;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/shared/utils/typing.d.ts
|
|
19
|
+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
|
20
|
+
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
21
|
+
type PublicData<T> = { [K in keyof T as T[K] extends Function ? never : K]: T[K] };
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/shared/utils/compose.d.ts
|
|
24
|
+
type AnyClass = new (...args: any[]) => any;
|
|
25
|
+
type Constructor<T = {}> = new (...args: any[]) => T;
|
|
26
|
+
type Mixin<T> = (base: Constructor) => Constructor<T>;
|
|
27
|
+
/**
|
|
28
|
+
* Composes multiple mixins into a single class that can be extended.
|
|
29
|
+
* Allows for multiple inheritance-like behavior by applying mixins sequentially.
|
|
30
|
+
*
|
|
31
|
+
* @param mixins - Array of mixin functions to compose
|
|
32
|
+
* @returns A class constructor that includes all mixin functionality
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* class User extends compose(Timestamp, SoftDelete) {
|
|
37
|
+
* constructor(public name: string) {
|
|
38
|
+
* super()
|
|
39
|
+
* }
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
declare function compose<M extends Array<(base: Constructor<any>) => Constructor<any>>>(...mixins: M): UnionToIntersection<ReturnType<M[number]>>;
|
|
44
|
+
/**
|
|
45
|
+
* Alternative compose function that starts with a base class
|
|
46
|
+
*
|
|
47
|
+
* @param baseClass - The base class to start with
|
|
48
|
+
* @param mixins - Array of mixin functions to apply
|
|
49
|
+
* @returns A class constructor that extends the base class with all mixin functionality
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* class User extends composeWith(BaseEntity, Timestamp, SoftDelete) {
|
|
54
|
+
* constructor(public name: string) {
|
|
55
|
+
* super()
|
|
56
|
+
* }
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
declare function composeWith<TBase extends Constructor, M extends Array<(base: Constructor) => Constructor<any>>>(baseClass: TBase, ...mixins: M): TBase & UnionToIntersection<ReturnType<M[number]>>;
|
|
61
|
+
declare function mixin<TBase extends Constructor>(Source: TBase): <TTarget extends Constructor>(Target: TTarget) => TTarget & TBase;
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/shared/services/ValidatorService.d.ts
|
|
64
|
+
type Valibot = typeof v;
|
|
65
|
+
type ValibotSchema = v.BaseSchema<unknown, unknown, v.BaseIssue<unknown>>;
|
|
66
|
+
type ValibotSchemaAsync = v.BaseSchemaAsync<unknown, unknown, v.BaseIssue<unknown>>;
|
|
67
|
+
interface ValidatorCallback<T extends ValibotSchema> {
|
|
68
|
+
(_v: typeof v): T;
|
|
69
|
+
}
|
|
70
|
+
type ValidatorCallbackAsync<T extends ValibotSchemaAsync> = {
|
|
71
|
+
(_v: typeof v): T;
|
|
72
|
+
};
|
|
73
|
+
type ValidatorResult<T extends v.ObjectEntries> = v.InferOutput<v.ObjectSchema<T, undefined>>;
|
|
74
|
+
type ValidatePayload<T extends ValibotSchema = ValibotSchema> = ValidatorCallback<T> | T;
|
|
75
|
+
type ValidateResult<T extends ValidatePayload> = T extends ValibotSchema ? v.InferOutput<T> : T extends ValidatorCallback<infer U> ? v.InferOutput<U> : unknown;
|
|
76
|
+
declare class ValidatorService {
|
|
77
|
+
create<T extends ValibotSchema>(cb: ValidatorCallback<T>): T;
|
|
78
|
+
validate<T extends ValibotSchema>(payload: any, cb: ValidatePayload<T>): v.InferOutput<T>;
|
|
79
|
+
validateAsync<T extends ValibotSchemaAsync>(payload: any, cb: ValidatorCallbackAsync<T> | T): Promise<v.InferOutput<T>>;
|
|
80
|
+
isValid<T extends ValibotSchema>(payload: any, cb: ValidatePayload<T>): boolean;
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/shared/utils/createId.d.ts
|
|
84
|
+
declare function createId(prefix?: string): string;
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/shared/utils/flatten.d.ts
|
|
87
|
+
declare function flatten(obj: any, prefix?: string, res?: any): Record<string, any>;
|
|
88
|
+
declare function unflatten(obj: any): Record<string, any>;
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/shared/utils/formatBytes.d.ts
|
|
91
|
+
declare function formatBytes(bytes: number, decimals?: number): string;
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/shared/utils/tryCatch.d.ts
|
|
94
|
+
interface Tryer {
|
|
95
|
+
(...args: any[]): any;
|
|
96
|
+
}
|
|
97
|
+
interface TryerAsync {
|
|
98
|
+
(...args: any[]): Promise<any> | any;
|
|
99
|
+
}
|
|
100
|
+
type TryCatchResult<T extends Tryer> = [null, ReturnType<T>] | [Error, null];
|
|
101
|
+
type TryCatchAsyncResult<T extends TryerAsync> = [null, Awaited<ReturnType<T>>] | [Error, null];
|
|
102
|
+
declare function tryCatch<T extends TryerAsync>(tryer: T): Promise<TryCatchAsyncResult<T>>;
|
|
103
|
+
declare namespace tryCatch {
|
|
104
|
+
var sync: <T extends Tryer>(tryer: T) => TryCatchResult<T>;
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { AnyClass, Constructor, EmmitterServiceOptions, Mixin, PartialBy, PublicData, UnionToIntersection, Valibot, ValibotSchema, ValibotSchemaAsync, ValidatePayload, ValidateResult, ValidatorCallback, ValidatorCallbackAsync, ValidatorResult, ValidatorService, compose, composeWith, createId, flatten, formatBytes, mixin, tryCatch, unflatten };
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import "lodash-es";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
//#region src/shared/utils/compose.ts
|
|
4
|
+
/**
|
|
5
|
+
* Composes multiple mixins into a single class that can be extended.
|
|
6
|
+
* Allows for multiple inheritance-like behavior by applying mixins sequentially.
|
|
7
|
+
*
|
|
8
|
+
* @param mixins - Array of mixin functions to compose
|
|
9
|
+
* @returns A class constructor that includes all mixin functionality
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* class User extends compose(Timestamp, SoftDelete) {
|
|
14
|
+
* constructor(public name: string) {
|
|
15
|
+
* super()
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
function compose(...mixins) {
|
|
21
|
+
return mixins.reduce((base, mixin) => mixin(base), class {});
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Alternative compose function that starts with a base class
|
|
25
|
+
*
|
|
26
|
+
* @param baseClass - The base class to start with
|
|
27
|
+
* @param mixins - Array of mixin functions to apply
|
|
28
|
+
* @returns A class constructor that extends the base class with all mixin functionality
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* class User extends composeWith(BaseEntity, Timestamp, SoftDelete) {
|
|
33
|
+
* constructor(public name: string) {
|
|
34
|
+
* super()
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
function composeWith(baseClass, ...mixins) {
|
|
40
|
+
return mixins.reduce((base, mixin) => mixin(base), baseClass);
|
|
41
|
+
}
|
|
42
|
+
function mixin(Source) {
|
|
43
|
+
return function(Target) {
|
|
44
|
+
class Mixed extends Target {
|
|
45
|
+
constructor(...args) {
|
|
46
|
+
super(...args);
|
|
47
|
+
const source = new Source(...args);
|
|
48
|
+
Object.assign(this, source);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
for (const key of Reflect.ownKeys(Source.prototype)) if (key !== "constructor") Object.defineProperty(Mixed.prototype, key, Object.getOwnPropertyDescriptor(Source.prototype, key));
|
|
52
|
+
return Mixed;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/shared/utils/formatBytes.ts
|
|
57
|
+
function formatBytes(bytes, decimals = 2) {
|
|
58
|
+
if (bytes === 0) return "0 Bytes";
|
|
59
|
+
const k = 1024;
|
|
60
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
61
|
+
const sizes = [
|
|
62
|
+
"Bytes",
|
|
63
|
+
"KB",
|
|
64
|
+
"MB",
|
|
65
|
+
"GB",
|
|
66
|
+
"TB",
|
|
67
|
+
"PB"
|
|
68
|
+
];
|
|
69
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
70
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/shared/utils/flatten.ts
|
|
74
|
+
function flatten(obj, prefix = "", res = {}) {
|
|
75
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
76
|
+
const newKey = prefix ? `${prefix}.${key}` : key;
|
|
77
|
+
if (value && typeof value === "object" && !Array.isArray(value)) flatten(value, newKey, res);
|
|
78
|
+
else res[newKey] = value;
|
|
79
|
+
}
|
|
80
|
+
return res;
|
|
81
|
+
}
|
|
82
|
+
function unflatten(obj) {
|
|
83
|
+
const result = {};
|
|
84
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
85
|
+
const parts = key.split(".");
|
|
86
|
+
let current = result;
|
|
87
|
+
parts.forEach((part, i) => {
|
|
88
|
+
if (i === parts.length - 1) current[part] = value;
|
|
89
|
+
else {
|
|
90
|
+
if (!current[part] || typeof current[part] !== "object") current[part] = {};
|
|
91
|
+
current = current[part];
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/shared/utils/tryCatch.ts
|
|
99
|
+
async function tryCatch(tryer) {
|
|
100
|
+
try {
|
|
101
|
+
return [null, await tryer()];
|
|
102
|
+
} catch (error) {
|
|
103
|
+
return [error, null];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
tryCatch.sync = function(tryer) {
|
|
107
|
+
try {
|
|
108
|
+
return [null, tryer()];
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return [error, null];
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/shared/utils/createId.ts
|
|
115
|
+
function uuid() {
|
|
116
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) return crypto.randomUUID();
|
|
117
|
+
if (typeof self !== "undefined" && self.crypto && self.crypto.randomUUID) return self.crypto.randomUUID();
|
|
118
|
+
if (typeof window !== "undefined" && window.crypto && window.crypto.randomUUID) return window.crypto.randomUUID();
|
|
119
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
|
|
120
|
+
const r = Math.random() * 16 | 0;
|
|
121
|
+
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function createId(prefix = "") {
|
|
125
|
+
return prefix + uuid();
|
|
126
|
+
}
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/shared/exceptions/BaseException.ts
|
|
129
|
+
var BaseException = class BaseException extends Error {
|
|
130
|
+
statusCode = 500;
|
|
131
|
+
constructor(message, statusCode = 500) {
|
|
132
|
+
super(message);
|
|
133
|
+
this.name = this.constructor.name;
|
|
134
|
+
this.statusCode = statusCode;
|
|
135
|
+
}
|
|
136
|
+
static fromError(error) {
|
|
137
|
+
return new BaseException(error.message, 500);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
//#endregion
|
|
141
|
+
//#region src/shared/services/ValidatorService.ts
|
|
142
|
+
var ValidatorService = class {
|
|
143
|
+
create(cb) {
|
|
144
|
+
return cb(v);
|
|
145
|
+
}
|
|
146
|
+
validate(payload, cb) {
|
|
147
|
+
const schema = typeof cb === "function" ? cb(v) : cb;
|
|
148
|
+
const { output, issues, success } = v.safeParse(schema, payload);
|
|
149
|
+
if (!success) {
|
|
150
|
+
const flatten = v.flatten(issues);
|
|
151
|
+
const messages = [];
|
|
152
|
+
if (flatten.root) messages.push(...flatten.root);
|
|
153
|
+
if (flatten.nested) Object.entries(flatten.nested).forEach((entry) => {
|
|
154
|
+
const [key, value] = entry;
|
|
155
|
+
messages.push(...value.map((v) => `${key}: ${v}`));
|
|
156
|
+
});
|
|
157
|
+
const error = new BaseException(messages.length ? messages.join(", ") : "Validation failed", 422);
|
|
158
|
+
error.name = "ValidationError";
|
|
159
|
+
Object.assign(error, { messages });
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
return output;
|
|
163
|
+
}
|
|
164
|
+
async validateAsync(payload, cb) {
|
|
165
|
+
const schema = typeof cb === "function" ? cb(v) : cb;
|
|
166
|
+
const { output, issues, success } = await v.safeParseAsync(schema, payload);
|
|
167
|
+
if (!success) {
|
|
168
|
+
const error = /* @__PURE__ */ new Error("Validation failed");
|
|
169
|
+
const flatten = v.flatten(issues);
|
|
170
|
+
const details = {
|
|
171
|
+
...flatten.root,
|
|
172
|
+
...flatten.nested
|
|
173
|
+
};
|
|
174
|
+
Object.assign(error, { details });
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
return output;
|
|
178
|
+
}
|
|
179
|
+
isValid(payload, cb) {
|
|
180
|
+
const schema = typeof cb === "function" ? cb(v) : cb;
|
|
181
|
+
const { success } = v.safeParse(schema, payload);
|
|
182
|
+
return success;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
new ValidatorService();
|
|
186
|
+
//#endregion
|
|
187
|
+
export { ValidatorService, compose, composeWith, createId, flatten, formatBytes, mixin, tryCatch, unflatten };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
import tseslint from "typescript-eslint";
|
|
4
|
+
import pluginVue from "eslint-plugin-vue";
|
|
5
|
+
import json from "@eslint/json";
|
|
6
|
+
import eslintConfigPrettier from "eslint-config-prettier/flat";
|
|
7
|
+
import { defineConfig } from "eslint/config";
|
|
8
|
+
|
|
9
|
+
export default defineConfig([
|
|
10
|
+
{ files: ["**/*.{js,mjs,cjs,ts,mts,cts,vue}"], plugins: { js }, extends: ["js/recommended"], languageOptions: { globals: { ...globals.browser, ...globals.node } } },
|
|
11
|
+
tseslint.configs.recommended,
|
|
12
|
+
pluginVue.configs["flat/essential"],
|
|
13
|
+
{ files: ["**/*.vue"], languageOptions: { parserOptions: { parser: tseslint.parser } } },
|
|
14
|
+
{ files: ["**/*.json"], plugins: { json }, language: "json/json", extends: ["json/recommended"] },
|
|
15
|
+
{ files: ["**/*.jsonc"], plugins: { json }, language: "json/jsonc", extends: ["json/recommended"] },
|
|
16
|
+
eslintConfigPrettier,
|
|
17
|
+
{
|
|
18
|
+
rules: {
|
|
19
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
]);
|
package/indexes.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fg from 'fast-glob'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
// generate indexes for folders
|
|
6
|
+
|
|
7
|
+
function generate(options) {
|
|
8
|
+
const folders = options.folders
|
|
9
|
+
const filename = options.filename
|
|
10
|
+
|
|
11
|
+
let content = ''
|
|
12
|
+
|
|
13
|
+
for (const folder of folders) {
|
|
14
|
+
const files = fg.sync(`${folder}/**/*.ts`, { ignore: ['**/index.ts'] })
|
|
15
|
+
|
|
16
|
+
for (const file of files) {
|
|
17
|
+
const filePath = path.relative(path.dirname(filename), file)
|
|
18
|
+
|
|
19
|
+
content += `export * from './${filePath}'\n`
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
content = content.trim()
|
|
24
|
+
|
|
25
|
+
fs.writeFileSync(filename, content)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
generate({
|
|
30
|
+
folders: ['src/shared/services', 'src/shared/utils'],
|
|
31
|
+
filename: 'src/shared/index.ts'
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
generate({
|
|
35
|
+
folders: ['src/server/services'],
|
|
36
|
+
filename: 'src/server/index.ts'
|
|
37
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sidekick-coder/zenith-kit",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "A collection of utilities and tools for building language servers and related applications.",
|
|
6
|
+
"keywords": [],
|
|
7
|
+
"author": "Henrique Oliveira <henriqueoliwork@gmail.com>",
|
|
8
|
+
"homepage": "https://github.com/sidekick-coder/language-kit",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/sidekick-coder/language-kit.git"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
"./shared": {
|
|
18
|
+
"import": "./dist/shared/index.mjs",
|
|
19
|
+
"require": "./dist/shared/index.js",
|
|
20
|
+
"types": "./dist/shared/index.d.mts"
|
|
21
|
+
},
|
|
22
|
+
"./server": {
|
|
23
|
+
"import": "./dist/server/index.mjs",
|
|
24
|
+
"require": "./dist/server/index.js",
|
|
25
|
+
"types": "./dist/server/index.d.mts"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"generate:indexes": "node indexes.js",
|
|
30
|
+
"build": "node indexes.js && tsdown"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@eslint/js": "^9.39.4",
|
|
34
|
+
"@eslint/json": "^0.14.0",
|
|
35
|
+
"@types/lodash-es": "^4.17.12",
|
|
36
|
+
"@types/node": "^25.6.0",
|
|
37
|
+
"@vue/tsconfig": "^0.9.1",
|
|
38
|
+
"eslint": "^9.39.4",
|
|
39
|
+
"eslint-config-prettier": "^10.1.8",
|
|
40
|
+
"eslint-plugin-vue": "^10.8.0",
|
|
41
|
+
"globals": "^17.5.0",
|
|
42
|
+
"jiti": "^2.6.1",
|
|
43
|
+
"tsdown": "^0.21.9",
|
|
44
|
+
"typescript": "^6.0.3",
|
|
45
|
+
"typescript-eslint": "^8.58.2"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"fast-glob": "^3.3.3",
|
|
49
|
+
"lodash-es": "^4.18.1",
|
|
50
|
+
"valibot": "^1.3.1"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": [
|
|
3
|
+
"@vue/tsconfig/tsconfig.dom.json"
|
|
4
|
+
],
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noUnusedLocals": false,
|
|
9
|
+
"noUnusedParameters": true,
|
|
10
|
+
"erasableSyntaxOnly": true,
|
|
11
|
+
"noFallthroughCasesInSwitch": true,
|
|
12
|
+
"noUncheckedSideEffectImports": true,
|
|
13
|
+
"strictPropertyInitialization": false,
|
|
14
|
+
"baseUrl": ".",
|
|
15
|
+
"lib": [
|
|
16
|
+
"es2023",
|
|
17
|
+
"dom",
|
|
18
|
+
"dom.iterable"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"include": [
|
|
22
|
+
"client/**/**/*.ts",
|
|
23
|
+
"client/**/**/*.vue",
|
|
24
|
+
"shared/**/**/*.ts",
|
|
25
|
+
]
|
|
26
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
4
|
+
"target": "ES2023",
|
|
5
|
+
"lib": [
|
|
6
|
+
"ES2023"
|
|
7
|
+
],
|
|
8
|
+
"types": [
|
|
9
|
+
"node"
|
|
10
|
+
],
|
|
11
|
+
"module": "ESNext",
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"moduleResolution": "bundler",
|
|
14
|
+
"allowImportingTsExtensions": true,
|
|
15
|
+
"verbatimModuleSyntax": true,
|
|
16
|
+
"moduleDetection": "force",
|
|
17
|
+
"noEmit": true,
|
|
18
|
+
"strict": true,
|
|
19
|
+
"noUnusedLocals": false,
|
|
20
|
+
"noUnusedParameters": true,
|
|
21
|
+
"erasableSyntaxOnly": true,
|
|
22
|
+
"noFallthroughCasesInSwitch": true,
|
|
23
|
+
"noUncheckedSideEffectImports": true,
|
|
24
|
+
"strictPropertyInitialization": false
|
|
25
|
+
},
|
|
26
|
+
"include": [
|
|
27
|
+
"src/server/**/*.ts",
|
|
28
|
+
"src/shared/**/*.ts"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
4
|
+
"target": "ES2023",
|
|
5
|
+
"lib": [
|
|
6
|
+
"ES2023"
|
|
7
|
+
],
|
|
8
|
+
"module": "ESNext",
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"verbatimModuleSyntax": true,
|
|
13
|
+
"moduleDetection": "force",
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"strict": false,
|
|
16
|
+
"noUnusedLocals": false,
|
|
17
|
+
"noUnusedParameters": true,
|
|
18
|
+
"erasableSyntaxOnly": true,
|
|
19
|
+
"noFallthroughCasesInSwitch": true,
|
|
20
|
+
"noUncheckedSideEffectImports": true,
|
|
21
|
+
"strictPropertyInitialization": false
|
|
22
|
+
},
|
|
23
|
+
"include": [
|
|
24
|
+
"src/shared/**/*.ts",
|
|
25
|
+
]
|
|
26
|
+
}
|
package/tsdown.config.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from 'tsdown'
|
|
2
|
+
|
|
3
|
+
export default defineConfig([
|
|
4
|
+
{
|
|
5
|
+
entry: 'src/shared/index.ts',
|
|
6
|
+
outDir: 'dist/shared',
|
|
7
|
+
tsconfig: 'tsconfig.shared.json',
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
entry: 'src/server/index.ts',
|
|
11
|
+
outDir: 'dist/server',
|
|
12
|
+
tsconfig: 'tsconfig.server.json',
|
|
13
|
+
}
|
|
14
|
+
])
|