topsyde-utils 1.0.150 → 1.0.152
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/application.d.ts +1 -0
- package/dist/application.js +12 -8
- package/dist/application.js.map +1 -1
- package/dist/client/rxjs/index.js.map +1 -1
- package/dist/client/rxjs/rxjs.js.map +1 -1
- package/dist/client/rxjs/useRxjs.js.map +1 -1
- package/dist/client/vite/plugins/index.js.map +1 -1
- package/dist/client/vite/plugins/topsydeUtilsVitePlugin.js.map +1 -1
- package/dist/consts.js.map +1 -1
- package/dist/enums.js.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/initializable.js.map +1 -1
- package/dist/server/bun/index.js.map +1 -1
- package/dist/server/bun/router/controller-discovery.js.map +1 -1
- package/dist/server/bun/router/index.js.map +1 -1
- package/dist/server/bun/router/router.internal.js.map +1 -1
- package/dist/server/bun/router/router.js.map +1 -1
- package/dist/server/bun/router/routes.js.map +1 -1
- package/dist/server/bun/websocket/Channel.js.map +1 -1
- package/dist/server/bun/websocket/Client.js.map +1 -1
- package/dist/server/bun/websocket/Message.js.map +1 -1
- package/dist/server/bun/websocket/Websocket.js.map +1 -1
- package/dist/server/bun/websocket/index.js.map +1 -1
- package/dist/server/bun/websocket/websocket.enums.js.map +1 -1
- package/dist/server/bun/websocket/websocket.guards.js.map +1 -1
- package/dist/server/bun/websocket/websocket.types.js.map +1 -1
- package/dist/server/controller.js.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/service.js.map +1 -1
- package/dist/singleton.js.map +1 -1
- package/dist/throwable.js.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/Console.js.map +1 -1
- package/dist/utils/Guards.js.map +1 -1
- package/dist/utils/Lib.js.map +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +5 -3
- package/src/__tests__/app.test.ts +205 -0
- package/src/__tests__/singleton.test.ts +402 -0
- package/src/__tests__/type-inference.test.ts +60 -0
- package/src/application.ts +48 -0
- package/src/client/rxjs/index.ts +5 -0
- package/src/client/rxjs/rxjs.ts +122 -0
- package/src/client/rxjs/useRxjs.ts +111 -0
- package/src/client/vite/plugins/index.ts +5 -0
- package/src/client/vite/plugins/topsydeUtilsVitePlugin.ts +80 -0
- package/src/consts.ts +48 -0
- package/src/enums.ts +14 -0
- package/src/errors.ts +56 -0
- package/src/index.ts +81 -0
- package/src/initializable.ts +375 -0
- package/src/server/bun/index.ts +6 -0
- package/src/server/bun/router/controller-discovery.ts +94 -0
- package/src/server/bun/router/index.ts +9 -0
- package/src/server/bun/router/router.internal.ts +64 -0
- package/src/server/bun/router/router.ts +51 -0
- package/src/server/bun/router/routes.ts +7 -0
- package/src/server/bun/websocket/Channel.ts +157 -0
- package/src/server/bun/websocket/Client.ts +129 -0
- package/src/server/bun/websocket/Message.ts +106 -0
- package/src/server/bun/websocket/Websocket.ts +221 -0
- package/src/server/bun/websocket/index.ts +14 -0
- package/src/server/bun/websocket/websocket.enums.ts +22 -0
- package/src/server/bun/websocket/websocket.guards.ts +6 -0
- package/src/server/bun/websocket/websocket.types.ts +186 -0
- package/src/server/controller.ts +121 -0
- package/src/server/index.ts +7 -0
- package/src/server/service.ts +36 -0
- package/src/singleton.ts +28 -0
- package/src/throwable.ts +87 -0
- package/src/types.ts +10 -0
- package/src/utils/Console.ts +85 -0
- package/src/utils/Guards.ts +61 -0
- package/src/utils/Lib.ts +506 -0
- package/src/utils/index.ts +9 -0
@@ -0,0 +1,186 @@
|
|
1
|
+
import { ServerWebSocket, WebSocketHandler } from "bun";
|
2
|
+
import Channel from "./Channel";
|
3
|
+
import Websocket from "./Websocket";
|
4
|
+
|
5
|
+
export type BunWebsocketMessage = string | Buffer<ArrayBufferLike>;
|
6
|
+
|
7
|
+
export type WebsocketChannel<T extends I_WebsocketChannel = Channel> = Map<string, T>;
|
8
|
+
export type WebsocketClients = Map<string, I_WebsocketClient>;
|
9
|
+
export type WebsocketMessageOptions = {
|
10
|
+
/**
|
11
|
+
* Additional data to include in the message content
|
12
|
+
* If an object is provided, it will be merged with the content
|
13
|
+
* If a primitive value is provided, it will be added as content.data
|
14
|
+
*/
|
15
|
+
data?: any;
|
16
|
+
|
17
|
+
/**
|
18
|
+
* Client information to include in the message
|
19
|
+
* Will be added as content.client
|
20
|
+
*/
|
21
|
+
client?: Partial<WebsocketEntityData> & {
|
22
|
+
[key: string]: any;
|
23
|
+
};
|
24
|
+
|
25
|
+
/**
|
26
|
+
* Channel metadata to include in the message
|
27
|
+
* If true, all metadata will be included
|
28
|
+
* If an array of strings, only the specified keys will be included
|
29
|
+
*/
|
30
|
+
includeMetadata?: boolean | string[];
|
31
|
+
|
32
|
+
/**
|
33
|
+
* Client IDs to exclude from receiving the broadcast
|
34
|
+
* Useful for sending messages to all clients except the sender
|
35
|
+
*/
|
36
|
+
excludeClients?: string[];
|
37
|
+
|
38
|
+
/**
|
39
|
+
* Channel to include in the message
|
40
|
+
* Defaults to the channel of the message
|
41
|
+
*/
|
42
|
+
channel?: string;
|
43
|
+
|
44
|
+
/**
|
45
|
+
* Whether to include timestamp in the message
|
46
|
+
* Defaults to true
|
47
|
+
*/
|
48
|
+
includeTimestamp?: boolean;
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Custom fields to add to the root of the message
|
52
|
+
* These will be merged with the message object
|
53
|
+
*/
|
54
|
+
customFields?: Record<string, any>;
|
55
|
+
|
56
|
+
/**
|
57
|
+
* Transform function to modify the final message before sending
|
58
|
+
* This is applied after all other processing
|
59
|
+
*/
|
60
|
+
transform?: (message: any) => any;
|
61
|
+
|
62
|
+
/**
|
63
|
+
* Priority of the message (higher numbers = higher priority)
|
64
|
+
* Can be used by clients to determine processing order
|
65
|
+
*/
|
66
|
+
priority?: number;
|
67
|
+
|
68
|
+
/**
|
69
|
+
* Message expiration time in milliseconds since epoch
|
70
|
+
* Can be used by clients to ignore outdated messages
|
71
|
+
*/
|
72
|
+
expiresAt?: number;
|
73
|
+
|
74
|
+
/**
|
75
|
+
* Metadata to include in the message
|
76
|
+
* If an array of strings, only the specified keys will be included
|
77
|
+
*/
|
78
|
+
metadata?: boolean | string[] | Record<string, string>;
|
79
|
+
};
|
80
|
+
|
81
|
+
export type WebsocketMessage<T extends Record<string, any> = Record<string, any>> = {
|
82
|
+
/**
|
83
|
+
* Message type identifier used for client-side routing
|
84
|
+
*/
|
85
|
+
type: string;
|
86
|
+
/**
|
87
|
+
* Message content - can be any data structure
|
88
|
+
* If a string is provided, it will be wrapped in {message: string}
|
89
|
+
*/
|
90
|
+
content: T;
|
91
|
+
/**
|
92
|
+
* Channel ID
|
93
|
+
*/
|
94
|
+
channel?: string;
|
95
|
+
/**
|
96
|
+
* Timestamp of the message
|
97
|
+
*/
|
98
|
+
timestamp?: string;
|
99
|
+
/**
|
100
|
+
* Any additional custom fields
|
101
|
+
*/
|
102
|
+
[key: string]: any;
|
103
|
+
};
|
104
|
+
|
105
|
+
export type WebsocketStructuredMessage<T extends Record<string, any> = Record<string, any>> = WebsocketMessage<T> & WebsocketMessageOptions;
|
106
|
+
|
107
|
+
export type WebsocketEntityId = string;
|
108
|
+
export type WebsocketEntityName = string;
|
109
|
+
export type WebsocketEntityData = { id: WebsocketEntityId; name: WebsocketEntityName };
|
110
|
+
|
111
|
+
export interface I_WebsocketEntity extends WebsocketEntityData {
|
112
|
+
ws: ServerWebSocket<WebsocketEntityData>;
|
113
|
+
}
|
114
|
+
|
115
|
+
export interface I_WebsocketClient extends I_WebsocketEntity {
|
116
|
+
channels: WebsocketChannel<I_WebsocketChannel>;
|
117
|
+
send(message: WebsocketStructuredMessage): any;
|
118
|
+
subscribe(channel: string): any;
|
119
|
+
joinChannel(channel: I_WebsocketChannel, send?: boolean): void;
|
120
|
+
leaveChannel(channel: I_WebsocketChannel, send?: boolean): void;
|
121
|
+
joinChannels(channels: I_WebsocketChannel[], send?: boolean): void;
|
122
|
+
leaveChannels(channels?: I_WebsocketChannel[], send?: boolean): void;
|
123
|
+
unsubscribe(channel: string): any;
|
124
|
+
whoami(): WebsocketEntityData;
|
125
|
+
}
|
126
|
+
|
127
|
+
export interface I_WebsocketChannelEntity<T extends Websocket = Websocket> extends WebsocketEntityData {
|
128
|
+
ws: T;
|
129
|
+
}
|
130
|
+
|
131
|
+
// New types for the broadcast method
|
132
|
+
export type BroadcastOptions = WebsocketMessageOptions & {
|
133
|
+
debug?: boolean;
|
134
|
+
};
|
135
|
+
export interface I_WebsocketChannel<T extends Websocket = Websocket> extends I_WebsocketChannelEntity<T> {
|
136
|
+
limit: number;
|
137
|
+
members: Map<string, I_WebsocketClient>;
|
138
|
+
metadata: Record<string, string>;
|
139
|
+
createdAt: Date;
|
140
|
+
broadcast(message: WebsocketStructuredMessage, options?: BroadcastOptions): void;
|
141
|
+
hasMember(client: I_WebsocketEntity | string): boolean;
|
142
|
+
addMember(entity: I_WebsocketClient): I_WebsocketClient | false;
|
143
|
+
removeMember(entity: I_WebsocketEntity): I_WebsocketClient | false;
|
144
|
+
getMember(client: I_WebsocketEntity | string): I_WebsocketClient | undefined;
|
145
|
+
getMembers(clients?: string[] | I_WebsocketEntity[]): I_WebsocketClient[];
|
146
|
+
getMetadata(): Record<string, string>;
|
147
|
+
getCreatedAt(): Date;
|
148
|
+
getId(): string;
|
149
|
+
getSize(): number;
|
150
|
+
getLimit(): number;
|
151
|
+
getName(): string;
|
152
|
+
canAddMember(): boolean;
|
153
|
+
}
|
154
|
+
|
155
|
+
/**
|
156
|
+
* Interface for implementing custom WebSocket behavior.
|
157
|
+
*
|
158
|
+
* @interface I_WebsocketInterface
|
159
|
+
*
|
160
|
+
* @property {Function} setup - Initializes the WebSocket handler with channels and clients
|
161
|
+
*
|
162
|
+
* The interface supports three optional handler methods:
|
163
|
+
*
|
164
|
+
* - `message`: Custom message handler that replaces the default handler
|
165
|
+
* - `open`: Connection handler that runs after the default open handler
|
166
|
+
* - `close`: Disconnection handler that runs before the default close handler
|
167
|
+
*/
|
168
|
+
export type WebsocketInterfaceHandlers = Partial<WebSocketHandler<WebsocketEntityData>>;
|
169
|
+
|
170
|
+
/**
|
171
|
+
* Interface for implementing custom WebSocket behavior.
|
172
|
+
*
|
173
|
+
* @interface I_WebsocketInterface
|
174
|
+
*
|
175
|
+
* @property {Function} setup - Initializes the WebSocket handler with channels and clients
|
176
|
+
*
|
177
|
+
* The interface supports three optional handler methods:
|
178
|
+
*
|
179
|
+
* - `message`: Custom message handler that replaces the default handler
|
180
|
+
* - `open`: Connection handler that runs after the default open handler
|
181
|
+
* - `close`: Disconnection handler that runs before the default close handler
|
182
|
+
*/
|
183
|
+
export interface I_WebsocketInterface {
|
184
|
+
handlers: (channels: WebsocketChannel, clients: WebsocketClients) => WebsocketInterfaceHandlers;
|
185
|
+
}
|
186
|
+
|
@@ -0,0 +1,121 @@
|
|
1
|
+
import { ERROR_CODE } from "../errors";
|
2
|
+
import Initializable, { InitializableOptions } from "../initializable";
|
3
|
+
import { I_ApplicationResponse } from "../types";
|
4
|
+
import { Guards } from "../utils";
|
5
|
+
|
6
|
+
export type ControllerResponse<T = unknown> = Promise<I_ApplicationResponse<T> | PromiseLike<I_ApplicationResponse<T>>>;
|
7
|
+
export type ControllerAction<T> = (req: Request) => ControllerResponse<T>;
|
8
|
+
|
9
|
+
export type ControllerMap<T = unknown> = Map<string, ControllerAction<T>>;
|
10
|
+
|
11
|
+
export type ControllerOptions<T = unknown> = InitializableOptions & {
|
12
|
+
path: string | undefined;
|
13
|
+
post: Map<string, ControllerAction<T>>;
|
14
|
+
get: ControllerMap<T>;
|
15
|
+
controllerMap: Map<string, ControllerMap<T>>;
|
16
|
+
};
|
17
|
+
|
18
|
+
export default abstract class Controller extends Initializable {
|
19
|
+
path: string | undefined;
|
20
|
+
post: Map<string, ControllerAction<any>> = new Map();
|
21
|
+
get: ControllerMap = new Map();
|
22
|
+
controllerMap: Map<string, ControllerMap> = new Map();
|
23
|
+
|
24
|
+
constructor() {
|
25
|
+
super();
|
26
|
+
this.POST();
|
27
|
+
this.GET();
|
28
|
+
this.setControllerMap();
|
29
|
+
}
|
30
|
+
|
31
|
+
public static Create<T>(this: new () => T): T {
|
32
|
+
return new this();
|
33
|
+
}
|
34
|
+
|
35
|
+
public static async AsyncCreate<T>(this: new () => T): Promise<T> {
|
36
|
+
return new this();
|
37
|
+
}
|
38
|
+
|
39
|
+
public async call<T>(request: Request): Promise<T> {
|
40
|
+
await this.isInitialized();
|
41
|
+
const action = this.resolve(request);
|
42
|
+
return await action(request);
|
43
|
+
}
|
44
|
+
|
45
|
+
public success<T>(data: T): I_ApplicationResponse<T> {
|
46
|
+
return { status: true, data };
|
47
|
+
}
|
48
|
+
|
49
|
+
public failure<T>(data: T): I_ApplicationResponse<T> {
|
50
|
+
return { status: false, data };
|
51
|
+
}
|
52
|
+
|
53
|
+
private setControllerMap() {
|
54
|
+
this.controllerMap.set("GET", this.get);
|
55
|
+
this.controllerMap.set("POST", this.post);
|
56
|
+
}
|
57
|
+
|
58
|
+
private resolve(req: Request): Function {
|
59
|
+
const url = new URL(req.url);
|
60
|
+
const endpointArray = url.pathname.split("/");
|
61
|
+
const actionName = endpointArray.slice(2).join("/");
|
62
|
+
|
63
|
+
if (!Guards.IsString(actionName, true)) throw ERROR_CODE.INVALID_ACTION;
|
64
|
+
|
65
|
+
const methodMap = this.controllerMap.get(req.method);
|
66
|
+
|
67
|
+
if (Guards.IsNil(methodMap)) {
|
68
|
+
throw ERROR_CODE.INVALID_METHOD;
|
69
|
+
}
|
70
|
+
|
71
|
+
return this.resolveAction(methodMap, actionName);
|
72
|
+
}
|
73
|
+
|
74
|
+
private resolveAction(methodMap: ControllerMap, actionName: string) {
|
75
|
+
const action = methodMap.get(actionName) as Function;
|
76
|
+
|
77
|
+
if (!Guards.IsFunction(action, true)) {
|
78
|
+
throw ERROR_CODE.NO_ACTION_IN_MAP;
|
79
|
+
}
|
80
|
+
|
81
|
+
return action;
|
82
|
+
}
|
83
|
+
|
84
|
+
/**
|
85
|
+
* Abstract method that needs to be implemented in derived classes.
|
86
|
+
* This method is used to map post actions to their corresponding handler methods.
|
87
|
+
*
|
88
|
+
* Each action is a string that represents a specific operation, and the handler is a function that performs this operation.
|
89
|
+
* The handler is bound to the current context (`this`) to ensure it has access to the class's properties and methods.
|
90
|
+
*
|
91
|
+
* Example implementation:
|
92
|
+
*
|
93
|
+
* ```typescript
|
94
|
+
* setActionsMap() {
|
95
|
+
* this.post.set("action", this.controllerMethod.bind(this));
|
96
|
+
* }
|
97
|
+
* ```
|
98
|
+
*
|
99
|
+
* In this example, when "https://webserver_url:port/controller/action" is called by the router, the `controllerMethod` method will be called.
|
100
|
+
*/
|
101
|
+
abstract POST(): void;
|
102
|
+
|
103
|
+
/**
|
104
|
+
* Abstract method that needs to be implemented in derived classes.
|
105
|
+
* This method is used to map actions to their corresponding handler methods.
|
106
|
+
*
|
107
|
+
* Each action is a string that represents a specific operation, and the handler is a function that performs this operation.
|
108
|
+
* The handler is bound to the current context (`this`) to ensure it has access to the class's properties and methods.
|
109
|
+
*
|
110
|
+
* Example implementation:
|
111
|
+
*
|
112
|
+
* ```typescript
|
113
|
+
* setActionsMap() {
|
114
|
+
* this.get.set("action", this.controllerMethod.bind(this));
|
115
|
+
* }
|
116
|
+
* ```
|
117
|
+
*
|
118
|
+
* In this example, when "https://webserver_url:port/controller/action" is called by the router, the `controllerMethod` method will be called.
|
119
|
+
*/
|
120
|
+
abstract GET(): void;
|
121
|
+
}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import { ERROR_CODE } from "../errors";
|
2
|
+
import Initializable from "../initializable";
|
3
|
+
import { Guards } from "../utils";
|
4
|
+
|
5
|
+
export default abstract class Service extends Initializable {
|
6
|
+
async validateRequestBody<T>(request: Request, keys: (keyof T)[]): Promise<T> {
|
7
|
+
return await Service.validateRequestBody(request, keys);
|
8
|
+
}
|
9
|
+
|
10
|
+
static async validateRequestBody<T>(request: Request, keys: (keyof T)[]): Promise<T> {
|
11
|
+
try {
|
12
|
+
let body;
|
13
|
+
try {
|
14
|
+
body = await request.json();
|
15
|
+
} catch (e) {
|
16
|
+
body = await request.text();
|
17
|
+
const cleanedBody = body
|
18
|
+
.replace(/\/\/.*$/gm, "") // Remove comments
|
19
|
+
.replace(/\n/g, "") // Remove newlines
|
20
|
+
.replace(/\s+/g, " ") // Replace multiple spaces with single space
|
21
|
+
.trim(); // Remove leading/trailing whitespace
|
22
|
+
|
23
|
+
body = JSON.parse(cleanedBody);
|
24
|
+
}
|
25
|
+
if (Guards.IsNil(body)) throw ERROR_CODE.REQ_BODY_EMPTY;
|
26
|
+
if (!Guards.IsType<T>(body, keys)) {
|
27
|
+
console.error(`Expected keys: ${keys.join(", ")}`);
|
28
|
+
throw ERROR_CODE.INVALID_METHOD_INPUT;
|
29
|
+
}
|
30
|
+
return body;
|
31
|
+
} catch (e) {
|
32
|
+
if (e instanceof Error || e instanceof SyntaxError) throw "Could not parse request body: " + e;
|
33
|
+
throw e;
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
package/src/singleton.ts
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
export default abstract class Singleton {
|
2
|
+
private static readonly _instances = new Map<string, Singleton>();
|
3
|
+
|
4
|
+
protected timestamp: Date;
|
5
|
+
|
6
|
+
protected constructor() {
|
7
|
+
this.timestamp = new Date();
|
8
|
+
}
|
9
|
+
|
10
|
+
public static GetInstance<T extends Singleton>(...args: any[]): T {
|
11
|
+
const className = this.name;
|
12
|
+
|
13
|
+
if (!Singleton._instances.has(className)) {
|
14
|
+
const instance = Reflect.construct(this, args) as T;
|
15
|
+
Singleton._instances.set(className, instance);
|
16
|
+
}
|
17
|
+
|
18
|
+
return Singleton._instances.get(className) as T;
|
19
|
+
}
|
20
|
+
|
21
|
+
public static ResetInstances(): void {
|
22
|
+
Singleton._instances.clear();
|
23
|
+
}
|
24
|
+
|
25
|
+
public static ResetInstance(className: string): void {
|
26
|
+
Singleton._instances.delete(className);
|
27
|
+
}
|
28
|
+
}
|
package/src/throwable.ts
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
import { Guards, Lib } from "./utils";
|
2
|
+
|
3
|
+
|
4
|
+
/**
|
5
|
+
* @description Custom error class for errors that are expected to be thrown
|
6
|
+
* and should not trigger retry mechanisms or be reported to error monitoring services.
|
7
|
+
*
|
8
|
+
* Use this class when you want to throw an error that:
|
9
|
+
* 1. Is an expected part of the application flow
|
10
|
+
* 2. Should not be retried by retry handlers
|
11
|
+
* 3. Should not be reported to error monitoring services like Sentry
|
12
|
+
*/
|
13
|
+
class Throwable extends Error {
|
14
|
+
/** Flag indicating this is a deliberate error that shouldn't be retried */
|
15
|
+
public readonly isDeliberate: boolean = true;
|
16
|
+
|
17
|
+
/** Original error if this Throwable wraps another error */
|
18
|
+
public readonly originalError?: Error;
|
19
|
+
|
20
|
+
/** Additional context that might be helpful for debugging */
|
21
|
+
public readonly context?: Record<string, unknown>;
|
22
|
+
|
23
|
+
/**
|
24
|
+
* Create a new Throwable error
|
25
|
+
* @param message Error message or existing Error object to wrap
|
26
|
+
* @param options Configuration options
|
27
|
+
*/
|
28
|
+
constructor(
|
29
|
+
message: unknown,
|
30
|
+
options: {
|
31
|
+
/** Whether to log this error to console (default: true) */
|
32
|
+
logError?: boolean;
|
33
|
+
/** Additional context data to attach to the error */
|
34
|
+
context?: Record<string, unknown>;
|
35
|
+
} = {},
|
36
|
+
) {
|
37
|
+
const { logError = true, context } = options;
|
38
|
+
|
39
|
+
// Format the message appropriately based on type
|
40
|
+
const _message = Guards.IsString(message) ? message : message instanceof Error ? message.message : JSON.stringify(message);
|
41
|
+
|
42
|
+
super(_message);
|
43
|
+
this.name = "Throwable";
|
44
|
+
this.context = context;
|
45
|
+
|
46
|
+
// Log the error if requested
|
47
|
+
if (logError) {
|
48
|
+
if (context) {
|
49
|
+
Lib.$Log("Throwable: ", _message, "Context:", context);
|
50
|
+
} else {
|
51
|
+
Lib.$Log("Throwable: ", message);
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
// Capture stack trace and cause if wrapping an existing error
|
56
|
+
if (message instanceof Error) {
|
57
|
+
this.stack = message.stack;
|
58
|
+
this.cause = message.cause;
|
59
|
+
this.originalError = message;
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
/**
|
64
|
+
* Check if an error is a Throwable
|
65
|
+
* @param e Any error to check
|
66
|
+
* @returns True if the error is a Throwable
|
67
|
+
*/
|
68
|
+
static IsThrowable(e: any): e is Throwable {
|
69
|
+
return e instanceof Throwable;
|
70
|
+
}
|
71
|
+
|
72
|
+
/**
|
73
|
+
* Create a Throwable from any error or message
|
74
|
+
* @param error Error or message to convert
|
75
|
+
* @param context Additional context to attach
|
76
|
+
* @returns A new Throwable instance
|
77
|
+
*/
|
78
|
+
static from(error: unknown, context?: Record<string, unknown>): Throwable {
|
79
|
+
if (error instanceof Throwable) {
|
80
|
+
return error;
|
81
|
+
}
|
82
|
+
|
83
|
+
return new Throwable(error, { context });
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
export default Throwable;
|
package/src/types.ts
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
export type ClassConstructor<T> = new (...args: any[]) => T;
|
2
|
+
export type NonNullableType<T> = Exclude<T, null | undefined>;
|
3
|
+
type ObjectKeysValues = string | number | string[] | number[] | null;
|
4
|
+
export type ObjectKeys<T, U = ObjectKeysValues> = Partial<Record<keyof T, U>>;
|
5
|
+
export type KVObj<T> = { key: string; value: T };
|
6
|
+
export interface I_ApplicationResponse<T = unknown> {
|
7
|
+
status: boolean | number;
|
8
|
+
data: T;
|
9
|
+
error?: T;
|
10
|
+
}
|
@@ -0,0 +1,85 @@
|
|
1
|
+
import { LOG_COLORS, LOG_ICONS } from "../consts";
|
2
|
+
|
3
|
+
export class Console {
|
4
|
+
/**
|
5
|
+
* Print a blank line
|
6
|
+
*/
|
7
|
+
static blank(): void {
|
8
|
+
console.log("");
|
9
|
+
}
|
10
|
+
|
11
|
+
/**
|
12
|
+
* Print a success message
|
13
|
+
*/
|
14
|
+
static success(message: string): void {
|
15
|
+
console.log(` ${LOG_COLORS.text.green}${LOG_ICONS.success}${LOG_COLORS.reset} ${message}`);
|
16
|
+
}
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Print an info message
|
20
|
+
*/
|
21
|
+
static info(message: string): void {
|
22
|
+
console.log(` ${LOG_COLORS.text.blue}${LOG_ICONS.info}${LOG_COLORS.reset} ${message}`);
|
23
|
+
}
|
24
|
+
|
25
|
+
/**
|
26
|
+
* Print a warning message
|
27
|
+
*/
|
28
|
+
static warning(message: string): void {
|
29
|
+
console.log(` ${LOG_COLORS.text.yellow}${LOG_ICONS.warning}${LOG_COLORS.reset} ${message}`);
|
30
|
+
}
|
31
|
+
|
32
|
+
/**
|
33
|
+
* Print an error message
|
34
|
+
*/
|
35
|
+
static error(message: string): void {
|
36
|
+
console.log(` ${LOG_COLORS.text.red}${LOG_ICONS.error}${LOG_COLORS.reset} ${message}`);
|
37
|
+
}
|
38
|
+
|
39
|
+
/**
|
40
|
+
* Print a debug message
|
41
|
+
*/
|
42
|
+
static debug(message: string): void {
|
43
|
+
console.log(` ${LOG_COLORS.text.magenta}${LOG_ICONS.debug}${LOG_COLORS.reset} ${message}`);
|
44
|
+
}
|
45
|
+
|
46
|
+
/**
|
47
|
+
* Print a list item with an arrow
|
48
|
+
*/
|
49
|
+
static item(label: string, value: string): void {
|
50
|
+
console.log(` ${LOG_COLORS.text.cyan}${LOG_ICONS.arrow}${LOG_COLORS.reset} ${LOG_COLORS.bright}${label}:${LOG_COLORS.reset} ${value}`);
|
51
|
+
}
|
52
|
+
|
53
|
+
/**
|
54
|
+
* Print a header with optional emoji
|
55
|
+
*/
|
56
|
+
static header(text: string, emoji?: string): void {
|
57
|
+
const icon = emoji ? `${emoji} ` : "";
|
58
|
+
console.log(` ${icon}${LOG_COLORS.bright}${text}${LOG_COLORS.reset}`);
|
59
|
+
}
|
60
|
+
|
61
|
+
/**
|
62
|
+
* Print a subheader with dimmed text
|
63
|
+
*/
|
64
|
+
static subheader(text: string): void {
|
65
|
+
console.log(` ${LOG_COLORS.dim}${text}${LOG_COLORS.reset}`);
|
66
|
+
}
|
67
|
+
|
68
|
+
/**
|
69
|
+
* Print a ready message with timing
|
70
|
+
*/
|
71
|
+
static ready(timeMs: number): void {
|
72
|
+
console.log(
|
73
|
+
` ${LOG_COLORS.text.green}✓${LOG_COLORS.reset} ${LOG_COLORS.dim}Ready in${LOG_COLORS.reset} ${LOG_COLORS.text.cyan}${timeMs}ms${LOG_COLORS.reset}`,
|
74
|
+
);
|
75
|
+
}
|
76
|
+
|
77
|
+
/**
|
78
|
+
* Get a colored string
|
79
|
+
*/
|
80
|
+
static colorize(text: string, color: keyof typeof LOG_COLORS.text): string {
|
81
|
+
return `${LOG_COLORS.text[color]}${text}${LOG_COLORS.reset}`;
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
export default Console;
|
@@ -0,0 +1,61 @@
|
|
1
|
+
import type { NonNullableType } from "../types";
|
2
|
+
import Lib from "./Lib";
|
3
|
+
|
4
|
+
class Guards {
|
5
|
+
public static IsString(value: any): value is string;
|
6
|
+
public static IsString(value: any, as_typeof: boolean): value is NonNullableType<string>;
|
7
|
+
public static IsString(value: any, as_typeof: boolean, excludeNull: true): value is NonNullableType<string>;
|
8
|
+
public static IsString(value: any, as_typeof: boolean = false, excludeNull = false): value is string | NonNullableType<string> {
|
9
|
+
const output = Lib.IsString(value, as_typeof);
|
10
|
+
return excludeNull ? !Guards.IsNil(value) && output : output;
|
11
|
+
}
|
12
|
+
|
13
|
+
public static IsNumber(value: any): value is number;
|
14
|
+
public static IsNumber(value: any, as_typeof: boolean): value is NonNullableType<number>;
|
15
|
+
public static IsNumber(value: any, as_typeof: boolean, excludeNull: true): value is NonNullableType<number>;
|
16
|
+
public static IsNumber(value: any, as_typeof: boolean = false, excludeNull = false): value is number | NonNullableType<number> {
|
17
|
+
const output = Lib.IsNumber(value, as_typeof);
|
18
|
+
return excludeNull ? !Guards.IsNil(value) && output : output;
|
19
|
+
}
|
20
|
+
|
21
|
+
public static IsBoolean(value: any): value is boolean;
|
22
|
+
public static IsBoolean(value: any, excludeNull: true): value is NonNullableType<boolean>;
|
23
|
+
public static IsBoolean(value: any, excludeNull = false): value is boolean | NonNullableType<boolean> {
|
24
|
+
const output = Lib.GetType(value, true) === "boolean";
|
25
|
+
return excludeNull ? !Guards.IsNil(value) && output : output;
|
26
|
+
}
|
27
|
+
|
28
|
+
public static IsArray<T>(value: any): value is T[];
|
29
|
+
public static IsArray<T>(value: any, excludeNull: true): value is NonNullableType<T[]>;
|
30
|
+
public static IsArray<T>(value: any, excludeNull = false): value is T[] | NonNullableType<T[]> {
|
31
|
+
const output = Lib.IsArray(value);
|
32
|
+
return excludeNull ? !Guards.IsNil(value) && output : output;
|
33
|
+
}
|
34
|
+
|
35
|
+
public static IsObject(value: any): value is object;
|
36
|
+
public static IsObject(value: any, excludeNull: true): value is NonNullableType<object>;
|
37
|
+
public static IsObject(value: any, excludeNull = false): value is object | NonNullableType<object> {
|
38
|
+
const output = Lib.IsObject(value);
|
39
|
+
return excludeNull ? !Guards.IsNil(value) && output : output;
|
40
|
+
}
|
41
|
+
|
42
|
+
public static IsFunction(value: any): value is Function;
|
43
|
+
public static IsFunction(value: any, excludeNull: true): value is NonNullableType<Function>;
|
44
|
+
public static IsFunction(value: any, excludeNull = false): value is Function | NonNullableType<Function> {
|
45
|
+
const output = Lib.IsFunction(value);
|
46
|
+
return excludeNull ? !Guards.IsNil(value) && output : output;
|
47
|
+
}
|
48
|
+
|
49
|
+
public static IsNil(value: any): value is null | undefined {
|
50
|
+
return Lib.IsNil(value);
|
51
|
+
}
|
52
|
+
|
53
|
+
public static IsType<T>(obj: any): obj is T;
|
54
|
+
public static IsType<T>(obj: any, keys: (keyof T)[]): obj is T;
|
55
|
+
public static IsType<T>(obj: any, keys?: (keyof T)[]): obj is T {
|
56
|
+
if (!keys) return !this.IsNil(obj);
|
57
|
+
return keys.every((key) => key in obj);
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
export default Guards;
|