@meridianjs/framework-utils 0.1.0 → 0.1.2

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/dist/index.d.mts CHANGED
@@ -156,12 +156,6 @@ type InferModel<M extends ModelDefinition> = M extends ModelDefinition<infer Sch
156
156
  */
157
157
  declare function MeridianService(models: Record<string, ModelDefinition>): new (container: MeridianContainer) => IModuleService;
158
158
 
159
- /**
160
- * Converts a DML ModelDefinition to a MikroORM EntitySchema.
161
- *
162
- * Automatically adds `created_at`, `updated_at`, and `deleted_at` timestamp
163
- * columns to every entity.
164
- */
165
159
  declare function dmlToEntitySchema(def: ModelDefinition): EntitySchema;
166
160
  /**
167
161
  * Wraps a MikroORM EntityManager into the Repository interface expected
package/dist/index.d.ts CHANGED
@@ -156,12 +156,6 @@ type InferModel<M extends ModelDefinition> = M extends ModelDefinition<infer Sch
156
156
  */
157
157
  declare function MeridianService(models: Record<string, ModelDefinition>): new (container: MeridianContainer) => IModuleService;
158
158
 
159
- /**
160
- * Converts a DML ModelDefinition to a MikroORM EntitySchema.
161
- *
162
- * Automatically adds `created_at`, `updated_at`, and `deleted_at` timestamp
163
- * columns to every entity.
164
- */
165
159
  declare function dmlToEntitySchema(def: ModelDefinition): EntitySchema;
