navis.js 5.6.0 → 5.7.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/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  A lightweight, serverless-first, microservice API framework designed for AWS Lambda and Node.js.
4
4
 
5
5
  **Author:** Syed Imran Ali
6
- **Version:** 5.6.0
6
+ **Version:** 5.7.0
7
7
  **License:** MIT
8
8
 
9
9
  ## Philosophy
@@ -190,13 +190,21 @@ navis metrics
190
190
  - ✅ **Enhanced database pool** - Support for 5 database types (PostgreSQL, MySQL, MongoDB, SQLite, SQL Server)
191
191
  - ✅ **Improved connection handling** - Better error handling and connection management
192
192
 
193
- ### v5.6 (Current)
193
+ ### v5.6
194
194
  - ✅ **Advanced query builders** - Fluent SQL query builder for all SQL databases
195
195
  - ✅ **MongoDB query builder** - Fluent MongoDB query builder with aggregation support
196
196
  - ✅ **Type-safe queries** - Full TypeScript support for query builders
197
197
  - ✅ **Complex queries** - Support for JOINs, nested WHERE conditions, GROUP BY, HAVING, ORDER BY
198
198
  - ✅ **Database-agnostic** - Automatic SQL dialect handling (PostgreSQL, MySQL, SQLite, SQL Server)
199
199
 
200
+ ### v5.7 (Current)
201
+ - ✅ **ORM-like features** - Model definitions with relationships, hooks, and validation
202
+ - ✅ **Database migrations** - Migration system with up/down support and tracking
203
+ - ✅ **Model relationships** - hasMany, belongsTo, hasOne relationship definitions
204
+ - ✅ **Lifecycle hooks** - beforeSave, afterSave, beforeCreate, afterCreate, etc.
205
+ - ✅ **Change tracking** - isDirty, getChanged for detecting model modifications
206
+ - ✅ **TypeScript support** - Full type definitions for models and migrations
207
+
200
208
  ## API Reference
201
209
 
202
210
  ### NavisApp
@@ -835,6 +843,9 @@ Future versions may include:
835
843
  ## Documentation
836
844
 
837
845
  - [V2 Features Guide](./docs/V2_FEATURES.md) - Complete v2 features documentation
846
+ - [V5.6 Features Guide](./docs/V5.6_FEATURES.md) - Advanced query builders documentation
847
+ - [V5.7 Features Guide](./docs/V5.7_FEATURES.md) - ORM-like features and migrations documentation
848
+ - [V5.7 Features Guide](./docs/V5.7_FEATURES.md) - ORM-like features and migrations documentation
838
849
  - [V3 Features Guide](./docs/V3_FEATURES.md) - Complete v3 features documentation
839
850
  - [V4 Features Guide](./docs/V4_FEATURES.md) - Complete v4 features documentation
