@luminix/support 0.0.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +3 -0
- package/.eslintrc.cjs +39 -0
- package/package.json +33 -0
- package/src/App/ServiceContainer.ts +32 -0
- package/src/App/index.ts +27 -0
- package/src/Arr.ts +33 -0
- package/src/Collection.ts +1398 -0
- package/src/Contracts/EventSource.ts +46 -0
- package/src/Exceptions/ReducerOverrideException.ts +8 -0
- package/src/Http/Client.ts +139 -0
- package/src/Http/Request.ts +45 -0
- package/src/Http/Response.ts +170 -0
- package/src/Http/index.ts +90 -0
- package/src/Js.ts +5 -0
- package/src/Mixins/Macroable.ts +81 -0
- package/src/Mixins/Reducible.ts +126 -0
- package/src/Obj.ts +70 -0
- package/src/Query.ts +29 -0
- package/src/Services/ApplicationService.ts +7 -0
- package/src/Str.ts +53 -0
- package/src/index.ts +26 -0
- package/tsconfig.json +25 -0
- package/vite.config.js +22 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createNanoEvents, Unsubscribe } from 'nanoevents';
|
|
2
|
+
|
|
3
|
+
export type Event<TData = any, TSource extends EventSource = EventSource> = TData & {
|
|
4
|
+
source: TSource;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type EventMap = {
|
|
8
|
+
[key: string]: (event: Event) => void
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export default class EventSource<TEvents extends EventMap = EventMap>
|
|
14
|
+
{
|
|
15
|
+
private emitter;
|
|
16
|
+
|
|
17
|
+
constructor()
|
|
18
|
+
{
|
|
19
|
+
this.emitter = createNanoEvents<TEvents>();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
on<E extends keyof TEvents>(event: E, callback: TEvents[E]): Unsubscribe
|
|
23
|
+
{
|
|
24
|
+
return this.emitter.on(event, callback);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
once<E extends keyof TEvents>(event: E, callback: TEvents[E]): void
|
|
28
|
+
{
|
|
29
|
+
const off = this.emitter.on(event, ((e: Parameters<TEvents[E]>[0]) => {
|
|
30
|
+
off();
|
|
31
|
+
callback(e);
|
|
32
|
+
}) as any);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
emit<E extends keyof TEvents>(event: E, ...data: Parameters<TEvents[E]>): void
|
|
36
|
+
{
|
|
37
|
+
this.emitter.emit(event, ...data);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static foo() {
|
|
41
|
+
|
|
42
|
+
return 'bar';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
|
|
2
|
+
export default class ReducerOverrideException extends Error {
|
|
3
|
+
[Symbol.toStringTag] = 'ReducerOverrideException';
|
|
4
|
+
|
|
5
|
+
constructor(name: string, target: unknown) {
|
|
6
|
+
super(`[Luminix] Cannot create reducer '${name}' on '${target}' as it is a reserved property`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
|
|
2
|
+
import { AxiosRequestConfig } from 'axios';
|
|
3
|
+
import * as Obj from '../Obj';
|
|
4
|
+
import Request from './Request';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export type RequestOptions = Omit<AxiosRequestConfig, 'url' | 'method'>;
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export default class Client {
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
protected options: AxiosRequestConfig = {}
|
|
14
|
+
) {}
|
|
15
|
+
|
|
16
|
+
private parseData(data?: object) {
|
|
17
|
+
if (Obj.get(this.options, 'headers[Content-Type]', 'application/json') === 'application/x-www-form-urlencoded' && data) {
|
|
18
|
+
return Obj.toFormData(data || {});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
baseUrl(baseUrl: string): this {
|
|
25
|
+
Obj.set(this.options, 'baseURL', baseUrl);
|
|
26
|
+
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
asForm(): this {
|
|
31
|
+
Obj.set(this.options, 'headers[Content-Type]', 'application/x-www-form-urlencoded');
|
|
32
|
+
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
accept(type: string): this {
|
|
37
|
+
Obj.set(this.options, 'headers[Accept]', type);
|
|
38
|
+
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
acceptJson(): this {
|
|
43
|
+
return this.accept('application/json');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
withHeaders(headers: Record<string, string>): this {
|
|
47
|
+
Obj.set(this.options, 'headers', Obj.merge(this.options.headers, headers));
|
|
48
|
+
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
replaceHeaders(headers: Record<string, string>): this {
|
|
53
|
+
Obj.set(this.options, 'headers', headers);
|
|
54
|
+
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
withOptions(options: RequestOptions): this {
|
|
59
|
+
this.options = Obj.merge(this.options, options);
|
|
60
|
+
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
replaceOptions(options: RequestOptions): this {
|
|
65
|
+
this.options = options;
|
|
66
|
+
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
withQueryParameters(params: string | object): this {
|
|
71
|
+
Obj.set(this.options, 'params', Obj.merge(this.options.params, params));
|
|
72
|
+
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
replaceQueryParameters(params: string | object): this {
|
|
77
|
+
Obj.set(this.options, 'params', params);
|
|
78
|
+
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
withBasicAuth(username: string, password: string): this {
|
|
83
|
+
Obj.set(this.options, 'headers.Authorization', `Basic ${btoa(`${username}:${password}`)}`);
|
|
84
|
+
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
withToken(token: string): this {
|
|
89
|
+
Obj.set(this.options, 'headers.Authorization', `Bearer ${token}`);
|
|
90
|
+
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
get<TResponse = any>(url: string, query?: string | object) {
|
|
95
|
+
return new Request<TResponse>({
|
|
96
|
+
...this.options,
|
|
97
|
+
params: query ?? this.options.params,
|
|
98
|
+
url,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
post<TResponse = any, TData = any>(url: string, data?: TData) {
|
|
103
|
+
return new Request<TResponse, TData>({
|
|
104
|
+
...this.options,
|
|
105
|
+
method: 'post',
|
|
106
|
+
url,
|
|
107
|
+
data: this.parseData(data ?? this.options.data),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
put<TResponse = any, TData = any>(url: string, data?: TData) {
|
|
112
|
+
return new Request<TResponse, TData>({
|
|
113
|
+
...this.options,
|
|
114
|
+
method: 'put',
|
|
115
|
+
url,
|
|
116
|
+
data: this.parseData(data ?? this.options.data),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
patch<TResponse = any, TData = any>(url: string, data?: TData) {
|
|
121
|
+
return new Request<TResponse, TData>({
|
|
122
|
+
...this.options,
|
|
123
|
+
method: 'patch',
|
|
124
|
+
url,
|
|
125
|
+
data: this.parseData(data ?? this.options.data),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
delete<TResponse = any>(url: string, query?: string | object) {
|
|
130
|
+
return new Request<TResponse>({
|
|
131
|
+
...this.options,
|
|
132
|
+
method: 'delete',
|
|
133
|
+
params: query ?? this.options.params,
|
|
134
|
+
url,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
};
|
|
139
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import axios, { AxiosRequestConfig, isAxiosError } from "axios";
|
|
2
|
+
import Response from "./Response";
|
|
3
|
+
|
|
4
|
+
export default class Request<TResponse = any, TData = any> implements Promise<Response<TResponse, TData>> {
|
|
5
|
+
|
|
6
|
+
private promise: Promise<Response<TResponse, TData>>;
|
|
7
|
+
private response?: Response<TResponse, TData>;
|
|
8
|
+
|
|
9
|
+
constructor(options: AxiosRequestConfig) {
|
|
10
|
+
this.promise = new Promise<Response<TResponse, TData>>((resolve, reject) => {
|
|
11
|
+
axios(options)
|
|
12
|
+
.then((response) => {
|
|
13
|
+
this.response = new Response(response);
|
|
14
|
+
resolve(this.response);
|
|
15
|
+
})
|
|
16
|
+
.catch((error) => {
|
|
17
|
+
if (isAxiosError(error) && error.response) {
|
|
18
|
+
this.response = new Response(error.response, error);
|
|
19
|
+
resolve(this.response);
|
|
20
|
+
} else {
|
|
21
|
+
reject(error);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
[Symbol.toStringTag]: string = 'Request';
|
|
28
|
+
|
|
29
|
+
then<TResult1 = Response<TResponse, TData>, TResult2 = never>(
|
|
30
|
+
onfulfilled?: ((value: Response<TResponse, TData>) => TResult1 | PromiseLike<TResult1>) | null | undefined,
|
|
31
|
+
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined
|
|
32
|
+
): Promise<TResult1 | TResult2> {
|
|
33
|
+
return this.promise.then(onfulfilled, onrejected);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
catch<TResult = never>(
|
|
37
|
+
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined
|
|
38
|
+
): Promise<Response<TResponse, TData> | TResult> {
|
|
39
|
+
return this.promise.catch(onrejected);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
finally(onfinally?: (() => void) | null | undefined): Promise<Response<TResponse, TData>> {
|
|
43
|
+
return this.promise.finally(onfinally);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
|
|
2
|
+
import { AxiosResponse } from 'axios';
|
|
3
|
+
import * as Obj from '../Obj';
|
|
4
|
+
|
|
5
|
+
export default class Response<TResponse = any, TData = any> {
|
|
6
|
+
|
|
7
|
+
constructor(
|
|
8
|
+
private response: AxiosResponse<TResponse, TData>,
|
|
9
|
+
private error?: Error
|
|
10
|
+
) {
|
|
11
|
+
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body(): string {
|
|
15
|
+
return typeof this.response.data === 'object'
|
|
16
|
+
? JSON.stringify(this.response.data)
|
|
17
|
+
: String(this.response.data);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
json(): TResponse;
|
|
21
|
+
json<K extends keyof TResponse>(key: K): TResponse[K];
|
|
22
|
+
json(key: string, defaultValue?: any): any;
|
|
23
|
+
json(key?: string, defaultValue?: any): any {
|
|
24
|
+
|
|
25
|
+
if (key) {
|
|
26
|
+
return Obj.get(this.response.data, key, defaultValue);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return this.response.data;
|
|
30
|
+
}
|
|
31
|
+
// collect(key?: string): Collection {}
|
|
32
|
+
status(): number {
|
|
33
|
+
return Number(this.response.status);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
successful(): boolean {
|
|
37
|
+
return this.status() >= 200 && this.status() < 300;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
redirect(): boolean {
|
|
41
|
+
return this.status() >= 300 && this.status() < 400;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
clientError(): boolean {
|
|
45
|
+
return this.status() >= 400 && this.status() < 500;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
serverError(): boolean {
|
|
49
|
+
return this.status() >= 500;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
failed(): boolean {
|
|
53
|
+
return this.clientError() || this.serverError();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
header(header: string): string {
|
|
57
|
+
return this.response.headers[header];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
headers(): Record<string, string> {
|
|
61
|
+
return this.response.headers as Record<string, string>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
ok(): boolean {
|
|
65
|
+
return this.status() === 200;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
created(): boolean {
|
|
69
|
+
return this.status() === 201;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
accepted(): boolean {
|
|
73
|
+
return this.status() === 202;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
noContent(): boolean {
|
|
77
|
+
return this.status() === 204;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
movedPermanently(): boolean {
|
|
81
|
+
return this.status() === 301;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
found(): boolean {
|
|
85
|
+
return this.status() === 302;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
badRequest(): boolean {
|
|
89
|
+
return this.status() === 400;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
unauthorized(): boolean {
|
|
93
|
+
return this.status() === 401;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
paymentRequired(): boolean {
|
|
97
|
+
return this.status() === 402;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
forbidden(): boolean {
|
|
101
|
+
return this.status() === 403;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
notFound(): boolean {
|
|
105
|
+
return this.status() === 404;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
requestTimeout(): boolean {
|
|
109
|
+
return this.status() === 408;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
conflict(): boolean {
|
|
113
|
+
return this.status() === 409;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
unprocessableEntity(): boolean {
|
|
117
|
+
return this.status() === 422;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
tooManyRequests(): boolean {
|
|
121
|
+
return this.status() === 429;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
throw(): this {
|
|
125
|
+
if (this.failed()) {
|
|
126
|
+
throw this.error || new Error(this.body());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
throwIf(condition: boolean | ((response: Response) => boolean)): this {
|
|
133
|
+
if (typeof condition === 'function') {
|
|
134
|
+
return condition(this) ? this.throw() : this;
|
|
135
|
+
}
|
|
136
|
+
return condition ? this.throw() : this;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
throwUnless(condition: boolean | ((response: Response) => boolean)): this {
|
|
140
|
+
if (typeof condition === 'function') {
|
|
141
|
+
return condition(this) ? this : this.throw();
|
|
142
|
+
}
|
|
143
|
+
return condition ? this : this.throw();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
throwIfStatus(statusCode: number | ((status: number, response: Response) => boolean)): this {
|
|
147
|
+
if (typeof statusCode === 'function' && statusCode(this.status(), this)) {
|
|
148
|
+
return this.throw();
|
|
149
|
+
}
|
|
150
|
+
return this.status() === statusCode ? this.throw() : this;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
throwUnlessStatus(statusCode: number | ((status: number, response: Response) => boolean)): this {
|
|
154
|
+
if (typeof statusCode === 'function' && !statusCode(this.status(), this)) {
|
|
155
|
+
return this.throw();
|
|
156
|
+
}
|
|
157
|
+
return this.status() === statusCode ? this : this.throw();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
throwIfClientError(): this {
|
|
161
|
+
return this.clientError() ? this.throw() : this;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
throwIfServerError(): this {
|
|
165
|
+
return this.serverError() ? this.throw() : this;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
};
|
|
170
|
+
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import Client, { RequestOptions } from "./Client";
|
|
2
|
+
import Response from "./Response";
|
|
3
|
+
import Request from "./Request";
|
|
4
|
+
import Macroable from "../Mixins/Macroable";
|
|
5
|
+
|
|
6
|
+
class HttpStatic {
|
|
7
|
+
|
|
8
|
+
get Client() {
|
|
9
|
+
return Client;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get Response() {
|
|
13
|
+
return Response;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get Request() {
|
|
17
|
+
return Request;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getClient(): Client {
|
|
21
|
+
return new Client();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
baseUrl(baseUrl: string): Client {
|
|
25
|
+
return this.getClient().baseUrl(baseUrl);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
asForm(): Client {
|
|
29
|
+
return this.getClient().asForm();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
accept(type: string): Client {
|
|
33
|
+
return this.getClient().accept(type);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
acceptJson(): Client {
|
|
37
|
+
return this.getClient().acceptJson();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
withHeaders(headers: Record<string, string>): Client {
|
|
41
|
+
return this.getClient().withHeaders(headers);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
withOptions(options: RequestOptions): Client {
|
|
45
|
+
return this.getClient().withOptions(options);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
withQueryParameters(params: string | object): Client {
|
|
50
|
+
return this.getClient().withQueryParameters(params);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
withBasicAuth(username: string, password: string): Client {
|
|
54
|
+
return this.getClient().withBasicAuth(username, password);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
withToken(token: string): Client {
|
|
58
|
+
return this.getClient().withToken(token);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get<TResponse = any>(url: string, query?: string | object) {
|
|
62
|
+
return this.getClient().get<TResponse>(url, query);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
post<TResponse = any, TData = any>(url: string, data?: TData) {
|
|
67
|
+
return this.getClient().post<TResponse, TData>(url, data);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
put<TResponse = any, TData = any>(url: string, data?: TData) {
|
|
72
|
+
return this.getClient().put<TResponse, TData>(url, data);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
patch<TResponse = any, TData = any>(url: string, data?: TData) {
|
|
77
|
+
return this.getClient().patch<TResponse, TData>(url, data);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
delete<TResponse = any>(url: string, query?: string | object) {
|
|
82
|
+
return this.getClient().delete<TResponse>(url, query);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const Http = new (Macroable(HttpStatic))();
|
|
88
|
+
|
|
89
|
+
export default Http;
|
|
90
|
+
|
package/src/Js.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
|
|
2
|
+
import { Constructor } from "../Js";
|
|
3
|
+
|
|
4
|
+
export type MacroMethodMap = Record<string, (...args: any[]) => any>;
|
|
5
|
+
|
|
6
|
+
export type MacroableInterface<TMacros extends MacroMethodMap> = {
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* Register a custom macro
|
|
10
|
+
*
|
|
11
|
+
* @param name
|
|
12
|
+
* @param macro
|
|
13
|
+
*/
|
|
14
|
+
macro<K extends keyof TMacros>(name: K, macro: TMacros[K]): void;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
*
|
|
18
|
+
* Checks if a macro is registered
|
|
19
|
+
*
|
|
20
|
+
* @param name
|
|
21
|
+
*/
|
|
22
|
+
hasMacro(name: string): boolean;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
* Flushes the existing macros.
|
|
27
|
+
*
|
|
28
|
+
*/
|
|
29
|
+
flushMacros(): void;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type MacroableOf<TBase extends Constructor, TMacros extends MacroMethodMap> = Omit<TBase, 'new'> & {
|
|
33
|
+
new (...args: ConstructorParameters<TBase>): InstanceType<TBase> & TMacros & MacroableInterface<TMacros>; /* & {
|
|
34
|
+
[key: string]: (...args: any[]) => any;
|
|
35
|
+
};*/
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default function Macroable<TMacros extends MacroMethodMap, TBase extends Constructor>(
|
|
39
|
+
Base: TBase
|
|
40
|
+
): MacroableOf<TBase, TMacros>
|
|
41
|
+
{
|
|
42
|
+
return class MacroableBase extends Base {
|
|
43
|
+
|
|
44
|
+
_macros: TMacros = {} as TMacros;
|
|
45
|
+
|
|
46
|
+
constructor(...args: any[]) {
|
|
47
|
+
super(...args);
|
|
48
|
+
|
|
49
|
+
return new Proxy(this, {
|
|
50
|
+
get(target, prop, receiver) {
|
|
51
|
+
if (Reflect.has(target, prop)) {
|
|
52
|
+
return Reflect.get(target, prop, receiver);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (typeof prop === 'string' && target.hasMacro(prop)) {
|
|
56
|
+
return target._macros[prop].bind(target);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return Reflect.get(target, prop, receiver);
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
macro<K extends keyof TMacros>(name: K, macro: TMacros[K]): void {
|
|
65
|
+
if (typeof macro !== 'function') {
|
|
66
|
+
throw new TypeError('Macro must be a function');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this._macros[name] = macro;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
hasMacro(name: string): boolean {
|
|
73
|
+
return name in this._macros && typeof this._macros[name] === 'function';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
flushMacros(): void {
|
|
77
|
+
this._macros = {} as TMacros;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
} as any;
|
|
81
|
+
}
|