@objectstack/driver-sql 4.0.2 → 4.0.4

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @objectstack/driver-sql@4.0.2 build /home/runner/work/framework/framework/packages/plugins/driver-sql
2
+ > @objectstack/driver-sql@4.0.4 build /home/runner/work/framework/framework/packages/plugins/driver-sql
3
3
  > tsup --config ../../../tsup.config.ts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -10,13 +10,13 @@
10
10
  CLI Cleaning output folder
11
11
  ESM Build start
12
12
  CJS Build start
13
- ESM dist/index.mjs 36.06 KB
14
- ESM dist/index.mjs.map 66.43 KB
15
- ESM ⚡️ Build success in 135ms
16
- CJS dist/index.js 37.74 KB
17
- CJS dist/index.js.map 66.50 KB
18
- CJS ⚡️ Build success in 136ms
13
+ ESM dist/index.mjs 36.51 KB
14
+ ESM dist/index.mjs.map 67.46 KB
15
+ ESM ⚡️ Build success in 132ms
16
+ CJS dist/index.js 38.20 KB
17
+ CJS dist/index.js.map 67.53 KB
18
+ CJS ⚡️ Build success in 133ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 13072ms
20
+ DTS ⚡️ Build success in 12409ms
21
21
  DTS dist/index.d.mts 7.80 KB
22
22
  DTS dist/index.d.ts 7.80 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @objectstack/driver-sql
2
2
 
3
+ ## 4.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [326b66b]
8
+ - @objectstack/spec@4.0.4
9
+ - @objectstack/core@4.0.4
10
+
11
+ ## 4.0.3
12
+
13
+ ### Patch Changes
14
+
15
+ - @objectstack/spec@4.0.3
16
+ - @objectstack/core@4.0.3
17
+
3
18
  ## 4.0.2
4
19
 
5
20
  ### Patch Changes