166
160
  /**
167
161
  * Wraps a MikroORM EntityManager into the Repository interface expected
package/dist/index.js CHANGED
@@ -55,10 +55,10 @@ function Module(key, definition) {
55
55
 
56
56
  // src/define-link.ts
57
57
  function normalizeEndpoint(input) {
58
- if ("linkable" in input) {
59
- return input;
58
+ if ("tableName" in input) {
59
+ return { linkable: input };
60
60
  }
61
- return { linkable: input };
61
+ return input;
62
62
  }
63
63
  function defineLink(left, right, options) {
64
64
  const leftEndpoint = normalizeEndpoint(left);
@@ -184,54 +184,88 @@ var model = {
184
184
  };
185
185
 
186
186
  // src/service-factory.ts
187
+ var UPDATE_RESERVED = /* @__PURE__ */ new Set([
188
+ "id",
189
+ "created_at",
190
+ "updated_at",
191
+ "deleted_at",
192
+ "__proto__",
193
+ "constructor",
194
+ "prototype"
195
+ ]);
187
196
  function MeridianService(models) {
188
197
  class BaseService {
189
198
  // Use private class field to avoid conflicting with the index signature
190
199
  #container;
191
200
  constructor(container) {
192
201
  this.#container = container;
202
+ const hasCustomMethod = (name) => typeof this[name] === "function";
193
203
  for (const [modelName, _modelDef] of Object.entries(models)) {
194
204
  const pluralName = `${modelName}s`;
195
205
  const repoToken = `${modelName.charAt(0).toLowerCase()}${modelName.slice(1)}Repository`;
196
206
  const capitalized = `${modelName.charAt(0).toUpperCase()}${modelName.slice(1)}`;
197
207
  const capitalizedPlural = `${pluralName.charAt(0).toUpperCase()}${pluralName.slice(1)}`;
198
- this[`list${capitalizedPlural}`] = async (filters = {}, options = {}) => {
199
- const repo = this.#container.resolve(repoToken);
200
- return repo.find(filters, options);
201
- };
202
- this[`listAndCount${capitalizedPlural}`] = async (filters = {}, options = {}) => {
203
- const repo = this.#container.resolve(repoToken);
204
- return repo.findAndCount(filters, options);
205
- };
206
- this[`retrieve${capitalized}`] = async (id) => {
207
- const repo = this.#container.resolve(repoToken);
208
- return repo.findOneOrFail({ id });
209
- };
210
- this[`create${capitalized}`] = async (data) => {
211
- const repo = this.#container.resolve(repoToken);
212
- const entity = repo.create(data);
213
- await repo.persistAndFlush(entity);
214
- return entity;
215
- };
216
- this[`update${capitalized}`] = async (id, data) => {
217
- const repo = this.#container.resolve(repoToken);
218
- const entity = await repo.findOneOrFail({ id });
219
- Object.assign(entity, data);
220
- await repo.flush();
221
- return entity;
222
- };
223
- this[`delete${capitalized}`] = async (id) => {
224
- const repo = this.#container.resolve(repoToken);
225
- const entity = await repo.findOneOrFail({ id });
226
- await repo.removeAndFlush(entity);
227
- };
228
- this[`softDelete${capitalized}`] = async (id) => {
229
- const repo = this.#container.resolve(repoToken);
230
- const entity = await repo.findOneOrFail({ id });
231
- entity.deleted_at = /* @__PURE__ */ new Date();
232
- await repo.flush();
233
- return entity;
234
- };
208
+ const listMethod = `list${capitalizedPlural}`;
209
+ if (!hasCustomMethod(listMethod)) {
210
+ this[listMethod] = async (filters = {}, options = {}) => {
211
+ const repo = this.#container.resolve(repoToken);
212
+ return repo.find({ deleted_at: null, ...filters }, options);
213
+ };
214
+ }
215
+ const listAndCountMethod = `listAndCount${capitalizedPlural}`;
216
+ if (!hasCustomMethod(listAndCountMethod)) {
217
+ this[listAndCountMethod] = async (filters = {}, options = {}) => {
218
+ const repo = this.#container.resolve(repoToken);
219
+ return repo.findAndCount({ deleted_at: null, ...filters }, options);
220
+ };
221
+ }
222
+ const retrieveMethod = `retrieve${capitalized}`;
223
+ if (!hasCustomMethod(retrieveMethod)) {
224
+ this[retrieveMethod] = async (id) => {
225
+ const repo = this.#container.resolve(repoToken);
226
+ return repo.findOneOrFail({ id, deleted_at: null });
227
+ };
228
+ }
229
+ const createMethod = `create${capitalized}`;
230
+ if (!hasCustomMethod(createMethod)) {
231
+ this[createMethod] = async (data) => {
232
+ const repo = this.#container.resolve(repoToken);
233
+ const entity = repo.create(data);
234
+ await repo.persistAndFlush(entity);
235
+ return entity;
236
+ };
237
+ }
238
+ const updateMethod = `update${capitalized}`;
239
+ if (!hasCustomMethod(updateMethod)) {
240
+ this[updateMethod] = async (id, data) => {
241
+ const repo = this.#container.resolve(repoToken);
242
+ const entity = await repo.findOneOrFail({ id, deleted_at: null });
243
+ const safe = Object.fromEntries(
244
+ Object.entries(data).filter(([k]) => !UPDATE_RESERVED.has(k))
245
+ );
246
+ Object.assign(entity, safe);
247
+ await repo.flush();
248
+ return entity;
249
+ };
250
+ }
251
+ const deleteMethod = `delete${capitalized}`;
252
+ if (!hasCustomMethod(deleteMethod)) {
253
+ this[deleteMethod] = async (id) => {
254
+ const repo = this.#container.resolve(repoToken);
255
+ const entity = await repo.findOneOrFail({ id });
256
+ await repo.removeAndFlush(entity);
257
+ };
258
+ }
259
+ const softDeleteMethod = `softDelete${capitalized}`;
260
+ if (!hasCustomMethod(softDeleteMethod)) {
261
+ this[softDeleteMethod] = async (id) => {
262
+ const repo = this.#container.resolve(repoToken);
263
+ const entity = await repo.findOneOrFail({ id, deleted_at: null });
264
+ entity.deleted_at = /* @__PURE__ */ new Date();
265
+ await repo.flush();
266
+ return entity;
267
+ };
268
+ }
235
269
  }
236
270
  }
237
271
  }
@@ -240,7 +274,15 @@ function MeridianService(models) {
240
274
 
241
275
  // src/orm-utils.ts
242
276
  var import_core = require("@mikro-orm/core");
277
+ var RESERVED_TIMESTAMP_KEYS = ["created_at", "updated_at", "deleted_at"];
243
278
  function dmlToEntitySchema(def) {
279
+ for (const key of RESERVED_TIMESTAMP_KEYS) {
280
+ if (key in def.schema) {
281
+ throw new Error(
282
+ `Model "${def.tableName}" defines reserved column "${key}". Meridian automatically manages created_at, updated_at, and deleted_at.`
283
+ );
284
+ }
285
+ }
244
286
  const properties = {};
245
287
  for (const [key, prop] of Object.entries(def.schema)) {
246
288
  if (prop instanceof IdProperty) {
@@ -322,9 +364,10 @@ function createRepository(em, entityName) {
322
364
  return repo.find(filters, options);
323
365
  },
324
366
  async findAndCount(filters, options = {}) {
367
+ const { limit, offset, ...countOptions } = options;
325
368
  const [data, count] = await Promise.all([
326
369
  repo.find(filters, options),
327
- repo.count(filters)
370
+ repo.count(filters, countOptions)
328
371
  ]);
329
372
  return [data, count];
330
373
  },
package/dist/index.mjs CHANGED
@@ -5,10 +5,10 @@ function Module(key, definition) {
5
5
 
6
6
  // src/define-link.ts
7
7
  function normalizeEndpoint(input) {
8
- if ("linkable" in input) {
9
- return input;
8
+ if ("tableName" in input) {
9
+ return { linkable: input };
10
10
  }
11
- return { linkable: input };
11
+ return input;
12
12
  }
13
13
  function defineLink(left, right, options) {
14
14
  const leftEndpoint = normalizeEndpoint(left);
@@ -134,54 +134,88 @@ var model = {
134
134
  };
135
135
 
136
136
  // src/service-factory.ts
137
+ var UPDATE_RESERVED = /* @__PURE__ */ new Set([
138
+ "id",
139
+ "created_at",
140
+ "updated_at",
141
+ "deleted_at",
142
+ "__proto__",
143
+ "constructor",
144
+ "prototype"
145
+ ]);
137
146
  function MeridianService(models) {
138
147
  class BaseService {
139
148
  // Use private class field to avoid conflicting with the index signature
140
149
  #container;
141
150
  constructor(container) {
142
151
  this.#container = container;
152
+ const hasCustomMethod = (name) => typeof this[name] === "function";
143
153
  for (const [modelName, _modelDef] of Object.entries(models)) {
144
154
  const pluralName = `${modelName}s`;
145
155
  const repoToken = `${modelName.charAt(0).toLowerCase()}${modelName.slice(1)}Repository`;
146
156
  const capitalized = `${modelName.charAt(0).toUpperCase()}${modelName.slice(1)}`;
147
157
  const capitalizedPlural = `${pluralName.charAt(0).toUpperCase()}${pluralName.slice(1)}`;
148
- this[`list${capitalizedPlural}`] = async (filters = {}, options = {}) => {
149
- const repo = this.#container.resolve(repoToken);
150
- return repo.find(filters, options);
151
- };
152
- this[`listAndCount${capitalizedPlural}`] = async (filters = {}, options = {}) => {
153
- const repo = this.#container.resolve(repoToken);
154
- return repo.findAndCount(filters, options);
155
- };
156
- this[`retrieve${capitalized}`] = async (id) => {
157
- const repo = this.#container.resolve(repoToken);
158
- return repo.findOneOrFail({ id });
159
- };
160
- this[`create${capitalized}`] = async (data) => {
161
- const repo = this.#container.resolve(repoToken);
162
- const entity = repo.create(data);
163
- await repo.persistAndFlush(entity);
164
- return entity;
165
- };
166
- this[`update${capitalized}`] = async (id, data) => {
167
- const repo = this.#container.resolve(repoToken);
168
- const entity = await repo.findOneOrFail({ id });
169
- Object.assign(entity, data);
170
- await repo.flush();
171
- return entity;
172
- };
173
- this[`delete${capitalized}`] = async (id) => {
174
- const repo = this.#container.resolve(repoToken);
175
- const entity = await repo.findOneOrFail({ id });
176
- await repo.removeAndFlush(entity);
177
- };
178
- this[`softDelete${capitalized}`] = async (id) => {
179
- const repo = this.#container.resolve(repoToken);
180
- const entity = await repo.findOneOrFail({ id });
181
- entity.deleted_at = /* @__PURE__ */ new Date();
182
- await repo.flush();
183
- return entity;
184
- };
158
+ const listMethod = `list${capitalizedPlural}`;
159
+ if (!hasCustomMethod(listMethod)) {
160
+ this[listMethod] = async (filters = {}, options = {}) => {
161
+ const repo = this.#container.resolve(repoToken);
162
+ return repo.find({ deleted_at: null, ...filters }, options);
163
+ };
164
+ }
165
+ const listAndCountMethod = `listAndCount${capitalizedPlural}`;
166
+ if (!hasCustomMethod(listAndCountMethod)) {
167
+ this[listAndCountMethod] = async (filters = {}, options = {}) => {
168
+ const repo = this.#container.resolve(repoToken);
169
+ return repo.findAndCount({ deleted_at: null, ...filters }, options);
170
+ };
171
+ }
172
+ const retrieveMethod = `retrieve${capitalized}`;
173
+ if (!hasCustomMethod(retrieveMethod)) {
174
+ this[retrieveMethod] = async (id) => {
175
+ const repo = this.#container.resolve(repoToken);
176
+ return repo.findOneOrFail({ id, deleted_at: null });
177
+ };
178
+ }
179
+ const createMethod = `create${capitalized}`;
180
+ if (!hasCustomMethod(createMethod)) {
181
+ this[createMethod] = async (data) => {
182
+ const repo = this.#container.resolve(repoToken);
183
+ const entity = repo.create(data);
184
+ await repo.persistAndFlush(entity);
185
+ return entity;
186
+ };
187
+ }
188
+ const updateMethod = `update${capitalized}`;
189
+ if (!hasCustomMethod(updateMethod)) {
190
+ this[updateMethod] = async (id, data) => {
191
+ const repo = this.#container.resolve(repoToken);
192
+ const entity = await repo.findOneOrFail({ id, deleted_at: null });
193
+ const safe = Object.fromEntries(
194
+ Object.entries(data).filter(([k]) => !UPDATE_RESERVED.has(k))
195
+ );
196
+ Object.assign(entity, safe);
197
+ await repo.flush();
198
+ return entity;
199
+ };
200
+ }
201
+ const deleteMethod = `delete${capitalized}`;
202
+ if (!hasCustomMethod(deleteMethod)) {
203
+ this[deleteMethod] = async (id) => {
204
+ const repo = this.#container.resolve(repoToken);
205
+ const entity = await repo.findOneOrFail({ id });
206
+ await repo.removeAndFlush(entity);
207
+ };
208
+ }
209
+ const softDeleteMethod = `softDelete${capitalized}`;
210
+ if (!hasCustomMethod(softDeleteMethod)) {
211
+ this[softDeleteMethod] = async (id) => {
212
+ const repo = this.#container.resolve(repoToken);
213
+ const entity = await repo.findOneOrFail({ id, deleted_at: null });
214
+ entity.deleted_at = /* @__PURE__ */ new Date();
215
+ await repo.flush();
216
+ return entity;
217
+ };
218
+ }
185
219
  }
186
220
  }
187
221
  }
@@ -190,7 +224,15 @@ function MeridianService(models) {
190
224
 
191
225
  // src/orm-utils.ts
192
226
  import { EntitySchema } from "@mikro-orm/core";
227
+ var RESERVED_TIMESTAMP_KEYS = ["created_at", "updated_at", "deleted_at"];
193
228
  function dmlToEntitySchema(def) {
229
+ for (const key of RESERVED_TIMESTAMP_KEYS) {
230
+ if (key in def.schema) {
231
+ throw new Error(
232
+ `Model "${def.tableName}" defines reserved column "${key}". Meridian automatically manages created_at, updated_at, and deleted_at.`
233
+ );
234
+ }
235
+ }
194
236
  const properties = {};
195
237
  for (const [key, prop] of Object.entries(def.schema)) {
196
238
  if (prop instanceof IdProperty) {
@@ -272,9 +314,10 @@ function createRepository(em, entityName) {
272
314
  return repo.find(filters, options);
273
315
  },
274
316
  async findAndCount(filters, options = {}) {
317
+ const { limit, offset, ...countOptions } = options;
275
318
  const [data, count] = await Promise.all([
276
319
  repo.find(filters, options),
277
- repo.count(filters)
320
+ repo.count(filters, countOptions)
278
321
  ]);
279
322
  return [data, count];
280
323
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meridianjs/framework-utils",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Utilities for building Meridian modules: DML, service factory, defineModule, defineLink",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",