@meridianjs/framework-utils 0.1.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.
@@ -0,0 +1,199 @@
1
+ import { ModuleDefinition, LinkableEntry, LinkEndpoint, LinkDefinition, MeridianContainer, IModuleService } from '@meridianjs/types';
2
+ import { EntitySchema, MikroORM, EntityManager } from '@mikro-orm/core';
3
+
4
+ /**
5
+ * Declares a Meridian module.
6
+ *
7
+ * @example
8
+ * export default Module("projectModuleService", {
9
+ * service: ProjectModuleService,
10
+ * models: [Project, Label],
11
+ * loaders: [defaultLoader],
12
+ * linkable: { project: { tableName: "project", primaryKey: "id" } },
13
+ * })
14
+ */
15
+ declare function Module(key: string, definition: Omit<ModuleDefinition, "key">): ModuleDefinition;
16
+
17
+ type LinkEndpointInput = LinkableEntry | LinkEndpoint;
18
+ /**
19
+ * Defines a cross-module relationship as a junction table.
20
+ * Neither module holds a foreign key — the link table is independent.
21
+ *
22
+ * @example
23
+ * export default defineLink(
24
+ * ProjectModule.linkable.project,
25
+ * { linkable: IssueModule.linkable.issue, isList: true }
26
+ * )
27
+ */
28
+ declare function defineLink(left: LinkEndpointInput, right: LinkEndpointInput, options?: {
29
+ readOnly?: boolean;
30
+ database?: {
31
+ extraColumns?: Record<string, {
32
+ type: string;
33
+ }>;
34
+ };
35
+ }): LinkDefinition;
36
+
37
+ /**
38
+ * Data Model Language (DML) — fluent API for defining Meridian data models.
39
+ *
40
+ * Each model definition is later converted to a database schema by the module loader.
41
+ * The actual ORM entity class is generated at runtime from this specification.
42
+ *
43
+ * @example
44
+ * const Project = model.define("project", {
45
+ * id: model.id().primaryKey(),
46
+ * name: model.text(),
47
+ * visibility: model.enum(["private", "public", "workspace"]),
48
+ * description: model.text().nullable(),
49
+ * })
50
+ */
51
+ declare class IdProperty {
52
+ readonly _type: "id";
53
+ _primaryKey: boolean;
54
+ primaryKey(): this;
55
+ }
56
+ declare class TextProperty {
57
+ readonly _type: "text";
58
+ _nullable: boolean;
59
+ _default?: string;
60
+ nullable(): this;
61
+ default(val: string): this;
62
+ }
63
+ declare class BooleanProperty {
64
+ readonly _type: "boolean";
65
+ _default?: boolean;
66
+ default(val: boolean): this;
67
+ }
68
+ declare class NumberProperty {
69
+ readonly _type: "number";
70
+ _nullable: boolean;
71
+ _default?: number;
72
+ nullable(): this;
73
+ default(val: number): this;
74
+ }
75
+ declare class DateProperty {
76
+ readonly _type: "date";
77
+ _nullable: boolean;
78
+ nullable(): this;
79
+ }
80
+ declare class JsonProperty {
81
+ readonly _type: "json";
82
+ _nullable: boolean;
83
+ nullable(): this;
84
+ }
85
+ declare class EnumProperty<T extends string> {
86
+ readonly _values: T[];
87
+ readonly _type: "enum";
88
+ constructor(_values: T[]);
89
+ _nullable: boolean;
90
+ _default?: T;
91
+ nullable(): this;
92
+ default(val: T): this;
93
+ }
94
+ type PropertyType = IdProperty | TextProperty | BooleanProperty | NumberProperty | DateProperty | JsonProperty | EnumProperty<string>;
95
+ type ModelSchema = Record<string, PropertyType>;
96
+ interface IndexDefinition {
97
+ /** Column names that form the index. */
98
+ columns: string[];
99
+ /** Whether this is a unique index. Defaults to false. */
100
+ unique?: boolean;
101
+ /** Optional explicit index name. MikroORM will auto-generate one if omitted. */
102
+ name?: string;
103
+ }
104
+ declare class ModelDefinition<Schema extends ModelSchema = ModelSchema> {
105
+ readonly tableName: string;
106
+ readonly schema: Schema;
107
+ readonly indexes: IndexDefinition[];
108
+ constructor(tableName: string, schema: Schema, indexes?: IndexDefinition[]);
109
+ }
110
+ declare const model: {
111
+ /**
112
+ * Define a new data model. The table name should be snake_case singular.
113
+ * Optionally pass index definitions as the third argument.
114
+ */
115
+ define<Schema extends ModelSchema>(tableName: string, schema: Schema, indexes?: IndexDefinition[]): ModelDefinition<Schema>;
116
+ /** UUID primary key field */
117
+ id: () => IdProperty;
118
+ /** Variable-length text / varchar */
119
+ text: () => TextProperty;
120
+ /** Boolean true/false */
121
+ boolean: () => BooleanProperty;
122
+ /** Integer or float */
123
+ number: () => NumberProperty;
124
+ /** Timestamp with timezone */
125
+ date: () => DateProperty;
126
+ /** JSONB column */
127
+ json: () => JsonProperty;
128
+ /** Enum column — values are enforced at application level */
129
+ enum: <T extends string>(values: T[]) => EnumProperty<T>;
130
+ };
131
+ type InferPropertyType<P extends PropertyType> = P extends IdProperty ? string : P extends TextProperty ? (P["_nullable"] extends true ? string | null : string) : P extends BooleanProperty ? boolean : P extends NumberProperty ? (P["_nullable"] extends true ? number | null : number) : P extends DateProperty ? (P["_nullable"] extends true ? Date | null : Date) : P extends JsonProperty ? (P["_nullable"] extends true ? unknown | null : unknown) : P extends EnumProperty<infer T> ? T : never;
132
+ type InferModel<M extends ModelDefinition> = M extends ModelDefinition<infer Schema> ? {
133
+ [K in keyof Schema]: InferPropertyType<Schema[K]>;
134
+ } & {
135
+ created_at: Date;
136
+ updated_at: Date;
137
+ deleted_at: Date | null;
138
+ } : never;
139
+
140
+ /**
141
+ * Base class factory that auto-generates CRUD methods for each model.
142
+ *
143
+ * For a model named "Project", the following methods are generated:
144
+ * - listProjects(filters?, options?) → Promise<Project[]>
145
+ * - listAndCountProjects(filters?, options?) → Promise<[Project[], number]>
146
+ * - retrieveProject(id) → Promise<Project>
147
+ * - createProject(data) → Promise<Project>
148
+ * - updateProject(id, data) → Promise<Project>
149
+ * - deleteProject(id) → Promise<void>
150
+ * - softDeleteProject(id) → Promise<Project>
151
+ *
152
+ * @example
153
+ * class ProjectModuleService extends MeridianService({ Project, Label }) {
154
+ * constructor(container: MeridianContainer) { super(container) }
155
+ * }
156
+ */
157
+ declare function MeridianService(models: Record<string, ModelDefinition>): new (container: MeridianContainer) => IModuleService;
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
+ declare function dmlToEntitySchema(def: ModelDefinition): EntitySchema;
166
+ /**
167
+ * Wraps a MikroORM EntityManager into the Repository interface expected
168
+ * by MeridianService's auto-generated CRUD methods.
169
+ *
170
+ * The `entityName` is the DML model's tableName (e.g. "user", "workspace").
171
+ * The resulting object is registered in the module container as `${entityName}Repository`.
172
+ */
173
+ declare function createRepository(em: EntityManager, entityName: string): MeridianRepository;
174
+ interface MeridianRepository {
175
+ find(filters: object, options?: object): Promise<unknown[]>;
176
+ findAndCount(filters: object, options?: object): Promise<[unknown[], number]>;
177
+ findOne(filters: object): Promise<unknown | null>;
178
+ findOneOrFail(filters: object): Promise<unknown>;
179
+ create(data: object): unknown;
180
+ persistAndFlush(entity: unknown): Promise<void>;
181
+ flush(): Promise<void>;
182
+ removeAndFlush(entity: unknown): Promise<void>;
183
+ }
184
+ /**
185
+ * Creates a MikroORM instance for a module's set of entities.
186
+ *
187
+ * In development (NODE_ENV !== "production"), schema is automatically
188
+ * synced to the database (safe mode — only adds, never drops).
189
+ *
190
+ * @param entitySchemas - MikroORM EntitySchema objects for this module's models
191
+ * @param databaseUrl - PostgreSQL connection URL
192
+ * @param sync - auto-sync schema (default: true in development)
193
+ */
194
+ declare function createModuleOrm(entitySchemas: EntitySchema[], databaseUrl: string, options?: {
195
+ sync?: boolean;
196
+ debug?: boolean;
197
+ }): Promise<MikroORM>;
198
+
199
+ export { BooleanProperty, DateProperty, EnumProperty, IdProperty, type IndexDefinition, type InferModel, JsonProperty, type MeridianRepository, MeridianService, ModelDefinition, type ModelSchema, Module, NumberProperty, TextProperty, createModuleOrm, createRepository, defineLink, dmlToEntitySchema, model };
@@ -0,0 +1,199 @@
1
+ import { ModuleDefinition, LinkableEntry, LinkEndpoint, LinkDefinition, MeridianContainer, IModuleService } from '@meridianjs/types';
2
+ import { EntitySchema, MikroORM, EntityManager } from '@mikro-orm/core';
3
+
4
+ /**
5
+ * Declares a Meridian module.
6
+ *
7
+ * @example
8
+ * export default Module("projectModuleService", {
9
+ * service: ProjectModuleService,
10
+ * models: [Project, Label],
11
+ * loaders: [defaultLoader],
12
+ * linkable: { project: { tableName: "project", primaryKey: "id" } },
13
+ * })
14
+ */
15
+ declare function Module(key: string, definition: Omit<ModuleDefinition, "key">): ModuleDefinition;
16
+
17
+ type LinkEndpointInput = LinkableEntry | LinkEndpoint;
18
+ /**
19
+ * Defines a cross-module relationship as a junction table.
20
+ * Neither module holds a foreign key — the link table is independent.
21
+ *
22
+ * @example
23
+ * export default defineLink(
24
+ * ProjectModule.linkable.project,
25
+ * { linkable: IssueModule.linkable.issue, isList: true }
26
+ * )
27
+ */
28
+ declare function defineLink(left: LinkEndpointInput, right: LinkEndpointInput, options?: {
29
+ readOnly?: boolean;
30
+ database?: {
31
+ extraColumns?: Record<string, {
32
+ type: string;
33
+ }>;
34
+ };
35
+ }): LinkDefinition;
36
+
37
+ /**
38
+ * Data Model Language (DML) — fluent API for defining Meridian data models.
39
+ *
40
+ * Each model definition is later converted to a database schema by the module loader.
41
+ * The actual ORM entity class is generated at runtime from this specification.
42
+ *
43
+ * @example
44
+ * const Project = model.define("project", {
45
+ * id: model.id().primaryKey(),
46
+ * name: model.text(),
47
+ * visibility: model.enum(["private", "public", "workspace"]),
48
+ * description: model.text().nullable(),
49
+ * })
50
+ */
51
+ declare class IdProperty {
52
+ readonly _type: "id";
53
+ _primaryKey: boolean;
54
+ primaryKey(): this;
55
+ }
56
+ declare class TextProperty {
57
+ readonly _type: "text";
58
+ _nullable: boolean;
59
+ _default?: string;
60
+ nullable(): this;
61
+ default(val: string): this;
62
+ }
63
+ declare class BooleanProperty {
64
+ readonly _type: "boolean";
65
+ _default?: boolean;
66
+ default(val: boolean): this;
67
+ }
68
+ declare class NumberProperty {
69
+ readonly _type: "number";
70
+ _nullable: boolean;
71
+ _default?: number;
72
+ nullable(): this;
73
+ default(val: number): this;
74
+ }
75
+ declare class DateProperty {
76
+ readonly _type: "date";
77
+ _nullable: boolean;
78
+ nullable(): this;
79
+ }
80
+ declare class JsonProperty {
81
+ readonly _type: "json";
82
+ _nullable: boolean;
83
+ nullable(): this;
84
+ }
85
+ declare class EnumProperty<T extends string> {
86
+ readonly _values: T[];
87
+ readonly _type: "enum";
88
+ constructor(_values: T[]);
89
+ _nullable: boolean;
90
+ _default?: T;
91
+ nullable(): this;
92
+ default(val: T): this;
93
+ }
94
+ type PropertyType = IdProperty | TextProperty | BooleanProperty | NumberProperty | DateProperty | JsonProperty | EnumProperty<string>;
95
+ type ModelSchema = Record<string, PropertyType>;
96
+ interface IndexDefinition {
97
+ /** Column names that form the index. */
98
+ columns: string[];
99
+ /** Whether this is a unique index. Defaults to false. */
100
+ unique?: boolean;
101
+ /** Optional explicit index name. MikroORM will auto-generate one if omitted. */
102
+ name?: string;
103
+ }
104
+ declare class ModelDefinition<Schema extends ModelSchema = ModelSchema> {
105
+ readonly tableName: string;
106
+ readonly schema: Schema;
107
+ readonly indexes: IndexDefinition[];
108
+ constructor(tableName: string, schema: Schema, indexes?: IndexDefinition[]);
109
+ }
110
+ declare const model: {
111
+ /**
112
+ * Define a new data model. The table name should be snake_case singular.
113
+ * Optionally pass index definitions as the third argument.
114
+ */
115
+ define<Schema extends ModelSchema>(tableName: string, schema: Schema, indexes?: IndexDefinition[]): ModelDefinition<Schema>;
116
+ /** UUID primary key field */
117
+ id: () => IdProperty;
118
+ /** Variable-length text / varchar */
119
+ text: () => TextProperty;
120
+ /** Boolean true/false */
121
+ boolean: () => BooleanProperty;
122
+ /** Integer or float */
123
+ number: () => NumberProperty;
124
+ /** Timestamp with timezone */
125
+ date: () => DateProperty;
126
+ /** JSONB column */
127
+ json: () => JsonProperty;
128
+ /** Enum column — values are enforced at application level */
129
+ enum: <T extends string>(values: T[]) => EnumProperty<T>;
130
+ };
131
+ type InferPropertyType<P extends PropertyType> = P extends IdProperty ? string : P extends TextProperty ? (P["_nullable"] extends true ? string | null : string) : P extends BooleanProperty ? boolean : P extends NumberProperty ? (P["_nullable"] extends true ? number | null : number) : P extends DateProperty ? (P["_nullable"] extends true ? Date | null : Date) : P extends JsonProperty ? (P["_nullable"] extends true ? unknown | null : unknown) : P extends EnumProperty<infer T> ? T : never;
132
+ type InferModel<M extends ModelDefinition> = M extends ModelDefinition<infer Schema> ? {
133
+ [K in keyof Schema]: InferPropertyType<Schema[K]>;
134
+ } & {
135
+ created_at: Date;
136
+ updated_at: Date;
137
+ deleted_at: Date | null;
138
+ } : never;
139
+
140
+ /**
141
+ * Base class factory that auto-generates CRUD methods for each model.
142
+ *
143
+ * For a model named "Project", the following methods are generated:
144
+ * - listProjects(filters?, options?) → Promise<Project[]>
145
+ * - listAndCountProjects(filters?, options?) → Promise<[Project[], number]>
146
+ * - retrieveProject(id) → Promise<Project>
147
+ * - createProject(data) → Promise<Project>
148
+ * - updateProject(id, data) → Promise<Project>
149
+ * - deleteProject(id) → Promise<void>
150
+ * - softDeleteProject(id) → Promise<Project>
151
+ *
152
+ * @example
153
+ * class ProjectModuleService extends MeridianService({ Project, Label }) {
154
+ * constructor(container: MeridianContainer) { super(container) }
155
+ * }
156
+ */
157
+ declare function MeridianService(models: Record<string, ModelDefinition>): new (container: MeridianContainer) => IModuleService;
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
+ declare function dmlToEntitySchema(def: ModelDefinition): EntitySchema;
166
+ /**
167
+ * Wraps a MikroORM EntityManager into the Repository interface expected
168
+ * by MeridianService's auto-generated CRUD methods.
169
+ *
170
+ * The `entityName` is the DML model's tableName (e.g. "user", "workspace").
171
+ * The resulting object is registered in the module container as `${entityName}Repository`.
172
+ */
173
+ declare function createRepository(em: EntityManager, entityName: string): MeridianRepository;
174
+ interface MeridianRepository {
175
+ find(filters: object, options?: object): Promise<unknown[]>;
176
+ findAndCount(filters: object, options?: object): Promise<[unknown[], number]>;
177
+ findOne(filters: object): Promise<unknown | null>;
178
+ findOneOrFail(filters: object): Promise<unknown>;
179
+ create(data: object): unknown;
180
+ persistAndFlush(entity: unknown): Promise<void>;
181
+ flush(): Promise<void>;
182
+ removeAndFlush(entity: unknown): Promise<void>;
183
+ }
184
+ /**
185
+ * Creates a MikroORM instance for a module's set of entities.
186
+ *
187
+ * In development (NODE_ENV !== "production"), schema is automatically
188
+ * synced to the database (safe mode — only adds, never drops).
189
+ *
190
+ * @param entitySchemas - MikroORM EntitySchema objects for this module's models
191
+ * @param databaseUrl - PostgreSQL connection URL
192
+ * @param sync - auto-sync schema (default: true in development)
193
+ */
194
+ declare function createModuleOrm(entitySchemas: EntitySchema[], databaseUrl: string, options?: {
195
+ sync?: boolean;
196
+ debug?: boolean;
197
+ }): Promise<MikroORM>;
198
+
199
+ export { BooleanProperty, DateProperty, EnumProperty, IdProperty, type IndexDefinition, type InferModel, JsonProperty, type MeridianRepository, MeridianService, ModelDefinition, type ModelSchema, Module, NumberProperty, TextProperty, createModuleOrm, createRepository, defineLink, dmlToEntitySchema, model };
package/dist/index.js ADDED
@@ -0,0 +1,384 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ BooleanProperty: () => BooleanProperty,
34
+ DateProperty: () => DateProperty,
35
+ EnumProperty: () => EnumProperty,
36
+ IdProperty: () => IdProperty,
37
+ JsonProperty: () => JsonProperty,
38
+ MeridianService: () => MeridianService,
39
+ ModelDefinition: () => ModelDefinition,
40
+ Module: () => Module,
41
+ NumberProperty: () => NumberProperty,
42
+ TextProperty: () => TextProperty,
43
+ createModuleOrm: () => createModuleOrm,
44
+ createRepository: () => createRepository,
45
+ defineLink: () => defineLink,
46
+ dmlToEntitySchema: () => dmlToEntitySchema,
47
+ model: () => model
48
+ });
49
+ module.exports = __toCommonJS(index_exports);
50
+
51
+ // src/define-module.ts
52
+ function Module(key, definition) {
53
+ return { key, ...definition };
54
+ }
55
+
56
+ // src/define-link.ts
57
+ function normalizeEndpoint(input) {
58
+ if ("linkable" in input) {
59
+ return input;
60
+ }
61
+ return { linkable: input };
62
+ }
63
+ function defineLink(left, right, options) {
64
+ const leftEndpoint = normalizeEndpoint(left);
65
+ const rightEndpoint = normalizeEndpoint(right);
66
+ const linkTableName = [
67
+ leftEndpoint.linkable.tableName,
68
+ rightEndpoint.linkable.tableName
69
+ ].join("_");
70
+ return {
71
+ left: leftEndpoint,
72
+ right: rightEndpoint,
73
+ readOnly: options?.readOnly,
74
+ extraColumns: options?.database?.extraColumns,
75
+ linkTableName,
76
+ entryPoint: linkTableName
77
+ };
78
+ }
79
+
80
+ // src/dml.ts
81
+ var IdProperty = class {
82
+ _type = "id";
83
+ _primaryKey = false;
84
+ primaryKey() {
85
+ this._primaryKey = true;
86
+ return this;
87
+ }
88
+ };
89
+ var TextProperty = class {
90
+ _type = "text";
91
+ _nullable = false;
92
+ _default;
93
+ nullable() {
94
+ this._nullable = true;
95
+ return this;
96
+ }
97
+ default(val) {
98
+ this._default = val;
99
+ return this;
100
+ }
101
+ };
102
+ var BooleanProperty = class {
103
+ _type = "boolean";
104
+ _default;
105
+ default(val) {
106
+ this._default = val;
107
+ return this;
108
+ }
109
+ };
110
+ var NumberProperty = class {
111
+ _type = "number";
112
+ _nullable = false;
113
+ _default;
114
+ nullable() {
115
+ this._nullable = true;
116
+ return this;
117
+ }
118
+ default(val) {
119
+ this._default = val;
120
+ return this;
121
+ }
122
+ };
123
+ var DateProperty = class {
124
+ _type = "date";
125
+ _nullable = false;
126
+ nullable() {
127
+ this._nullable = true;
128
+ return this;
129
+ }
130
+ };
131
+ var JsonProperty = class {
132
+ _type = "json";
133
+ _nullable = false;
134
+ nullable() {
135
+ this._nullable = true;
136
+ return this;
137
+ }
138
+ };
139
+ var EnumProperty = class {
140
+ constructor(_values) {
141
+ this._values = _values;
142
+ }
143
+ _type = "enum";
144
+ _nullable = false;
145
+ _default;
146
+ nullable() {
147
+ this._nullable = true;
148
+ return this;
149
+ }
150
+ default(val) {
151
+ this._default = val;
152
+ return this;
153
+ }
154
+ };
155
+ var ModelDefinition = class {
156
+ constructor(tableName, schema, indexes = []) {
157
+ this.tableName = tableName;
158
+ this.schema = schema;
159
+ this.indexes = indexes;
160
+ }
161
+ };
162
+ var model = {
163
+ /**
164
+ * Define a new data model. The table name should be snake_case singular.
165
+ * Optionally pass index definitions as the third argument.
166
+ */
167
+ define(tableName, schema, indexes) {
168
+ return new ModelDefinition(tableName, schema, indexes ?? []);
169
+ },
170
+ /** UUID primary key field */
171
+ id: () => new IdProperty(),
172
+ /** Variable-length text / varchar */
173
+ text: () => new TextProperty(),
174
+ /** Boolean true/false */
175
+ boolean: () => new BooleanProperty(),
176
+ /** Integer or float */
177
+ number: () => new NumberProperty(),
178
+ /** Timestamp with timezone */
179
+ date: () => new DateProperty(),
180
+ /** JSONB column */
181
+ json: () => new JsonProperty(),
182
+ /** Enum column — values are enforced at application level */
183
+ enum: (values) => new EnumProperty(values)
184
+ };
185
+
186
+ // src/service-factory.ts
187
+ function MeridianService(models) {
188
+ class BaseService {
189
+ // Use private class field to avoid conflicting with the index signature
190
+ #container;
191
+ constructor(container) {
192
+ this.#container = container;
193
+ for (const [modelName, _modelDef] of Object.entries(models)) {
194
+ const pluralName = `${modelName}s`;
195
+ const repoToken = `${modelName.charAt(0).toLowerCase()}${modelName.slice(1)}Repository`;
196
+ const capitalized = `${modelName.charAt(0).toUpperCase()}${modelName.slice(1)}`;
197
+ 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
+ };
235
+ }
236
+ }
237
+ }
238
+ return BaseService;
239
+ }
240
+
241
+ // src/orm-utils.ts
242
+ var import_core = require("@mikro-orm/core");
243
+ function dmlToEntitySchema(def) {
244
+ const properties = {};
245
+ for (const [key, prop] of Object.entries(def.schema)) {
246
+ if (prop instanceof IdProperty) {
247
+ properties[key] = {
248
+ type: "uuid",
249
+ primary: prop._primaryKey,
250
+ defaultRaw: "gen_random_uuid()",
251
+ nullable: false
252
+ };
253
+ } else if (prop instanceof TextProperty) {
254
+ properties[key] = {
255
+ type: "text",
256
+ nullable: prop._nullable,
257
+ ...prop._default !== void 0 ? { default: prop._default } : {}
258
+ };
259
+ } else if (prop instanceof BooleanProperty) {
260
+ properties[key] = {
261
+ type: "boolean",
262
+ nullable: false,
263
+ ...prop._default !== void 0 ? { default: prop._default } : {}
264
+ };
265
+ } else if (prop instanceof NumberProperty) {
266
+ properties[key] = {
267
+ type: "integer",
268
+ nullable: prop._nullable,
269
+ ...prop._default !== void 0 ? { default: prop._default } : {}
270
+ };
271
+ } else if (prop instanceof DateProperty) {
272
+ properties[key] = {
273
+ type: "Date",
274
+ nullable: prop._nullable
275
+ };
276
+ } else if (prop instanceof JsonProperty) {
277
+ properties[key] = {
278
+ type: "json",
279
+ nullable: prop._nullable
280
+ };
281
+ } else if (prop instanceof EnumProperty) {
282
+ properties[key] = {
283
+ type: "string",
284
+ nullable: prop._nullable,
285
+ enum: true,
286
+ items: prop._values,
287
+ ...prop._default !== void 0 ? { default: prop._default } : {}
288
+ };
289
+ }
290
+ }
291
+ properties.created_at = {
292
+ type: "Date",
293
+ nullable: false,
294
+ onCreate: () => /* @__PURE__ */ new Date()
295
+ };
296
+ properties.updated_at = {
297
+ type: "Date",
298
+ nullable: false,
299
+ onCreate: () => /* @__PURE__ */ new Date(),
300
+ onUpdate: () => /* @__PURE__ */ new Date()
301
+ };
302
+ properties.deleted_at = {
303
+ type: "Date",
304
+ nullable: true
305
+ };
306
+ const indexes = (def.indexes ?? []).map((idx) => ({
307
+ ...idx.name ? { name: idx.name } : {},
308
+ properties: idx.columns,
309
+ unique: idx.unique ?? false
310
+ }));
311
+ return new import_core.EntitySchema({
312
+ name: def.tableName,
313
+ tableName: def.tableName,
314
+ properties,
315
+ ...indexes.length > 0 ? { indexes } : {}
316
+ });
317
+ }
318
+ function createRepository(em, entityName) {
319
+ const repo = em.getRepository(entityName);
320
+ return {
321
+ async find(filters, options = {}) {
322
+ return repo.find(filters, options);
323
+ },
324
+ async findAndCount(filters, options = {}) {
325
+ const [data, count] = await Promise.all([
326
+ repo.find(filters, options),
327
+ repo.count(filters)
328
+ ]);
329
+ return [data, count];
330
+ },
331
+ async findOne(filters) {
332
+ return repo.findOne(filters);
333
+ },
334
+ async findOneOrFail(filters) {
335
+ return repo.findOneOrFail(filters);
336
+ },
337
+ create(data) {
338
+ return repo.create(data);
339
+ },
340
+ async persistAndFlush(entity) {
341
+ await em.persistAndFlush(entity);
342
+ },
343
+ async flush() {
344
+ await em.flush();
345
+ },
346
+ async removeAndFlush(entity) {
347
+ await em.removeAndFlush(entity);
348
+ }
349
+ };
350
+ }
351
+ async function createModuleOrm(entitySchemas, databaseUrl, options = {}) {
352
+ const { MikroORM } = await import("@mikro-orm/core");
353
+ const { PostgreSqlDriver } = await import("@mikro-orm/postgresql");
354
+ const orm = await MikroORM.init({
355
+ entities: entitySchemas,
356
+ clientUrl: databaseUrl,
357
+ driver: PostgreSqlDriver,
358
+ debug: options.debug ?? false
359
+ });
360
+ const shouldSync = options.sync ?? process.env.NODE_ENV !== "production";
361
+ if (shouldSync) {
362
+ const generator = orm.getSchemaGenerator();
363
+ await generator.updateSchema({ safe: true });
364
+ }
365
+ return orm;
366
+ }
367
+ // Annotate the CommonJS export names for ESM import in node:
368
+ 0 && (module.exports = {
369
+ BooleanProperty,
370
+ DateProperty,
371
+ EnumProperty,
372
+ IdProperty,
373
+ JsonProperty,
374
+ MeridianService,
375
+ ModelDefinition,
376
+ Module,
377
+ NumberProperty,
378
+ TextProperty,
379
+ createModuleOrm,
380
+ createRepository,
381
+ defineLink,
382
+ dmlToEntitySchema,
383
+ model
384
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,333 @@
1
+ // src/define-module.ts
2
+ function Module(key, definition) {
3
+ return { key, ...definition };
4
+ }
5
+
6
+ // src/define-link.ts
7
+ function normalizeEndpoint(input) {
8
+ if ("linkable" in input) {
9
+ return input;
10
+ }
11
+ return { linkable: input };
12
+ }
13
+ function defineLink(left, right, options) {
14
+ const leftEndpoint = normalizeEndpoint(left);
15
+ const rightEndpoint = normalizeEndpoint(right);
16
+ const linkTableName = [
17
+ leftEndpoint.linkable.tableName,
18
+ rightEndpoint.linkable.tableName
19
+ ].join("_");
20
+ return {
21
+ left: leftEndpoint,
22
+ right: rightEndpoint,
23
+ readOnly: options?.readOnly,
24
+ extraColumns: options?.database?.extraColumns,
25
+ linkTableName,
26
+ entryPoint: linkTableName
27
+ };
28
+ }
29
+
30
+ // src/dml.ts
31
+ var IdProperty = class {
32
+ _type = "id";
33
+ _primaryKey = false;
34
+ primaryKey() {
35
+ this._primaryKey = true;
36
+ return this;
37
+ }
38
+ };
39
+ var TextProperty = class {
40
+ _type = "text";
41
+ _nullable = false;
42
+ _default;
43
+ nullable() {
44
+ this._nullable = true;
45
+ return this;
46
+ }
47
+ default(val) {
48
+ this._default = val;
49
+ return this;
50
+ }
51
+ };
52
+ var BooleanProperty = class {
53
+ _type = "boolean";
54
+ _default;
55
+ default(val) {
56
+ this._default = val;
57
+ return this;
58
+ }
59
+ };
60
+ var NumberProperty = class {
61
+ _type = "number";
62
+ _nullable = false;
63
+ _default;
64
+ nullable() {
65
+ this._nullable = true;
66
+ return this;
67
+ }
68
+ default(val) {
69
+ this._default = val;
70
+ return this;
71
+ }
72
+ };
73
+ var DateProperty = class {
74
+ _type = "date";
75
+ _nullable = false;
76
+ nullable() {
77
+ this._nullable = true;
78
+ return this;
79
+ }
80
+ };
81
+ var JsonProperty = class {
82
+ _type = "json";
83
+ _nullable = false;
84
+ nullable() {
85
+ this._nullable = true;
86
+ return this;
87
+ }
88
+ };
89
+ var EnumProperty = class {
90
+ constructor(_values) {
91
+ this._values = _values;
92
+ }
93
+ _type = "enum";
94
+ _nullable = false;
95
+ _default;
96
+ nullable() {
97
+ this._nullable = true;
98
+ return this;
99
+ }
100
+ default(val) {
101
+ this._default = val;
102
+ return this;
103
+ }
104
+ };
105
+ var ModelDefinition = class {
106
+ constructor(tableName, schema, indexes = []) {
107
+ this.tableName = tableName;
108
+ this.schema = schema;
109
+ this.indexes = indexes;
110
+ }
111
+ };
112
+ var model = {
113
+ /**
114
+ * Define a new data model. The table name should be snake_case singular.
115
+ * Optionally pass index definitions as the third argument.
116
+ */
117
+ define(tableName, schema, indexes) {
118
+ return new ModelDefinition(tableName, schema, indexes ?? []);
119
+ },
120
+ /** UUID primary key field */
121
+ id: () => new IdProperty(),
122
+ /** Variable-length text / varchar */
123
+ text: () => new TextProperty(),
124
+ /** Boolean true/false */
125
+ boolean: () => new BooleanProperty(),
126
+ /** Integer or float */
127
+ number: () => new NumberProperty(),
128
+ /** Timestamp with timezone */
129
+ date: () => new DateProperty(),
130
+ /** JSONB column */
131
+ json: () => new JsonProperty(),
132
+ /** Enum column — values are enforced at application level */
133
+ enum: (values) => new EnumProperty(values)
134
+ };
135
+
136
+ // src/service-factory.ts
137
+ function MeridianService(models) {
138
+ class BaseService {
139
+ // Use private class field to avoid conflicting with the index signature
140
+ #container;
141
+ constructor(container) {
142
+ this.#container = container;
143
+ for (const [modelName, _modelDef] of Object.entries(models)) {
144
+ const pluralName = `${modelName}s`;
145
+ const repoToken = `${modelName.charAt(0).toLowerCase()}${modelName.slice(1)}Repository`;
146
+ const capitalized = `${modelName.charAt(0).toUpperCase()}${modelName.slice(1)}`;
147
+ 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
+ };
185
+ }
186
+ }
187
+ }
188
+ return BaseService;
189
+ }
190
+
191
+ // src/orm-utils.ts
192
+ import { EntitySchema } from "@mikro-orm/core";
193
+ function dmlToEntitySchema(def) {
194
+ const properties = {};
195
+ for (const [key, prop] of Object.entries(def.schema)) {
196
+ if (prop instanceof IdProperty) {
197
+ properties[key] = {
198
+ type: "uuid",
199
+ primary: prop._primaryKey,
200
+ defaultRaw: "gen_random_uuid()",
201
+ nullable: false
202
+ };
203
+ } else if (prop instanceof TextProperty) {
204
+ properties[key] = {
205
+ type: "text",
206
+ nullable: prop._nullable,
207
+ ...prop._default !== void 0 ? { default: prop._default } : {}
208
+ };
209
+ } else if (prop instanceof BooleanProperty) {
210
+ properties[key] = {
211
+ type: "boolean",
212
+ nullable: false,
213
+ ...prop._default !== void 0 ? { default: prop._default } : {}
214
+ };
215
+ } else if (prop instanceof NumberProperty) {
216
+ properties[key] = {
217
+ type: "integer",
218
+ nullable: prop._nullable,
219
+ ...prop._default !== void 0 ? { default: prop._default } : {}
220
+ };
221
+ } else if (prop instanceof DateProperty) {
222
+ properties[key] = {
223
+ type: "Date",
224
+ nullable: prop._nullable
225
+ };
226
+ } else if (prop instanceof JsonProperty) {
227
+ properties[key] = {
228
+ type: "json",
229
+ nullable: prop._nullable
230
+ };
231
+ } else if (prop instanceof EnumProperty) {
232
+ properties[key] = {
233
+ type: "string",
234
+ nullable: prop._nullable,
235
+ enum: true,
236
+ items: prop._values,
237
+ ...prop._default !== void 0 ? { default: prop._default } : {}
238
+ };
239
+ }
240
+ }
241
+ properties.created_at = {
242
+ type: "Date",
243
+ nullable: false,
244
+ onCreate: () => /* @__PURE__ */ new Date()
245
+ };
246
+ properties.updated_at = {
247
+ type: "Date",
248
+ nullable: false,
249
+ onCreate: () => /* @__PURE__ */ new Date(),
250
+ onUpdate: () => /* @__PURE__ */ new Date()
251
+ };
252
+ properties.deleted_at = {
253
+ type: "Date",
254
+ nullable: true
255
+ };
256
+ const indexes = (def.indexes ?? []).map((idx) => ({
257
+ ...idx.name ? { name: idx.name } : {},
258
+ properties: idx.columns,
259
+ unique: idx.unique ?? false
260
+ }));
261
+ return new EntitySchema({
262
+ name: def.tableName,
263
+ tableName: def.tableName,
264
+ properties,
265
+ ...indexes.length > 0 ? { indexes } : {}
266
+ });
267
+ }
268
+ function createRepository(em, entityName) {
269
+ const repo = em.getRepository(entityName);
270
+ return {
271
+ async find(filters, options = {}) {
272
+ return repo.find(filters, options);
273
+ },
274
+ async findAndCount(filters, options = {}) {
275
+ const [data, count] = await Promise.all([
276
+ repo.find(filters, options),
277
+ repo.count(filters)
278
+ ]);
279
+ return [data, count];
280
+ },
281
+ async findOne(filters) {
282
+ return repo.findOne(filters);
283
+ },
284
+ async findOneOrFail(filters) {
285
+ return repo.findOneOrFail(filters);
286
+ },
287
+ create(data) {
288
+ return repo.create(data);
289
+ },
290
+ async persistAndFlush(entity) {
291
+ await em.persistAndFlush(entity);
292
+ },
293
+ async flush() {
294
+ await em.flush();
295
+ },
296
+ async removeAndFlush(entity) {
297
+ await em.removeAndFlush(entity);
298
+ }
299
+ };
300
+ }
301
+ async function createModuleOrm(entitySchemas, databaseUrl, options = {}) {
302
+ const { MikroORM } = await import("@mikro-orm/core");
303
+ const { PostgreSqlDriver } = await import("@mikro-orm/postgresql");
304
+ const orm = await MikroORM.init({
305
+ entities: entitySchemas,
306
+ clientUrl: databaseUrl,
307
+ driver: PostgreSqlDriver,
308
+ debug: options.debug ?? false
309
+ });
310
+ const shouldSync = options.sync ?? process.env.NODE_ENV !== "production";
311
+ if (shouldSync) {
312
+ const generator = orm.getSchemaGenerator();
313
+ await generator.updateSchema({ safe: true });
314
+ }
315
+ return orm;
316
+ }
317
+ export {
318
+ BooleanProperty,
319
+ DateProperty,
320
+ EnumProperty,
321
+ IdProperty,
322
+ JsonProperty,
323
+ MeridianService,
324
+ ModelDefinition,
325
+ Module,
326
+ NumberProperty,
327
+ TextProperty,
328
+ createModuleOrm,
329
+ createRepository,
330
+ defineLink,
331
+ dmlToEntitySchema,
332
+ model
333
+ };
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@meridianjs/framework-utils",
3
+ "version": "0.1.0",
4
+ "description": "Utilities for building Meridian modules: DML, service factory, defineModule, defineLink",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.mts",
12
+ "default": "./dist/index.mjs"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ }
18
+ }
19
+ },
20
+ "scripts": {
21
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
22
+ "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
23
+ "test": "vitest run",
24
+ "typecheck": "tsc --noEmit",
25
+ "clean": "rm -rf dist",
26
+ "prepublishOnly": "npm run build"
27
+ },
28
+ "dependencies": {
29
+ "@meridianjs/types": "^0.1.0"
30
+ },
31
+ "peerDependencies": {
32
+ "@mikro-orm/core": "^6.0.0",
33
+ "@mikro-orm/postgresql": "^6.0.0"
34
+ },
35
+ "peerDependenciesMeta": {
36
+ "@mikro-orm/core": {
37
+ "optional": true
38
+ },
39
+ "@mikro-orm/postgresql": {
40
+ "optional": true
41
+ }
42
+ },
43
+ "devDependencies": {
44
+ "tsup": "^8.3.5",
45
+ "typescript": "*"
46
+ },
47
+ "files": [
48
+ "dist"
49
+ ],
50
+ "publishConfig": {
51
+ "access": "public"
52
+ }
53
+ }