@elysiumjs/core 0.1.1 → 0.3.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/index.ts +5 -1
- package/macros.ts +16 -0
- package/package.json +3 -3
- package/src/app.ts +175 -32
- package/src/console.ts +1 -1
- package/src/env.ts +58 -0
- package/src/http.ts +44 -43
- package/src/middleware.ts +5 -5
- package/src/model.ts +27 -3
- package/src/repository.ts +11 -20
- package/src/utils.ts +52 -0
- package/src/wamp.ts +38 -34
- package/src/websocket.ts +11 -6
- package/src/worker.ts +16 -9
- package/types.d.ts +35 -0
- package/.prettierignore +0 -5
- package/.prettierrc +0 -25
- package/bun.lock +0 -262
package/src/repository.ts
CHANGED
|
@@ -24,7 +24,6 @@ import { pgTable } from 'drizzle-orm/pg-core';
|
|
|
24
24
|
|
|
25
25
|
import { Application } from './app';
|
|
26
26
|
import { Database } from './database';
|
|
27
|
-
import { Tenancy } from './model';
|
|
28
27
|
|
|
29
28
|
/**
|
|
30
29
|
* Database's primary column type.
|
|
@@ -178,24 +177,12 @@ export const Repository = <
|
|
|
178
177
|
return db ?? Database.getConnection(connection);
|
|
179
178
|
}
|
|
180
179
|
|
|
181
|
-
/**
|
|
182
|
-
* The drizzle's table schema wrapped by this repository.
|
|
183
|
-
*/
|
|
184
|
-
public static get table() {
|
|
185
|
-
if (model.supportTenancy) {
|
|
186
|
-
const tenant = Tenancy.getCurrentTenant() ?? 'public';
|
|
187
|
-
return Tenancy.withTenant(tenant, model);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return table;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
180
|
/**
|
|
194
181
|
* Retrieves all the records in the database.
|
|
195
182
|
* @returns All the records in the database.
|
|
196
183
|
*/
|
|
197
184
|
public async all(): Promise<TSelect[]> {
|
|
198
|
-
return await this.db.select().from(
|
|
185
|
+
return await this.db.select().from(model.table);
|
|
199
186
|
}
|
|
200
187
|
|
|
201
188
|
/**
|
|
@@ -204,7 +191,7 @@ export const Repository = <
|
|
|
204
191
|
* @returns The record with the given id.
|
|
205
192
|
*/
|
|
206
193
|
public async find(id: TId): Promise<TSelect | null> {
|
|
207
|
-
const [row] = await this.db.select().from(
|
|
194
|
+
const [row] = await this.db.select().from(model.table).where(eq(model.table.id, id));
|
|
208
195
|
return (row ?? null) as TSelect | null;
|
|
209
196
|
}
|
|
210
197
|
|
|
@@ -214,7 +201,7 @@ export const Repository = <
|
|
|
214
201
|
* @returns The inserted record.
|
|
215
202
|
*/
|
|
216
203
|
public async insert(data: TInsert): Promise<TSelect> {
|
|
217
|
-
const [row] = await this.db.insert(
|
|
204
|
+
const [row] = await this.db.insert(model.table).values(data).returning();
|
|
218
205
|
return row as TSelect;
|
|
219
206
|
}
|
|
220
207
|
|
|
@@ -225,7 +212,11 @@ export const Repository = <
|
|
|
225
212
|
* @returns The updated record.
|
|
226
213
|
*/
|
|
227
214
|
public async update(id: TId, data: TUpdate): Promise<TSelect> {
|
|
228
|
-
const [row] = await this.db
|
|
215
|
+
const [row] = await this.db
|
|
216
|
+
.update(model.table)
|
|
217
|
+
.set(data)
|
|
218
|
+
.where(eq(model.table.id, id))
|
|
219
|
+
.returning();
|
|
229
220
|
return row as TSelect;
|
|
230
221
|
}
|
|
231
222
|
|
|
@@ -235,7 +226,7 @@ export const Repository = <
|
|
|
235
226
|
* @returns The updated records.
|
|
236
227
|
*/
|
|
237
228
|
public async updateAll(data: TUpdate): Promise<TSelect[]> {
|
|
238
|
-
return await this.db.update(
|
|
229
|
+
return await this.db.update(model.table).set(data).returning();
|
|
239
230
|
}
|
|
240
231
|
|
|
241
232
|
/**
|
|
@@ -244,7 +235,7 @@ export const Repository = <
|
|
|
244
235
|
* @returns The deleted record.
|
|
245
236
|
*/
|
|
246
237
|
public async delete(id: TId): Promise<TSelect> {
|
|
247
|
-
const [row] = await this.db.delete(
|
|
238
|
+
const [row] = await this.db.delete(model.table).where(eq(model.table.id, id)).returning();
|
|
248
239
|
return row as TSelect;
|
|
249
240
|
}
|
|
250
241
|
|
|
@@ -253,7 +244,7 @@ export const Repository = <
|
|
|
253
244
|
* @returns All the records in the database.
|
|
254
245
|
*/
|
|
255
246
|
public async deleteAll(): Promise<TSelect[]> {
|
|
256
|
-
return await this.db.delete(
|
|
247
|
+
return await this.db.delete(model.table).returning();
|
|
257
248
|
}
|
|
258
249
|
}
|
|
259
250
|
|
package/src/utils.ts
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
import type { Elysia } from 'elysia';
|
|
16
16
|
|
|
17
|
+
import { isObject, isPrimitive } from 'radash';
|
|
18
|
+
|
|
17
19
|
export namespace Symbols {
|
|
18
20
|
export const app = Symbol('app');
|
|
19
21
|
|
|
@@ -39,3 +41,53 @@ export namespace Symbols {
|
|
|
39
41
|
export const nextTick = () => new Promise((resolve) => process.nextTick(resolve));
|
|
40
42
|
|
|
41
43
|
export type ElysiaPlugin = () => Promise<Elysia>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Deeply merges objects from right to left
|
|
47
|
+
* Arrays are also merged if they are found in child objects
|
|
48
|
+
*
|
|
49
|
+
* @param target The target object to merge into
|
|
50
|
+
* @param sources One or more source objects to merge from
|
|
51
|
+
* @returns The merged object (same reference as target)
|
|
52
|
+
*/
|
|
53
|
+
export function deepMerge<T extends Record<string, any | any[]>>(target: T, ...sources: T[]): T {
|
|
54
|
+
if (!sources.length) return target;
|
|
55
|
+
|
|
56
|
+
const source = sources.shift();
|
|
57
|
+
if (source === undefined) return target;
|
|
58
|
+
|
|
59
|
+
if (isObject(target) && isObject(source)) {
|
|
60
|
+
for (const key in source) {
|
|
61
|
+
if (isObject(source[key])) {
|
|
62
|
+
if (!target[key]) {
|
|
63
|
+
Object.assign(target, { [key]: source[key] });
|
|
64
|
+
}
|
|
65
|
+
} else if (Array.isArray(source[key])) {
|
|
66
|
+
if (!Array.isArray(target[key])) {
|
|
67
|
+
target[key] = [] as any;
|
|
68
|
+
}
|
|
69
|
+
// Merge arrays by concatenating and removing duplicates for primitive values
|
|
70
|
+
if (source[key].length > 0 && !isObject(source[key][0])) {
|
|
71
|
+
target[key] = [...new Set([...target[key], ...source[key]])] as any;
|
|
72
|
+
} else {
|
|
73
|
+
// For arrays of objects, merge each item
|
|
74
|
+
source[key].forEach((item: any, index: number) => {
|
|
75
|
+
if (isObject(item) && target[key][index]) {
|
|
76
|
+
target[key][index] = deepMerge(target[key][index], item);
|
|
77
|
+
} else {
|
|
78
|
+
if (!target[key].includes(item)) {
|
|
79
|
+
target[key].push(item);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
} else if (isPrimitive(source[key])) {
|
|
85
|
+
target[key] ??= source[key];
|
|
86
|
+
} else {
|
|
87
|
+
deepMerge(target[key], source[key]);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return deepMerge(target, ...sources);
|
|
93
|
+
}
|
package/src/wamp.ts
CHANGED
|
@@ -223,43 +223,47 @@ export namespace Wamp {
|
|
|
223
223
|
onReconnectSuccess: metadata.reconnectSuccess?.bind(controller)
|
|
224
224
|
});
|
|
225
225
|
|
|
226
|
-
const registrations: WampRegistration[] = metadata.registrations ?? [];
|
|
227
|
-
const subscriptions: WampSubscription[] = metadata.subscriptions ?? [];
|
|
228
|
-
|
|
229
|
-
for (const registration of registrations) {
|
|
230
|
-
w.register(
|
|
231
|
-
registration.topic,
|
|
232
|
-
registration.handler.bind(controller),
|
|
233
|
-
registration.options
|
|
234
|
-
).then((r: { topic: string; requestId: string; registrationId: string }) => {
|
|
235
|
-
// TODO: Use the logger service here
|
|
236
|
-
console.log(`Registered ${registration.topic} with id ${r.registrationId}`);
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
for (const subscription of subscriptions) {
|
|
241
|
-
w.subscribe(
|
|
242
|
-
subscription.topic,
|
|
243
|
-
subscription.handler.bind(controller),
|
|
244
|
-
subscription.options
|
|
245
|
-
).then(
|
|
246
|
-
(s: {
|
|
247
|
-
topic: string;
|
|
248
|
-
requestId: string;
|
|
249
|
-
subscriptionId: string;
|
|
250
|
-
subscriptionKey: string;
|
|
251
|
-
}) => {
|
|
252
|
-
// TODO: Use the logger service here
|
|
253
|
-
console.log(`Subscribed ${subscription.topic} with id ${s.subscriptionId}`);
|
|
254
|
-
}
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
226
|
const app = new Elysia();
|
|
259
227
|
|
|
260
228
|
app.onStart(async (_) => {
|
|
261
|
-
|
|
262
|
-
|
|
229
|
+
try {
|
|
230
|
+
await w.connect();
|
|
231
|
+
metadata.open?.call(controller);
|
|
232
|
+
|
|
233
|
+
const registrations: WampRegistration[] = metadata.registrations ?? [];
|
|
234
|
+
const subscriptions: WampSubscription[] = metadata.subscriptions ?? [];
|
|
235
|
+
|
|
236
|
+
for (const registration of registrations) {
|
|
237
|
+
w.register(
|
|
238
|
+
registration.topic,
|
|
239
|
+
registration.handler.bind(controller),
|
|
240
|
+
registration.options
|
|
241
|
+
).then((r: { topic: string; requestId: string; registrationId: string }) => {
|
|
242
|
+
// TODO: Use the logger service here
|
|
243
|
+
console.log(`Registered ${registration.topic} with id ${r.registrationId}`);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
for (const subscription of subscriptions) {
|
|
248
|
+
w.subscribe(
|
|
249
|
+
subscription.topic,
|
|
250
|
+
subscription.handler.bind(controller),
|
|
251
|
+
subscription.options
|
|
252
|
+
).then(
|
|
253
|
+
(s: {
|
|
254
|
+
topic: string;
|
|
255
|
+
requestId: string;
|
|
256
|
+
subscriptionId: string;
|
|
257
|
+
subscriptionKey: string;
|
|
258
|
+
}) => {
|
|
259
|
+
// TODO: Use the logger service here
|
|
260
|
+
console.log(`Subscribed ${subscription.topic} with id ${s.subscriptionId}`);
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
} catch (error) {
|
|
265
|
+
// noop
|
|
266
|
+
}
|
|
263
267
|
});
|
|
264
268
|
|
|
265
269
|
return app;
|
package/src/websocket.ts
CHANGED
|
@@ -108,10 +108,11 @@ export namespace Websocket {
|
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
110
|
* Marks a method as the websocket "open" event handler.
|
|
111
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
112
111
|
*
|
|
113
112
|
* This decorator should be used on a websocket controller method. Only one "open" event handler
|
|
114
113
|
* can be defined per websocket controller.
|
|
114
|
+
*
|
|
115
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
115
116
|
*/
|
|
116
117
|
export const onOpen = (): MethodDecorator => {
|
|
117
118
|
return function (target, propertyKey, descriptor) {
|
|
@@ -123,10 +124,11 @@ export namespace Websocket {
|
|
|
123
124
|
|
|
124
125
|
/**
|
|
125
126
|
* Marks a method as the websocket "close" event handler.
|
|
126
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
127
127
|
*
|
|
128
128
|
* This decorator should be used on a websocket controller method. Only one "close" event handler
|
|
129
129
|
* can be defined per websocket controller.
|
|
130
|
+
*
|
|
131
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
130
132
|
*/
|
|
131
133
|
export const onClose = (): MethodDecorator => {
|
|
132
134
|
return function (target, propertyKey, descriptor) {
|
|
@@ -138,14 +140,15 @@ export namespace Websocket {
|
|
|
138
140
|
|
|
139
141
|
/**
|
|
140
142
|
* Marks a method as the websocket "message" event handler.
|
|
141
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
142
143
|
*
|
|
143
144
|
* This decorator should be used on a websocket controller method. Only one "message" event handler
|
|
144
145
|
* can be defined per websocket controller.
|
|
145
146
|
*
|
|
147
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
148
|
+
*
|
|
146
149
|
* @param schema The schema of the message body.
|
|
147
150
|
*/
|
|
148
|
-
export const onMessage = (schema
|
|
151
|
+
export const onMessage = (schema?: TSchema): MethodDecorator => {
|
|
149
152
|
return function (target, propertyKey, descriptor) {
|
|
150
153
|
const metadata = Reflect.getMetadata(Symbols.websocket, target.constructor) ?? {};
|
|
151
154
|
metadata.message = descriptor.value;
|
|
@@ -156,10 +159,11 @@ export namespace Websocket {
|
|
|
156
159
|
|
|
157
160
|
/**
|
|
158
161
|
* Marks a method as the websocket "drain" event handler.
|
|
159
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
160
162
|
*
|
|
161
163
|
* This decorator should be used on a websocket controller method. Only one "drain" event handler
|
|
162
164
|
* can be defined per websocket controller.
|
|
165
|
+
*
|
|
166
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
163
167
|
*/
|
|
164
168
|
export const onDrain = (): MethodDecorator => {
|
|
165
169
|
return function (target, propertyKey, descriptor) {
|
|
@@ -171,10 +175,11 @@ export namespace Websocket {
|
|
|
171
175
|
|
|
172
176
|
/**
|
|
173
177
|
* Marks a method as the websocket "error" event handler.
|
|
174
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
175
178
|
*
|
|
176
179
|
* This decorator should be used on a websocket controller method. Only one "error" event handler
|
|
177
180
|
* can be defined per websocket controller.
|
|
181
|
+
*
|
|
182
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
178
183
|
*/
|
|
179
184
|
export const onError = (): MethodDecorator => {
|
|
180
185
|
return function (target, propertyKey, descriptor) {
|
package/src/worker.ts
CHANGED
|
@@ -665,8 +665,6 @@ export class Worker extends InteractsWithConsole {
|
|
|
665
665
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
666
666
|
*/
|
|
667
667
|
export class WorkerPool extends InteractsWithConsole {
|
|
668
|
-
#jobFiles: string[] = [];
|
|
669
|
-
|
|
670
668
|
private workers: BunWorker[] = [];
|
|
671
669
|
private currentWorkerIndex = 0;
|
|
672
670
|
private queueWorkerMap: Map<string, BunWorker[]> = new Map();
|
|
@@ -681,9 +679,7 @@ export class WorkerPool extends InteractsWithConsole {
|
|
|
681
679
|
* Initialize the worker pool.
|
|
682
680
|
*/
|
|
683
681
|
public async init() {
|
|
684
|
-
//
|
|
685
|
-
this.#jobFiles = await Array.fromAsync(new Bun.Glob('./src/**/*.job.ts').scan());
|
|
686
|
-
this.debug(`WorkerPool initialized: found ${this.#jobFiles.length} job files`);
|
|
682
|
+
// noop for now
|
|
687
683
|
}
|
|
688
684
|
|
|
689
685
|
/**
|
|
@@ -691,10 +687,21 @@ export class WorkerPool extends InteractsWithConsole {
|
|
|
691
687
|
* @param queues The queues to listen from the worker.
|
|
692
688
|
*/
|
|
693
689
|
public async addWorker(queues: string[] = []): Promise<void> {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
690
|
+
let worker: BunWorker;
|
|
691
|
+
|
|
692
|
+
if (ELYSIUM_BUILD) {
|
|
693
|
+
worker = new BunWorker('./index.js', {
|
|
694
|
+
argv: ['work', ...queues.map((queue) => `--queue=${queue}`)]
|
|
695
|
+
});
|
|
696
|
+
} else {
|
|
697
|
+
// Preload all job files to make them available in worker thread.
|
|
698
|
+
const jobFiles = await Array.fromAsync(new Bun.Glob('./src/**/*.job.ts').scan());
|
|
699
|
+
|
|
700
|
+
worker = new BunWorker('./index.ts', {
|
|
701
|
+
preload: jobFiles,
|
|
702
|
+
argv: ['work', ...queues.map((queue) => `--queue=${queue}`)]
|
|
703
|
+
});
|
|
704
|
+
}
|
|
698
705
|
|
|
699
706
|
worker.addEventListener('error', (e) => {
|
|
700
707
|
this.trace({
|
package/types.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Copyright (c) 2025-present Workbud Technologies Inc. All rights reserved.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
declare namespace globalThis {
|
|
16
|
+
/**
|
|
17
|
+
* A macro indicating if the code is running from a build.
|
|
18
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
19
|
+
*/
|
|
20
|
+
var ELYSIUM_BUILD: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare module '@elysiumjs/core' {
|
|
24
|
+
/**
|
|
25
|
+
* The type for all registered environment variables.
|
|
26
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
27
|
+
*/
|
|
28
|
+
type AppEnv = Record<string, unknown>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The type for the context of the application.
|
|
32
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
33
|
+
*/
|
|
34
|
+
type AppHttpContext = import('elysia').SingletonBase;
|
|
35
|
+
}
|
package/.prettierignore
DELETED
package/.prettierrc
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"useTabs": true,
|
|
3
|
-
"singleQuote": true,
|
|
4
|
-
"trailingComma": "none",
|
|
5
|
-
"printWidth": 100,
|
|
6
|
-
"plugins": [
|
|
7
|
-
"@ianvs/prettier-plugin-sort-imports"
|
|
8
|
-
],
|
|
9
|
-
"importOrder": [
|
|
10
|
-
"<TYPES>^(node:)",
|
|
11
|
-
"<TYPES>",
|
|
12
|
-
"<TYPES>^[.]",
|
|
13
|
-
"",
|
|
14
|
-
"<BUILTIN_MODULES>",
|
|
15
|
-
"",
|
|
16
|
-
"<THIRD_PARTY_MODULES>",
|
|
17
|
-
"",
|
|
18
|
-
"^\\$",
|
|
19
|
-
"",
|
|
20
|
-
"^[.]"
|
|
21
|
-
],
|
|
22
|
-
"importOrderParserPlugins": ["typescript", "decorators-legacy"],
|
|
23
|
-
"importOrderTypeScriptVersion": "5.8.0",
|
|
24
|
-
"overrides": []
|
|
25
|
-
}
|