@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.
- package/dist/index.d.mts +199 -0
- package/dist/index.d.ts +199 -0
- package/dist/index.js +384 -0
- package/dist/index.mjs +333 -0
- package/package.json +53 -0
package/dist/index.d.mts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|