@elysiumjs/core 0.3.2 → 0.4.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/index.ts +1 -1
- package/package.json +6 -6
- package/src/app.ts +52 -51
- package/src/cache.ts +1 -1
- package/src/console.ts +17 -11
- package/src/database.ts +130 -3
- package/src/event.ts +2 -0
- package/src/http.ts +75 -9
- package/src/middleware.ts +25 -2
- package/src/model.ts +33 -7
- package/src/module.ts +10 -4
- package/src/redis.ts +4 -3
- package/src/repository.ts +52 -9
- package/src/service.ts +7 -5
- package/src/wamp.ts +307 -128
- package/tsconfig.json +47 -0
package/src/model.ts
CHANGED
|
@@ -12,7 +12,14 @@
|
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
14
|
|
|
15
|
-
import type {
|
|
15
|
+
import type { BuildExtraConfigColumns } from 'drizzle-orm';
|
|
16
|
+
import type {
|
|
17
|
+
PgColumn,
|
|
18
|
+
PgColumnBuilderBase,
|
|
19
|
+
PgSchema,
|
|
20
|
+
PgTable,
|
|
21
|
+
PgTableExtraConfigValue
|
|
22
|
+
} from 'drizzle-orm/pg-core';
|
|
16
23
|
import type { TSchema } from 'elysia';
|
|
17
24
|
|
|
18
25
|
import { getTableConfig, pgSchema, pgTable } from 'drizzle-orm/pg-core';
|
|
@@ -65,7 +72,9 @@ const parseTypes = (element: PgColumn) => {
|
|
|
65
72
|
let type = (() => {
|
|
66
73
|
switch (element.dataType) {
|
|
67
74
|
case 'string':
|
|
68
|
-
return t.String(
|
|
75
|
+
return t.String({
|
|
76
|
+
format: element.columnType === 'PgUUID' ? 'uuid' : undefined
|
|
77
|
+
});
|
|
69
78
|
case 'number':
|
|
70
79
|
return t.Number();
|
|
71
80
|
case 'boolean':
|
|
@@ -89,6 +98,8 @@ const parseTypes = (element: PgColumn) => {
|
|
|
89
98
|
|
|
90
99
|
if (!element.notNull) {
|
|
91
100
|
type = t.Optional(type);
|
|
101
|
+
} else {
|
|
102
|
+
type = t.Required(type);
|
|
92
103
|
}
|
|
93
104
|
|
|
94
105
|
return type;
|
|
@@ -103,7 +114,7 @@ const parseTypes = (element: PgColumn) => {
|
|
|
103
114
|
export type ModelClass<
|
|
104
115
|
TTableName extends string,
|
|
105
116
|
TColumnsMap extends Record<string, PgColumnBuilderBase>,
|
|
106
|
-
TTable extends PgTable = ReturnType<typeof pgTable<
|
|
117
|
+
TTable extends PgTable = ReturnType<typeof pgTable<TTableName, TColumnsMap>>
|
|
107
118
|
> = {
|
|
108
119
|
/**
|
|
109
120
|
* The data type returned by the select queries.
|
|
@@ -136,10 +147,17 @@ export type ModelClass<
|
|
|
136
147
|
*/
|
|
137
148
|
readonly columns: TColumnsMap;
|
|
138
149
|
|
|
150
|
+
/**
|
|
151
|
+
* The extra configuration for the table.
|
|
152
|
+
*/
|
|
153
|
+
readonly extraConfig?: (
|
|
154
|
+
self: BuildExtraConfigColumns<TTableName, TColumnsMap, 'pg'>
|
|
155
|
+
) => PgTableExtraConfigValue[];
|
|
156
|
+
|
|
139
157
|
/**
|
|
140
158
|
* The validation schema for creating records.
|
|
141
159
|
*/
|
|
142
|
-
readonly
|
|
160
|
+
readonly insertSchema: TSchema;
|
|
143
161
|
|
|
144
162
|
/**
|
|
145
163
|
* The validation schema for updating records.
|
|
@@ -179,9 +197,12 @@ export const Model = <
|
|
|
179
197
|
TColumnsMap extends Record<string, PgColumnBuilderBase>
|
|
180
198
|
>(
|
|
181
199
|
tableName: TTableName,
|
|
182
|
-
columns: TColumnsMap
|
|
200
|
+
columns: TColumnsMap,
|
|
201
|
+
extraConfig?: (
|
|
202
|
+
self: BuildExtraConfigColumns<TTableName, TColumnsMap, 'pg'>
|
|
203
|
+
) => PgTableExtraConfigValue[]
|
|
183
204
|
) => {
|
|
184
|
-
const table = pgTable(tableName, columns);
|
|
205
|
+
const table = pgTable(tableName, columns, extraConfig);
|
|
185
206
|
|
|
186
207
|
class M {
|
|
187
208
|
/**
|
|
@@ -227,10 +248,15 @@ export const Model = <
|
|
|
227
248
|
*/
|
|
228
249
|
public static readonly columns: TColumnsMap = columns;
|
|
229
250
|
|
|
251
|
+
/**
|
|
252
|
+
* The extra configuration for the table.
|
|
253
|
+
*/
|
|
254
|
+
public static readonly extraConfig = extraConfig;
|
|
255
|
+
|
|
230
256
|
/**
|
|
231
257
|
* The validation schema for creating records.
|
|
232
258
|
*/
|
|
233
|
-
public static readonly
|
|
259
|
+
public static readonly insertSchema = createSchemaFromDrizzle(table, { mode: 'create' });
|
|
234
260
|
|
|
235
261
|
/**
|
|
236
262
|
* The validation schema for updating records.
|
package/src/module.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// limitations under the License.
|
|
14
14
|
|
|
15
15
|
import type { Class } from 'type-fest';
|
|
16
|
-
import type { Route } from './http';
|
|
16
|
+
import type { ElysiaApp, Route } from './http';
|
|
17
17
|
|
|
18
18
|
import { Elysia } from 'elysia';
|
|
19
19
|
import { assign } from 'radash';
|
|
@@ -30,6 +30,12 @@ export type ModuleProps = {
|
|
|
30
30
|
* The list of controllers provided by the module.
|
|
31
31
|
*/
|
|
32
32
|
controllers?: Class<any>[];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The base path for all the controllers registered by this module.
|
|
36
|
+
* This value will be prefixed to each controllers path.
|
|
37
|
+
*/
|
|
38
|
+
prefix?: Route;
|
|
33
39
|
};
|
|
34
40
|
|
|
35
41
|
/**
|
|
@@ -52,14 +58,14 @@ export abstract class Module {
|
|
|
52
58
|
*/
|
|
53
59
|
public static register(options: ModuleProps = {}) {
|
|
54
60
|
return function (target: ModuleClass) {
|
|
55
|
-
async function handleModule(m: Module): Promise<
|
|
61
|
+
async function handleModule(m: Module): Promise<ElysiaApp> {
|
|
56
62
|
// TODO: Use the logger service here
|
|
57
63
|
console.log(`Registering module ${target.name}`);
|
|
58
64
|
await nextTick();
|
|
59
65
|
|
|
60
66
|
const props = assign({ controllers: [] }, options) as Required<ModuleProps>;
|
|
61
67
|
|
|
62
|
-
const plugin:
|
|
68
|
+
const plugin: ElysiaApp = new Elysia({ name: target.name, prefix: options.prefix });
|
|
63
69
|
plugin.decorate('module', m);
|
|
64
70
|
|
|
65
71
|
const middlewares = Reflect.getMetadata(Symbols.middlewares, target) ?? [];
|
|
@@ -70,7 +76,7 @@ export abstract class Module {
|
|
|
70
76
|
if (app === undefined) {
|
|
71
77
|
// TODO: Use the logger service here
|
|
72
78
|
console.error(
|
|
73
|
-
`Invalid controller class ${controller.name} registered in module ${target.name}. Ensure that you either used the @
|
|
79
|
+
`Invalid controller class ${controller.name} registered in module ${target.name}. Ensure that you either used the @Http.controller(), the @Websocket.controller(), or the @Wamp.controller() decorators on the class.`
|
|
74
80
|
);
|
|
75
81
|
process.exit(1);
|
|
76
82
|
} else {
|
package/src/redis.ts
CHANGED
|
@@ -342,7 +342,7 @@ export namespace Redis {
|
|
|
342
342
|
if (!Service.exists(getConnectionName(name))) {
|
|
343
343
|
// TODO: Use logger service here
|
|
344
344
|
console.error(
|
|
345
|
-
`No connection with name ${name} found. Please make sure to register the connection before using it.`
|
|
345
|
+
`No Redis connection with name ${name} found. Please make sure to register the connection before using it.`
|
|
346
346
|
);
|
|
347
347
|
process.exit(1);
|
|
348
348
|
}
|
|
@@ -352,11 +352,12 @@ export namespace Redis {
|
|
|
352
352
|
|
|
353
353
|
/**
|
|
354
354
|
* Creates and registers a new redis connection.
|
|
355
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
356
355
|
*
|
|
357
356
|
* This will make the registered connection available for dependency injection with
|
|
358
357
|
* the key `redis.connection.{name}`, where `{name}` is replaced with the given name.
|
|
359
358
|
*
|
|
359
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
360
|
+
*
|
|
360
361
|
* @param name The name of the connection.
|
|
361
362
|
* @param config The configuration for the redis connection.
|
|
362
363
|
* @returns The newly created and registered redis connection.
|
|
@@ -364,7 +365,7 @@ export namespace Redis {
|
|
|
364
365
|
export const registerConnection = (name: string, config: RedisConnectionProps) => {
|
|
365
366
|
if (Service.exists(getConnectionName(name))) {
|
|
366
367
|
// TODO: Use logger service here
|
|
367
|
-
console.error(`A connection with the name ${name} has already been registered.`);
|
|
368
|
+
console.error(`A Redis connection with the name ${name} has already been registered.`);
|
|
368
369
|
process.exit(1);
|
|
369
370
|
}
|
|
370
371
|
|
package/src/repository.ts
CHANGED
|
@@ -20,7 +20,6 @@ import type { DatabaseConnection } from './database';
|
|
|
20
20
|
import type { ModelClass } from './model';
|
|
21
21
|
|
|
22
22
|
import { eq } from 'drizzle-orm';
|
|
23
|
-
import { pgTable } from 'drizzle-orm/pg-core';
|
|
24
23
|
|
|
25
24
|
import { Application } from './app';
|
|
26
25
|
import { Database } from './database';
|
|
@@ -39,9 +38,13 @@ export type IdType = number | string;
|
|
|
39
38
|
* @template TColumnsMap The table columns config.
|
|
40
39
|
*/
|
|
41
40
|
export interface RepositoryInterface<
|
|
42
|
-
TModel extends ModelClass<TColumnsMap>,
|
|
41
|
+
TModel extends ModelClass<TTableName, TColumnsMap>,
|
|
43
42
|
TId extends IdType = string,
|
|
43
|
+
TTableName extends string = TModel extends ModelClass<infer TTableName, infer _>
|
|
44
|
+
? TTableName
|
|
45
|
+
: string,
|
|
44
46
|
TColumnsMap extends Record<string, PgColumnBuilderBase> = TModel extends ModelClass<
|
|
47
|
+
TTableName,
|
|
45
48
|
infer TColumnsMap
|
|
46
49
|
>
|
|
47
50
|
? TColumnsMap
|
|
@@ -104,14 +107,18 @@ export interface RepositoryInterface<
|
|
|
104
107
|
* @template TColumnsMap The table columns config.
|
|
105
108
|
*/
|
|
106
109
|
export type RepositoryClass<
|
|
107
|
-
TModel extends ModelClass<TColumnsMap>,
|
|
110
|
+
TModel extends ModelClass<TTableName, TColumnsMap>,
|
|
108
111
|
TId extends IdType = string,
|
|
112
|
+
TTableName extends string = TModel extends ModelClass<infer TTableName, infer _>
|
|
113
|
+
? TTableName
|
|
114
|
+
: string,
|
|
109
115
|
TColumnsMap extends Record<string, PgColumnBuilderBase> = TModel extends ModelClass<
|
|
116
|
+
TTableName,
|
|
110
117
|
infer TColumnsMap
|
|
111
118
|
>
|
|
112
119
|
? TColumnsMap
|
|
113
120
|
: Record<string, PgColumnBuilderBase>
|
|
114
|
-
> = Class<RepositoryInterface<TModel, TId, TColumnsMap>> & {
|
|
121
|
+
> = Class<RepositoryInterface<TModel, TId, TTableName, TColumnsMap>> & {
|
|
115
122
|
/**
|
|
116
123
|
* The drizzle's table schema wrapped by the repository.
|
|
117
124
|
*/
|
|
@@ -134,9 +141,13 @@ export type RepositoryClass<
|
|
|
134
141
|
* @template model The model class wrapped by the repository.
|
|
135
142
|
*/
|
|
136
143
|
export const Repository = <
|
|
137
|
-
TModel extends ModelClass<TColumnsMap>,
|
|
144
|
+
TModel extends ModelClass<TTableName, TColumnsMap>,
|
|
138
145
|
TId extends IdType = string,
|
|
146
|
+
TTableName extends string = TModel extends ModelClass<infer TTableName, infer _>
|
|
147
|
+
? TTableName
|
|
148
|
+
: string,
|
|
139
149
|
TColumnsMap extends Record<string, PgColumnBuilderBase> = TModel extends ModelClass<
|
|
150
|
+
TTableName,
|
|
140
151
|
infer TColumnsMap
|
|
141
152
|
>
|
|
142
153
|
? TColumnsMap
|
|
@@ -148,9 +159,7 @@ export const Repository = <
|
|
|
148
159
|
type TInsert = TModel['$inferInsert'];
|
|
149
160
|
type TUpdate = TModel['$inferUpdate'];
|
|
150
161
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
class R implements RepositoryInterface<TModel, TId, TColumnsMap> {
|
|
162
|
+
class R implements RepositoryInterface<TModel, TId, TTableName, TColumnsMap> {
|
|
154
163
|
/**
|
|
155
164
|
* The drizzle's table schema wrapped by this repository.
|
|
156
165
|
*/
|
|
@@ -167,7 +176,8 @@ export const Repository = <
|
|
|
167
176
|
* The database connection used by this repository.
|
|
168
177
|
*/
|
|
169
178
|
public get db(): DatabaseConnection {
|
|
170
|
-
const connection = (this.constructor as RepositoryClass<TModel, TId, TColumnsMap>)
|
|
179
|
+
const connection = (this.constructor as RepositoryClass<TModel, TId, TTableName, TColumnsMap>)
|
|
180
|
+
.connection;
|
|
171
181
|
let db: DatabaseConnection | null = null;
|
|
172
182
|
|
|
173
183
|
if (connection === 'default') {
|
|
@@ -185,6 +195,25 @@ export const Repository = <
|
|
|
185
195
|
return await this.db.select().from(model.table);
|
|
186
196
|
}
|
|
187
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Get a paginated subset of the records in the database.
|
|
200
|
+
* @param page The page number to retrieve.
|
|
201
|
+
* @param count The number of records to retrieve per page.
|
|
202
|
+
* @returns A paginated response containing the records and the total number of records.
|
|
203
|
+
*/
|
|
204
|
+
public async paginate(
|
|
205
|
+
count: number,
|
|
206
|
+
page: number = 1
|
|
207
|
+
): Promise<{ page: number; data: TSelect[]; total: number }> {
|
|
208
|
+
const total = await this.db.$count(model.table);
|
|
209
|
+
const offset = Math.max(page - 1, 0) * count;
|
|
210
|
+
return {
|
|
211
|
+
page,
|
|
212
|
+
total,
|
|
213
|
+
data: await this.db.select().from(model.table).offset(offset).limit(count)
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
188
217
|
/**
|
|
189
218
|
* Retrieves a record by its id.
|
|
190
219
|
* @param id The id of the record to retrieve.
|
|
@@ -195,6 +224,20 @@ export const Repository = <
|
|
|
195
224
|
return (row ?? null) as TSelect | null;
|
|
196
225
|
}
|
|
197
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Finds the first record with a column matching the given value.
|
|
229
|
+
* @param column The column to filter by.
|
|
230
|
+
* @param value The value the column should match.
|
|
231
|
+
* @returns The first record with a column matching the given value.
|
|
232
|
+
*/
|
|
233
|
+
public async findBy<TKey extends keyof TSelect>(
|
|
234
|
+
column: TKey,
|
|
235
|
+
value: TSelect[TKey]
|
|
236
|
+
): Promise<TSelect | null> {
|
|
237
|
+
const [row] = await this.db.select().from(model.table).where(eq(model.table[column], value));
|
|
238
|
+
return (row ?? null) as TSelect | null;
|
|
239
|
+
}
|
|
240
|
+
|
|
198
241
|
/**
|
|
199
242
|
* Inserts a new record in the database.
|
|
200
243
|
* @param data The data to insert.
|
package/src/service.ts
CHANGED
|
@@ -169,15 +169,17 @@ export namespace Service {
|
|
|
169
169
|
/**
|
|
170
170
|
* Resolves a registered service and set it as a parameter value.
|
|
171
171
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
172
|
-
* @param
|
|
172
|
+
* @param service An optional name for the service. If not set, the name of the parameter's type is used instead.
|
|
173
173
|
*/
|
|
174
|
-
export const inject = (
|
|
174
|
+
export const inject = (service?: string | ServiceClass): ParameterDecorator => {
|
|
175
175
|
return function (target, propertyKey, parameterIndex) {
|
|
176
176
|
const services = Reflect.getMetadata(Symbols.services, target, propertyKey!) ?? [];
|
|
177
|
-
const types
|
|
177
|
+
const types: Function[] =
|
|
178
|
+
Reflect.getMetadata('design:paramtypes', target, propertyKey!) ?? [];
|
|
179
|
+
const name = isString(service) ? service : (service?.name ?? types[parameterIndex].name);
|
|
178
180
|
|
|
179
181
|
services.push({
|
|
180
|
-
name
|
|
182
|
+
name,
|
|
181
183
|
index: parameterIndex
|
|
182
184
|
});
|
|
183
185
|
|
|
@@ -223,7 +225,7 @@ export namespace Service {
|
|
|
223
225
|
if (s === null) {
|
|
224
226
|
// TODO: Use the logger service here
|
|
225
227
|
console.error(
|
|
226
|
-
`Cannot inject
|
|
228
|
+
`Cannot inject service ${dependency.name}. No service was registered with the name: ${dependency.name}`
|
|
227
229
|
);
|
|
228
230
|
process.exit(1);
|
|
229
231
|
}
|