@devbro/neko-sql 0.1.36 → 0.1.37

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.
Files changed (2) hide show
  1. package/README.md +541 -28
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,33 +1,45 @@
1
1
  # @devbro/neko-sql
2
2
 
3
- SQL engine for quick abstracted relational database connections.
3
+ A powerful, type-safe SQL query builder and database abstraction layer for Node.js and TypeScript. Build queries programmatically with a fluent API and support for multiple database engines.
4
4
 
5
- ## supported features
5
+ ## Installation
6
6
 
7
- - data manipulation(select, insert, update, delete)
8
- - schema manipulation(create table, alter table, ...)
9
- - transactions
10
- - joins
7
+ ```bash
8
+ npm install @devbro/neko-sql
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - 🎯 **Unified API** - Same interface for PostgreSQL, MySQL, and SQLite
14
+ - 🔨 **Query Builder** - Fluent, chainable API for building SQL queries
15
+ - 🛡️ **Type-Safe** - Full TypeScript support with type inference
16
+ - 🔄 **Transactions** - Complete transaction support with rollback
17
+ - 🔗 **Joins** - Support for all join types (inner, left, right, cross)
18
+ - 📊 **Schema Builder** - Create and modify database schemas
19
+ - 🚀 **Migration Ready** - Built-in support for database migrations
20
+ - ⚡ **Async/Await** - Modern promise-based API
21
+ - 🔒 **SQL Injection Protection** - Parameterized queries by default
11
22
 
12
- ## Suported Databases
23
+ ## Supported Databases
13
24
 
14
- currently supported databases:
25
+ ### Currently Supported
15
26
 
16
- - postgresql
17
- - sqlite
18
- - mysql
27
+ - ✅ **PostgreSQL** - v9.5+
28
+ - ✅ **MySQL** - v5.7+ / MariaDB v10.2+
29
+ - ✅ **SQLite** - v3.8+
19
30
 
20
- future planned:
31
+ ### Planned Support
21
32
 
22
- - mssql
33
+ - 🔜 **Microsoft SQL Server** (MSSQL)
23
34
 
24
- ## how to use
35
+ ## Quick Start
25
36
 
26
- ### PostgreSQL Example
37
+ ### PostgreSQL
27
38
 
28
39
  ```typescript
29
40
  import { PostgresqlConnection } from '@devbro/neko-sql';
30
41
 
