@savioruz/owi 0.0.5

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 (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +372 -0
  3. package/install.js +107 -0
  4. package/package.json +44 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Owi Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,372 @@
1
+ # Owi - Simple Database Migrations for Swift
2
+
3
+ ![GitHub Release](https://img.shields.io/github/v/release/savioruz/owi)
4
+ ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/savioruz/owi/ci.yml)
5
+ ![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/savioruz/owi/total)
6
+
7
+
8
+ A clean and simple database migration tool for Swift, designed to work seamlessly with Vapor and other Swift web frameworks.
9
+ See [Changelogs](CHANGELOG.md)
10
+
11
+ # Table of Contents
12
+
13
+ 1. [Overview](#overview)
14
+ - [Introduction](#overview)
15
+ - [Features](#overview)
16
+
17
+ 2. [Installation](#installation)
18
+ - [As a Library (for Vapor apps)](#as-a-library-for-vapor-apps)
19
+ - [As a CLI Tool](#as-a-cli-tool)
20
+ - [Quick Install](#quick-install)
21
+ - [Homebrew Installation](#homebrew)
22
+ - [Manual Installation](#manual-installation)
23
+
24
+ 3. [Migration File Format](#migration-file-format)
25
+ - [Example](#migration-file-format)
26
+ - [Rules](#rules)
27
+
28
+ 4. [CLI Usage](#cli-usage)
29
+ - [Create a New Migration](#create-a-new-migration)
30
+ - [Run Migrations](#run-migrations)
31
+ - [Rollback Migrations](#rollback-migrations)
32
+ - [Check Status](#check-status)
33
+ - [CLI Options](#cli-options)
34
+
35
+ 5. [Library Usage (Vapor Integration)](#library-usage-vapor-integration)
36
+ - [Using Vapor’s Database Connection](#using-vapors-database-connection-recommended)
37
+ - [Integration in `configure.swift`](#in-configureswift)
38
+ - [Standalone Usage (Without Vapor)](#standalone-usage-without-vapor)
39
+ - [Key Differences](#key-difference)
40
+
41
+ 6. [API Reference](#api-reference)
42
+ - [`Runner`](#runner)
43
+ - [`Parser`](#parser)
44
+ - [`Migration`](#migration)
45
+ - [`SchemaVersion`](#schemaversion)
46
+
47
+ 7. [Database Schema](#database-schema)
48
+ - [Schema Tracking Table (`owi_schema`)](#database-schema)
49
+ - [Example Table Content](#database-schema)
50
+
51
+ 8. [Contributing](#contributing)
52
+
53
+ 9. [License](#license)
54
+
55
+ 10. [Inspiration](#inspiration)
56
+
57
+
58
+ ## Installation
59
+
60
+ ### As a Library (for Vapor apps)
61
+
62
+ Add to your `Package.swift`:
63
+
64
+ ```swift
65
+ .package(url: "https://github.com/savioruz/owi.git", from: "0.0.1")
66
+ ```
67
+
68
+ Then add to your target:
69
+
70
+ ```swift
71
+ .product(name: "Owi", package: "owi")
72
+ ```
73
+
74
+ ### As a CLI Tool
75
+
76
+ #### Quick Install
77
+
78
+ Using the install script:
79
+
80
+ ```bash
81
+ curl -fsSL https://raw.githubusercontent.com/savioruz/owi/main/install.sh | sh
82
+ ```
83
+
84
+ Or download and run:
85
+
86
+ ```bash
87
+ curl -fsSL -o install.sh https://raw.githubusercontent.com/savioruz/owi/main/install.sh
88
+ chmod +x install.sh
89
+ ./install.sh
90
+ ```
91
+
92
+ #### Homebrew
93
+
94
+ ```bash
95
+ brew tap savioruz/homebrew-tap
96
+ brew install owi
97
+ ```
98
+
99
+ #### Manual Installation
100
+
101
+ Download the latest release from [GitHub Releases](https://github.com/savioruz/owi/releases):
102
+
103
+ ## Migration File Format
104
+
105
+ Migrations are written in SQL files with a specific format:
106
+
107
+ ```sql
108
+ -- migrate:up
109
+ CREATE TABLE users (id SERIAL PRIMARY KEY);
110
+
111
+ -- migrate:down
112
+ DROP TABLE users;
113
+
114
+ -- migrate:up
115
+ ALTER TABLE users ADD COLUMN email VARCHAR(255);
116
+
117
+ -- migrate:down
118
+ ALTER TABLE users DROP COLUMN email;
119
+ ```
120
+
121
+ ### Rules
122
+
123
+ 1. Each file can contain **single or multiple up/down pairs**
124
+ 2. Sections are marked with `-- migrate:up` and `-- migrate:down`
125
+ 3. Down sections are applied in **reverse order** during rollback
126
+ 4. File names should follow the pattern: `{number}_{description}.sql`
127
+ - Example: `001_create_users_table.sql`
128
+
129
+ ## CLI Usage
130
+
131
+ ### Create a New Migration
132
+
133
+ ```bash
134
+ owi new create_users_table
135
+
136
+ # With custom directory
137
+ owi new create_users_table -m ./db/migrations
138
+ ```
139
+
140
+ This creates a new migration file with an auto-incremented number:
141
+
142
+ ```
143
+ migrations/
144
+ 001_create_users_table.sql
145
+ ```
146
+
147
+ ### Run Migrations
148
+
149
+ ```bash
150
+ # SQLite
151
+ owi migrate -u ./db.sqlite -m ./migrations
152
+
153
+ # PostgreSQL
154
+ owi migrate -u "postgres://user:pass@localhost:5432/mydb" --type postgres -m ./migrations
155
+
156
+ # MySQL
157
+ owi migrate -u "mysql://user:pass@localhost:3306/mydb" --type mysql -m ./migrations
158
+ ```
159
+
160
+ ### Rollback Migrations
161
+
162
+ ```bash
163
+ # Rollback last migration
164
+ owi rollback -u ./db.sqlite
165
+
166
+ # Rollback last 3 migrations
167
+ owi rollback -u ./db.sqlite --count 3
168
+ ```
169
+
170
+ ### Check Status
171
+
172
+ ```bash
173
+ owi status -u ./db.sqlite -m ./migrations
174
+ ```
175
+
176
+ Output:
177
+ ```
178
+ Migration Status:
179
+ ─────────────────────────────────────
180
+ ✓ 001_create_users_table
181
+ ✓ 002_add_email_to_users
182
+ ✗ 003_create_posts_table
183
+ ─────────────────────────────────────
184
+ Applied: 2, Pending: 1
185
+ ```
186
+
187
+ ### CLI Options
188
+
189
+ All commands support these options:
190
+
191
+ - `-u, --database-url <url>` - Database URL or path
192
+ - `-m, --migrations-dir <dir>` - Migrations directory (default: `./migrations`)
193
+ - `--type <type>` - Database type: `sqlite`, `postgres`, or `mysql` (default: `sqlite`)
194
+
195
+ ## Library Usage (Vapor Integration)
196
+
197
+ ### Using Vapor's Database Connection (Recommended)
198
+
199
+ When integrating with Vapor, pass your existing database connection to avoid connection pool issues:
200
+
201
+ ```swift
202
+ import Vapor
203
+ import Owi
204
+
205
+ func configureMigrations(_ app: Application) async throws {
206
+ // Use Vapor's existing database connection - no pool management needed!
207
+ let driver = PostgresDriver(database: app.db(.psql))
208
+
209
+ // Create runner
210
+ let runner = Runner(
211
+ driver: driver,
212
+ migrationDir: "./Migrations"
213
+ )
214
+
215
+ // Run migrations
216
+ try await runner.migrate()
217
+
218
+ // No need to call close() - Vapor manages the connection!
219
+ }
220
+ ```
221
+
222
+ **For MySQL:**
223
+ ```swift
224
+ let driver = MySQLDriver(database: app.db(.mysql))
225
+ ```
226
+
227
+ **For SQLite:**
228
+ ```swift
229
+ let driver = SQLiteDriver(database: app.db(.sqlite))
230
+ ```
231
+
232
+ ### In `configure.swift`
233
+
234
+ ```swift
235
+ public func configure(_ app: Application) async throws {
236
+ // ... your database configuration
237
+
238
+ // Run migrations on startup (optional)
239
+ try await configureMigrations(app)
240
+
241
+ // ... rest of configuration
242
+ }
243
+ ```
244
+
245
+ ### Standalone Usage (Without Vapor)
246
+
247
+ If you're building a CLI tool or not using Vapor, create your own connection:
248
+
249
+ ```swift
250
+ import Owi
251
+ import PostgresKit
252
+
253
+ // Create configuration using native PostgresKit types
254
+ let config = PostgresConfiguration(
255
+ hostname: "localhost",
256
+ port: 5432,
257
+ username: "postgres",
258
+ password: "postgres",
259
+ database: "myapp",
260
+ tls: .disable
261
+ )
262
+
263
+ // Create driver (manages its own connection pool)
264
+ let driver = try await PostgresDriver(configuration: config)
265
+
266
+ // Create runner
267
+ let runner = Runner(driver: driver, migrationDir: "./Migrations")
268
+
269
+ // Run migrations
270
+ try await runner.migrate()
271
+
272
+ // Check status
273
+ try await runner.status()
274
+
275
+ // Rollback
276
+ try await runner.rollback(count: 1)
277
+
278
+ // IMPORTANT: Close the connection when done!
279
+ try await runner.close()
280
+ ```
281
+
282
+ **Key Difference:**
283
+ - **With Vapor database**: `PostgresDriver(database: app.db(.psql))` - NO `close()` needed
284
+ - **With configuration**: `PostgresDriver(configuration: config)` - MUST call `close()`
285
+
286
+ ## API Reference
287
+
288
+ ### `Runner`
289
+
290
+ Main migration runner.
291
+
292
+ ```swift
293
+ public struct Runner {
294
+ public init(
295
+ driver: any DatabaseDriver,
296
+ migrationDir: String,
297
+ schemaTableName: String = "owi_schema"
298
+ )
299
+
300
+ public func migrate() async throws
301
+ public func rollback(count: Int = 1) async throws
302
+ public func status() async throws
303
+ public func close() async throws
304
+ }
305
+ ```
306
+
307
+ ### `Parser`
308
+
309
+ Parses migration files.
310
+
311
+ ```swift
312
+ public struct Parser {
313
+ public func parse(fileURL: URL) throws -> Migration
314
+ public func loadMigrations(from directoryURL: URL) throws -> [Migration]
315
+ }
316
+ ```
317
+
318
+ ### `Migration`
319
+
320
+ Represents a single migration.
321
+
322
+ ```swift
323
+ public struct Migration {
324
+ public let id: String
325
+ public let upSQL: String
326
+ public let downSQL: String
327
+ public let filePath: String
328
+ public var version: Int? // Extracted from ID (e.g., "001" -> 1)
329
+ }
330
+ ```
331
+
332
+ ### `SchemaVersion`
333
+
334
+ Represents the current schema version state in the database.
335
+
336
+ ```swift
337
+ public struct SchemaVersion {
338
+ public let id: Int // Always 1
339
+ public let version: Int // Current migration version
340
+ public let dirty: Bool // Is a migration in progress?
341
+ public let modifiedAt: Date // Last modification time
342
+ }
343
+ ```
344
+
345
+ ### Database Schema
346
+
347
+ The schema tracking table (`owi_schema` by default, customizable) stores **a single row**:
348
+
349
+ | Column | Type | Description |
350
+ |--------|------|-------------|
351
+ | `id` | INTEGER | Always 1 (enforced by CHECK constraint) |
352
+ | `version` | INTEGER | Current migration version (e.g., 0, 1, 2, 3) |
353
+ | `dirty` | BOOLEAN/INTEGER | Migration in progress flag |
354
+ | `modified_at` | TIMESTAMP/TEXT | Last modification timestamp |
355
+
356
+ Example table content:
357
+ ```
358
+ id | version | dirty | modified_at
359
+ 1 | 3 | 0 | 2025-10-26 20:08:53
360
+ ```
361
+
362
+ ## Contributing
363
+
364
+ Contributions are welcome! Please feel free to submit a Pull Request.
365
+
366
+ ## License
367
+
368
+ [MIT License](LICENSE) - feel free to use in your projects!
369
+
370
+ ## Inspiration
371
+
372
+ Inspired by tools like [dbmate](https://github.com/amacneil/dbmate.git), but designed specifically for Swift and Vapor.
package/install.js ADDED
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execSync } = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const https = require('https');
7
+
8
+ const REPO = 'savioruz/owi';
9
+
10
+ function getPlatform() {
11
+ const platform = process.platform;
12
+ const arch = process.arch;
13
+
14
+ if (platform === 'darwin') {
15
+ return 'owi-macos.tar.gz';
16
+ } else if (platform === 'linux') {
17
+ if (arch === 'x64') {
18
+ return 'owi-linux-x86_64.tar.gz';
19
+ } else if (arch === 'arm64') {
20
+ return 'owi-linux-aarch64.tar.gz';
21
+ }
22
+ }
23
+
24
+ throw new Error(`Unsupported platform: ${platform} ${arch}`);
25
+ }
26
+
27
+ function getVersion() {
28
+ const packageJson = require('./package.json');
29
+ return packageJson.version;
30
+ }
31
+
32
+ function download(url, dest) {
33
+ return new Promise((resolve, reject) => {
34
+ const file = fs.createWriteStream(dest);
35
+
36
+ https.get(url, {
37
+ headers: { 'User-Agent': 'owi-npm-installer' },
38
+ followRedirect: true
39
+ }, (response) => {
40
+ // Handle redirects
41
+ if (response.statusCode === 302 || response.statusCode === 301) {
42
+ download(response.headers.location, dest).then(resolve).catch(reject);
43
+ return;
44
+ }
45
+
46
+ if (response.statusCode !== 200) {
47
+ reject(new Error(`Failed to download: ${response.statusCode}`));
48
+ return;
49
+ }
50
+
51
+ response.pipe(file);
52
+
53
+ file.on('finish', () => {
54
+ file.close();
55
+ resolve();
56
+ });
57
+ }).on('error', (err) => {
58
+ fs.unlink(dest, () => {});
59
+ reject(err);
60
+ });
61
+ });
62
+ }
63
+
64
+ async function install() {
65
+ try {
66
+ const platformFile = getPlatform();
67
+ const version = getVersion();
68
+
69
+ // Use latest if version is development
70
+ const versionTag = version === '0.0.5' ? 'latest/download' : `download/v${version}`;
71
+ const url = `https://github.com/${REPO}/releases/${versionTag}/${platformFile}`;
72
+
73
+ console.log(`Downloading owi from ${url}...`);
74
+
75
+ const binDir = path.join(__dirname, 'bin');
76
+ const tarPath = path.join(__dirname, platformFile);
77
+
78
+ // Create bin directory
79
+ if (!fs.existsSync(binDir)) {
80
+ fs.mkdirSync(binDir, { recursive: true });
81
+ }
82
+
83
+ // Download the tarball
84
+ await download(url, tarPath);
85
+
86
+ // Extract the binary
87
+ console.log('Extracting binary...');
88
+ execSync(`tar -xzf ${tarPath} -C ${binDir}`, { stdio: 'inherit' });
89
+
90
+ // Make it executable
91
+ const binaryPath = path.join(binDir, 'owi');
92
+ fs.chmodSync(binaryPath, '755');
93
+
94
+ // Clean up
95
+ fs.unlinkSync(tarPath);
96
+
97
+ console.log('✓ owi installed successfully!');
98
+ console.log('Run "owi --help" to get started.');
99
+ } catch (error) {
100
+ console.error('Installation failed:', error.message);
101
+ console.error('\nYou can download the binary manually from:');
102
+ console.error(`https://github.com/${REPO}/releases/latest`);
103
+ process.exit(1);
104
+ }
105
+ }
106
+
107
+ install();
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@savioruz/owi",
3
+ "version": "0.0.5",
4
+ "description": "Simple database migration tool for Swift and Vapor",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "owi": "./bin/owi"
8
+ },
9
+ "scripts": {
10
+ "postinstall": "node install.js"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/savioruz/owi.git"
15
+ },
16
+ "keywords": [
17
+ "migration",
18
+ "database",
19
+ "swift",
20
+ "vapor",
21
+ "cli",
22
+ "postgres",
23
+ "mysql",
24
+ "sqlite"
25
+ ],
26
+ "author": "savioruz",
27
+ "license": "MIT",
28
+ "bugs": {
29
+ "url": "https://github.com/savioruz/owi/issues"
30
+ },
31
+ "homepage": "https://github.com/savioruz/owi#readme",
32
+ "os": [
33
+ "darwin",
34
+ "linux"
35
+ ],
36
+ "cpu": [
37
+ "x64",
38
+ "arm64"
39
+ ],
40
+ "files": [
41
+ "install.js",
42
+ "bin/"
43
+ ]
44
+ }