package/README.md ADDED
@@ -0,0 +1,476 @@
1
+ # @objectstack/driver-sql
2
+
3
+ SQL Driver for ObjectStack - Supports PostgreSQL, MySQL, SQLite via Knex.js.
4
+
5
+ ## Features
6
+
7
+ - **Multi-Database Support**: PostgreSQL, MySQL, SQLite, and other Knex-supported databases
8
+ - **Query Builder**: Powerful Knex.js query builder integration
9
+ - **Migrations**: Database schema migrations with version control
10
+ - **Connection Pooling**: Efficient connection management
11
+ - **Transactions**: Full ACID transaction support
12
+ - **Raw SQL**: Execute raw SQL when needed
13
+ - **Type-Safe**: Full TypeScript support with inferred types
14
+ - **Production-Ready**: Battle-tested Knex.js under the hood
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pnpm add @objectstack/driver-sql knex
20
+ ```
21
+
22
+ ### Database-Specific Drivers
23
+
24
+ Install the driver for your database:
25
+
26
+ ```bash
27
+ # PostgreSQL
28
+ pnpm add pg
29
+
30
+ # MySQL
31
+ pnpm add mysql2
32
+
33
+ # SQLite
34
+ pnpm add better-sqlite3
35
+ ```
36
+
37
+ ## Basic Usage
38
+
39
+ ### PostgreSQL
40
+
41
+ ```typescript
42
+ import { defineStack } from '@objectstack/spec';
43
+ import { DriverSQL } from '@objectstack/driver-sql';
44
+
45
+ const stack = defineStack({
46
+ driver: DriverSQL.configure({
47
+ client: 'pg',
48
+ connection: {
49
+ host: 'localhost',
50
+ port: 5432,
51
+ user: 'postgres',
52
+ password: process.env.DB_PASSWORD,
53
+ database: 'myapp',
54
+ },
55
+ pool: {
56
+ min: 2,
57
+ max: 10,
58
+ },
59
+ }),
60
+ });
61
+ ```
62
+
63
+ ### MySQL
64
+
65
+ ```typescript
66
+ const stack = defineStack({
67
+ driver: DriverSQL.configure({
68
+ client: 'mysql2',
69
+ connection: {
70
+ host: 'localhost',
71
+ port: 3306,
72
+ user: 'root',
73
+ password: process.env.DB_PASSWORD,
74
+ database: 'myapp',
75
+ },
76
+ }),
77
+ });
78
+ ```
79
+
80
+ ### SQLite
81
+
82
+ ```typescript
83
+ const stack = defineStack({
84
+ driver: DriverSQL.configure({
85
+ client: 'better-sqlite3',
86
+ connection: {
87
+ filename: './data/app.db',
88
+ },
89
+ useNullAsDefault: true,
90
+ }),
91
+ });
92
+ ```
93
+
94
+ ## Configuration Options
95
+
96
+ ```typescript
97
+ interface SQLDriverConfig {
98
+ /** Knex client (pg, mysql2, better-sqlite3, etc.) */
99
+ client: string;
100
+
101
+ /** Database connection config */
102
+ connection: {
103
+ host?: string;
104
+ port?: number;
105
+ user?: string;
106
+ password?: string;
107
+ database?: string;
108
+ filename?: string; // For SQLite
109
+ };
110
+
111
+ /** Connection pool settings */
112
+ pool?: {
113
+ min?: number;
114
+ max?: number;
115
+ idleTimeoutMillis?: number;
116
+ };
117
+
118
+ /** Use NULL as default for unsupported features (SQLite) */
119
+ useNullAsDefault?: boolean;
120
+
121
+ /** Enable query debugging */
122
+ debug?: boolean;
123
+
124
+ /** Migrations configuration */
125
+ migrations?: {
126
+ directory?: string;
127
+ tableName?: string;
128
+ };
129
+ }
130
+ ```
131
+
132
+ ## Database Operations
133
+
134
+ The SQL driver implements the standard ObjectStack driver interface:
135
+
136
+ ```typescript
137
+ import type { IDriver } from '@objectstack/spec';
138
+
139
+ // All standard operations are supported:
140
+ // find, findOne, insert, update, delete, count
141
+ ```
142
+
143
+ ### Advanced Queries
144
+
145
+ ```typescript
146
+ // The SQL driver supports all ObjectQL query features:
147
+ const results = await kernel.getDriver().find({
148
+ object: 'opportunity',
149
+ filters: [
150
+ { field: 'amount', operator: 'gte', value: 10000 },
151
+ { field: 'stage', operator: 'in', value: ['proposal', 'negotiation'] },
152
+ ],
153
+ sort: [{ field: 'amount', direction: 'desc' }],
154
+ limit: 100,
155
+ offset: 0,
156
+ });
157
+ ```
158
+
159
+ ## Migrations
160
+
161
+ ### Creating Migrations
162
+
163
+ ```typescript
164
+ // migrations/001_create_users.ts
165
+ export async function up(knex) {
166
+ await knex.schema.createTable('objectstack_user', (table) => {
167
+ table.string('id').primary();
168
+ table.string('name').notNullable();
169
+ table.string('email').notNullable().unique();
170
+ table.timestamps(true, true);
171
+ });
172
+ }
173
+
174
+ export async function down(knex) {
175
+ await knex.schema.dropTable('objectstack_user');
176
+ }
177
+ ```
178
+
179
+ ### Running Migrations
180
+
181
+ ```bash
182
+ # Run all pending migrations
183
+ npx knex migrate:latest
184
+
185
+ # Rollback last migration
186
+ npx knex migrate:rollback
187
+
188
+ # Check migration status
189
+ npx knex migrate:status
190
+ ```
191
+
192
+ ### Migration Configuration
193
+
194
+ Create `knexfile.js` in your project root:
195
+
196
+ ```javascript
197
+ module.exports = {
198
+ development: {
199
+ client: 'pg',
200
+ connection: {
201
+ host: 'localhost',
202
+ user: 'postgres',
203
+ password: process.env.DB_PASSWORD,
204
+ database: 'myapp_dev',
205
+ },
206
+ migrations: {
207
+ directory: './migrations',
208
+ tableName: 'objectstack_migrations',
209
+ },
210
+ },
211
+ production: {
212
+ client: 'pg',
213
+ connection: process.env.DATABASE_URL,
214
+ pool: {
215
+ min: 2,
216
+ max: 10,
217
+ },
218
+ migrations: {
219
+ directory: './migrations',
220
+ tableName: 'objectstack_migrations',
221
+ },
222
+ },
223
+ };
224
+ ```
225
+
226
+ ## Transactions
227
+
228
+ ```typescript
229
+ const driver = kernel.getDriver();
230
+
231
+ await driver.transaction(async (trx) => {
232
+ // All operations within this callback use the same transaction
233
+ const account = await trx.insert({
234
+ object: 'account',
235
+ data: { name: 'Acme Corp' },
236
+ });
237
+
238
+ await trx.insert({
239
+ object: 'contact',
240
+ data: {
241
+ name: 'John Doe',
242
+ account_id: account.id,
243
+ },
244
+ });
245
+
246
+ // If an error is thrown, all changes are rolled back
247
+ // If successful, changes are committed
248
+ });
249
+ ```
250
+
251
+ ## Raw SQL Queries
252
+
253
+ When ObjectQL isn't sufficient, execute raw SQL:
254
+
255
+ ```typescript
256
+ const driver = kernel.getDriver();
257
+
258
+ // Raw query
259
+ const results = await driver.raw(`
260
+ SELECT
261
+ c.name,
262
+ COUNT(o.id) as opportunity_count,
263
+ SUM(o.amount) as total_revenue
264
+ FROM objectstack_account c
265
+ LEFT JOIN objectstack_opportunity o ON o.account_id = c.id
266
+ WHERE o.stage = 'closed_won'
267
+ GROUP BY c.id, c.name
268
+ ORDER BY total_revenue DESC
269
+ LIMIT 10
270
+ `);
271
+
272
+ // Raw query with parameters (prevent SQL injection)
273
+ const results = await driver.raw(
274
+ 'SELECT * FROM objectstack_user WHERE email = ?',
275
+ ['user@example.com']
276
+ );
277
+ ```
278
+
279
+ ## Database-Specific Features
280
+
281
+ ### PostgreSQL Features
282
+
283
+ ```typescript
284
+ // Use PostgreSQL-specific features
285
+ const results = await driver.raw(`
286
+ SELECT * FROM objectstack_opportunity
287
+ WHERE data @> '{"industry": "Technology"}'::jsonb
288
+ `);
289
+
290
+ // Full-text search
291
+ const results = await driver.raw(`
292
+ SELECT * FROM objectstack_article
293
+ WHERE to_tsvector('english', title || ' ' || body) @@ to_tsquery('objectstack')
294
+ `);
295
+ ```
296
+
297
+ ### MySQL Features
298
+
299
+ ```typescript
300
+ // Use MySQL-specific features
301
+ const results = await driver.raw(`
302
+ SELECT * FROM objectstack_product
303
+ WHERE MATCH(name, description) AGAINST ('widget' IN NATURAL LANGUAGE MODE)
304
+ `);
305
+ ```
306
+
307
+ ## Connection Management
308
+
309
+ ```typescript
310
+ // Get underlying Knex instance
311
+ const knex = driver.getKnex();
312
+
313
+ // Check connection
314
+ await driver.checkConnection();
315
+
316
+ // Close all connections
317
+ await driver.destroy();
318
+ ```
319
+
320
+ ## Performance Optimization
321
+
322
+ ### Indexes
323
+
324
+ ```typescript
325
+ // Create index migration
326
+ export async function up(knex) {
327
+ await knex.schema.table('objectstack_opportunity', (table) => {
328
+ table.index('account_id');
329
+ table.index('stage');
330
+ table.index(['created_at', 'stage']); // Composite index
331
+ });
332
+ }
333
+ ```
334
+
335
+ ### Query Optimization
336
+
337
+ ```typescript
338
+ // Use explain to analyze queries
339
+ const plan = await driver.raw('EXPLAIN ANALYZE SELECT ...');
340
+
341
+ // Create covering indexes for frequently accessed columns
342
+ // Use partial indexes for filtered queries (PostgreSQL)
343
+ await knex.raw(`
344
+ CREATE INDEX idx_active_opportunities
345
+ ON objectstack_opportunity(account_id, amount)
346
+ WHERE stage NOT IN ('closed_won', 'closed_lost')
347
+ `);
348
+ ```
349
+
350
+ ## Best Practices
351
+
352
+ 1. **Connection Pooling**: Configure appropriate pool size based on load
353
+ 2. **Migrations**: Always use migrations for schema changes, never raw DDL
354
+ 3. **Transactions**: Use transactions for multi-step operations
355
+ 4. **Prepared Statements**: Use parameterized queries to prevent SQL injection
356
+ 5. **Indexes**: Create indexes on frequently queried fields
357
+ 6. **Monitoring**: Monitor slow query logs and connection pool metrics
358
+ 7. **Backups**: Implement regular database backups
359
+
360
+ ## Environment-Specific Configuration
361
+
362
+ ```typescript
363
+ // config/database.ts
364
+ export const getDatabaseConfig = () => {
365
+ const env = process.env.NODE_ENV || 'development';
366
+
367
+ const configs = {
368
+ development: {
369
+ client: 'better-sqlite3',
370
+ connection: { filename: './data/dev.db' },
371
+ useNullAsDefault: true,
372
+ debug: true,
373
+ },
374
+ test: {
375
+ client: 'better-sqlite3',
376
+ connection: { filename: ':memory:' },
377
+ useNullAsDefault: true,
378
+ },
379
+ production: {
380
+ client: 'pg',
381
+ connection: process.env.DATABASE_URL,
382
+ pool: { min: 2, max: 10 },
383
+ ssl: { rejectUnauthorized: false },
384
+ },
385
+ };
386
+
387
+ return configs[env] || configs.development;
388
+ };
389
+
390
+ const stack = defineStack({
391
+ driver: DriverSQL.configure(getDatabaseConfig()),
392
+ });
393
+ ```
394
+
395
+ ## Troubleshooting
396
+
397
+ ### Connection Issues
398
+
399
+ ```typescript
400
+ // Test database connection
401
+ try {
402
+ await driver.checkConnection();
403
+ console.log('Database connected successfully');
404
+ } catch (error) {
405
+ console.error('Database connection failed:', error);
406
+ }
407
+ ```
408
+
409
+ ### Migration Errors
410
+
411
+ ```bash
412
+ # Check migration status
413
+ npx knex migrate:status
414
+
415
+ # Rollback and re-run
416
+ npx knex migrate:rollback
417
+ npx knex migrate:latest
418
+ ```
419
+
420
+ ### Query Debugging
421
+
422
+ ```typescript
423
+ // Enable query logging
424
+ const stack = defineStack({
425
+ driver: DriverSQL.configure({
426
+ client: 'pg',
427
+ connection: { /* ... */ },
428
+ debug: true, // Log all queries
429
+ }),
430
+ });
431
+ ```
432
+
433
+ ## Deployment
434
+
435
+ ### Heroku PostgreSQL
436
+
437
+ ```bash
438
+ # Heroku automatically provides DATABASE_URL
439
+ heroku addons:create heroku-postgresql:hobby-dev
440
+
441
+ # Run migrations on deployment
442
+ echo "npx knex migrate:latest" > Procfile.release
443
+ ```
444
+
445
+ ### Railway PostgreSQL
446
+
447
+ ```bash
448
+ # Use Railway's DATABASE_URL
449
+ railway up
450
+ ```
451
+
452
+ ### Vercel PostgreSQL
453
+
454
+ ```typescript
455
+ // Vercel uses connection pooling
456
+ import { createClient } from '@vercel/postgres';
457
+
458
+ const stack = defineStack({
459
+ driver: DriverSQL.configure({
460
+ client: 'pg',
461
+ connection: process.env.POSTGRES_URL,
462
+ }),
463
+ });
464
+ ```
465
+
466
+ ## License
467
+
468
+ Apache-2.0
469
+
470
+ ## See Also
471
+
472
+ - [Knex.js Documentation](https://knexjs.org/)
473
+ - [PostgreSQL Documentation](https://www.postgresql.org/docs/)
474
+ - [MySQL Documentation](https://dev.mysql.com/doc/)
475
+ - [@objectstack/driver-turso](../driver-turso/) - Edge-first SQLite alternative
476
+ - [@objectstack/driver-memory](../driver-memory/) - In-memory driver for testing
package/dist/index.js CHANGED
@@ -109,7 +109,7 @@ var SqlDriver = class {
109
109
  // Lifecycle
110
110
  // ===================================
111
111
  async connect() {
112
- return Promise.resolve();
112
+ await this.ensureDatabaseExists();
113
113
  }
114
114
  async checkHealth() {
115
115
  try {
@@ -814,7 +814,19 @@ var SqlDriver = class {
814
814
  }
815
815
  // ── Database helpers ────────────────────────────────────────────────────────
816
816
  async ensureDatabaseExists() {
817
- if (this.isSqlite) return;
817
+ if (this.isSqlite) {
818
+ const conn = this.config.connection;
819
+ const filename = typeof conn === "string" ? conn : conn?.filename;
820
+ if (filename && filename !== ":memory:" && !filename.startsWith(":")) {
821
+ const { dirname } = await import("path");
822
+ const { mkdir } = await import("fs/promises");
823
+ const dir = dirname(filename);
824
+ if (dir && dir !== ".") {
825
+ await mkdir(dir, { recursive: true });
826
+ }
827
+ }
828
+ return;
829
+ }
818
830
  if (!this.isPostgres && !this.isMysql) return;
819
831
  try {
820
832
  await this.knex.raw("SELECT 1");