42
+ // Create connection
31
43
  const conn = new PostgresqlConnection({
32
44
  host: 'localhost',
33
45
  database: 'mydb',
@@ -36,34 +48,37 @@ const conn = new PostgresqlConnection({
36
48
  port: 5432,
37
49
  });
38
50
 
51
+ // Connect to database
39
52
  await conn.connect();
40
53
 
41
- // Use Query builder
42
- const query = conn.getQuery();
43
- const results = await query.table('users').whereOp('id', '=', 1).get();
54
+ // Execute a simple query
55
+ const users = await conn.getQuery().table('users').whereOp('status', '=', 'active').get();
56
+
57
+ console.log(users);
44
58
 
59
+ // Don't forget to disconnect
45
60
  await conn.disconnect();
46
61
  ```
47
62
 
48
- ### SQLite Example
63
+ ### SQLite
49
64
 
50
65
  ```typescript
51
66
  import { SqliteConnection } from '@devbro/neko-sql';
52
67
 
53
68
  const conn = new SqliteConnection({
54
69
  filename: './database.db',
70
+ // Or use in-memory database
71
+ // filename: ':memory:',
55
72
  });
56
73
 
57
74
  await conn.connect();
58
75
 
59
- // Use Query builder
60
- const query = conn.getQuery();
61
- const results = await query.table('users').whereOp('id', '=', 1).get();
76
+ const users = await conn.getQuery().table('users').whereOp('age', '>', 18).get();
62
77
 
63
78
  await conn.disconnect();
64
79
  ```
65
80
 
66
- ### MySQL Example
81
+ ### MySQL
67
82
 
68
83
  ```typescript
69
84
  import { MysqlConnection } from '@devbro/neko-sql';
@@ -78,13 +93,511 @@ const conn = new MysqlConnection({
78
93
 
79
94
  await conn.connect();
80
95
 
81
- // Use Query builder
82
- const query = conn.getQuery();
83
- const results = await query.table('users').whereOp('id', '=', 1).get();
96
+ const activeUsers = await conn
97
+ .getQuery()
98
+ .table('users')
99
+ .whereOp('is_active', '=', true)
100
+ .orderBy('created_at', 'desc')
101
+ .limit(10)
102
+ .get();
84
103
 
85
104
  await conn.disconnect();
86
105
  ```
87
106
 
88
- please check test classes for more robust examples
107
+ ## Query Builder API
108
+
109
+ ### Basic Queries
110
+
111
+ #### Select
112
+
113
+ ```typescript
114
+ const query = conn.getQuery();
115
+
116
+ // Select all columns
117
+ const allUsers = await query.table('users').get();
118
+
119
+ // Select specific columns
120
+ const users = await query.table('users').select(['id', 'name', 'email']).get();
121
+
122
+ // Select first record
123
+ const user = await query.table('users').whereOp('id', '=', 1).first();
124
+
125
+ // Count records
126
+ const count = await query.table('users').whereOp('status', '=', 'active').count();
127
+ ```
128
+
129
+ #### Insert
130
+
131
+ ```typescript
132
+ // Insert single record
133
+ await query.table('users').insert({
134
+ name: 'John Doe',
135
+ email: 'john@example.com',
136
+ age: 30,
137
+ });
138
+
139
+ // Insert and get ID
140
+ const userId = await query.table('users').insertGetId({
141
+ name: 'Jane Smith',
142
+ email: 'jane@example.com',
143
+ });
144
+
145
+ // Batch insert (multiple records)
146
+ await query.table('users').insert([
147
+ { name: 'User 1', email: 'user1@example.com' },
148
+ { name: 'User 2', email: 'user2@example.com' },
149
+ { name: 'User 3', email: 'user3@example.com' },
150
+ ]);
151
+
152
+ // Batch insert and get IDs
153
+ const ids = await query.table('users').insertGetId([
154
+ { name: 'User A', email: 'a@example.com' },
155
+ { name: 'User B', email: 'b@example.com' },
156
+ ]);
157
+ ```
158
+
159
+ #### Update
160
+
161
+ ```typescript
162
+ // Update records
163
+ await query.table('users').whereOp('id', '=', 1).update({
164
+ name: 'Updated Name',
165
+ updated_at: new Date(),
166
+ });
167
+
168
+ // Update multiple records
169
+ await query.table('users').whereOp('status', '=', 'pending').update({ status: 'active' });
170
+ ```
171
+
172
+ #### Delete
173
+
174
+ ```typescript
175
+ // Delete specific record
176
+ await query.table('users').whereOp('id', '=', 1).delete();
177
+
178
+ // Delete multiple records
179
+ await query.table('users').whereOp('status', '=', 'inactive').delete();
180
+ ```
181
+
182
+ ### Where Clauses
183
+
184
+ ```typescript
185
+ // Basic where
186
+ query.whereOp('age', '>', 18);
187
+ query.whereOp('status', '=', 'active');
188
+ query.whereOp('email', 'LIKE', '%@example.com');
189
+
190
+ // Multiple where conditions (AND)
191
+ query.whereOp('age', '>', 18).whereOp('status', '=', 'active');
192
+
193
+ // Or where
194
+ query.whereOp('role', '=', 'admin').orWhereOp('role', '=', 'moderator');
195
+
196
+ // Where in
197
+ query.whereIn('status', ['active', 'pending', 'approved']);
198
+
199
+ // Where not in
200
+ query.whereNotIn('status', ['banned', 'deleted']);
201
+
202
+ // Where null
203
+ query.whereNull('deleted_at');
204
+
205
+ // Where not null
206
+ query.whereNotNull('email_verified_at');
207
+
208
+ // Where between
209
+ query.whereBetween('age', [18, 65]);
210
+ ```
211
+
212
+ ### Joins
213
+
214
+ ```typescript
215
+ // Inner join
216
+ const results = await query
217
+ .table('users')
218
+ .join('posts', 'users.id', '=', 'posts.user_id')
219
+ .select(['users.name', 'posts.title'])
220
+ .get();
221
+
222
+ // Left join
223
+ const users = await query
224
+ .table('users')
225
+ .leftJoin('profiles', 'users.id', '=', 'profiles.user_id')
226
+ .get();
227
+
228
+ // Right join
229
+ const data = await query
230
+ .table('orders')
231
+ .rightJoin('customers', 'orders.customer_id', '=', 'customers.id')
232
+ .get();
233
+
234
+ // Multiple joins
235
+ const results = await query
236
+ .table('users')
237
+ .join('posts', 'users.id', '=', 'posts.user_id')
238
+ .join('categories', 'posts.category_id', '=', 'categories.id')
239
+ .select(['users.name', 'posts.title', 'categories.name as category'])
240
+ .get();
241
+ ```
242
+
243
+ ### Ordering and Limiting
244
+
245
+ ```typescript
246
+ // Order by
247
+ query.orderBy('created_at', 'desc');
248
+ query.orderBy('name', 'asc');
249
+
250
+ // Multiple order by
251
+ query.orderBy('status', 'asc').orderBy('created_at', 'desc');
252
+
253
+ // Limit
254
+ query.limit(10);
255
+
256
+ // Offset
257
+ query.offset(20);
258
+
259
+ // Pagination
260
+ query.limit(10).offset(20); // Page 3, 10 items per page
261
+ ```
262
+
263
+ ### Grouping and Aggregates
264
+
265
+ ```typescript
266
+ // Group by
267
+ const results = await query
268
+ .table('orders')
269
+ .select(['status', 'COUNT(*) as count'])
270
+ .groupBy('status')
271
+ .get();
272
+
273
+ // Having clause
274
+ const data = await query
275
+ .table('users')
276
+ .select(['country', 'COUNT(*) as user_count'])
277
+ .groupBy('country')
278
+ .having('user_count', '>', 10)
279
+ .get();
280
+
281
+ // Aggregate functions
282
+ const totalUsers = await query.table('users').count();
283
+ const avgAge = await query.table('users').avg('age');
284
+ const maxPrice = await query.table('products').max('price');
285
+ const minPrice = await query.table('products').min('price');
286
+ const totalRevenue = await query.table('orders').sum('total');
287
+ ```
288
+
289
+ ### Raw Queries
290
+
291
+ ```typescript
292
+ // Execute raw SQL
293
+ const results = await conn.raw('SELECT * FROM users WHERE age > ?', [18]);
294
+
295
+ // Raw where clause
296
+ query.table('users').whereRaw('DATE(created_at) = CURDATE()');
297
+
298
+ // Raw select
299
+ query.table('users').selectRaw('COUNT(*) as total, AVG(age) as average_age');
300
+ ```
301
+
302
+ ## Schema Builder
303
+
304
+ ### Creating Tables
305
+
306
+ ```typescript
307
+ import { Schema, Blueprint } from '@devbro/neko-sql';
308
+
309
+ const schema = new Schema(conn, conn.getSchemaGrammar());
310
+
311
+ // Create a new table
312
+ await schema.createTable('users', (table: Blueprint) => {
313
+ table.id(); // Auto-incrementing primary key
314
+ table.string('name', 255);
315
+ table.string('email', 255).unique();
316
+ table.string('password');
317
+ table.integer('age').nullable();
318
+ table.boolean('is_active').default(true);
319
+ table.text('bio').nullable();
320
+ table.timestamps(); // created_at and updated_at
321
+ });
322
+
323
+ // Create table with foreign key
324
+ await schema.createTable('posts', (table: Blueprint) => {
325
+ table.id();
326
+ table.string('title');
327
+ table.text('content');
328
+ table.integer('user_id').unsigned();
329
+ table.foreign('user_id').references('id').on('users').onDelete('cascade');
330
+ table.timestamps();
331
+ });
332
+ ```
333
+
334
+ ### Modifying Tables
335
+
336
+ ```typescript
337
+ // Add columns
338
+ await schema.alterTable('users', (table: Blueprint) => {
339
+ table.string('phone', 20).nullable();
340
+ table.timestamp('last_login').nullable();
341
+ });
342
+
343
+ // Drop columns
344
+ await schema.alterTable('users', (table: Blueprint) => {
345
+ table.dropColumn('phone');
346
+ });
347
+
348
+ // Rename column
349
+ await schema.alterTable('users', (table: Blueprint) => {
350
+ table.renameColumn('name', 'full_name');
351
+ });
352
+ ```
353
+
354
+ ### Dropping Tables
355
+
356
+ ```typescript
357
+ // Drop table if exists
358
+ await schema.dropTableIfExists('users');
359
+
360
+ // Drop table
361
+ await schema.dropTable('users');
362
+ ```
363
+
364
+ ### Available Column Types
365
+
366
+ ```typescript
367
+ table.id(); // Auto-incrementing ID
368
+ table.integer('column'); // Integer
369
+ table.bigInteger('column'); // Big integer
370
+ table.string('column', 255); // VARCHAR
371
+ table.text('column'); // TEXT
372
+ table.boolean('column'); // Boolean
373
+ table.date('column'); // Date
374
+ table.datetime('column'); // Datetime
375
+ table.timestamp('column'); // Timestamp
376
+ table.json('column'); // JSON
377
+ table.decimal('column', 10, 2); // Decimal
378
+ table.float('column'); // Float
379
+ table.double('column'); // Double
380
+
381
+ // Modifiers
382
+ column.nullable(); // Allow NULL
383
+ column.default(value); // Set default value
384
+ column.unique(); // Add unique constraint
385
+ column.unsigned(); // Unsigned (for numbers)
386
+ column.primary(); // Set as primary key
387
+ ```
388
+
389
+ ## Transactions
390
+
391
+ ```typescript
392
+ // Using transactions
393
+ await conn.beginTransaction();
394
+
395
+ try {
396
+ // Execute queries
397
+ await conn.getQuery().table('users').insert({ name: 'John' });
398
+ await conn.getQuery().table('logs').insert({ action: 'user_created' });
399
+
400
+ // Commit if successful
401
+ await conn.commit();
402
+ } catch (error) {
403
+ // Rollback on error
404
+ await conn.rollback();
405
+ throw error;
406
+ }
407
+ ```
408
+
409
+ ## Database Management
410
+
411
+ ### Create/Drop Database
412
+
413
+ ```typescript
414
+ // Check if database exists
415
+ const exists = await conn.existsDatabase('mydb');
416
+
417
+ // Create database
418
+ await conn.createDatabase('mydb');
419
+
420
+ // Drop database
421
+ await conn.dropDatabase('mydb');
422
+ ```
423
+
424
+ ## Connection Pooling
425
+
426
+ For production applications, use connection pooling:
427
+
428
+ ```typescript
429
+ import { PostgresqlConnection } from '@devbro/neko-sql';
430
+
431
+ const conn = new PostgresqlConnection({
432
+ host: 'localhost',
433
+ database: 'mydb',
434
+ user: 'myuser',
435
+ password: 'mypassword',
436
+ port: 5432,
437
+ // Connection pool settings
438
+ max: 20, // Maximum number of connections
439
+ min: 5, // Minimum number of connections
440
+ idleTimeoutMillis: 30000,
441
+ connectionTimeoutMillis: 2000,
442
+ });
443
+ ```
444
+
445
+ ## Best Practices
446
+
447
+ 1. **Always Close Connections** - Use `await conn.disconnect()` when done
448
+ 2. **Use Transactions** - For multiple related operations
449
+ 3. **Parameterized Queries** - Use the query builder or raw queries with parameters
450
+ 4. **Connection Pooling** - Enable pooling in production
451
+ 5. **Error Handling** - Wrap database operations in try-catch blocks
452
+ 6. **Environment Variables** - Store credentials in environment variables
453
+ 7. **Migrations** - Use schema builder for database versioning
454
+
455
+ ## Advanced Example
456
+
457
+ ```typescript
458
+ import { PostgresqlConnection, Schema, Blueprint } from '@devbro/neko-sql';
459
+
460
+ async function main() {
461
+ const conn = new PostgresqlConnection({
462
+ host: process.env.DB_HOST,
463
+ database: process.env.DB_NAME,
464
+ user: process.env.DB_USER,
465
+ password: process.env.DB_PASSWORD,
466
+ port: parseInt(process.env.DB_PORT || '5432'),
467
+ });
468
+
469
+ try {
470
+ await conn.connect();
471
+
472
+ // Create schema
473
+ const schema = new Schema(conn, conn.getSchemaGrammar());
474
+
475
+ await schema.createTable('users', (table: Blueprint) => {
476
+ table.id();
477
+ table.string('email', 250).unique();
478
+ table.string('first_name').default('');
479
+ table.timestamps();
480
+ });
481
+
482
+ // Insert data
483
+ const userId = await conn.getQuery().table('users').insertGetId({
484
+ email: 'john@example.com',
485
+ first_name: 'John',
486
+ });
487
+
488
+ // Query with joins
489
+ const results = await conn
490
+ .getQuery()
491
+ .table('users')
492
+ .leftJoin('profiles', 'users.id', '=', 'profiles.user_id')
493
+ .whereOp('users.id', '=', userId)
494
+ .select(['users.*', 'profiles.bio'])
495
+ .first();
496
+
497
+ console.log('User:', results);
498
+ } catch (error) {
499
+ console.error('Database error:', error);
500
+ } finally {
501
+ await conn.disconnect();
502
+ }
503
+ }
504
+
505
+ main();
506
+ ```
507
+
508
+ ## TypeScript Support
509
+
510
+ Full TypeScript definitions included:
511
+
512
+ ```typescript
513
+ import { Connection, Query, Schema, Blueprint } from '@devbro/neko-sql';
514
+
515
+ // Type-safe queries
516
+ interface User {
517
+ id: number;
518
+ name: string;
519
+ email: string;
520
+ created_at: Date;
521
+ }
522
+
523
+ const users = await conn.getQuery().table<User>('users').get();
524
+
525
+ // users is typed as User[]
526
+ ```
527
+
528
+ ## Troubleshooting
529
+
530
+ ### Connection Issues
531
+
532
+ ```typescript
533
+ // Test connection
534
+ try {
535
+ await conn.connect();
536
+ console.log('Connected successfully');
537
+ } catch (error) {
538
+ console.error('Connection failed:', error.message);
539
+ }
540
+ ```
541
+
542
+ ### Debugging Queries
543
+
544
+ Enable query logging to see generated SQL:
545
+
546
+ ```typescript
547
+ // Log executed queries (implementation may vary)
548
+ conn.on('query', (sql) => {
549
+ console.log('Executed:', sql);
550
+ });
551
+ ```
552
+
553
+ ## Migration Example
554
+
555
+ ```typescript
556
+ // migrations/001_create_users_table.ts
557
+ import { Schema, Blueprint, Connection } from '@devbro/neko-sql';
558
+
559
+ export async function up(conn: Connection) {
560
+ const schema = new Schema(conn, conn.getSchemaGrammar());
561
+
562
+ await schema.createTable('users', (table: Blueprint) => {
563
+ table.id();
564
+ table.string('email').unique();
565
+ table.string('password');
566
+ table.timestamps();
567
+ });
568
+ }
569
+
570
+ export async function down(conn: Connection) {
571
+ const schema = new Schema(conn, conn.getSchemaGrammar());
572
+ await schema.dropTable('users');
573
+ }
574
+ ```
575
+
576
+ ## Examples and Tests
577
+
578
+ For more comprehensive examples, check the test files in the repository:
579
+
580
+ - `/tests/query.test.ts` - Query builder examples
581
+ - `/tests/schema.test.ts` - Schema builder examples
582
+ - `/tests/transactions.test.ts` - Transaction examples
583
+
584
+ ## Contributing
585
+
586
+ Contributions are welcome! Please feel free to submit a Pull Request.
587
+
588
+ Areas we'd love help with:
589
+
590
+ - MSSQL support
591
+ - Additional query builder features
592
+ - Performance optimizations
593
+ - Documentation improvements
594
+
595
+ ## License
596
+
597
+ MIT
598
+
599
+ ## Related Packages
89
600
 
90
- ## APIs
601
+ - [@devbro/neko-orm](https://www.npmjs.com/package/@devbro/neko-orm) - Object-relational mapping
602
+ - [@devbro/neko-cache](https://www.npmjs.com/package/@devbro/neko-cache) - Caching solution
603
+ - [@devbro/pashmak](https://www.npmjs.com/package/@devbro/pashmak) - Full-stack TypeScript framework
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devbro/neko-sql",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "description": "generic sql generator",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",