@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/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(R.table);
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(R.table).where(eq(R.table.id, id));
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(R.table).values(data).returning();
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.update(R.table).set(data).where(eq(R.table.id, id)).returning();
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(R.table).set(data).returning();
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(R.table).where(eq(R.table.id, id)).returning();
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(R.table).returning();
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
- await w.connect();
262
- metadata.open?.call(controller);
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: TSchema): MethodDecorator => {
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
- // Preload all job files to make them available in worker thread.
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
- const worker = new BunWorker('./index.ts', {
695
- preload: this.#jobFiles,
696
- argv: ['work', ...queues.map((queue) => `--queue=${queue}`)]
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
@@ -1,5 +0,0 @@
1
- # Package Managers
2
- package-lock.json
3
- pnpm-lock.yaml
4
- yarn.lock
5
- bun.lock
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
- }