@noego/proper 0.0.3 → 0.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.
package/readme.md CHANGED
@@ -1,29 +1,36 @@
1
1
  ## SQL Proper
2
2
 
3
- **SQL Proper** is a lightweight and straightforward SQL migration tool for both **MySQL/MariaDB** (via `mysql2`) and **SQLite**. It allows you to:
3
+ **SQL Proper** is a lightweight and straightforward SQL migration tool for both **MySQL/MariaDB** (via `mysql2`) and **SQLite**, with:
4
4
 
5
- - Easily run migrations (`up`) or roll them back (`down`)
6
- - Reset your database to a clean state
7
- - Check migration status
8
- - Create new migration files
9
- - Seamlessly manage your migration process via a simple **CLI** or by **importing** the library in your Node.js project
5
+ - A simple **CLI** for application developers
6
+ - A focused **programmatic API** for Node.js
7
+ - A small, testable **framework core** for contributors and advanced users
8
+
9
+ This README is written primarily for **developers integrating or extending** SQL Proper. If you only need usage details, you can skim **Quick Start (CLI)** and **Usage in Code (Import)**.
10
10
 
11
11
  ---
12
12
 
13
- ## Quick Start (CLI)
13
+ ## Installation
14
14
 
15
- Below is a fast-track on **how to work** with SQL Proper. Skip to [Configuration](#configuration) to learn how to connect to your database.
15
+ Add SQL Proper to your project:
16
16
 
17
- 1. **Install** SQL Proper globally or locally:
18
- ```bash
19
- # Globally (optional)
20
- npm install -g @noego/proper
17
+ ```bash
18
+ # Globally (optional, for CLI-only usage)
19
+ npm install -g @noego/proper
21
20
 
22
- # Or locally to your project:
23
- npm install --save @noego/proper
24
- ```
21
+ # Or locally to your project (recommended)
22
+ npm install --save @noego/proper
23
+ ```
24
+
25
+ For TypeScript projects, the emitted `.d.ts` files from the build are included in the published package; you don’t need to install separate type packages.
25
26
 
26
- 2. **Initialize a config file** (creates `proper.json` by default):
27
+ ---
28
+
29
+ ## Quick Start (CLI)
30
+
31
+ Below is a fast-track on **how to work** with SQL Proper from the command line. Skip to [Configuration](#configuration) to learn how to connect to your database or to [Programmatic API](#programmatic-api) for using it from code.
32
+
33
+ 1. **Initialize a config file** (creates `proper.json` by default):
27
34
  ```bash
28
35
  proper init
29
36
  ```
@@ -44,33 +51,33 @@ Below is a fast-track on **how to work** with SQL Proper. Skip to [Configuration
44
51
 
45
52
  Also create a `migrations/` directory in your project root to hold `.up.sql` and `.down.sql` files. File names follow `<timestamp>_<name>.up.sql` and `<timestamp>_<name>.down.sql`.
46
53
 
47
- 3. **Create a new migration** (generates timestamped `.up.sql` & `.down.sql` files):
54
+ 2. **Create a new migration** (generates timestamped `.up.sql` & `.down.sql` files):
48
55
  ```bash
49
56
  proper create --name "create_users_table"
50
57
  ```
51
58
  Edit those `.sql` files and add your migration scripts.
52
59
 
53
- 4. **Apply migrations**:
60
+ 3. **Apply migrations**:
54
61
  ```bash
55
62
  proper up
56
63
  ```
57
64
  - Applies all **pending** migrations.
58
65
  - Use `--increment <number>` to limit the count, e.g. `proper up --increment 1`.
59
66
 
60
- 5. **Roll back migrations**:
67
+ 4. **Roll back migrations**:
61
68
  ```bash
62
69
  proper down
63
70
  ```
64
71
  - Rolls back the **latest** applied migrations.
65
72
  - Use `--increment <number>` to limit the count, e.g. `proper down --increment 1`.
66
73
 
67
- 6. **Reset your database**:
74
+ 5. **Reset your database**:
68
75
  ```bash
69
76
  proper reset
70
77
  ```
71
78
  - Rolls back **all** migrations, then re-applies them.
72
79
 
73
- 7. **Check status**:
80
+ 6. **Check status**:
74
81
  ```bash
75
82
  proper status
76
83
  ```
@@ -117,9 +124,9 @@ proper up --config path/to/my-custom.json
117
124
 
118
125
  ---
119
126
 
120
- ## Usage in Code (Import)
127
+ ## Programmatic API
121
128
 
122
- If you prefer integrating migrations in your Node.js scripts, import SQL Proper:
129
+ If you prefer integrating migrations directly in your Node.js services or test harnesses, import SQL Proper:
123
130
 
124
131
  ```ts
125
132
  import { MigrationRunnerFactory } from "@noego/proper";
@@ -127,6 +134,10 @@ import { MigrationRunnerFactory } from "@noego/proper";
127
134
  (async () => {
128
135
  // Suppose "myconfig.json" is your config file
129
136
  const runner = await MigrationRunnerFactory.create("myconfig.json");
137
+
138
+ // Or pass an existing connection (useful for testing or connection pooling):
139
+ // const runner = await MigrationRunnerFactory.create("myconfig.json", existingConnection);
140
+
130
141
  await runner.setup();
131
142
 
132
143
  // Apply pending migrations
@@ -144,15 +155,213 @@ import { MigrationRunnerFactory } from "@noego/proper";
144
155
  })();
145
156
  ```
146
157
 
147
- **Key methods**:
148
- - `setup()`: Prepares DB environment (creates migration table if missing).
149
- - `getMigrations()`: Loads all migrations from the configured folder.
158
+ ### Public exports
159
+
160
+ From `@noego/proper`:
161
+
162
+ - `MigrationRunnerFactory`
163
+ - `create(configPath: string, connection?: any)`: create a runner backed by config, optionally reusing an existing DB connection (see examples below).
164
+ - `createEmpty(configPath: string)`: create a runner without connecting to a database (used by `init`, `create`).
165
+ - `MigrationConfig`: config model used by the framework.
166
+ - `MigrationRunner` (alias of `MySQLMigrationRunner`): convenience export for MySQL-specific runner.
167
+
168
+ From `@noego/proper/runners`:
169
+
170
+ - `BaseSQLRunner`, `SQLRunner`, `SQLiteRunner`: low-level database runners for advanced integrations and testing.
171
+
172
+ These are implemented in the `framework/` directory and re-exported via `framework/lib/runner.ts`. Check the generated `.d.ts` files in `lib/` and `bin/` for the exact TypeScript shapes in a given release.
173
+
174
+ ### Core runner API
175
+
176
+ The `MigrationRunner` instances returned from `MigrationRunnerFactory.create` expose:
177
+
178
+ - `setup()`: Prepare DB environment (creates migration table if missing).
179
+ - `getMigrations()`: Load all migrations from the configured folder.
150
180
  - `getPendingMigrations()` / `getCompletedMigrations()`: Filtered migration sets.
151
- - `migrate(nodes, forward)`: Executes migrations (forward = `true` for `up`).
152
- - `reset()`: Rolls back everything, then reapplies them.
153
- - `createMigration(name)`: Scaffolds `<timestamp>_<name>.up.sql` and `.down.sql`.
154
- - `init(configPath)`: Creates a default `proper.json` if missing.
155
- - `close()`: Closes the DB connection.
181
+ - `migrate(nodes, forward)`: Execute migrations (`forward = true` for `up`, `false` for `down`).
182
+ - `reset()`: Roll back everything, then reapply them (used by CLI `reset`).
183
+ - `createMigration(name)`: Scaffold `<timestamp>_<name>.up.sql` and `.down.sql` files.
184
+ - `init(configPath)`: Create a default `proper.json` if missing.
185
+ - `query(sql: string, params?)`: Execute read-only queries (used by CLI `query`).
186
+ - `close()`: Close the DB connection (no-op if you provided the connection).
187
+
188
+ ---
189
+
190
+ ## Testing with In-Memory Databases
191
+
192
+ For test isolation and faster test execution, you can pass an existing database connection to Proper. This is particularly useful when working with SQLite `:memory:` databases where you want multiple tests to share the same in-memory instance.
193
+
194
+ **Why This Matters:** When using SQLite `:memory:` databases for testing, the database only exists as long as the connection is open. By creating the connection yourself and passing it to Proper, you can:
195
+
196
+ 1. **Create** the `:memory:` connection once
197
+ 2. **Run migrations** using Proper with that connection
198
+ 3. **Run your tests** using the same connection with a fully migrated schema
199
+
200
+ Without this capability, you'd have to let Proper create the connection, but then you couldn't access that same in-memory database from your tests (it would be lost when Proper closes). Connection injection solves this by letting you control the lifecycle.
201
+
202
+ ### Example: Using a Shared `:memory:` Connection
203
+
204
+ ```ts
205
+ import { MigrationRunnerFactory } from "@noego/proper";
206
+ import * as sqlite from "sqlite";
207
+ import * as sqlite3 from "sqlite3";
208
+
209
+ // Create a single :memory: connection that all tests will share
210
+ const memoryDb = await sqlite.open({
211
+ filename: ':memory:',
212
+ driver: sqlite3.Database
213
+ });
214
+
215
+ // Pass the connection to Proper
216
+ const runner = await MigrationRunnerFactory.create("test-config.json", memoryDb);
217
+
218
+ // Setup and run migrations on the shared connection
219
+ await runner.setup();
220
+ const pending = await runner.getPendingMigrations();
221
+ await runner.migrate(pending, true);
222
+
223
+ // Now your tests can use the same memoryDb instance
224
+ // All queries will run against the same in-memory database
225
+ ```
226
+
227
+ ### Example: Test Setup with Connection Injection
228
+
229
+ ```ts
230
+ import { describe, beforeAll, afterAll, test } from '@jest/globals';
231
+
232
+ describe('Database Tests', () => {
233
+ let db;
234
+ let runner;
235
+
236
+ beforeAll(async () => {
237
+ // Step 1: Create the :memory: database connection
238
+ // This connection will persist for all tests in this suite
239
+ db = await sqlite.open({
240
+ filename: ':memory:',
241
+ driver: sqlite3.Database
242
+ });
243
+
244
+ // Step 2: Pass the connection to Proper and run migrations
245
+ // Proper will use YOUR connection instead of creating its own
246
+ runner = await MigrationRunnerFactory.create("proper.json", db);
247
+ await runner.setup();
248
+ await runner.migrate(await runner.getPendingMigrations(), true);
249
+
250
+ // Step 3: Now your tests can use the same 'db' connection
251
+ // The schema is fully migrated and ready for testing
252
+ });
253
+
254
+ afterAll(async () => {
255
+ await runner.close();
256
+ });
257
+
258
+ test('should query migrated database', async () => {
259
+ // Use the same db connection that was migrated by Proper
260
+ // This works because we created the connection ourselves
261
+ const result = await db.get('SELECT * FROM users WHERE id = ?', [1]);
262
+ expect(result).toBeDefined();
263
+ });
264
+ });
265
+ ```
266
+
267
+ ---
268
+
269
+ ## Seeding
270
+
271
+ SQL Proper supports a powerful seeding feature that allows you to populate your database with initial data (reference data, test users, etc.). Seeds can be SQL files or JavaScript/TypeScript modules.
272
+
273
+ ### Configuration
274
+
275
+ Configure seeds in your `proper.json`:
276
+
277
+ ```json
278
+ {
279
+ "seeds": {
280
+ "migrationsDir": "database/seed/migrations",
281
+ "dataDir": "database/seed/data",
282
+ "list": ["muscles", "exercises", "equipments", "user"]
283
+ }
284
+ }
285
+ ```
286
+
287
+ - **migrationsDir**: Directory containing seed implementation files (`.sql`, `.ts`, or `.js`).
288
+ - **dataDir**: (Optional) Directory containing JSON data files for seeds.
289
+ - **list**: Default list of seed names to run when no names are provided to the CLI.
290
+
291
+ ### CLI Usage
292
+
293
+ Run seeds via the CLI:
294
+
295
+ ```bash
296
+ # Run default seeds defined in config
297
+ proper seed
298
+
299
+ # Run specific seeds
300
+ proper seed muscles exercises
301
+
302
+ # Seed rollback (runs down migration for seeds)
303
+ proper seed --action down muscles
304
+
305
+ # Transactional mode
306
+ proper seed --transactional seed # Wrap each seed in a transaction
307
+ proper seed --transactional runner # Wrap all seeds in one transaction
308
+ ```
309
+
310
+ ### Seed Implementations
311
+
312
+ Seeds can be implemented in two ways:
313
+
314
+ **1. SQL Pair** (`<name>.up.sql` and `<name>.down.sql`) in `migrationsDir`.
315
+ - Simple raw SQL execution.
316
+ - Good for static reference data.
317
+
318
+ **2. TypeScript/JavaScript Module** (`<name>.ts` or `<name>.js`) in `migrationsDir`.
319
+ - Exports `up` and `down` functions.
320
+ - Receives a `runner` and a `context` object.
321
+ - `context` contains:
322
+ - `data`: Loaded JSON data matching the seed name (from `dataDir`).
323
+ - `dialect`: The current database dialect (`sql`, `sqlite`, etc.).
324
+ - `log`: A logger function.
325
+
326
+ **Example TS Seed (`database/seed/migrations/user.ts`):**
327
+
328
+ ```ts
329
+ import type { IMigrationRunner, SeedContext } from "@noego/proper";
330
+
331
+ export async function up(runner: IMigrationRunner, ctx: SeedContext) {
332
+ const users = ctx.data as any[]; // Data loaded from database/seed/data/user.json
333
+
334
+ for (const user of users) {
335
+ await runner.query("INSERT INTO users (email, name) VALUES (?, ?)", [user.email, user.name]);
336
+ ctx.log?.(`Created user ${user.email}`);
337
+ }
338
+ }
339
+
340
+ export async function down(runner: IMigrationRunner, ctx: SeedContext) {
341
+ const users = ctx.data as any[];
342
+ for (const user of users) {
343
+ await runner.query("DELETE FROM users WHERE email = ?", [user.email]);
344
+ }
345
+ }
346
+ ```
347
+
348
+ ### Programmatic API
349
+
350
+ Run seeds from your code (great for tests):
351
+
352
+ ```ts
353
+ import { MigrationRunnerFactory, runSeedsWithRunner, loadMigrationConfig } from "@noego/proper";
354
+
355
+ // Load config
356
+ const config = loadMigrationConfig("proper.json");
357
+ const runner = await MigrationRunnerFactory.create("proper.json");
358
+
359
+ // Run seeds
360
+ await runSeedsWithRunner(runner, config, 'up', {
361
+ names: ['muscles', 'exercises'], // Optional: override list
362
+ validate: true, // Optional: validate JSON data against schema
363
+ });
364
+ ```
156
365
 
157
366
  ---
158
367
 
@@ -199,19 +408,66 @@ SQL_PASSWORD=supersecret proper up --config proper.json
199
408
 
200
409
  ---
201
410
 
202
- ## Development
411
+ ## Project Layout (for contributors)
412
+
413
+ This is a small, framework-style project. The main pieces are:
414
+
415
+ - `cli.ts`: CLI entrypoint (compiled to `bin/cli.js` and published as the `proper` binary).
416
+ - `index.ts`: Library entrypoint, exporting `MigrationRunnerFactory`, `MigrationConfig`, and `MigrationRunner`.
417
+ - `framework/`: Core framework classes:
418
+ - `MigrationCLI.ts`: CLI parser and command wiring.
419
+ - `MigrationRunner.ts`: main migration runner + factory.
420
+ - `MigrationConfig.ts`: config reading/validation.
421
+ - `MigrationDirectoryReader.ts`: migration discovery from the filesystem.
422
+ - `MigrationStatus.ts`, `MigrationNode.ts`, `MigrationFilter.ts`: migration model, status helpers, and filtering logic.
423
+ - `SQLRunner.ts`: MySQL and SQLite runner implementations.
424
+ - `errors.ts`: domain-specific error types (used heavily by CLI and tests).
425
+ - `framework/lib/runner.ts`: re-exports low-level SQL runners for consumers (`@noego/proper/runners`).
426
+ - `helper/`: small utilities (e.g. string helpers).
427
+ - `tests/`: Jest-based unit tests for the framework and CLI.
428
+ - `example/`: runnable MySQL + migrations example (with `docker-compose` and `Makefile`).
429
+
430
+ Build artifacts are written to:
431
+
432
+ - `bin/`: compiled CLI and public library entrypoints.
433
+ - `lib/`: compiled runner exports (`runners` subpath).
434
+
435
+ The package’s `"files"` array in `package.json` ensures only `bin/`, `lib/`, and `readme.md` are published.
436
+
437
+ ---
438
+
439
+ ## Development & Contributing
440
+
441
+ Requirements:
442
+
443
+ - Node.js 16+ (LTS recommended)
444
+ - npm (or a compatible package manager)
203
445
 
204
- - Node: 16+ recommended
205
- - Install: `npm install`
206
- - Build: `npm run build` (emits to `bin/` via tsup)
207
- - Test: `npm test`
446
+ Local workflow:
447
+
448
+ - Install dependencies: `npm install`
449
+ - Run tests: `npm test`
450
+ - Build the package: `npm run build` (emits to `bin/` and `lib/` via `tsup`)
208
451
  - Run CLI from source (TypeScript): `npx tsx cli.ts --config sqlite/proper.json up`
209
452
 
453
+ Test tooling:
454
+
455
+ - Jest with `ts-jest` and `jest-extended` (see `jest.config.ts`).
456
+ - Core framework tests in `tests/unit/framework`.
457
+ - CLI tests in `tests/unit/cli.test.ts`.
458
+
210
459
  Debugging in VS Code:
460
+
211
461
  - Use the provided `.vscode/launch.json`:
212
- - “Jest: Current File” runs the open test file
213
- - “Jest: All Tests” runs the full suite
214
- - “Jest: CLI Test File” runs only `tests/unit/cli.test.ts`
462
+ - “Jest: Current File” runs the open test file.
463
+ - “Jest: All Tests” runs the full suite.
464
+ - “Jest: CLI Test File” runs only `tests/unit/cli.test.ts`.
465
+
466
+ Contribution guidelines (informal for now):
467
+
468
+ - Keep the public API surface (`index.ts`, `framework/lib/runner.ts`, CLI flags) backward compatible where possible.
469
+ - Add or update tests in `tests/unit` when changing framework or CLI behavior.
470
+ - Prefer small, focused abstractions in `framework/` over adding logic in the CLI.
215
471
 
216
472
  ---
217
473