@neupgroup/mapper 1.6.0 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,44 +20,53 @@ Example:
20
20
  process.exit(0);
21
21
  }
22
22
  const connectionName = args[0];
23
- const type = args[1] || 'api';
23
+ const type = (args[1] || 'api').toLowerCase();
24
24
  if (!connectionName) {
25
25
  console.error('Error: Connection name is required.');
26
26
  console.log('Usage: npm run create-connection <connectionName> [type]');
27
27
  process.exit(1);
28
28
  }
29
- const connectionDir = path.resolve(process.cwd(), 'src/connection');
30
- if (!fs.existsSync(connectionDir))
31
- fs.mkdirSync(connectionDir, { recursive: true });
32
- const filePath = path.join(connectionDir, `${connectionName}.ts`);
33
- let configTemplate = '';
34
- if (type === 'mysql') {
35
- configTemplate = `
36
- type: 'mysql',
37
- host: 'localhost',
38
- port: 3306,
39
- user: 'root',
40
- password: '',
41
- database: '${connectionName}'
42
- `;
29
+ const configDir = path.resolve(process.cwd(), 'src/config');
30
+ if (!fs.existsSync(configDir))
31
+ fs.mkdirSync(configDir, { recursive: true });
32
+ const filePath = path.join(configDir, `${connectionName}.ts`);
33
+ let template = {
34
+ name: connectionName,
35
+ type: type
36
+ };
37
+ if (type === 'mysql' || type === 'postgres') {
38
+ template = {
39
+ ...template,
40
+ host: '',
41
+ port: type === 'mysql' ? 3306 : 5432,
42
+ user: '',
43
+ password: '',
44
+ database: ''
45
+ };
43
46
  }
44
47
  else if (type === 'sqlite') {
45
- configTemplate = `
46
- type: 'sqlite',
47
- filename: './${connectionName}.db'
48
- `;
48
+ template = {
49
+ ...template,
50
+ filename: ''
51
+ };
49
52
  }
50
- else {
51
- configTemplate = `
52
- type: '${type}',
53
- // Add configuration here
54
- `;
53
+ else if (type === 'mongodb') {
54
+ template = {
55
+ ...template,
56
+ url: ''
57
+ };
55
58
  }
56
- const fileContent = `import { ConnectionConfig } from '@neupgroup/mapper';
57
-
58
- export const config: ConnectionConfig = {
59
- ${configTemplate}
60
- };
59
+ else if (type === 'api') {
60
+ template = {
61
+ ...template,
62
+ baseUrl: '',
63
+ headers: {}
64
+ };
65
+ }
66
+ const fileContent = `
67
+ export const connections = [
68
+ ${JSON.stringify(template, null, 4)}
69
+ ];
61
70
  `;
62
- fs.writeFileSync(filePath, fileContent.trim());
63
- console.log(`Created connection file: ${filePath} `);
71
+ fs.writeFileSync(filePath, fileContent.trim() + '\n');
72
+ console.log(`Created connection configuration: ${filePath}`);
@@ -43,18 +43,37 @@ const filePath = path.join(migrationDir, fileName);
43
43
  const fileContent = `
44
44
  import { Mapper, TableMigrator } from '@neupgroup/mapper';
45
45
 
46
+ export const usesConnection = 'default';
47
+
46
48
  export async function up() {
47
- // const table = Mapper.schemas().table('${tableName}');
48
- // table.useConnection('default');
49
- // table.addColumn('id').type('int').isPrimary().autoIncrement();
50
- // ... add more columns
51
- // await table.exec();
52
- console.log('Migrating up: ${tableName}');
49
+ const schema = Mapper.schema().table('${tableName}');
50
+ schema.useConnection(usesConnection);
51
+
52
+ /**
53
+ * CASE 1: CREATE SCHEMA (Requires .exec())
54
+ * Use this when defining a new schema. addColumn calls are batched.
55
+ */
56
+ // schema.addColumn('id').type('int').isPrimary().autoIncrement();
57
+ // schema.addColumn('name').type('string').notNull();
58
+ // await schema.exec();
59
+
60
+ /**
61
+ * CASE 2: ALTER SCHEMA (Queued actions)
62
+ * These methods are queued and only execute when you call .exec()
63
+ */
64
+ // schema.dropColumn('old_field');
65
+ // schema.dropUnique('field_name');
66
+ // await schema.exec();
53
67
  }
54
68
 
55
69
  export async function down() {
56
- // Drop table or revert changes
57
- console.log('Migrating down: ${tableName}');
70
+ /**
71
+ * DROP SCHEMA (Immediate action)
72
+ * This will drop the schema from the DB and delete the local schema file.
73
+ */
74
+ const schema = Mapper.schema().table('${tableName}');
75
+ schema.useConnection(usesConnection);
76
+ await schema.dropTable().exec();
58
77
  }
59
78
  `;
60
79
  fs.writeFileSync(filePath, fileContent.trim());
@@ -7,20 +7,23 @@ if (args.includes('--help') || args.includes('-h')) {
7
7
  Usage: npm run migrate [command]
8
8
 
9
9
  Commands:
10
- up Run all pending migrations (default)
11
- down Roll back the last completed migration
10
+ (none) Run all pending migrations to reach top level
11
+ up Migrate one level up
12
+ down Migrate one level down (rollback)
13
+ refresh Roll back all migrations and run them all again
12
14
 
13
15
  Options:
14
16
  --help, -h Show this help message
15
17
 
16
18
  Description:
17
19
  This command will look for migration files in src/migration,
18
- load database connections from src/connection, and execute
19
- pending changes on your database while updating local schema files.
20
+ load database connections from src/config and src/connection,
21
+ and execute pending changes on your database while updating local schema files.
20
22
  `);
21
23
  process.exit(0);
22
24
  }
