@platformatic/db 3.4.1 → 3.5.0
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 +1 -1
- package/config.d.ts +442 -107
- package/eslint.config.js +9 -5
- package/index.d.ts +53 -31
- package/index.js +30 -139
- package/lib/application.js +102 -0
- package/lib/capability.js +35 -0
- package/lib/commands/index.js +59 -0
- package/lib/commands/migrations-apply.js +63 -0
- package/lib/commands/migrations-create.js +48 -0
- package/lib/commands/print-schema.js +31 -0
- package/lib/commands/seed.js +74 -0
- package/lib/commands/types.js +22 -0
- package/lib/config.js +52 -0
- package/lib/errors.js +16 -12
- package/lib/generator.js +229 -0
- package/lib/{migrator.mjs → migrator.js} +46 -38
- package/lib/{root-endpoint/index.js → root.js} +6 -7
- package/lib/schema.js +41 -20
- package/lib/{generator/code-templates.js → templates.js} +57 -16
- package/lib/types.js +161 -0
- package/lib/upgrade.js +8 -12
- package/lib/utils.js +12 -23
- package/lib/versions/0.18.0.js +3 -5
- package/lib/versions/{from-zero-twenty-height-to-will-see.js → 0.28.0.js} +3 -5
- package/lib/versions/2.0.0.js +3 -5
- package/lib/versions/3.0.0.js +14 -0
- package/package.json +32 -40
- package/schema.json +1385 -164
- package/tsconfig.json +16 -6
- package/db.mjs +0 -86
- package/help/compile.txt +0 -17
- package/help/create.txt +0 -13
- package/help/help.txt +0 -11
- package/help/migrations apply.txt +0 -45
- package/help/migrations create.txt +0 -27
- package/help/migrations.txt +0 -4
- package/help/schema.txt +0 -25
- package/help/seed.txt +0 -36
- package/help/start.txt +0 -47
- package/help/types.txt +0 -40
- package/index.test-d.ts +0 -43
- package/lib/adjust-config.js +0 -42
- package/lib/create.mjs +0 -89
- package/lib/gen-migration.mjs +0 -53
- package/lib/gen-schema.mjs +0 -68
- package/lib/gen-types.mjs +0 -202
- package/lib/generator/README.md +0 -38
- package/lib/generator/db-generator.js +0 -260
- package/lib/generator.d.ts +0 -7
- package/lib/migrate.mjs +0 -81
- package/lib/seed.mjs +0 -90
- package/lib/stackable.js +0 -49
- /package/{lib/root-endpoint/public → public}/images/dark_mode.svg +0 -0
- /package/{lib/root-endpoint/public → public}/images/favicon.ico +0 -0
- /package/{lib/root-endpoint/public → public}/images/light_mode.svg +0 -0
- /package/{lib/root-endpoint/public → public}/images/platformatic-logo-dark.svg +0 -0
- /package/{lib/root-endpoint/public → public}/images/platformatic-logo-light.svg +0 -0
- /package/{lib/root-endpoint/public → public}/images/triangle_dark.svg +0 -0
- /package/{lib/root-endpoint/public → public}/images/triangle_light.svg +0 -0
- /package/{lib/root-endpoint/public → public}/index.html +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { kMetadata, loadConfiguration, loadModule } from '@platformatic/foundation'
|
|
2
|
+
import { access } from 'fs/promises'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
4
|
+
import { resolve } from 'node:path'
|
|
5
|
+
import { transform } from '../config.js'
|
|
6
|
+
import { MissingSeedFileError } from '../errors.js'
|
|
7
|
+
import { Migrator } from '../migrator.js'
|
|
8
|
+
import { schema } from '../schema.js'
|
|
9
|
+
import { setupDB } from '../utils.js'
|
|
10
|
+
|
|
11
|
+
export async function seed (logger, configFile, args, { colorette: { bold }, logFatalError }) {
|
|
12
|
+
const config = await loadConfiguration(configFile, schema, { transform })
|
|
13
|
+
|
|
14
|
+
if (config.migrations !== undefined) {
|
|
15
|
+
const migrator = new Migrator(config.migrations, config.db, logger)
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const hasMigrationsToApply = await migrator.hasMigrationsToApply()
|
|
19
|
+
if (hasMigrationsToApply) {
|
|
20
|
+
logFatalError(logger, 'You must apply migrations before seeding the database.')
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
} finally {
|
|
24
|
+
await migrator.close()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!args.length) {
|
|
29
|
+
throw new MissingSeedFileError()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const root = config[kMetadata].root
|
|
33
|
+
const seedFile = resolve(root, args[0])
|
|
34
|
+
await access(seedFile)
|
|
35
|
+
|
|
36
|
+
logger.info(`Seeding from ${bold(seedFile)}`)
|
|
37
|
+
|
|
38
|
+
const importedModule = await loadModule(createRequire(resolve(root, 'noop.js')), seedFile)
|
|
39
|
+
|
|
40
|
+
const seedFunction = typeof importedModule?.seed !== 'function' ? importedModule : importedModule.seed
|
|
41
|
+
|
|
42
|
+
if (typeof seedFunction !== 'function') {
|
|
43
|
+
logFatalError(logger, 'Cannot find seed function.')
|
|
44
|
+
logFatalError(logger, "If you use an ESM module use the signature 'export async function seed (opts)'.")
|
|
45
|
+
logFatalError(logger, "If you use a CJS module use the signature 'module.exports = async function seed (opts)'.")
|
|
46
|
+
logFatalError(logger, "If you use Typescript use the signature 'export async function seed(opts)'")
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const { db, sql, entities } = await setupDB(logger, config.db)
|
|
51
|
+
await seedFunction({ db, sql, entities, logger })
|
|
52
|
+
logger.info('Seeding complete.')
|
|
53
|
+
|
|
54
|
+
// Once done seeding, close your connection.
|
|
55
|
+
await db.dispose()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const helpFooter = `
|
|
59
|
+
This is a convenience method that loads a JavaScript file and configure @platformatic/sql-mapper to connect to the database specified in the configuration file.
|
|
60
|
+
|
|
61
|
+
Here is an example of a seed file:
|
|
62
|
+
|
|
63
|
+
\`\`\`
|
|
64
|
+
'use strict'
|
|
65
|
+
|
|
66
|
+
module.exports = async function ({ entities, db, sql }) {
|
|
67
|
+
await entities.graph.save({ input: { name: 'Hello' } })
|
|
68
|
+
await db.query(sql\`INSERT INTO graphs (name) VALUES ('Hello 2');\`)
|
|
69
|
+
}
|
|
70
|
+
\`\`\`
|
|
71
|
+
|
|
72
|
+
You can find more details about the configuration format here:
|
|
73
|
+
* [Platformatic DB Configuration](https://docs.platformatic.dev/docs/db/configuration)
|
|
74
|
+
`
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { loadConfiguration } from '@platformatic/foundation'
|
|
2
|
+
import { transform } from '../config.js'
|
|
3
|
+
import { schema } from '../schema.js'
|
|
4
|
+
import { execute } from '../types.js'
|
|
5
|
+
|
|
6
|
+
export async function generateTypes (logger, configFile, _args) {
|
|
7
|
+
const config = await loadConfiguration(configFile, schema, { transform })
|
|
8
|
+
|
|
9
|
+
const count = await execute({ logger, config })
|
|
10
|
+
|
|
11
|
+
if (count === 0) {
|
|
12
|
+
logger.warn('No entities found in your schema. Types were NOT generated.')
|
|
13
|
+
logger.warn('Make sure you have applied all the migrations and try again.')
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const helpFooter = `
|
|
18
|
+
As a result of executing this command, the Platformatic DB will generate a \`types\` folder with a typescript file for each database entity. It will also generate a \`plt-env.d.ts\` file that injects the types into the Application instance.
|
|
19
|
+
|
|
20
|
+
You can find more details about the configuration format here:
|
|
21
|
+
* [Platformatic DB Configuration](https://docs.platformatic.dev/docs/db/configuration)
|
|
22
|
+
`
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { kMetadata } from '@platformatic/foundation'
|
|
2
|
+
import { transform as serviceTransform } from '@platformatic/service'
|
|
3
|
+
import { readFile } from 'node:fs/promises'
|
|
4
|
+
import { resolve as resolvePath } from 'node:path'
|
|
5
|
+
|
|
6
|
+
export async function transform (config) {
|
|
7
|
+
config = await serviceTransform(config)
|
|
8
|
+
|
|
9
|
+
if (
|
|
10
|
+
config.db &&
|
|
11
|
+
config.db.connectionString.indexOf('sqlite') === 0 &&
|
|
12
|
+
config.db.connectionString !== 'sqlite://:memory:'
|
|
13
|
+
) {
|
|
14
|
+
const originalSqlitePath = config.db.connectionString.replace('sqlite://', '')
|
|
15
|
+
const sqliteFullPath = resolvePath(config[kMetadata].root, originalSqlitePath)
|
|
16
|
+
config.db.connectionString = 'sqlite://' + sqliteFullPath
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* c8 ignore next 3 */
|
|
20
|
+
if (config.db.graphql?.schemaPath) {
|
|
21
|
+
config.db.graphql.schema = await readFile(config.db.graphql.schemaPath, 'utf8')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* c8 ignore next 2 */
|
|
25
|
+
const arePostgresqlSchemaDefined =
|
|
26
|
+
config.db?.connectionString.indexOf('postgres') === 0 && config.db?.schema?.length > 0
|
|
27
|
+
const migrationsTableName = arePostgresqlSchemaDefined ? 'public.versions' : 'versions'
|
|
28
|
+
|
|
29
|
+
// relative-to-absolute migrations path
|
|
30
|
+
if (config.migrations) {
|
|
31
|
+
config.migrations.table = config.migrations.table || migrationsTableName
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (config.migrations && config.db) {
|
|
35
|
+
// TODO remove the ignores
|
|
36
|
+
/* c8 ignore next 4 */
|
|
37
|
+
config.db.ignore = config.db.ignore || {}
|
|
38
|
+
config.db.ignore = Object.assign(
|
|
39
|
+
{},
|
|
40
|
+
{
|
|
41
|
+
[config.migrations.table || migrationsTableName]: true
|
|
42
|
+
},
|
|
43
|
+
config.db.ignore
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (config.types?.autogenerate === 'true') {
|
|
48
|
+
config.types.autogenerate = true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return config
|
|
52
|
+
}
|
package/lib/errors.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
import createError from '@fastify/error'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
export const ERROR_PREFIX = 'PLT_DB'
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
5
|
+
export const MigrateMissingMigrationsError = createError(
|
|
6
|
+
`${ERROR_PREFIX}_MIGRATE_ERROR`,
|
|
7
|
+
'Missing "migrations" section in config file'
|
|
8
|
+
)
|
|
9
|
+
export const UnknownDatabaseError = createError(`${ERROR_PREFIX}_UNKNOWN_DATABASE_ERROR`, 'Unknown database')
|
|
10
|
+
export const MigrateMissingMigrationsDirError = createError(
|
|
11
|
+
`${ERROR_PREFIX}_MIGRATE_ERROR`,
|
|
12
|
+
'Migrations directory %s does not exist'
|
|
13
|
+
)
|
|
14
|
+
export const MissingSeedFileError = createError(`${ERROR_PREFIX}_MISSING_SEED_FILE_ERROR`, 'Missing seed file')
|
|
15
|
+
export const MigrationsToApplyError = createError(
|
|
16
|
+
`${ERROR_PREFIX}_MIGRATIONS_TO_APPLY_ERROR`,
|
|
17
|
+
'You have migrations to apply.'
|
|
18
|
+
)
|
package/lib/generator.js
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { Generator as ServiceGenerator } from '@platformatic/service'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import {
|
|
4
|
+
ENVIRONMENT_TEMPLATE,
|
|
5
|
+
jsHelperMySQL,
|
|
6
|
+
jsHelperPostgres,
|
|
7
|
+
jsHelperSqlite,
|
|
8
|
+
moviesTestJS,
|
|
9
|
+
moviesTestTS,
|
|
10
|
+
README
|
|
11
|
+
} from './templates.js'
|
|
12
|
+
|
|
13
|
+
export class Generator extends ServiceGenerator {
|
|
14
|
+
constructor (opts = {}) {
|
|
15
|
+
super({
|
|
16
|
+
...opts,
|
|
17
|
+
module: '@platformatic/db'
|
|
18
|
+
})
|
|
19
|
+
this.connectionStrings = {
|
|
20
|
+
postgres: 'postgres://postgres:postgres@127.0.0.1:5432/postgres',
|
|
21
|
+
sqlite: 'sqlite://./db.sqlite',
|
|
22
|
+
mysql: 'mysql://root@127.0.0.1:3306/platformatic',
|
|
23
|
+
mariadb: 'mysql://root@127.0.0.1:3306/platformatic'
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getConfigFieldsDefinitions () {
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
var: 'DATABASE_URL',
|
|
31
|
+
label: 'What is the connection string?',
|
|
32
|
+
default: this.connectionStrings.sqlite,
|
|
33
|
+
type: 'string',
|
|
34
|
+
configValue: 'connectionString'
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
var: 'PLT_APPLY_MIGRATIONS',
|
|
38
|
+
label: 'Should migrations be applied automatically on startup?',
|
|
39
|
+
default: true,
|
|
40
|
+
type: 'boolean'
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getDefaultConfig () {
|
|
46
|
+
const defaultBaseConfig = super.getDefaultConfig()
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
...defaultBaseConfig,
|
|
50
|
+
database: 'sqlite',
|
|
51
|
+
connectionString: null,
|
|
52
|
+
plugin: true,
|
|
53
|
+
tests: true,
|
|
54
|
+
types: true,
|
|
55
|
+
migrations: 'migrations',
|
|
56
|
+
createMigrations: true
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async prepareQuestions () {
|
|
61
|
+
if (!this.config.connectionString) {
|
|
62
|
+
const def = this.getConfigFieldsDefinitions().find(q => q.var === 'DATABASE_URL')
|
|
63
|
+
this.questions.push({
|
|
64
|
+
type: 'input',
|
|
65
|
+
name: def.configValue,
|
|
66
|
+
message: def.label,
|
|
67
|
+
default: def.default
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.questions.push({
|
|
72
|
+
type: 'list',
|
|
73
|
+
name: 'createMigrations',
|
|
74
|
+
message: 'Do you want to create default migrations?',
|
|
75
|
+
default: true,
|
|
76
|
+
choices: [
|
|
77
|
+
{ name: 'yes', value: true },
|
|
78
|
+
{ name: 'no', value: false }
|
|
79
|
+
]
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
await super.prepareQuestions()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getMoviesMigrationDo () {
|
|
86
|
+
const key = {
|
|
87
|
+
postgres: 'SERIAL',
|
|
88
|
+
sqlite: 'INTEGER',
|
|
89
|
+
mysql: 'INTEGER UNSIGNED AUTO_INCREMENT',
|
|
90
|
+
mariadb: 'INTEGER UNSIGNED AUTO_INCREMENT'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return `
|
|
94
|
+
-- Add SQL in this file to create the database tables for your API
|
|
95
|
+
CREATE TABLE IF NOT EXISTS movies (
|
|
96
|
+
id ${key[this.config.database]} PRIMARY KEY,
|
|
97
|
+
title TEXT NOT NULL
|
|
98
|
+
);
|
|
99
|
+
`
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getMoviesMigrationUndo () {
|
|
103
|
+
return '-- Add SQL in this file to drop the database tables\nDROP TABLE movies;'
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
setConfigFields (fields) {
|
|
107
|
+
super.setConfigFields(fields)
|
|
108
|
+
this.config.database = this.getDatabaseFromConnectionString()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
getDatabaseFromConnectionString () {
|
|
112
|
+
if (this.config.connectionString) {
|
|
113
|
+
if (this.config.connectionString.indexOf('://') !== -1) {
|
|
114
|
+
const splitted = this.config.connectionString.split('://')
|
|
115
|
+
return splitted[0]
|
|
116
|
+
}
|
|
117
|
+
return null
|
|
118
|
+
}
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async _beforePrepare () {
|
|
123
|
+
if (this.config.isUpdating) {
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
await super._beforePrepare()
|
|
128
|
+
|
|
129
|
+
this.config.connectionString = this.config.connectionString || this.connectionStrings[this.config.database]
|
|
130
|
+
this.config.dependencies = {
|
|
131
|
+
'@platformatic/db': `^${this.platformaticVersion}`
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!this.config.isRuntimeContext) {
|
|
135
|
+
this.addEnvVars(
|
|
136
|
+
{
|
|
137
|
+
PLT_SERVER_HOSTNAME: this.config.hostname,
|
|
138
|
+
PLT_SERVER_LOGGER_LEVEL: 'info',
|
|
139
|
+
PORT: 3042
|
|
140
|
+
},
|
|
141
|
+
{ overwrite: false, default: true }
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.addEnvVars(
|
|
146
|
+
{
|
|
147
|
+
DATABASE_URL: this.connectionStrings[this.config.database],
|
|
148
|
+
PLT_APPLY_MIGRATIONS: 'true'
|
|
149
|
+
},
|
|
150
|
+
{ overwrite: false, default: true }
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async _afterPrepare () {
|
|
155
|
+
if (this.config.isUpdating) {
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (this.config.createMigrations) {
|
|
160
|
+
this.addFile({ path: 'migrations', file: '001.do.sql', contents: this.getMoviesMigrationDo() })
|
|
161
|
+
this.addFile({ path: 'migrations', file: '001.undo.sql', contents: this.getMoviesMigrationUndo() })
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this.addFile({ path: '', file: 'README.md', contents: README })
|
|
165
|
+
|
|
166
|
+
if (this.config.plugin) {
|
|
167
|
+
switch (this.config.database) {
|
|
168
|
+
case 'sqlite':
|
|
169
|
+
this.testHelperCustomizations = jsHelperSqlite
|
|
170
|
+
break
|
|
171
|
+
case 'mysql':
|
|
172
|
+
this.testHelperCustomizations = jsHelperMySQL(this.config.connectionString)
|
|
173
|
+
break
|
|
174
|
+
case 'postgres':
|
|
175
|
+
this.testHelperCustomizations = jsHelperPostgres(this.config.connectionString)
|
|
176
|
+
break
|
|
177
|
+
case 'mariadb':
|
|
178
|
+
this.testHelperCustomizations = jsHelperMySQL(this.config.connectionString)
|
|
179
|
+
break
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (this.config.createMigrations) {
|
|
183
|
+
if (this.config.typescript) {
|
|
184
|
+
this.addFile({ path: join('test', 'routes'), file: 'movies.test.ts', contents: moviesTestTS })
|
|
185
|
+
} else {
|
|
186
|
+
this.addFile({ path: join('test', 'routes'), file: 'movies.test.js', contents: moviesTestJS })
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
super._afterPrepare()
|
|
192
|
+
|
|
193
|
+
this.addFile({ path: '', file: 'plt-env.d.ts', contents: ENVIRONMENT_TEMPLATE })
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async _getConfigFileContents () {
|
|
197
|
+
const config = await super._getConfigFileContents()
|
|
198
|
+
delete config.service
|
|
199
|
+
config.$schema = `https://schemas.platformatic.dev/@platformatic/db/${this.platformaticVersion}.json`
|
|
200
|
+
|
|
201
|
+
config.db = {
|
|
202
|
+
connectionString: `{${this.getEnvVarName('DATABASE_URL')}}`,
|
|
203
|
+
graphql: true,
|
|
204
|
+
openapi: true,
|
|
205
|
+
schemalock: true
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
config.watch = {
|
|
209
|
+
ignore: ['*.sqlite', '*.sqlite-journal']
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (this.config.migrations) {
|
|
213
|
+
config.migrations = {
|
|
214
|
+
dir: this.config.migrations,
|
|
215
|
+
autoApply: `{${this.getEnvVarName('PLT_APPLY_MIGRATIONS')}}`
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
this.addFile({ path: 'migrations', file: '.gitkeep', contents: '' })
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (this.config.types === true) {
|
|
222
|
+
config.types = {
|
|
223
|
+
autogenerate: true
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return config
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { join, basename } from 'path'
|
|
2
|
-
import Postgrator from 'postgrator'
|
|
3
1
|
import { createConnectionPool } from '@platformatic/sql-mapper'
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
2
|
+
import { readdir, stat } from 'node:fs/promises'
|
|
3
|
+
import { basename } from 'node:path'
|
|
4
|
+
import Postgrator from 'postgrator'
|
|
5
|
+
import { MigrateMissingMigrationsDirError, MigrateMissingMigrationsError } from './errors.js'
|
|
6
6
|
|
|
7
|
-
class Migrator {
|
|
7
|
+
export class Migrator {
|
|
8
8
|
constructor (migrationConfig, coreConfig, logger) {
|
|
9
9
|
this.coreConfig = coreConfig
|
|
10
10
|
this.migrationDir = migrationConfig.dir
|
|
@@ -28,7 +28,7 @@ class Migrator {
|
|
|
28
28
|
|
|
29
29
|
const { db, sql } = await createConnectionPool({
|
|
30
30
|
...this.coreConfig,
|
|
31
|
-
log: this.logger
|
|
31
|
+
log: this.logger
|
|
32
32
|
})
|
|
33
33
|
|
|
34
34
|
let driver
|
|
@@ -44,13 +44,12 @@ class Migrator {
|
|
|
44
44
|
driver = 'sqlite3'
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
const database = driver !== 'sqlite3'
|
|
48
|
-
? new URL(this.coreConfig.connectionString).pathname.replace(/^\//, '')
|
|
49
|
-
: ''
|
|
47
|
+
const database = driver !== 'sqlite3' ? new URL(this.coreConfig.connectionString).pathname.replace(/^\//, '') : ''
|
|
50
48
|
|
|
51
49
|
this.db = db
|
|
52
50
|
|
|
53
|
-
|
|
51
|
+
// Glob patterns should always use / as a path separator, even on Windows systems, as \ is used to escape glob characters.
|
|
52
|
+
const migrationPattern = this.migrationDir + '/*'
|
|
54
53
|
this.logger.debug(`Migrating from ${migrationPattern}`)
|
|
55
54
|
|
|
56
55
|
this.postgrator = new Postgrator({
|
|
@@ -58,40 +57,31 @@ class Migrator {
|
|
|
58
57
|
driver,
|
|
59
58
|
database,
|
|
60
59
|
schemaTable: this.migrationsTable || 'versions',
|
|
61
|
-
execQuery: async
|
|
60
|
+
execQuery: async query => {
|
|
62
61
|
const res = await db.query(sql`${sql.__dangerous__rawValue(query)}`)
|
|
63
62
|
return { rows: res }
|
|
64
63
|
},
|
|
65
64
|
validateChecksums: this.validateChecksums,
|
|
66
65
|
newline: this.newline,
|
|
67
|
-
currentSchema: ['pg', 'mysql'].includes(driver) ? this.currentSchema : undefined
|
|
66
|
+
currentSchema: ['pg', 'mysql'].includes(driver) ? this.currentSchema : undefined
|
|
68
67
|
})
|
|
69
68
|
|
|
70
69
|
if (this.validateChecksums === true) {
|
|
71
|
-
this.postgrator.on(
|
|
72
|
-
|
|
73
|
-
(migration) => {
|
|
74
|
-
/* c8 ignore next 3 */
|
|
75
|
-
const migrationName = basename(migration.filename)
|
|
76
|
-
this.logger.info(`verifying checksum of migration ${migrationName}`)
|
|
77
|
-
}
|
|
78
|
-
)
|
|
79
|
-
}
|
|
80
|
-
this.postgrator.on(
|
|
81
|
-
'migration-started',
|
|
82
|
-
(migration) => {
|
|
83
|
-
const migrationName = basename(migration.filename)
|
|
84
|
-
this.logger.info(`running ${migrationName}`)
|
|
85
|
-
}
|
|
86
|
-
)
|
|
87
|
-
this.postgrator.on(
|
|
88
|
-
'migration-finished',
|
|
89
|
-
(migration) => {
|
|
90
|
-
this.appliedMigrationsCount++
|
|
70
|
+
this.postgrator.on('validation-started', migration => {
|
|
71
|
+
/* c8 ignore next 3 */
|
|
91
72
|
const migrationName = basename(migration.filename)
|
|
92
|
-
this.logger.
|
|
93
|
-
}
|
|
94
|
-
|
|
73
|
+
this.logger.info(`verifying checksum of migration ${migrationName}`)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
this.postgrator.on('migration-started', migration => {
|
|
77
|
+
const migrationName = basename(migration.filename)
|
|
78
|
+
this.logger.info(`running ${migrationName}`)
|
|
79
|
+
})
|
|
80
|
+
this.postgrator.on('migration-finished', migration => {
|
|
81
|
+
this.appliedMigrationsCount++
|
|
82
|
+
const migrationName = basename(migration.filename)
|
|
83
|
+
this.logger.debug(`completed ${migrationName}`)
|
|
84
|
+
})
|
|
95
85
|
}
|
|
96
86
|
|
|
97
87
|
async checkMigrationsDirectoryExists () {
|
|
@@ -99,7 +89,7 @@ class Migrator {
|
|
|
99
89
|
await stat(this.migrationDir)
|
|
100
90
|
} catch (err) {
|
|
101
91
|
if (err.code === 'ENOENT') {
|
|
102
|
-
throw new
|
|
92
|
+
throw new MigrateMissingMigrationsDirError(this.migrationDir)
|
|
103
93
|
}
|
|
104
94
|
}
|
|
105
95
|
}
|
|
@@ -177,7 +167,7 @@ class Migrator {
|
|
|
177
167
|
}
|
|
178
168
|
} catch (err) {
|
|
179
169
|
if (err.code === 'ENOENT') {
|
|
180
|
-
throw new
|
|
170
|
+
throw new MigrateMissingMigrationsDirError(this.migrationDir)
|
|
181
171
|
}
|
|
182
172
|
}
|
|
183
173
|
}
|
|
@@ -191,4 +181,22 @@ class Migrator {
|
|
|
191
181
|
}
|
|
192
182
|
}
|
|
193
183
|
|
|
194
|
-
export
|
|
184
|
+
export async function execute (logger, config, to, rollback) {
|
|
185
|
+
const migrationsConfig = config.migrations
|
|
186
|
+
if (migrationsConfig === undefined) {
|
|
187
|
+
throw new MigrateMissingMigrationsError()
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const migrator = new Migrator(migrationsConfig, config.db, logger)
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
if (rollback) {
|
|
194
|
+
await migrator.rollbackMigration()
|
|
195
|
+
} else {
|
|
196
|
+
await migrator.applyMigrations(to)
|
|
197
|
+
}
|
|
198
|
+
return migrator.appliedMigrationsCount > 0
|
|
199
|
+
} finally {
|
|
200
|
+
await migrator.close()
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const userAgentParser = require('my-ua-parser')
|
|
1
|
+
import fastifyStatic from '@fastify/static'
|
|
2
|
+
import userAgentParser from 'my-ua-parser'
|
|
3
|
+
import path from 'node:path'
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
export async function root (app) {
|
|
7
6
|
app.register(fastifyStatic, {
|
|
8
|
-
root: path.join(
|
|
7
|
+
root: path.join(import.meta.dirname, '../public')
|
|
9
8
|
})
|
|
10
9
|
// root endpoint
|
|
11
10
|
app.route({
|
|
@@ -21,6 +20,6 @@ module.exports = async (app, opts) => {
|
|
|
21
20
|
}
|
|
22
21
|
}
|
|
23
22
|
return { message: 'Welcome to Platformatic! Please visit https://docs.platformatic.dev' }
|
|
24
|
-
}
|
|
23
|
+
}
|
|
25
24
|
})
|
|
26
25
|
}
|