@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/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 { PgColumn, PgColumnBuilderBase, PgSchema, PgTable } from 'drizzle-orm/pg-core';
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<string, TColumnsMap>>
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 createSchema: TSchema;
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 createSchema = createSchemaFromDrizzle(table, { mode: 'create' });
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<Elysia<Route>> {
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: Elysia<Route> = new Elysia({ name: target.name });
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 @http(), or @websocket() decorators on the controller.`
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
- const table = pgTable(model.tableName, model.columns);
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>).connection;
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 name An optional name for the service. If not set, the name of the parameter's type is used instead.
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 = (name?: string): ParameterDecorator => {
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 = Reflect.getMetadata('design:paramtypes', target, propertyKey!) ?? [];
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: name ?? types[parameterIndex].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 a service. No service was registered with the name: ${dependency.name}`
228
+ `Cannot inject service ${dependency.name}. No service was registered with the name: ${dependency.name}`
227
229
  );
228
230
  process.exit(1);
229
231
  }