mevn-orm 4.0.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ import { Model, DB, getDB, configure, createKnexConfig, configureDatabase, setMigrationConfig, getMigrationConfig, makeMigration, migrateLatest, migrateRollback, migrateCurrentVersion, migrateList } from './src/model.js';
2
+ export { Model, DB, getDB, configure, createKnexConfig, configureDatabase, setMigrationConfig, getMigrationConfig, makeMigration, migrateLatest, migrateRollback, migrateCurrentVersion, migrateList, };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import { Model, DB, getDB, configure, createKnexConfig, configureDatabase, setMigrationConfig, getMigrationConfig, makeMigration, migrateLatest, migrateRollback, migrateCurrentVersion, migrateList, } from './src/model.js';
2
+ export { Model, DB, getDB, configure, createKnexConfig, configureDatabase, setMigrationConfig, getMigrationConfig, makeMigration, migrateLatest, migrateRollback, migrateCurrentVersion, migrateList, };
@@ -0,0 +1,46 @@
1
+ import type { Knex } from 'knex';
2
+ declare let DB: Knex | undefined;
3
+ type SimpleDialect = 'sqlite' | 'better-sqlite3' | 'mysql' | 'mysql2' | 'postgres' | 'postgresql' | 'pg' | 'pgnative' | 'cockroachdb' | 'redshift' | 'mssql' | 'oracledb' | 'oracle';
4
+ interface SimpleDatabaseConfig {
5
+ dialect: SimpleDialect;
6
+ connectionString?: string;
7
+ filename?: string;
8
+ host?: string;
9
+ port?: number;
10
+ user?: string;
11
+ password?: string;
12
+ database?: string;
13
+ ssl?: boolean | Record<string, unknown>;
14
+ debug?: boolean;
15
+ pool?: Knex.PoolConfig;
16
+ }
17
+ interface MigrationResult {
18
+ batch: number;
19
+ log: string[];
20
+ }
21
+ /** Returns the active Knex instance or throws if the ORM has not been configured. */
22
+ declare const getDB: () => Knex;
23
+ /** Configures the ORM using a Knex config object or an existing Knex instance. */
24
+ declare const configure: (config: Knex.Config | Knex) => Knex;
25
+ /** Sets default migration options used by migration helpers. */
26
+ declare const setMigrationConfig: (config: Knex.MigratorConfig) => Knex.MigratorConfig;
27
+ /** Returns the currently configured default migration options. */
28
+ declare const getMigrationConfig: () => Knex.MigratorConfig;
29
+ /** Builds a Knex config from a simplified, dialect-first configuration object. */
30
+ declare const createKnexConfig: (config: SimpleDatabaseConfig) => Knex.Config;
31
+ /** Configures the ORM from simplified database options. */
32
+ declare const configureDatabase: (config: SimpleDatabaseConfig) => Knex;
33
+ /** Generates a migration file and returns its path. */
34
+ declare const makeMigration: (name: string, config?: Knex.MigratorConfig) => Promise<string>;
35
+ /** Runs pending migrations and returns the batch number and migration filenames. */
36
+ declare const migrateLatest: (config?: Knex.MigratorConfig) => Promise<MigrationResult>;
37
+ /** Rolls back migrations. Set `all` to true to rollback all completed batches. */
38
+ declare const migrateRollback: (config?: Knex.MigratorConfig, all?: boolean) => Promise<MigrationResult>;
39
+ /** Returns the current migration version recorded by Knex. */
40
+ declare const migrateCurrentVersion: (config?: Knex.MigratorConfig) => Promise<string>;
41
+ /** Returns completed and pending migration filenames. */
42
+ declare const migrateList: (config?: Knex.MigratorConfig) => Promise<{
43
+ completed: string[];
44
+ pending: string[];
45
+ }>;
46
+ export { DB, getDB, configure, createKnexConfig, configureDatabase, setMigrationConfig, getMigrationConfig, makeMigration, migrateLatest, migrateRollback, migrateCurrentVersion, migrateList, };
@@ -0,0 +1,196 @@
1
+ import knexModule from 'knex';
2
+ const knexFactory = knexModule.knex ??
3
+ knexModule;
4
+ const toError = (error) => {
5
+ if (error instanceof Error) {
6
+ return error;
7
+ }
8
+ return new Error(String(error));
9
+ };
10
+ let DB;
11
+ let defaultMigrationConfig = {};
12
+ /** Returns the active Knex instance or throws if the ORM has not been configured. */
13
+ const getDB = () => {
14
+ if (!DB) {
15
+ throw new Error('Mevn ORM is not configured. Call configure({ client, connection, ... }) before using Model.');
16
+ }
17
+ return DB;
18
+ };
19
+ /** Configures the ORM using a Knex config object or an existing Knex instance. */
20
+ const configure = (config) => {
21
+ if (typeof config === 'function') {
22
+ DB = config;
23
+ return DB;
24
+ }
25
+ DB = knexFactory(config);
26
+ return DB;
27
+ };
28
+ /** Sets default migration options used by migration helpers. */
29
+ const setMigrationConfig = (config) => {
30
+ defaultMigrationConfig = { ...config };
31
+ return { ...defaultMigrationConfig };
32
+ };
33
+ /** Returns the currently configured default migration options. */
34
+ const getMigrationConfig = () => ({ ...defaultMigrationConfig });
35
+ const resolveMigrationConfig = (config) => ({
36
+ ...defaultMigrationConfig,
37
+ ...(config ?? {}),
38
+ });
39
+ const normalizeDialect = (dialect) => {
40
+ switch (dialect) {
41
+ case 'sqlite':
42
+ return 'sqlite3';
43
+ case 'mysql':
44
+ return 'mysql2';
45
+ case 'postgres':
46
+ case 'postgresql':
47
+ case 'pg':
48
+ return 'pg';
49
+ case 'oracle':
50
+ return 'oracledb';
51
+ default:
52
+ return dialect;
53
+ }
54
+ };
55
+ const requireField = (value, field, dialect) => {
56
+ if (value === undefined || value === null || value === '') {
57
+ throw new Error(`Missing required field "${field}" for dialect "${dialect}".`);
58
+ }
59
+ };
60
+ const buildConnection = (config) => {
61
+ if (config.connectionString) {
62
+ return config.connectionString;
63
+ }
64
+ const client = normalizeDialect(config.dialect);
65
+ if (client === 'sqlite3' || client === 'better-sqlite3') {
66
+ requireField(config.filename, 'filename', config.dialect);
67
+ return { filename: config.filename };
68
+ }
69
+ if (client === 'mssql') {
70
+ const server = config.host;
71
+ requireField(server, 'host', config.dialect);
72
+ requireField(config.user, 'user', config.dialect);
73
+ requireField(config.database, 'database', config.dialect);
74
+ const connection = {
75
+ server: server,
76
+ user: config.user,
77
+ database: config.database,
78
+ };
79
+ if (typeof config.port === 'number') {
80
+ connection.port = config.port;
81
+ }
82
+ if (config.password !== undefined) {
83
+ connection.password = config.password;
84
+ }
85
+ if (config.ssl) {
86
+ connection.options = { encrypt: true };
87
+ }
88
+ return connection;
89
+ }
90
+ requireField(config.host, 'host', config.dialect);
91
+ requireField(config.user, 'user', config.dialect);
92
+ requireField(config.database, 'database', config.dialect);
93
+ const connection = {
94
+ host: config.host,
95
+ user: config.user,
96
+ database: config.database,
97
+ };
98
+ if (typeof config.port === 'number') {
99
+ connection.port = config.port;
100
+ }
101
+ if (config.password !== undefined) {
102
+ connection.password = config.password;
103
+ }
104
+ if (config.ssl !== undefined) {
105
+ connection.ssl = config.ssl;
106
+ }
107
+ return connection;
108
+ };
109
+ /** Builds a Knex config from a simplified, dialect-first configuration object. */
110
+ const createKnexConfig = (config) => {
111
+ const client = normalizeDialect(config.dialect);
112
+ const base = {
113
+ client,
114
+ connection: buildConnection(config),
115
+ };
116
+ if (config.pool) {
117
+ base.pool = config.pool;
118
+ }
119
+ if (typeof config.debug === 'boolean') {
120
+ base.debug = config.debug;
121
+ }
122
+ if (client === 'sqlite3') {
123
+ base.useNullAsDefault = true;
124
+ }
125
+ return base;
126
+ };
127
+ /** Configures the ORM from simplified database options. */
128
+ const configureDatabase = (config) => configure(createKnexConfig(config));
129
+ /** Generates a migration file and returns its path. */
130
+ const makeMigration = async (name, config) => {
131
+ try {
132
+ return await getDB().migrate.make(name, resolveMigrationConfig(config));
133
+ }
134
+ catch (error) {
135
+ throw toError(error);
136
+ }
137
+ };
138
+ /** Runs pending migrations and returns the batch number and migration filenames. */
139
+ const migrateLatest = async (config) => {
140
+ try {
141
+ const [batch, log] = await getDB().migrate.latest(resolveMigrationConfig(config));
142
+ return { batch, log };
143
+ }
144
+ catch (error) {
145
+ throw toError(error);
146
+ }
147
+ };
148
+ /** Rolls back migrations. Set `all` to true to rollback all completed batches. */
149
+ const migrateRollback = async (config, all = false) => {
150
+ try {
151
+ const [batch, log] = all
152
+ ? await getDB().migrate.rollback(resolveMigrationConfig(config), true)
153
+ : await getDB().migrate.rollback(resolveMigrationConfig(config));
154
+ return { batch, log };
155
+ }
156
+ catch (error) {
157
+ throw toError(error);
158
+ }
159
+ };
160
+ /** Returns the current migration version recorded by Knex. */
161
+ const migrateCurrentVersion = async (config) => {
162
+ try {
163
+ return await getDB().migrate.currentVersion(resolveMigrationConfig(config));
164
+ }
165
+ catch (error) {
166
+ throw toError(error);
167
+ }
168
+ };
169
+ /** Returns completed and pending migration filenames. */
170
+ const migrateList = async (config) => {
171
+ try {
172
+ const [completed, pending] = await getDB().migrate.list(resolveMigrationConfig(config));
173
+ const toName = (entry) => {
174
+ if (typeof entry === 'string') {
175
+ return entry;
176
+ }
177
+ if (entry && typeof entry === 'object') {
178
+ if ('name' in entry) {
179
+ return String(entry.name);
180
+ }
181
+ if ('file' in entry) {
182
+ return String(entry.file);
183
+ }
184
+ }
185
+ return String(entry);
186
+ };
187
+ return {
188
+ completed: completed.map(toName),
189
+ pending: pending.map(toName),
190
+ };
191
+ }
192
+ catch (error) {
193
+ throw toError(error);
194
+ }
195
+ };
196
+ export { DB, getDB, configure, createKnexConfig, configureDatabase, setMigrationConfig, getMigrationConfig, makeMigration, migrateLatest, migrateRollback, migrateCurrentVersion, migrateList, };
@@ -0,0 +1,51 @@
1
+ import type { Knex } from 'knex';
2
+ type Row = Record<string, unknown>;
3
+ declare class Model {
4
+ #private;
5
+ [key: string]: any;
6
+ static currentTable: string;
7
+ static currentQuery: Knex.QueryBuilder<Row, Row[]> | undefined;
8
+ fillable: string[];
9
+ hidden: string[];
10
+ modelName: string;
11
+ table: string;
12
+ id?: number;
13
+ constructor(properties?: Row);
14
+ /** Inserts the current model using `fillable` attributes and reloads it from the database. */
15
+ save(): Promise<this>;
16
+ /** Updates the current row by primary key and returns a refreshed model instance. */
17
+ update(properties: Row): Promise<this>;
18
+ /** Deletes the current row by primary key. */
19
+ delete(): Promise<void>;
20
+ /** Updates rows in the model table, optionally scoped by `where()`. */
21
+ static update(properties: Row): Promise<number | undefined>;
22
+ /** Deletes rows in the model table, optionally scoped by `where()`. */
23
+ static destroy(): Promise<number | undefined>;
24
+ /** Finds a single model by primary key. */
25
+ static find(this: typeof Model, id: number | string, columns?: string | string[]): Promise<Model | null>;
26
+ /** Finds a model by primary key or throws if it does not exist. */
27
+ static findOrFail(this: typeof Model, id: number | string, columns?: string | string[]): Promise<Model>;
28
+ /** Creates and returns a single model record. */
29
+ static create(this: typeof Model, properties: Row): Promise<Model>;
30
+ /** Creates multiple model records and returns created model instances. */
31
+ static createMany(this: typeof Model, properties: Row[]): Promise<Model[]>;
32
+ /** Returns the first matching row or creates it with merged values when missing. */
33
+ static firstOrCreate(this: typeof Model, attributes: Row, values?: Row): Promise<Model>;
34
+ /** Applies a query scope used by chained static query methods. */
35
+ static where(this: typeof Model, conditions?: Row): typeof Model;
36
+ /** Returns the first model for the current scope (or table if unscoped). */
37
+ static first(this: typeof Model, columns?: string | string[]): Promise<Model | null>;
38
+ /** Returns all models for the current scope (or table if unscoped). */
39
+ static all(this: typeof Model, columns?: string | string[]): Promise<Model[]>;
40
+ /** Returns a row count for the current scope (or table if unscoped). */
41
+ static count(this: typeof Model, column?: string): Promise<number>;
42
+ /** Removes internal and hidden fields from a model instance. */
43
+ stripColumns<T extends Model>(model: T, keepInternalState?: boolean): T;
44
+ }
45
+ interface Model {
46
+ hasOne(Related: typeof Model, localKey?: number | string, foreignKey?: string): Promise<Model | null>;
47
+ hasMany(Related: typeof Model, localKey?: number | string, foreignKey?: string): Promise<Model[]>;
48
+ belongsTo(Related: typeof Model, foreignKey?: string, ownerKey?: string): Promise<Model | null>;
49
+ }
50
+ export { Model };
51
+ export { DB, getDB, configure, createKnexConfig, configureDatabase, setMigrationConfig, getMigrationConfig, makeMigration, migrateLatest, migrateRollback, migrateCurrentVersion, migrateList, } from './config.js';
@@ -0,0 +1,243 @@
1
+ import pluralize from 'pluralize';
2
+ import { getDB } from './config.js';
3
+ import { createRelationshipMethods } from './relationships.js';
4
+ const toError = (error) => {
5
+ if (error instanceof Error) {
6
+ return error;
7
+ }
8
+ return new Error(String(error));
9
+ };
10
+ class Model {
11
+ #private;
12
+ static currentTable = pluralize(this.name.toLowerCase());
13
+ // `where()` stores a static scoped query consumed by `first()`.
14
+ static currentQuery;
15
+ fillable;
16
+ hidden;
17
+ modelName;
18
+ table;
19
+ id;
20
+ constructor(properties = {}) {
21
+ Object.assign(this, properties);
22
+ this.fillable = [];
23
+ this.hidden = [];
24
+ this.#private = ['fillable', 'hidden'];
25
+ this.modelName = this.constructor.name.toLowerCase();
26
+ this.table = pluralize(this.constructor.name.toLowerCase());
27
+ }
28
+ /** Inserts the current model using `fillable` attributes and reloads it from the database. */
29
+ async save() {
30
+ try {
31
+ const rows = {};
32
+ for (const field of this.fillable) {
33
+ rows[field] = this[field];
34
+ }
35
+ const inserted = await getDB()(this.table).insert(rows);
36
+ const idValue = Array.isArray(inserted) ? inserted[0] : inserted;
37
+ const id = typeof idValue === 'bigint' ? Number(idValue) : Number(idValue);
38
+ const fields = await getDB()(this.table).where({ id }).first();
39
+ if (!fields) {
40
+ throw new Error(`Failed to load inserted record for table "${this.table}"`);
41
+ }
42
+ Object.assign(this, fields);
43
+ this.id = id;
44
+ return this.stripColumns(this, true);
45
+ }
46
+ catch (error) {
47
+ throw toError(error);
48
+ }
49
+ }
50
+ /** Updates the current row by primary key and returns a refreshed model instance. */
51
+ async update(properties) {
52
+ if (this.id === undefined) {
53
+ throw new Error('Cannot update model without id');
54
+ }
55
+ try {
56
+ await getDB()(this.table).where({ id: this.id }).update(properties);
57
+ const fields = await getDB()(this.table).where({ id: this.id }).first();
58
+ if (!fields) {
59
+ throw new Error(`Failed to load updated record for table "${this.table}"`);
60
+ }
61
+ const next = new this.constructor(fields);
62
+ return this.stripColumns(next);
63
+ }
64
+ catch (error) {
65
+ throw toError(error);
66
+ }
67
+ }
68
+ /** Deletes the current row by primary key. */
69
+ async delete() {
70
+ if (this.id === undefined) {
71
+ throw new Error('Cannot delete model without id');
72
+ }
73
+ try {
74
+ await getDB()(this.table).where({ id: this.id }).del();
75
+ }
76
+ catch (error) {
77
+ throw toError(error);
78
+ }
79
+ }
80
+ /** Updates rows in the model table, optionally scoped by `where()`. */
81
+ static async update(properties) {
82
+ try {
83
+ const table = pluralize(this.name.toLowerCase());
84
+ const query = this.currentQuery ?? getDB()(table);
85
+ return await query.update(properties);
86
+ }
87
+ catch (error) {
88
+ throw toError(error);
89
+ }
90
+ finally {
91
+ this.currentQuery = undefined;
92
+ }
93
+ }
94
+ /** Deletes rows in the model table, optionally scoped by `where()`. */
95
+ static async destroy() {
96
+ try {
97
+ const table = pluralize(this.name.toLowerCase());
98
+ const query = this.currentQuery ?? getDB()(table);
99
+ return await query.delete();
100
+ }
101
+ catch (error) {
102
+ throw toError(error);
103
+ }
104
+ finally {
105
+ this.currentQuery = undefined;
106
+ }
107
+ }
108
+ /** Finds a single model by primary key. */
109
+ static async find(id, columns = '*') {
110
+ const table = pluralize(this.name.toLowerCase());
111
+ try {
112
+ const fields = await getDB()(table).where({ id }).first(columns);
113
+ return fields ? new this(fields) : null;
114
+ }
115
+ catch (error) {
116
+ throw toError(error);
117
+ }
118
+ }
119
+ /** Finds a model by primary key or throws if it does not exist. */
120
+ static async findOrFail(id, columns = '*') {
121
+ const found = await this.find(id, columns);
122
+ if (!found) {
123
+ throw new Error(`${this.name} with id "${id}" not found`);
124
+ }
125
+ return found;
126
+ }
127
+ /** Creates and returns a single model record. */
128
+ static async create(properties) {
129
+ const table = pluralize(this.name.toLowerCase());
130
+ try {
131
+ const inserted = await getDB()(table).insert(properties);
132
+ const idValue = Array.isArray(inserted) ? inserted[0] : inserted;
133
+ const id = typeof idValue === 'bigint' ? Number(idValue) : Number(idValue);
134
+ const record = await getDB()(table).where({ id }).first();
135
+ if (!record) {
136
+ throw new Error(`Failed to load created record for table "${table}"`);
137
+ }
138
+ const model = new this(record);
139
+ return model.stripColumns(model);
140
+ }
141
+ catch (error) {
142
+ throw toError(error);
143
+ }
144
+ }
145
+ /** Creates multiple model records and returns created model instances. */
146
+ static async createMany(properties) {
147
+ if (properties.length === 0) {
148
+ return [];
149
+ }
150
+ try {
151
+ const records = [];
152
+ for (const property of properties) {
153
+ records.push(await this.create(property));
154
+ }
155
+ return records;
156
+ }
157
+ catch (error) {
158
+ throw toError(error);
159
+ }
160
+ }
161
+ /** Returns the first matching row or creates it with merged values when missing. */
162
+ static async firstOrCreate(attributes, values = {}) {
163
+ const table = pluralize(this.name.toLowerCase());
164
+ try {
165
+ const record = await getDB()(table).where(attributes).first();
166
+ if (record) {
167
+ const model = new this(record);
168
+ return model.stripColumns(model);
169
+ }
170
+ return this.create({ ...attributes, ...values });
171
+ }
172
+ catch (error) {
173
+ throw toError(error);
174
+ }
175
+ }
176
+ /** Applies a query scope used by chained static query methods. */
177
+ static where(conditions = {}) {
178
+ const table = pluralize(this.name.toLowerCase());
179
+ this.currentQuery = getDB()(table).where(conditions);
180
+ return this;
181
+ }
182
+ /** Returns the first model for the current scope (or table if unscoped). */
183
+ static async first(columns = '*') {
184
+ try {
185
+ const table = pluralize(this.name.toLowerCase());
186
+ const query = this.currentQuery ?? getDB()(table);
187
+ const rows = await query.first(columns);
188
+ return rows ? new this(rows) : null;
189
+ }
190
+ catch (error) {
191
+ throw toError(error);
192
+ }
193
+ finally {
194
+ this.currentQuery = undefined;
195
+ }
196
+ }
197
+ /** Returns all models for the current scope (or table if unscoped). */
198
+ static async all(columns = '*') {
199
+ try {
200
+ const table = pluralize(this.name.toLowerCase());
201
+ const query = this.currentQuery ?? getDB()(table);
202
+ const rows = await query.select(columns);
203
+ return rows.map((row) => new this(row));
204
+ }
205
+ catch (error) {
206
+ throw toError(error);
207
+ }
208
+ finally {
209
+ this.currentQuery = undefined;
210
+ }
211
+ }
212
+ /** Returns a row count for the current scope (or table if unscoped). */
213
+ static async count(column = '*') {
214
+ try {
215
+ const table = pluralize(this.name.toLowerCase());
216
+ const query = this.currentQuery ?? getDB()(table);
217
+ const result = await query.count({ count: column }).first();
218
+ if (!result) {
219
+ return 0;
220
+ }
221
+ return Number(result.count);
222
+ }
223
+ catch (error) {
224
+ throw toError(error);
225
+ }
226
+ finally {
227
+ this.currentQuery = undefined;
228
+ }
229
+ }
230
+ /** Removes internal and hidden fields from a model instance. */
231
+ stripColumns(model, keepInternalState = false) {
232
+ // Hide internal ORM fields and caller-defined hidden attributes.
233
+ const privateKeys = keepInternalState ? [] : this.#private;
234
+ const hiddenKeys = Array.isArray(this.hidden) ? this.hidden : [];
235
+ for (const key of [...privateKeys, ...hiddenKeys]) {
236
+ delete model[key];
237
+ }
238
+ return model;
239
+ }
240
+ }
241
+ Object.assign(Model.prototype, createRelationshipMethods(getDB));
242
+ export { Model };
243
+ export { DB, getDB, configure, createKnexConfig, configureDatabase, setMigrationConfig, getMigrationConfig, makeMigration, migrateLatest, migrateRollback, migrateCurrentVersion, migrateList, } from './config.js';
@@ -0,0 +1,18 @@
1
+ import type { Knex } from 'knex';
2
+ type Row = Record<string, unknown>;
3
+ interface RelationshipModel {
4
+ [key: string]: unknown;
5
+ table: string;
6
+ modelName: string;
7
+ id?: number | string;
8
+ stripColumns<T extends RelationshipModel>(model: T, keepInternalState?: boolean): T;
9
+ }
10
+ type RelatedModelCtor = new (properties?: Row) => RelationshipModel;
11
+ interface RelationshipMethods {
12
+ hasOne(this: RelationshipModel, Related: RelatedModelCtor, localKey?: number | string, foreignKey?: string): Promise<RelationshipModel | null>;
13
+ hasMany(this: RelationshipModel, Related: RelatedModelCtor, localKey?: number | string, foreignKey?: string): Promise<RelationshipModel[]>;
14
+ belongsTo(this: RelationshipModel, Related: RelatedModelCtor, foreignKey?: string, ownerKey?: string): Promise<RelationshipModel | null>;
15
+ }
16
+ /** Builds relationship methods that run against the active Knex instance. */
17
+ declare const createRelationshipMethods: (getDB: () => Knex) => RelationshipMethods;
18
+ export { createRelationshipMethods };
@@ -0,0 +1,50 @@
1
+ /** Builds relationship methods that run against the active Knex instance. */
2
+ const createRelationshipMethods = (getDB) => ({
3
+ async hasOne(Related, localKey, foreignKey) {
4
+ const table = new Related().table;
5
+ const relation = {};
6
+ const keyValue = localKey ?? this.id;
7
+ const relationKey = foreignKey ?? `${this.modelName}_id`;
8
+ if (keyValue !== undefined) {
9
+ relation[relationKey] = keyValue;
10
+ const result = await getDB()(table).where(relation).first();
11
+ if (result) {
12
+ const related = new Related(result);
13
+ return related.stripColumns(related);
14
+ }
15
+ }
16
+ return null;
17
+ },
18
+ async hasMany(Related, localKey, foreignKey) {
19
+ const table = new Related().table;
20
+ const relation = {};
21
+ const keyValue = localKey ?? this.id;
22
+ const relationKey = foreignKey ?? `${this.modelName}_id`;
23
+ if (keyValue === undefined) {
24
+ return [];
25
+ }
26
+ relation[relationKey] = keyValue;
27
+ const rows = await getDB()(table).where(relation).select('*');
28
+ return rows.map((row) => {
29
+ const related = new Related(row);
30
+ return related.stripColumns(related);
31
+ });
32
+ },
33
+ async belongsTo(Related, foreignKey, ownerKey = 'id') {
34
+ const table = new Related().table;
35
+ const relation = {};
36
+ const relationKey = foreignKey ?? `${new Related().modelName}_id`;
37
+ const relationValue = this[relationKey];
38
+ if (relationValue === undefined || relationValue === null) {
39
+ return null;
40
+ }
41
+ relation[ownerKey] = relationValue;
42
+ const row = await getDB()(table).where(relation).first();
43
+ if (!row) {
44
+ return null;
45
+ }
46
+ const related = new Related(row);
47
+ return related.stripColumns(related);
48
+ },
49
+ });
50
+ export { createRelationshipMethods };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mevn-orm",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
4
4
  "description": "simple ORM for express js",