840
851
  - [V5 Features Guide](./docs/V5_FEATURES.md) - Complete v5 features documentation
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Migration: Create users table
3
+ */
4
+
5
+ module.exports = {
6
+ async up(dbPool) {
7
+ const dbType = dbPool.type.toLowerCase();
8
+
9
+ if (dbType === 'mongodb') {
10
+ // MongoDB: Create collection (collections are created automatically)
11
+ const collection = dbPool.db.collection('users');
12
+ await collection.createIndex({ email: 1 }, { unique: true });
13
+ } else {
14
+ // SQL: Create table
15
+ const sql = `
16
+ CREATE TABLE IF NOT EXISTS users (
17
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
18
+ name TEXT NOT NULL,
19
+ email TEXT NOT NULL UNIQUE,
20
+ age INTEGER,
21
+ status TEXT DEFAULT 'active',
22
+ created_at DATETIME,
23
+ updated_at DATETIME
24
+ )
25
+ `.replace(/AUTOINCREMENT/g, dbType === 'postgres' || dbType === 'postgresql'
26
+ ? 'SERIAL'
27
+ : dbType === 'mysql' || dbType === 'mariadb'
28
+ ? 'AUTO_INCREMENT'
29
+ : dbType === 'mssql' || dbType === 'sqlserver'
30
+ ? 'IDENTITY(1,1)'
31
+ : 'AUTOINCREMENT'
32
+ ).replace(/DATETIME/g, dbType === 'postgres' || dbType === 'postgresql'
33
+ ? 'TIMESTAMP'
34
+ : dbType === 'mysql' || dbType === 'mariadb'
35
+ ? 'DATETIME'
36
+ : dbType === 'mssql' || dbType === 'sqlserver'
37
+ ? 'DATETIME2'
38
+ : 'DATETIME'
39
+ );
40
+
41
+ await dbPool.query(sql);
42
+ }
43
+ },
44
+
45
+ async down(dbPool) {
46
+ const dbType = dbPool.type.toLowerCase();
47
+
48
+ if (dbType === 'mongodb') {
49
+ const collection = dbPool.db.collection('users');
50
+ await collection.drop();
51
+ } else {
52
+ await dbPool.query('DROP TABLE IF EXISTS users');
53
+ }
54
+ },
55
+ };
56
+
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Migration: Create posts table
3
+ */
4
+
5
+ module.exports = {
6
+ async up(dbPool) {
7
+ const dbType = dbPool.type.toLowerCase();
8
+
9
+ if (dbType === 'mongodb') {
10
+ const collection = dbPool.db.collection('posts');
11
+ await collection.createIndex({ user_id: 1 });
12
+ await collection.createIndex({ created_at: -1 });
13
+ } else {
14
+ const sql = `
15
+ CREATE TABLE IF NOT EXISTS posts (
16
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
17
+ title TEXT NOT NULL,
18
+ content TEXT,
19
+ user_id INTEGER NOT NULL,
20
+ created_at DATETIME,
21
+ updated_at DATETIME,
22
+ FOREIGN KEY (user_id) REFERENCES users(id)
23
+ )
24
+ `.replace(/AUTOINCREMENT/g, dbType === 'postgres' || dbType === 'postgresql'
25
+ ? 'SERIAL'
26
+ : dbType === 'mysql' || dbType === 'mariadb'
27
+ ? 'AUTO_INCREMENT'
28
+ : dbType === 'mssql' || dbType === 'sqlserver'
29
+ ? 'IDENTITY(1,1)'
30
+ : 'AUTOINCREMENT'
31
+ ).replace(/DATETIME/g, dbType === 'postgres' || dbType === 'postgresql'
32
+ ? 'TIMESTAMP'
33
+ : dbType === 'mysql' || dbType === 'mariadb'
34
+ ? 'DATETIME'
35
+ : dbType === 'mssql' || dbType === 'sqlserver'
36
+ ? 'DATETIME2'
37
+ : 'DATETIME'
38
+ );
39
+
40
+ await dbPool.query(sql);
41
+ }
42
+ },
43
+
44
+ async down(dbPool) {
45
+ const dbType = dbPool.type.toLowerCase();
46
+
47
+ if (dbType === 'mongodb') {
48
+ const collection = dbPool.db.collection('posts');
49
+ await collection.drop();
50
+ } else {
51
+ await dbPool.query('DROP TABLE IF EXISTS posts');
52
+ }
53
+ },
54
+ };
55
+
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Migration: Create comments table
3
+ */
4
+
5
+ module.exports = {
6
+ async up(dbPool) {
7
+ const dbType = dbPool.type.toLowerCase();
8
+
9
+ if (dbType === 'mongodb') {
10
+ const collection = dbPool.db.collection('comments');
11
+ await collection.createIndex({ post_id: 1 });
12
+ await collection.createIndex({ user_id: 1 });
13
+ } else {
14
+ const sql = `
15
+ CREATE TABLE IF NOT EXISTS comments (
16
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
17
+ text TEXT NOT NULL,
18
+ post_id INTEGER NOT NULL,
19
+ user_id INTEGER NOT NULL,
20
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
21
+ FOREIGN KEY (post_id) REFERENCES posts(id),
22
+ FOREIGN KEY (user_id) REFERENCES users(id)
23
+ )
24
+ `.replace(/AUTOINCREMENT/g, dbType === 'postgres' || dbType === 'postgresql'
25
+ ? 'SERIAL'
26
+ : dbType === 'mysql' || dbType === 'mariadb'
27
+ ? 'AUTO_INCREMENT'
28
+ : dbType === 'mssql' || dbType === 'sqlserver'
29
+ ? 'IDENTITY(1,1)'
30
+ : 'AUTOINCREMENT'
31
+ ).replace(/DATETIME/g, dbType === 'postgres' || dbType === 'postgresql'
32
+ ? 'TIMESTAMP'
33
+ : dbType === 'mysql' || dbType === 'mariadb'
34
+ ? 'DATETIME'
35
+ : dbType === 'mssql' || dbType === 'sqlserver'
36
+ ? 'DATETIME2'
37
+ : 'DATETIME'
38
+ );
39
+
40
+ await dbPool.query(sql);
41
+ }
42
+ },
43
+
44
+ async down(dbPool) {
45
+ const dbType = dbPool.type.toLowerCase();
46
+
47
+ if (dbType === 'mongodb') {
48
+ const collection = dbPool.db.collection('comments');
49
+ await collection.drop();
50
+ } else {
51
+ await dbPool.query('DROP TABLE IF EXISTS comments');
52
+ }
53
+ },
54
+ };
55
+
@@ -0,0 +1,319 @@
1
+ /**
2
+ * ORM-like Features and Migrations Demo - Navis.js
3
+ * Demonstrates Model definitions and database migrations (v5.7)
4
+ */
5
+
6
+ const { NavisApp, createPool, Model, createMigration, response } = require('../src/index');
7
+ const path = require('path');
8
+
9
+ const app = new NavisApp();
10
+
11
+ // ============================================
12
+ // Define Models
13
+ // ============================================
14
+
15
+ class User extends Model {
16
+ static get tableName() {
17
+ return 'users';
18
+ }
19
+
20
+ static get primaryKey() {
21
+ return 'id';
22
+ }
23
+
24
+ async validate() {
25
+ if (!this.email || !this.email.includes('@')) {
26
+ throw new Error('Invalid email address');
27
+ }
28
+ return true;
29
+ }
30
+
31
+ async beforeSave() {
32
+ if (this._isNew) {
33
+ this.created_at = new Date();
34
+ }
35
+ this.updated_at = new Date();
36
+ }
37
+ }
38
+
39
+ class Post extends Model {
40
+ static get tableName() {
41
+ return 'posts';
42
+ }
43
+
44
+ static get primaryKey() {
45
+ return 'id';
46
+ }
47
+
48
+ async beforeSave() {
49
+ if (this._isNew) {
50
+ this.created_at = new Date();
51
+ }
52
+ this.updated_at = new Date();
53
+ }
54
+ }
55
+
56
+ class Comment extends Model {
57
+ static get tableName() {
58
+ return 'comments';
59
+ }
60
+
61
+ static get primaryKey() {
62
+ return 'id';
63
+ }
64
+ }
65
+
66
+ // Define relationships
67
+ Post.belongsTo('author', User, 'user_id');
68
+ User.hasMany('posts', Post, 'user_id');
69
+ Post.hasMany('comments', Comment, 'post_id');
70
+ Comment.belongsTo('post', Post, 'post_id');
71
+ Comment.belongsTo('author', User, 'user_id');
72
+
73
+ // ============================================
74
+ // API Routes
75
+ // ============================================
76
+
77
+ // Initialize database
78
+ let db = null;
79
+
80
+ app.get('/init', async (req, res) => {
81
+ try {
82
+ db = createPool({
83
+ type: 'sqlite',
84
+ connectionString: ':memory:',
85
+ });
86
+
87
+ await db.connect();
88
+
89
+ // Set database for models
90
+ User.setDatabase(db);
91
+ Post.setDatabase(db);
92
+ Comment.setDatabase(db);
93
+
94
+ // Run migrations
95
+ const migration = createMigration(db, path.join(__dirname, 'migrations'));
96
+ await migration.init();
97
+ const result = await migration.up();
98
+
99
+ response.success(res, {
100
+ message: 'Database initialized',
101
+ migrations: result,
102
+ });
103
+ } catch (error) {
104
+ response.error(res, `Init error: ${error.message}`, 500);
105
+ }
106
+ });
107
+
108
+ // Create user
109
+ app.post('/users', async (req, res) => {
110
+ try {
111
+ const user = await User.create({
112
+ name: req.body.name || 'John Doe',
113
+ email: req.body.email || 'john@example.com',
114
+ age: req.body.age || 30,
115
+ });
116
+
117
+ response.success(res, {
118
+ message: 'User created',
119
+ user: user.toJSON(),
120
+ }, 201);
121
+ } catch (error) {
122
+ response.error(res, `Error: ${error.message}`, 400);
123
+ }
124
+ });
125
+
126
+ // Get all users
127
+ app.get('/users', async (req, res) => {
128
+ try {
129
+ const users = await User.find({}, {
130
+ orderBy: 'name',
131
+ orderDirection: 'ASC',
132
+ });
133
+
134
+ response.success(res, {
135
+ users: users.map(u => u.toJSON()),
136
+ });
137
+ } catch (error) {
138
+ response.error(res, `Error: ${error.message}`, 500);
139
+ }
140
+ });
141
+
142
+ // Get user by ID
143
+ app.get('/users/:id', async (req, res) => {
144
+ try {
145
+ const user = await User.findById(req.params.id);
146
+
147
+ if (!user) {
148
+ return response.error(res, 'User not found', 404);
149
+ }
150
+
151
+ response.success(res, {
152
+ user: user.toJSON(),
153
+ });
154
+ } catch (error) {
155
+ response.error(res, `Error: ${error.message}`, 500);
156
+ }
157
+ });
158
+
159
+ // Update user
160
+ app.put('/users/:id', async (req, res) => {
161
+ try {
162
+ const user = await User.findById(req.params.id);
163
+
164
+ if (!user) {
165
+ return response.error(res, 'User not found', 404);
166
+ }
167
+
168
+ // Update fields
169
+ if (req.body.name) user.name = req.body.name;
170
+ if (req.body.email) user.email = req.body.email;
171
+ if (req.body.age) user.age = req.body.age;
172
+
173
+ await user.save();
174
+
175
+ response.success(res, {
176
+ message: 'User updated',
177
+ user: user.toJSON(),
178
+ });
179
+ } catch (error) {
180
+ response.error(res, `Error: ${error.message}`, 400);
181
+ }
182
+ });
183
+
184
+ // Delete user
185
+ app.delete('/users/:id', async (req, res) => {
186
+ try {
187
+ const user = await User.findById(req.params.id);
188
+
189
+ if (!user) {
190
+ return response.error(res, 'User not found', 404);
191
+ }
192
+
193
+ await user.delete();
194
+
195
+ response.success(res, {
196
+ message: 'User deleted',
197
+ });
198
+ } catch (error) {
199
+ response.error(res, `Error: ${error.message}`, 500);
200
+ }
201
+ });
202
+
203
+ // Create post
204
+ app.post('/posts', async (req, res) => {
205
+ try {
206
+ const post = await Post.create({
207
+ title: req.body.title || 'My First Post',
208
+ content: req.body.content || 'This is the content',
209
+ user_id: req.body.user_id || 1,
210
+ });
211
+
212
+ response.success(res, {
213
+ message: 'Post created',
214
+ post: post.toJSON(),
215
+ }, 201);
216
+ } catch (error) {
217
+ response.error(res, `Error: ${error.message}`, 400);
218
+ }
219
+ });
220
+
221
+ // Get posts with author
222
+ app.get('/posts', async (req, res) => {
223
+ try {
224
+ const posts = await Post.find({}, {
225
+ orderBy: 'created_at',
226
+ orderDirection: 'DESC',
227
+ });
228
+
229
+ // Load relationships
230
+ const postsWithAuthor = await Promise.all(
231
+ posts.map(async (post) => {
232
+ const postData = post.toJSON();
233
+ const author = await post.author;
234
+ return {
235
+ ...postData,
236
+ author: author ? author.toJSON() : null,
237
+ };
238
+ })
239
+ );
240
+
241
+ response.success(res, {
242
+ posts: postsWithAuthor,
243
+ });
244
+ } catch (error) {
245
+ response.error(res, `Error: ${error.message}`, 500);
246
+ }
247
+ });
248
+
249
+ // Get user with posts (relationship)
250
+ app.get('/users/:id/posts', async (req, res) => {
251
+ try {
252
+ const user = await User.findById(req.params.id);
253
+
254
+ if (!user) {
255
+ return response.error(res, 'User not found', 404);
256
+ }
257
+
258
+ const posts = await user.posts;
259
+
260
+ response.success(res, {
261
+ user: user.toJSON(),
262
+ posts: posts.map(p => p.toJSON()),
263
+ });
264
+ } catch (error) {
265
+ response.error(res, `Error: ${error.message}`, 500);
266
+ }
267
+ });
268
+
269
+ // Migration status
270
+ app.get('/migrations/status', async (req, res) => {
271
+ try {
272
+ if (!db) {
273
+ return response.error(res, 'Database not initialized', 400);
274
+ }
275
+
276
+ const migration = createMigration(db, path.join(__dirname, 'migrations'));
277
+ const status = await migration.status();
278
+
279
+ response.success(res, status);
280
+ } catch (error) {
281
+ response.error(res, `Error: ${error.message}`, 500);
282
+ }
283
+ });
284
+
285
+ // Health check
286
+ app.get('/health', (req, res) => {
287
+ response.success(res, {
288
+ status: 'ok',
289
+ features: [
290
+ 'ORM-like Model definitions',
291
+ 'Model relationships (hasMany, belongsTo, hasOne)',
292
+ 'Model hooks (beforeSave, afterSave, etc.)',
293
+ 'Model validation',
294
+ 'Database migrations',
295
+ 'Migration up/down',
296
+ 'Migration tracking',
297
+ ],
298
+ });
299
+ });
300
+
301
+ // Start server
302
+ const PORT = process.env.PORT || 3000;
303
+ app.listen(PORT, () => {
304
+ console.log(`🚀 Navis.js ORM & Migrations Demo running on http://localhost:${PORT}`);
305
+ console.log('\n📊 Available endpoints:');
306
+ console.log(` GET http://localhost:${PORT}/init - Initialize database and run migrations`);
307
+ console.log(` POST http://localhost:${PORT}/users - Create user`);
308
+ console.log(` GET http://localhost:${PORT}/users - Get all users`);
309
+ console.log(` GET http://localhost:${PORT}/users/:id - Get user by ID`);
310
+ console.log(` PUT http://localhost:${PORT}/users/:id - Update user`);
311
+ console.log(` DELETE http://localhost:${PORT}/users/:id - Delete user`);
312
+ console.log(` POST http://localhost:${PORT}/posts - Create post`);
313
+ console.log(` GET http://localhost:${PORT}/posts - Get all posts with authors`);
314
+ console.log(` GET http://localhost:${PORT}/users/:id/posts - Get user's posts`);
315
+ console.log(` GET http://localhost:${PORT}/migrations/status - Get migration status`);
316
+ console.log(` GET http://localhost:${PORT}/health - Health check`);
317
+ console.log('\n💡 Note: Call /init first to set up the database');
318
+ });
319
+