@h3ravel/arquebus 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 arquebus
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,94 @@
1
+ <div align="center">
2
+ <img src="https://h3ravel.toneflix.net/logo-full.svg" width="100" alt="H3ravel logo" />
3
+ <h1 align="center"><a href="https://h3ravel.toneflix.net/arquebus">H3ravel Arquebus</a></h1>
4
+ <a href="https://www.npmjs.com/package/@h3ravel/arquebus"><img alt="NPM version" src="https://img.shields.io/npm/v/@h3ravel/arquebus.svg"></a>
5
+ <a href="https://github.com/h3ravel/arquebus/workflows/tests"><img alt="ci" src="https://github.com/h3ravel/arquebus/workflows/tests/badge.svg"></a>
6
+ <a href="https://github.com/h3ravel/arquebus/blob/main/README.md"><img alt="GitHub" src="https://img.shields.io/github/license/h3ravel/arquebus"></a>
7
+ <br />
8
+ </div>
9
+
10
+ Arquebus ORM is a Beautiful, expressive framework-agnostic Object-Relational Mapper (ORM) inspired by Laravel's Eloquent, designed for TypeScript applications and for the H3ravel Framework that makes it enjoyable to interact with your database. When using Arquebus, each database table has a corresponding "Model" that is used to interact with that table. In addition to retrieving records from the database table, Arquebus models allow you to insert, update, and delete records from the database as well.
11
+
12
+ > Arquebus is a Typescript and Modern JS rewrite of [Sutando](https://sutando.org/) and is heavily inspired by Laravel's ORM [Eloquent](https://laravel.com/docs/12.x/eloquent).
13
+
14
+ ## ✨ Features
15
+
16
+ - Supports MySQL, PostgreSQL, SQLite and other databases
17
+ - Concise syntax and intuitive operations
18
+ - Model relationships for handling complex data queries and operations
19
+ - Powerful query builder
20
+ - Customized data type conversion for model attributes
21
+ - Easy-to-use transaction
22
+ - Support for hooks to execute custom logic at different stages of model operations
23
+ - Simple plugin mechanism for easy expansion
24
+
25
+ ## Documentation
26
+
27
+ Check the full documentation on [https://h3ravel.toneflix.net/arquebus](https://h3ravel.toneflix.net/arquebus)
28
+
29
+ ## Quick Start
30
+
31
+ Let’s take mysql as an example.
32
+
33
+ Install Arquebus and mysql database library
34
+
35
+ ```sh
36
+ $ npm install @h3ravel/arquebus mysql2 --save
37
+ ```
38
+
39
+ The easiest way to make SQL queries is to use the Database query builder. It allows you to construct simple and complex SQL queries using JavaScript methods.
40
+
41
+ ```ts
42
+ import { arquebus, Model } from '@h3ravel/arquebus';
43
+
44
+ // Add SQL Connection Info
45
+ arquebus.addConnection({
46
+ client: 'mysql2',
47
+ connection: {
48
+ host: '127.0.0.1',
49
+ port: 3306,
50
+ user: 'root',
51
+ password: '',
52
+ database: 'test',
53
+ },
54
+ });
55
+
56
+ const db = arquebus.connection();
57
+
58
+ // Query Builder
59
+ const users = await db.table('users').where('age', '>', 35).get();
60
+
61
+ // ORM
62
+ class User extends Model {}
63
+
64
+ // Query Data
65
+ const users = await User.query().where('age', '>', 35).get();
66
+
67
+ // Insert
68
+ const user = new User();
69
+ user.name = 'David Bowie';
70
+ await user.save();
71
+
72
+ // Delete
73
+ await user.delete();
74
+
75
+ // Pagination
76
+ const users = await User.query().paginate();
77
+
78
+ // Eager Loading
79
+ const users = await User.query().with('posts').get();
80
+
81
+ // Constraining Eager Loads
82
+ const users = await User.query()
83
+ .with({
84
+ posts: (q) => q.where('likes_count', '>', 100),
85
+ })
86
+ .get();
87
+
88
+ // Lazy Eager Loading
89
+ await user.load('posts');
90
+ ```
91
+
92
+ ## Show Your Support
93
+
94
+ Please ⭐️ this repository if this project helped you
package/bin/cli.js ADDED
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env node
2
+ import { program } from 'commander'
3
+ import path from 'path'
4
+ import { promisify } from 'util'
5
+ import fs from 'fs'
6
+ import * as color from 'colorette'
7
+ import resolveFrom from 'resolve-from'
8
+ import { snake } from 'radashi'
9
+ import { success, exit, twoColumnDetail, findUpConfig, findUpModulePath, findModulePkg, TableGuesser, localModuleCheck } from './utils.js'
10
+ import cliPkg from '../package.json' with { type: 'json' }
11
+
12
+ const writeFile = promisify(fs.writeFile)
13
+ const readFile = promisify(fs.readFile)
14
+
15
+ const env = {
16
+ modulePath: findModulePkg('arquebus') || resolveFrom.silent(process.cwd(), 'arquebus') || findUpModulePath(process.cwd(), 'arquebus'),
17
+ cwd: process.cwd(),
18
+ configPath: findUpConfig(process.cwd(), 'arquebus.config', ['js', 'cjs'])
19
+ }
20
+ let modulePackage = {}
21
+ try {
22
+ modulePackage = require(path.join(env.modulePath, 'package.json'))
23
+ }
24
+ catch {
25
+ /* empty */
26
+ }
27
+ function getArquebusModule (modulePath) {
28
+ localModuleCheck(env)
29
+ }
30
+ const cliVersion = [
31
+ 'Arquebus CLI version:',
32
+ color.green(cliPkg.version),
33
+ ].join(' ')
34
+ const localVersion = [
35
+ 'Arquebus Local version:',
36
+ color.green(modulePackage.version || 'None'),
37
+ ].join(' ')
38
+ program
39
+ .name('arquebus')
40
+ .version(`${cliVersion}\n${localVersion}`)
41
+ program
42
+ .command('init')
43
+ .description('Create a fresh arquebus config.')
44
+ .action(async () => {
45
+ localModuleCheck(env)
46
+ const type = 'js'
47
+ if (env.configPath) {
48
+ exit(`Error: ${env.configPath} already exists`)
49
+ }
50
+ try {
51
+ const stubPath = `./arquebus.config.${type}`
52
+ const code = await readFile(env.modulePath +
53
+ '/src/stubs/arquebus.config-' +
54
+ type +
55
+ '.stub')
56
+ await writeFile(stubPath, code)
57
+ success(color.green(`Created ${stubPath}`))
58
+ }
59
+ catch (e) {
60
+ exit(e)
61
+ }
62
+ })
63
+ program
64
+ .command('migrate:make <name>')
65
+ .description('Create a new migration file.')
66
+ .option('--table', 'The table to migrate')
67
+ .option('--create', 'The table to be created')
68
+ .action(async (name, opts) => {
69
+ if (!env.configPath) {
70
+ exit('Error: arquebus config not found. Run `arquebus init` first.')
71
+ }
72
+ try {
73
+ name = snake(name)
74
+ let table = opts.table
75
+ let create = opts.create || false
76
+ if (!table && typeof create === 'string') {
77
+ table = create
78
+ create = true
79
+ }
80
+ if (!table) {
81
+ const guessed = TableGuesser.guess(name)
82
+ table = guessed[0]
83
+ create = guessed[1]
84
+ }
85
+ const MigrationCreator = getArquebusModule('src/migrations/migration-creator')
86
+ const creator = new MigrationCreator('')
87
+ const fileName = await creator.create(name, env.cwd + `/${config?.migrations?.path || 'migrations'}`, table, create)
88
+ success(color.green(`Created Migration: ${fileName}`))
89
+ }
90
+ catch (err) {
91
+ exit(err)
92
+ }
93
+ })
94
+ program
95
+ .command('migrate:publish <package>')
96
+ .description('Publish any migration files from packages.')
97
+ .action(async (pkg, opts) => {
98
+ if (!env.configPath) {
99
+ exit('Error: arquebus config not found. Run `arquebus init` first.')
100
+ }
101
+ try {
102
+ const packagePath = findModulePkg(pkg)
103
+ if (!packagePath) {
104
+ exit(`Error: package ${pkg} not found`)
105
+ }
106
+ const MigrationCreator = getArquebusModule('src/migrations/migration-creator')
107
+ const creator = new MigrationCreator(path.join(packagePath, 'migrations'))
108
+ console.log(color.green('Publishing migrations:'))
109
+ const fileNames = await creator.publish(env.cwd + `/${config?.migrations?.path || 'migrations'}`, (fileName, oldPath, newPath) => {
110
+ console.log(newPath + ' ' + color.green('DONE'))
111
+ })
112
+ }
113
+ catch (err) {
114
+ exit(err)
115
+ }
116
+ })
117
+ program
118
+ .command('migrate:run')
119
+ .description('Run all pending migrations.')
120
+ .option('--step', 'Force the migrations to be run so they can be rolled back individually.')
121
+ .option('--path <path>', 'The path to the migrations directory.')
122
+ .action(async (opts) => {
123
+ if (!env.configPath) {
124
+ exit('Error: arquebus config not found. Run `arquebus init` first.')
125
+ }
126
+ try {
127
+ const { migrateRun } = getArquebusModule('src/migrate')
128
+ await migrateRun(config, opts, true)
129
+ }
130
+ catch (err) {
131
+ exit(err)
132
+ }
133
+ })
134
+ program
135
+ .command('migrate:rollback')
136
+ .description('Rollback the last database migration.')
137
+ .option('--step <number>', 'The number of migrations to be reverted.')
138
+ .option('--path <path>', 'The path to the migrations directory.')
139
+ .action(async (opts) => {
140
+ if (!env.configPath) {
141
+ exit('Error: arquebus config not found. Run `arquebus init` first.')
142
+ }
143
+ try {
144
+ const { migrateRollback } = getArquebusModule('src/migrate')
145
+ await migrateRollback(config, opts, true)
146
+ }
147
+ catch (err) {
148
+ exit(err)
149
+ }
150
+ })
151
+ program
152
+ .command('migrate:status')
153
+ .description('Show the status of each migration.')
154
+ .option('--path <path>', 'The path to the migrations directory.')
155
+ .action(async (opts) => {
156
+ if (!env.configPath) {
157
+ exit('Error: arquebus config not found. Run `arquebus init` first.')
158
+ }
159
+ try {
160
+ const { migrateStatus } = getArquebusModule('src/migrate')
161
+ const migrations = await migrateStatus(config, opts, true)
162
+ if (migrations.length > 0) {
163
+ twoColumnDetail(color.gray('Migration name'), color.gray('Batch / Status'))
164
+ migrations.forEach(migration => {
165
+ const status = migration.ran
166
+ ? `[${migration.batch}] ${color.green('Ran')}`
167
+ : color.yellow('Pending')
168
+ twoColumnDetail(migration.name, status)
169
+ })
170
+ }
171
+ else {
172
+ console.log('No migrations found')
173
+ }
174
+ }
175
+ catch (err) {
176
+ exit(err)
177
+ }
178
+ })
179
+ program
180
+ .command('model:make <name>')
181
+ .description('Create a new Model file.')
182
+ .option('--force', 'Force creation if model already exists.', false)
183
+ .action(async (name, opts) => {
184
+ if (!env.configPath) {
185
+ exit('Error: arquebus config not found. Run `arquebus init` first.')
186
+ }
187
+ try {
188
+ const modelPath = path.join(env.cwd, config?.models?.path || 'models', name?.toLowerCase() + '.js')
189
+ if (!opts.force && fs.existsSync(modelPath)) {
190
+ exit('Model already exists.')
191
+ }
192
+ await promisify(fs.mkdir)(path.dirname(modelPath), { recursive: true })
193
+ const stubPath = path.join(env.modulePath, 'src/stubs/model-js.stub')
194
+ let stub = await readFile(stubPath, 'utf-8')
195
+ stub = stub.replace(/{{ name }}/g, name)
196
+ await writeFile(modelPath, stub)
197
+ success(color.green(`Created Model: ${modelPath}`))
198
+ }
199
+ catch (err) {
200
+ exit(err)
201
+ }
202
+ })
203
+ program.parse()
package/bin/utils.js ADDED
@@ -0,0 +1,141 @@
1
+ import * as color from 'colorette'
2
+
3
+ import escalade from 'escalade/sync'
4
+ import path from 'path'
5
+ import resolveFrom from 'resolve-from'
6
+
7
+ function success (text) {
8
+ console.log(text)
9
+ process.exit(0)
10
+ }
11
+
12
+ function exit (text) {
13
+ if (text instanceof Error) {
14
+ if (text.message) {
15
+ console.error(color.red(text.message))
16
+ }
17
+ console.error(color.red(`${text.detail ? `${text.detail}\n` : ''}${text.stack}`))
18
+ }
19
+ else {
20
+ console.error(color.red(text))
21
+ }
22
+ process.exit(1)
23
+ }
24
+
25
+ function twoColumnDetail (name, value) {
26
+ const regex = /\x1b\[\d+m/g
27
+ const width = Math.min(process.stdout.columns, 100)
28
+ const dots = Math.max(width - name.replace(regex, '').length - value.replace(regex, '').length - 10, 0)
29
+ return console.log(name, color.gray('.'.repeat(dots)), value)
30
+ }
31
+
32
+ function findUpConfig (cwd, name, extensions) {
33
+ return escalade(cwd, (dir, names) => {
34
+ for (const ext of extensions) {
35
+ const filename = `${name}.${ext}`
36
+ if (names.includes(filename)) {
37
+ return filename
38
+ }
39
+ }
40
+ return false
41
+ })
42
+ }
43
+
44
+ function findUpModulePath (cwd, packageName) {
45
+ const modulePackagePath = escalade(cwd, (dir, names) => {
46
+ if (names.includes('package.json')) {
47
+ return 'package.json'
48
+ }
49
+ return false
50
+ })
51
+ try {
52
+ if (modulePackage.name === packageName) {
53
+ return path.join(path.dirname(modulePackagePath), modulePackage.main || 'index.js')
54
+ }
55
+ }
56
+ catch (e) {
57
+ /* empty */
58
+ }
59
+ }
60
+
61
+ function findModulePkg (moduleId, options = {}) {
62
+ const parts = moduleId.replace(/\\/g, '/').split('/')
63
+ let packageName = ''
64
+ // Handle scoped package name
65
+ if (parts.length > 0 && parts[0][0] === '@') {
66
+ packageName += parts.shift() + '/'
67
+ }
68
+ packageName += parts.shift()
69
+ const packageJson = path.join(packageName, 'package.json')
70
+ const resolved = resolveFrom.silent(options.cwd || process.cwd(), packageJson)
71
+ if (!resolved) {
72
+ return
73
+ }
74
+ return path.join(path.dirname(resolved), parts.join('/'))
75
+ }
76
+
77
+ const join = path.join
78
+
79
+ async function getMigrationPaths (cwd, migrator, defaultPath, path) {
80
+ if (path) {
81
+ return [join(cwd, path)]
82
+ }
83
+ return [
84
+ ...migrator.getPaths(),
85
+ join(cwd, defaultPath),
86
+ ]
87
+ }
88
+
89
+ function localModuleCheck (env) {
90
+ if (!env.modulePath) {
91
+ console.log(color.red('No local arquebus install found.'))
92
+ exit('Try running: npm install arquebus --save')
93
+ }
94
+ }
95
+ class TableGuesser {
96
+ static CREATE_PATTERNS = [
97
+ /^create_(\w+)_table$/,
98
+ /^create_(\w+)$/
99
+ ]
100
+ static CHANGE_PATTERNS = [
101
+ /.+_(to|from|in)_(\w+)_table$/,
102
+ /.+_(to|from|in)_(\w+)$/
103
+ ]
104
+ static guess (migration) {
105
+ for (const pattern of TableGuesser.CREATE_PATTERNS) {
106
+ const matches = migration.match(pattern)
107
+ if (matches) {
108
+ return [matches[1], true]
109
+ }
110
+ }
111
+ for (const pattern of TableGuesser.CHANGE_PATTERNS) {
112
+ const matches = migration.match(pattern)
113
+ if (matches) {
114
+ return [matches[2], false]
115
+ }
116
+ }
117
+ return []
118
+ }
119
+ }
120
+
121
+ export { exit }
122
+ export { success }
123
+ export { twoColumnDetail }
124
+ export { findUpModulePath }
125
+ export { findModulePkg }
126
+ export { findUpConfig }
127
+ export { localModuleCheck }
128
+ export { getMigrationPaths }
129
+ export { TableGuesser }
130
+
131
+ export default {
132
+ exit,
133
+ success,
134
+ twoColumnDetail,
135
+ findUpModulePath,
136
+ findModulePkg,
137
+ findUpConfig,
138
+ localModuleCheck,
139
+ getMigrationPaths,
140
+ TableGuesser
141
+ }