5
5
  "type": "module",
6
6
  "peerDependencies": {
@@ -56,7 +56,18 @@
56
56
  "email": "stanleymasinde1@gmail.com"
57
57
  },
58
58
  "homepage": "https://github.com/StanleyMasinde/mevn-orm#readme",
59
- "main": "index.ts",
59
+ "main": "dist/index.js",
60
+ "types": "dist/index.d.ts",
61
+ "exports": {
62
+ ".": {
63
+ "types": "./dist/index.d.ts",
64
+ "import": "./dist/index.js",
65
+ "default": "./dist/index.js"
66
+ }
67
+ },
68
+ "files": [
69
+ "dist"
70
+ ],
60
71
  "scripts": {
61
72
  "pretest": "node --import tsx initDb.ts",
62
73
  "test": "vitest run",
@@ -67,6 +78,7 @@
67
78
  "migrate:rollback": "node --import tsx scripts/migrate.ts rollback",
68
79
  "migrate:list": "node --import tsx scripts/migrate.ts list",
69
80
  "migrate:version": "node --import tsx scripts/migrate.ts version",
70
- "lint": "eslint --ext .js,.ts ./"
81
+ "lint": "eslint --ext .js,.ts ./",
82
+ "build": "tsc -p tsconfig.build.json"
71
83
  }
72
84
  }
package/.env.example DELETED
@@ -1,9 +0,0 @@
1
- NODE_ENV=testing
2
-
3
- DB_HOST=localhost
4
- DB_USER=root
5
- DB_PASSWORD=
6
- DB_DATABASE=mevn_orm
7
- DB_CLIENT=mysql2
8
-
9
- TESTING_DB=sqlite3