23
- const command = args[0] || 'up'; // 'up' or 'down'
25
+ // Commands: '' (all up), 'up' (1 up), 'down' (1 down), 'refresh' (all down then all up)
26
+ const command = args[0] || 'all';
24
27
  const migrationDir = path.resolve(process.cwd(), 'src/migration');
25
28
  const indexFilePath = path.join(migrationDir, 'index.ts');
26
29
  if (!fs.existsSync(indexFilePath)) {
@@ -29,23 +32,31 @@ if (!fs.existsSync(indexFilePath)) {
29
32
  }
30
33
  async function run() {
31
34
  // Load connections
32
- const connectionDir = path.resolve(process.cwd(), 'src/connection');
33
- if (fs.existsSync(connectionDir)) {
34
- const { StaticMapper } = await import('../fluent-mapper.js');
35
- const connFiles = fs.readdirSync(connectionDir).filter(f => f.endsWith('.ts'));
36
- for (const file of connFiles) {
37
- const name = file.replace('.ts', '');
38
- const filePath = path.resolve(connectionDir, file);
39
- try {
40
- const mod = await import('file://' + filePath);
41
- const config = mod.config;
42
- if (config) {
43
- console.log(`Loading connection: ${name}`);
44
- StaticMapper.makeConnection(name, config.type, config);
35
+ const dirs = [
36
+ path.resolve(process.cwd(), 'src/connection'),
37
+ path.resolve(process.cwd(), 'src/config')
38
+ ];
39
+ for (const dir of dirs) {
40
+ if (fs.existsSync(dir)) {
41
+ const { StaticMapper } = await import('../fluent-mapper.js');
42
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.ts'));
43
+ for (const file of files) {
44
+ const name = file.replace('.ts', '');
45
+ const filePath = path.resolve(dir, file);
46
+ try {
47
+ const mod = await import('file://' + filePath);
48
+ if (mod.connections && Array.isArray(mod.connections)) {
49
+ for (const conn of mod.connections) {
50
+ StaticMapper.makeConnection(conn.name, conn.type, conn);
51
+ }
52
+ }
53
+ else if (mod.config) {
54
+ StaticMapper.makeConnection(name, mod.config.type, mod.config);
55
+ }
56
+ }
57
+ catch (e) {
58
+ console.warn(`Failed to load connection from ${file}: ${e.message}`);
45
59
  }
46
- }
47
- catch (e) {
48
- console.warn(`Failed to load connection ${name}: ${e.message}`);
49
60
  }
50
61
  }
51
62
  }
@@ -60,51 +71,75 @@ async function run() {
60
71
  const migrations = matchMigrations[1].split(',').map(s => s.trim().replace(/['"]/g, '')).filter(Boolean);
61
72
  let completed = matchCompleted ? matchCompleted[1].split(',').map(s => s.trim().replace(/['"]/g, '')).filter(Boolean) : [];
62
73
  let currentVersion = matchVersion ? parseInt(matchVersion[1]) : -1;
63
- if (command === 'up') {
74
+ // Helper functions
75
+ const runUp = async (migrationName) => {
76
+ console.log(`Running migration UP: ${migrationName}...`);
77
+ const filePath = path.join(migrationDir, `${migrationName}.ts`);
78
+ const mod = await import('file://' + path.resolve(filePath));
79
+ if (mod.up) {
80
+ await mod.up();
81
+ completed.push(migrationName);
82
+ currentVersion = migrations.indexOf(migrationName);
83
+ console.log(`Completed UP: ${migrationName}`);
84
+ return true;
85
+ }
86
+ else {
87
+ console.error(`Migration ${migrationName} does not have an up() function.`);
88
+ return false;
89
+ }
90
+ };
91
+ const runDown = async (migrationName) => {
92
+ console.log(`Rolling back migration DOWN: ${migrationName}...`);
93
+ const filePath = path.join(migrationDir, `${migrationName}.ts`);
94
+ const mod = await import('file://' + path.resolve(filePath));
95
+ if (mod.down) {
96
+ await mod.down();
97
+ completed.pop();
98
+ currentVersion = completed.length > 0 ? migrations.indexOf(completed[completed.length - 1]) : -1;
99
+ console.log(`Completed DOWN: ${migrationName}`);
100
+ return true;
101
+ }
102
+ else {
103
+ console.error(`Migration ${migrationName} does not have a down() function.`);
104
+ return false;
105
+ }
106
+ };
107
+ if (command === 'all' || command === 'up') {
64
108
  const pending = migrations.filter(m => !completed.includes(m));
65
109
  if (pending.length === 0) {
66
110
  console.log('No pending migrations.');
67
- return;
68
111
  }
69
- console.log(`Found ${pending.length} pending migrations.`);
70
- for (const m of pending) {
71
- console.log(`Running migration UP: ${m}...`);
72
- const filePath = path.join(migrationDir, `${m}.ts`);
73
- const absolutePath = path.resolve(filePath);
74
- const mod = await import('file://' + absolutePath);
75
- if (mod.up) {
76
- await mod.up();
77
- completed.push(m);
78
- currentVersion = migrations.indexOf(m);
79
- console.log(`Completed UP: ${m}`);
80
- }
81
- else {
82
- console.error(`Migration ${m} does not have an up() function.`);
83
- break;
112
+ else {
113
+ const toRun = command === 'up' ? [pending[0]] : pending;
114
+ for (const m of toRun) {
115
+ if (!(await runUp(m)))
116
+ break;
84
117
  }
85
118
  }
86
119
  }
87
120
  else if (command === 'down') {
88
121
  if (completed.length === 0) {
89
122
  console.log('No migrations to roll back.');
90
- return;
91
- }
92
- const lastMigrationName = completed[completed.length - 1];
93
- console.log(`Rolling back migration: ${lastMigrationName}...`);
94
- const filePath = path.join(migrationDir, `${lastMigrationName}.ts`);
95
- const absolutePath = path.resolve(filePath);
96
- const mod = await import('file://' + absolutePath);
97
- if (mod.down) {
98
- await mod.down();
99
- completed.pop();
100
- currentVersion = completed.length > 0 ? migrations.indexOf(completed[completed.length - 1]) : -1;
101
- console.log(`Completed DOWN: ${lastMigrationName}`);
102
123
  }
103
124
  else {
104
- console.error(`Migration ${lastMigrationName} does not have a down() function.`);
125
+ const lastMigrationName = completed[completed.length - 1];
126
+ await runDown(lastMigrationName);
127
+ }
128
+ }
129
+ else if (command === 'refresh') {
130
+ console.log('Refreshing migrations (Rollback all then run all)...');
131
+ // 1. Rollback all
132
+ const toRollback = [...completed].reverse();
133
+ for (const m of toRollback) {
134
+ await runDown(m);
135
+ }
136
+ // 2. Run all
137
+ for (const m of migrations) {
138
+ if (!(await runUp(m)))
139
+ break;
105
140
  }
106
141
  }
107
- // Update index.ts
142
+ // Save state back to index.ts
108
143
  const indexContent = `
109
144
  export const migrations = [
110
145
  ${migrations.map(m => ` '${m}'`).join(',\n')}
@@ -117,7 +152,7 @@ ${completed.map(m => ` '${m}'`).join(',\n')}
117
152
  export const currentVersion = ${currentVersion};
118
153
  `;
119
154
  fs.writeFileSync(indexFilePath, indexContent.trim() + '\n');
120
- console.log(`Migration runner finished. Current version: ${currentVersion}`);
155
+ console.log(`Migration runner finished. Current version index: ${currentVersion}`);
121
156
  }
122
157
  run().catch(err => {
123
158
  console.error('Migration failed:', err);
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Automatically discovers and registers connections and schemas
3
+ * from the standard directory structure.
4
+ */
5
+ export declare function discover(): Promise<void>;
@@ -0,0 +1,79 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { StaticMapper } from './fluent-mapper.js';
4
+ /**
5
+ * Automatically discovers and registers connections and schemas
6
+ * from the standard directory structure.
7
+ */
8
+ export async function discover() {
9
+ // 1. Discover Connections
10
+ const configDirs = [
11
+ path.resolve(process.cwd(), 'src/config'),
12
+ path.resolve(process.cwd(), 'src/connection')
13
+ ];
14
+ for (const dir of configDirs) {
15
+ if (fs.existsSync(dir)) {
16
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.ts') || f.endsWith('.js'));
17
+ for (const file of files) {
18
+ const filePath = path.resolve(dir, file);
19
+ try {
20
+ const mod = await import('file://' + filePath);
21
+ // Support connections array
22
+ if (mod.connections && Array.isArray(mod.connections)) {
23
+ for (const conn of mod.connections) {
24
+ StaticMapper.makeConnection(conn.name, conn.type, conn);
25
+ }
26
+ }
27
+ // Support legacy config object
28
+ else if (mod.config) {
29
+ const name = file.split('.')[0];
30
+ StaticMapper.makeConnection(name, mod.config.type, mod.config);
31
+ }
32
+ }
33
+ catch (e) {
34
+ // console.warn(`Discovery: Failed to load connection from ${file}`);
35
+ }
36
+ }
37
+ }
38
+ }
39
+ // 2. Discover Schemas
40
+ const schemasDir = path.resolve(process.cwd(), 'src/schemas');
41
+ if (fs.existsSync(schemasDir)) {
42
+ const files = fs.readdirSync(schemasDir).filter(f => f.endsWith('.ts') || f.endsWith('.js'));
43
+ for (const file of files) {
44
+ const schemaName = file.split('.')[0];
45
+ const filePath = path.resolve(schemasDir, file);
46
+ try {
47
+ const mod = await import('file://' + filePath);
48
+ // Look for the exported schema object (usually named after the file/table)
49
+ const schemaDef = mod[schemaName] || mod.schema || mod.default;
50
+ if (schemaDef && schemaDef.fields) {
51
+ const connectionName = schemaDef.usesConnection || 'default';
52
+ StaticMapper.connection(connectionName)
53
+ .schema(schemaName)
54
+ .collection(schemaDef.collectionName || schemaName)
55
+ .structure(schemaDef.fields);
56
+ // Apply options if present
57
+ if (schemaDef.insertableFields || schemaDef.updatableFields) {
58
+ const manager = StaticMapper.getFluentMapper().mapper.getSchemaManager();
59
+ const schema = manager.create(schemaName); // This might throw if exists, should use update
60
+ // ... existing StaticMapper already handles this in discovery pattern?
61
+ // Actually StaticMapper.schemas(schemaName) works.
62
+ const wrapper = StaticMapper.schemas(schemaName);
63
+ if (schemaDef.insertableFields)
64
+ wrapper.insertableFields = schemaDef.insertableFields;
65
+ if (schemaDef.updatableFields)
66
+ wrapper.updatableFields = schemaDef.updatableFields;
67
+ if (schemaDef.massUpdateable !== undefined)
68
+ wrapper.massEditAllowed = schemaDef.massUpdateable;
69
+ if (schemaDef.massDeletable !== undefined)
70
+ wrapper.massDeleteAllowed = schemaDef.massDeletable;
71
+ }
72
+ }
73
+ }
74
+ catch (e) {
75
+ // console.warn(`Discovery: Failed to load schema from ${file}`);
76
+ }
77
+ }
78
+ }
79
+ }
package/dist/errors.js CHANGED
@@ -40,7 +40,7 @@ export class SchemaExistingError extends MapperError {
40
40
  }
41
41
  export class SchemaMissingError extends MapperError {
42
42
  constructor(name) {
43
- super(`Unknown schema '${name}'.`, 'SCHEMA_UNKNOWN', `Ensure you have defined the schema '${name}' using 'Mapper.schema().create("${name}")...'`);
43
+ super(`Unknown schema '${name}'.`, 'SCHEMA_UNKNOWN', `The schema '${name}' is not registered. Ensure you have run migrations, called 'Mapper.discover()', or defined it manually using 'Mapper.schema().create("${name}")'.`);
44
44
  }
45
45
  }
46
46
  export class SchemaConfigurationError extends MapperError {
@@ -1,10 +1,28 @@
1
- import { SchemaManager, ConnectionType } from './index.js';
1
+ import { Connections, SchemaManager, type SchemaDef, ConnectionType } from './index.js';
2
2
  import { TableMigrator } from './migrator.js';
3
3
  export declare class FluentQueryBuilder {
4
4
  private mapper;
5
5
  private schemaName;
6
6
  private query;
7
- constructor(mapper: any, schemaName: string);
7
+ private _migrator?;
8
+ constructor(mapper: any, schemaName: string, connectionName?: string);
9
+ getDef(): SchemaDef;
10
+ set fields(config: any);
11
+ set insertableFields(val: string[]);
12
+ set updatableFields(val: string[]);
13
+ set deleteType(val: 'softDelete' | 'hardDelete');
14
+ set massDeleteAllowed(val: boolean);
15
+ set massEditAllowed(val: boolean);
16
+ structure(config: any): this;
17
+ collection(collectionName: string): this;
18
+ get migrator(): TableMigrator;
19
+ useConnection(name: string): this;
20
+ addColumn(name: string): import("./migrator.js").ColumnBuilder;
21
+ selectColumn(name: string): import("./migrator.js").ColumnBuilder;
22
+ dropColumn(name: string): this;
23
+ drop(): this;
24
+ exec(): Promise<void>;
25
+ dropTable(): Promise<void>;
8
26
  where(field: string, value: any, operator?: string): this;
9
27
  whereComplex(raw: string): this;
10
28
  limit(n: number): this;
@@ -69,11 +87,11 @@ export declare class FluentConnectionSelector {
69
87
  private mapper;
70
88
  private connectionName;
71
89
  constructor(mapper: any, connectionName: string);
72
- schema(schemaName: string): FluentSchemaBuilder;
90
+ schema(schemaName: string): FluentQueryBuilder;
91
+ schemas(schemaName: string): FluentQueryBuilder;
73
92
  query(schemaName: string): FluentQueryBuilder;
74
93
  table(tableName: string): FluentQueryBuilder;
75
94
  collection(collectionName: string): FluentQueryBuilder;
76
- schemas(schemaName: string): FluentSchemaWrapper;
77
95
  path(path: string): FluentApiRequestBuilder;
78
96
  header(key: string | Record<string, string | string[]>, value?: string | string[]): FluentApiRequestBuilder;
79
97
  headers(headers: Record<string, string> | any[]): FluentApiRequestBuilder;
@@ -87,6 +105,8 @@ export declare class FluentMapper {
87
105
  private mapper;
88
106
  constructor(mapper: any);
89
107
  query(schemaName: string): FluentQueryBuilder;
108
+ schema(name: string): FluentQueryBuilder;
109
+ table(name: string): FluentQueryBuilder;
90
110
  makeConnection(name: string, type: ConnectionType, config: Record<string, any>): FluentConnectionBuilder;
91
111
  useConnection(connectionName: string): FluentConnectionSelector;
92
112
  connection(connectionOrConfig: string | Record<string, any>): FluentConnectionSelector;
@@ -96,6 +116,7 @@ export declare class FluentMapper {
96
116
  add(schemaName: string, data: Record<string, any>): Promise<any>;
97
117
  update(schemaName: string, filters: Record<string, any>, data: Record<string, any>): Promise<void>;
98
118
  delete(schemaName: string, filters: Record<string, any>): Promise<void>;
119
+ dropTable(name: string): Promise<void>;
99
120
  }
100
121
  export declare class StaticMapper {
101
122
  private static instance;
@@ -103,36 +124,27 @@ export declare class StaticMapper {
103
124
  static makeConnection(name: string, type: ConnectionType, config: Record<string, any>): FluentConnectionBuilder;
104
125
  static makeTempConnection(type: ConnectionType, config: Record<string, any>): FluentConnectionBuilder;
105
126
  static query(schemaName: string): FluentQueryBuilder;
127
+ static schema(name: string): FluentQueryBuilder;
128
+ static schema(): SchemaManagerWrapper;
129
+ static table(name: string): FluentQueryBuilder;
106
130
  static connection(connectionOrConfig: string | Record<string, any>): FluentConnectionSelector;
107
131
  static useConnection(connectionName: string): FluentConnectionSelector;
108
- static schemas(name?: string): FluentSchemaWrapper | SchemaManagerWrapper;
132
+ static schemas(name?: string): any;
109
133
  static get(schemaName: string, filters?: Record<string, any>): Promise<Record<string, any>[]>;
110
134
  static getOne(schemaName: string, filters?: Record<string, any>): Promise<Record<string, any> | null>;
111
135
  static add(schemaName: string, data: Record<string, any>): Promise<any>;
112
136
  static update(schemaName: string, filters: Record<string, any>, data: Record<string, any>): Promise<void>;
113
137
  static delete(schemaName: string, filters: Record<string, any>): Promise<void>;
138
+ static dropTable(name: string): Promise<void>;
139
+ static getConnections(): Connections;
140
+ static discover(): Promise<void>;
114
141
  }
115
142
  export declare const Mapper: typeof StaticMapper;
116
143
  export default Mapper;
117
- export declare class FluentSchemaWrapper {
118
- private manager;
119
- private name;
120
- private connectionName?;
121
- constructor(manager: SchemaManager, name: string, connectionName?: string | undefined);
122
- private getDef;
123
- set fields(config: any);
124
- set insertableFields(val: string[]);
125
- set updatableFields(val: string[]);
126
- set deleteType(val: 'softDelete' | 'hardDelete');
127
- set massDeleteAllowed(val: boolean);
128
- set massEditAllowed(val: boolean);
129
- get(...fields: string[]): any;
130
- limit(n: number): FluentQueryBuilder;
131
- offset(n: number): FluentQueryBuilder;
132
- insert(data: Record<string, any>): Promise<any>;
133
- }
134
144
  export declare class SchemaManagerWrapper {
135
145
  private manager;
136
146
  constructor(manager: SchemaManager);
137
147
  table(name: string): TableMigrator;
148
+ schema(name: string): TableMigrator;
149
+ dropTable(name: string): Promise<void>;
138
150
  }