rake-db 1.3.2 → 2.0.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/.env +3 -0
- package/.env.local +1 -0
- package/README.md +1 -545
- package/db.ts +16 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.esm.js +190 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +201 -0
- package/dist/index.js.map +1 -0
- package/jest-setup.ts +3 -0
- package/migrations/20221009210157_first.ts +8 -0
- package/migrations/20221009210200_second.ts +5 -0
- package/package.json +55 -41
- package/rollup.config.js +3 -0
- package/src/commands/createOrDrop.test.ts +145 -0
- package/src/commands/createOrDrop.ts +107 -0
- package/src/commands/generate.test.ts +133 -0
- package/src/commands/generate.ts +85 -0
- package/src/commands/migrateOrRollback.test.ts +118 -0
- package/src/commands/migrateOrRollback.ts +108 -0
- package/src/common.test.ts +281 -0
- package/src/common.ts +224 -0
- package/src/index.ts +2 -0
- package/src/migration/change.ts +20 -0
- package/src/migration/changeTable.test.ts +417 -0
- package/src/migration/changeTable.ts +375 -0
- package/src/migration/createTable.test.ts +269 -0
- package/src/migration/createTable.ts +169 -0
- package/src/migration/migration.test.ts +341 -0
- package/src/migration/migration.ts +296 -0
- package/src/migration/migrationUtils.ts +281 -0
- package/src/rakeDb.ts +29 -0
- package/src/test-utils.ts +45 -0
- package/tsconfig.json +12 -0
- package/dist/lib/createAndDrop.d.ts +0 -2
- package/dist/lib/createAndDrop.js +0 -63
- package/dist/lib/defaults.d.ts +0 -2
- package/dist/lib/defaults.js +0 -5
- package/dist/lib/errorCodes.d.ts +0 -4
- package/dist/lib/errorCodes.js +0 -7
- package/dist/lib/generate.d.ts +0 -1
- package/dist/lib/generate.js +0 -99
- package/dist/lib/help.d.ts +0 -2
- package/dist/lib/help.js +0 -24
- package/dist/lib/init.d.ts +0 -2
- package/dist/lib/init.js +0 -276
- package/dist/lib/migrate.d.ts +0 -4
- package/dist/lib/migrate.js +0 -189
- package/dist/lib/migration.d.ts +0 -37
- package/dist/lib/migration.js +0 -159
- package/dist/lib/schema/changeTable.d.ts +0 -23
- package/dist/lib/schema/changeTable.js +0 -109
- package/dist/lib/schema/column.d.ts +0 -31
- package/dist/lib/schema/column.js +0 -201
- package/dist/lib/schema/createTable.d.ts +0 -10
- package/dist/lib/schema/createTable.js +0 -53
- package/dist/lib/schema/foreignKey.d.ts +0 -11
- package/dist/lib/schema/foreignKey.js +0 -53
- package/dist/lib/schema/index.d.ts +0 -3
- package/dist/lib/schema/index.js +0 -54
- package/dist/lib/schema/primaryKey.d.ts +0 -9
- package/dist/lib/schema/primaryKey.js +0 -24
- package/dist/lib/schema/table.d.ts +0 -43
- package/dist/lib/schema/table.js +0 -110
- package/dist/lib/schema/timestamps.d.ts +0 -3
- package/dist/lib/schema/timestamps.js +0 -9
- package/dist/lib/utils.d.ts +0 -26
- package/dist/lib/utils.js +0 -114
- package/dist/rake-db.d.ts +0 -2
- package/dist/rake-db.js +0 -34
- package/dist/types.d.ts +0 -94
- package/dist/types.js +0 -40
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSchemaMigrations,
|
|
3
|
+
getDatabaseAndUserFromOptions,
|
|
4
|
+
getFirstWordAndRest,
|
|
5
|
+
getMigrationConfigWithDefaults,
|
|
6
|
+
getMigrationFiles,
|
|
7
|
+
getTextAfterTo,
|
|
8
|
+
joinColumns,
|
|
9
|
+
joinWords,
|
|
10
|
+
migrationConfigDefaults,
|
|
11
|
+
setAdapterOptions,
|
|
12
|
+
setAdminCredentialsToOptions,
|
|
13
|
+
sortAsc,
|
|
14
|
+
sortDesc,
|
|
15
|
+
} from './common';
|
|
16
|
+
import Enquirer from 'enquirer';
|
|
17
|
+
import { Adapter } from 'pqb';
|
|
18
|
+
import { readdir } from 'fs/promises';
|
|
19
|
+
import path from 'path';
|
|
20
|
+
|
|
21
|
+
jest.mock('enquirer', () => {
|
|
22
|
+
class Snippet {
|
|
23
|
+
constructor(public params: Record<string, unknown>) {}
|
|
24
|
+
run() {}
|
|
25
|
+
}
|
|
26
|
+
Snippet.prototype.run = jest.fn();
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
Snippet,
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
jest.mock('fs/promises', () => ({
|
|
34
|
+
readdir: jest.fn(),
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
describe('common', () => {
|
|
38
|
+
describe('getMigrationConfigWithDefaults', () => {
|
|
39
|
+
it('should return config with defaults', () => {
|
|
40
|
+
const result = getMigrationConfigWithDefaults({
|
|
41
|
+
migrationsPath: 'custom-path',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
expect(result).toEqual({
|
|
45
|
+
migrationsPath: 'custom-path',
|
|
46
|
+
migrationsTable: 'schemaMigrations',
|
|
47
|
+
requireTs: expect.any(Function),
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('getDatabaseAndUserFromOptions', () => {
|
|
53
|
+
it('should return data from connectionString', () => {
|
|
54
|
+
const result = getDatabaseAndUserFromOptions({
|
|
55
|
+
connectionString: 'postgres://user:password@localhost:5432/dbname',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(result).toEqual({
|
|
59
|
+
database: 'dbname',
|
|
60
|
+
user: 'user',
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should return data from options when no connectionString', () => {
|
|
65
|
+
const result = getDatabaseAndUserFromOptions({
|
|
66
|
+
database: 'dbname',
|
|
67
|
+
user: 'user',
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expect(result).toEqual({
|
|
71
|
+
database: 'dbname',
|
|
72
|
+
user: 'user',
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('setAdapterOptions', () => {
|
|
78
|
+
it('should set options in connectionString to postgres', () => {
|
|
79
|
+
const result = setAdapterOptions(
|
|
80
|
+
{
|
|
81
|
+
connectionString: 'postgres://user:password@localhost:5432/dbname',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
database: 'updated-db',
|
|
85
|
+
user: 'updated-user',
|
|
86
|
+
password: 'updated-password',
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
expect(result).toEqual({
|
|
91
|
+
connectionString:
|
|
92
|
+
'postgres://updated-user:updated-password@localhost:5432/updated-db',
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should set object options', () => {
|
|
97
|
+
const result = setAdapterOptions(
|
|
98
|
+
{
|
|
99
|
+
database: 'dbname',
|
|
100
|
+
user: 'user',
|
|
101
|
+
password: 'password',
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
database: 'updated-db',
|
|
105
|
+
user: 'updated-user',
|
|
106
|
+
password: 'updated-password',
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
expect(result).toEqual({
|
|
111
|
+
database: 'updated-db',
|
|
112
|
+
user: 'updated-user',
|
|
113
|
+
password: 'updated-password',
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('setAdminCredentialsToOptions', () => {
|
|
119
|
+
beforeEach(() => {
|
|
120
|
+
(Enquirer as any).Snippet.prototype.run.mockReturnValueOnce({
|
|
121
|
+
values: {
|
|
122
|
+
user: 'admin-user',
|
|
123
|
+
password: 'admin-password',
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should set admin credentials to connectionString', async () => {
|
|
129
|
+
const result = await setAdminCredentialsToOptions({
|
|
130
|
+
connectionString: 'postgres://user:password@localhost:5432/dbname',
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(result).toEqual({
|
|
134
|
+
connectionString:
|
|
135
|
+
'postgres://admin-user:admin-password@localhost:5432/dbname',
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should set admin credentials to options', async () => {
|
|
140
|
+
const result = await setAdminCredentialsToOptions({
|
|
141
|
+
database: 'dbname',
|
|
142
|
+
user: 'user',
|
|
143
|
+
password: 'password',
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(result).toEqual({
|
|
147
|
+
database: 'dbname',
|
|
148
|
+
user: 'admin-user',
|
|
149
|
+
password: 'admin-password',
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('createSchemaMigrations', () => {
|
|
155
|
+
const log = console.log;
|
|
156
|
+
const mockedLog = jest.fn();
|
|
157
|
+
const mockedQuery = jest.fn();
|
|
158
|
+
|
|
159
|
+
const db = new Adapter({ connectionString: 'test' });
|
|
160
|
+
db.query = mockedQuery;
|
|
161
|
+
|
|
162
|
+
beforeAll(() => {
|
|
163
|
+
console.log = mockedLog;
|
|
164
|
+
});
|
|
165
|
+
beforeEach(() => {
|
|
166
|
+
jest.clearAllMocks();
|
|
167
|
+
});
|
|
168
|
+
afterAll(() => {
|
|
169
|
+
console.log = log;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should create a "schemaMigrations" table', async () => {
|
|
173
|
+
mockedQuery.mockReturnValueOnce(undefined);
|
|
174
|
+
|
|
175
|
+
await createSchemaMigrations(db, migrationConfigDefaults);
|
|
176
|
+
|
|
177
|
+
expect(mockedQuery.mock.calls).toEqual([
|
|
178
|
+
[`CREATE TABLE "schemaMigrations" ( version TEXT NOT NULL )`],
|
|
179
|
+
]);
|
|
180
|
+
|
|
181
|
+
expect(mockedLog.mock.calls).toEqual([['Created versions table']]);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should inform if table already exists', async () => {
|
|
185
|
+
mockedQuery.mockRejectedValue({ code: '42P07' });
|
|
186
|
+
|
|
187
|
+
await createSchemaMigrations(db, migrationConfigDefaults);
|
|
188
|
+
|
|
189
|
+
expect(mockedQuery.mock.calls).toEqual([
|
|
190
|
+
[`CREATE TABLE "schemaMigrations" ( version TEXT NOT NULL )`],
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
expect(mockedLog.mock.calls).toEqual([['Versions table exists']]);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('getFirstWordAndRest', () => {
|
|
198
|
+
it('should return pair of first word and rest', () => {
|
|
199
|
+
expect(getFirstWordAndRest('fooBarBaz')).toEqual(['foo', 'barBaz']);
|
|
200
|
+
expect(getFirstWordAndRest('foo-barBaz')).toEqual(['foo', 'barBaz']);
|
|
201
|
+
expect(getFirstWordAndRest('foo_barBaz')).toEqual(['foo', 'barBaz']);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should return input when it is a single word', () => {
|
|
205
|
+
expect(getFirstWordAndRest('foo')).toEqual(['foo']);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('getTextAfterTo', () => {
|
|
210
|
+
it('should return text after To or to', () => {
|
|
211
|
+
expect(getTextAfterTo('addColumnToTable')).toBe('table');
|
|
212
|
+
expect(getTextAfterTo('add-column-to-table')).toBe('table');
|
|
213
|
+
expect(getTextAfterTo('add_column_to_table')).toBe('table');
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('getMigrationFiles', () => {
|
|
218
|
+
it('should return files with versions', async () => {
|
|
219
|
+
const version = '12345678901234';
|
|
220
|
+
const files = [`${version}_a.ts`, `${version}_b.ts`, `${version}_c.ts`];
|
|
221
|
+
(readdir as jest.Mock).mockReturnValueOnce(files);
|
|
222
|
+
|
|
223
|
+
const result = await getMigrationFiles(migrationConfigDefaults, true);
|
|
224
|
+
expect(result).toEqual(
|
|
225
|
+
files.map((file) => ({
|
|
226
|
+
path: path.join(migrationConfigDefaults.migrationsPath, file),
|
|
227
|
+
version,
|
|
228
|
+
})),
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should return empty array on error', async () => {
|
|
233
|
+
(readdir as jest.Mock).mockRejectedValue(new Error());
|
|
234
|
+
|
|
235
|
+
const result = await getMigrationFiles(migrationConfigDefaults, true);
|
|
236
|
+
expect(result).toEqual([]);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should throw if file is not a .ts file', async () => {
|
|
240
|
+
(readdir as jest.Mock).mockReturnValueOnce(['file.js']);
|
|
241
|
+
|
|
242
|
+
await expect(
|
|
243
|
+
getMigrationFiles(migrationConfigDefaults, true),
|
|
244
|
+
).rejects.toThrow('Only .ts files are supported');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should throw on improper version', async () => {
|
|
248
|
+
(readdir as jest.Mock).mockReturnValueOnce(['1234567890_file.ts']);
|
|
249
|
+
|
|
250
|
+
await expect(
|
|
251
|
+
getMigrationFiles(migrationConfigDefaults, true),
|
|
252
|
+
).rejects.toThrow(
|
|
253
|
+
'Migration file name should start with 14 digit version',
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
describe('sortAsc', () => {
|
|
259
|
+
it('should sort ascending', () => {
|
|
260
|
+
expect(sortAsc(['a', 'c', 'b'])).toEqual(['a', 'b', 'c']);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe('sortDesc', () => {
|
|
265
|
+
it('should sort descending', () => {
|
|
266
|
+
expect(sortDesc(['a', 'c', 'b'])).toEqual(['c', 'b', 'a']);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('joinWords', () => {
|
|
271
|
+
it('should join words', () => {
|
|
272
|
+
expect(joinWords('foo', 'bar', 'baz')).toEqual('fooBarBaz');
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe('joinColumns', () => {
|
|
277
|
+
it('should join columns', () => {
|
|
278
|
+
expect(joinColumns(['a', 'b', 'c'])).toBe('"a", "b", "c"');
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
});
|
package/src/common.ts
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { Adapter, AdapterOptions } from 'pqb';
|
|
2
|
+
import Enquirer from 'enquirer';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { readdir } from 'fs/promises';
|
|
5
|
+
|
|
6
|
+
export type MigrationConfig = {
|
|
7
|
+
migrationsPath: string;
|
|
8
|
+
migrationsTable: string;
|
|
9
|
+
requireTs(path: string): void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const registered = false;
|
|
13
|
+
|
|
14
|
+
export const migrationConfigDefaults = {
|
|
15
|
+
migrationsPath: path.resolve(process.cwd(), 'src', 'migrations'),
|
|
16
|
+
migrationsTable: 'schemaMigrations',
|
|
17
|
+
requireTs(path: string) {
|
|
18
|
+
if (!registered) {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
20
|
+
require('ts-node').register({ compilerOptions: { module: 'CommonJS' } });
|
|
21
|
+
}
|
|
22
|
+
require(path);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const getMigrationConfigWithDefaults = (
|
|
27
|
+
config: Partial<MigrationConfig>,
|
|
28
|
+
) => {
|
|
29
|
+
return { ...migrationConfigDefaults, ...config };
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const getDatabaseAndUserFromOptions = (
|
|
33
|
+
options: AdapterOptions,
|
|
34
|
+
): { database: string; user: string } => {
|
|
35
|
+
if (options.connectionString) {
|
|
36
|
+
const url = new URL(options.connectionString);
|
|
37
|
+
return {
|
|
38
|
+
database: url.pathname.slice(1),
|
|
39
|
+
user: url.username,
|
|
40
|
+
};
|
|
41
|
+
} else {
|
|
42
|
+
return {
|
|
43
|
+
database: options.database as string,
|
|
44
|
+
user: options.user as string,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const setAdapterOptions = (
|
|
50
|
+
options: AdapterOptions,
|
|
51
|
+
set: { database?: string; user?: string; password?: string },
|
|
52
|
+
): AdapterOptions => {
|
|
53
|
+
if (options.connectionString) {
|
|
54
|
+
const url = new URL(options.connectionString);
|
|
55
|
+
|
|
56
|
+
if ('database' in set) {
|
|
57
|
+
url.pathname = `/${set.database}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (set.user !== undefined) {
|
|
61
|
+
url.username = set.user;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (set.password !== undefined) {
|
|
65
|
+
url.password = set.password;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { ...options, connectionString: url.toString() };
|
|
69
|
+
} else {
|
|
70
|
+
return {
|
|
71
|
+
...options,
|
|
72
|
+
...set,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const askAdminCredentials = async (): Promise<{
|
|
78
|
+
user: string;
|
|
79
|
+
password: string;
|
|
80
|
+
}> => {
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
82
|
+
const prompt = new (Enquirer as any).Snippet({
|
|
83
|
+
message: `What are postgres admin login and password?`,
|
|
84
|
+
fields: [
|
|
85
|
+
{
|
|
86
|
+
name: 'user',
|
|
87
|
+
required: true,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'password',
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
values: {
|
|
94
|
+
user: 'postgres',
|
|
95
|
+
password: '',
|
|
96
|
+
},
|
|
97
|
+
template: 'Admin user: {{user}}\nAdmin password: {{password}}',
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const { values } = await prompt.run();
|
|
101
|
+
if (!values.password) values.password = '';
|
|
102
|
+
|
|
103
|
+
return values;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const setAdminCredentialsToOptions = async (
|
|
107
|
+
options: AdapterOptions,
|
|
108
|
+
): Promise<AdapterOptions> => {
|
|
109
|
+
const values = await askAdminCredentials();
|
|
110
|
+
return setAdapterOptions(options, values);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const createSchemaMigrations = async (
|
|
114
|
+
db: Adapter,
|
|
115
|
+
config: MigrationConfig,
|
|
116
|
+
) => {
|
|
117
|
+
try {
|
|
118
|
+
await db.query(
|
|
119
|
+
`CREATE TABLE "${config.migrationsTable}" ( version TEXT NOT NULL )`,
|
|
120
|
+
);
|
|
121
|
+
console.log('Created versions table');
|
|
122
|
+
} catch (err) {
|
|
123
|
+
if ((err as Record<string, unknown>).code === '42P07') {
|
|
124
|
+
console.log('Versions table exists');
|
|
125
|
+
} else {
|
|
126
|
+
throw err;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const getFirstWordAndRest = (
|
|
132
|
+
input: string,
|
|
133
|
+
): [string] | [string, string] => {
|
|
134
|
+
const index = input.search(/(?=[A-Z])|[-_]/);
|
|
135
|
+
if (index !== -1) {
|
|
136
|
+
const restStart =
|
|
137
|
+
input[index] === '-' || input[index] === '_' ? index + 1 : index;
|
|
138
|
+
const rest = input.slice(restStart);
|
|
139
|
+
return [input.slice(0, index), rest[0].toLowerCase() + rest.slice(1)];
|
|
140
|
+
} else {
|
|
141
|
+
return [input];
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const getTextAfterRegExp = (
|
|
146
|
+
input: string,
|
|
147
|
+
regex: RegExp,
|
|
148
|
+
length: number,
|
|
149
|
+
): string | undefined => {
|
|
150
|
+
let index = input.search(regex);
|
|
151
|
+
if (index === -1) return;
|
|
152
|
+
|
|
153
|
+
if (input[index] === '-' || input[index] === '_') index++;
|
|
154
|
+
index += length;
|
|
155
|
+
|
|
156
|
+
const start = input[index] == '-' || input[index] === '_' ? index + 1 : index;
|
|
157
|
+
const text = input.slice(start);
|
|
158
|
+
return text[0].toLowerCase() + text.slice(1);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const getTextAfterTo = (input: string): string | undefined => {
|
|
162
|
+
return getTextAfterRegExp(input, /(To|-to|_to)[A-Z-_]/, 2);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export const getTextAfterFrom = (input: string): string | undefined => {
|
|
166
|
+
return getTextAfterRegExp(input, /(From|-from|_from)[A-Z-_]/, 4);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export type MigrationFile = {
|
|
170
|
+
path: string;
|
|
171
|
+
version: string;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const getMigrationFiles = async (
|
|
175
|
+
config: MigrationConfig,
|
|
176
|
+
up: boolean,
|
|
177
|
+
): Promise<MigrationFile[]> => {
|
|
178
|
+
const { migrationsPath } = config;
|
|
179
|
+
|
|
180
|
+
let files: string[];
|
|
181
|
+
try {
|
|
182
|
+
files = await readdir(migrationsPath);
|
|
183
|
+
} catch (_) {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const sort = up ? sortAsc : sortDesc;
|
|
188
|
+
return sort(files).map((file) => {
|
|
189
|
+
if (!file.endsWith('.ts')) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
`Only .ts files are supported for migration, received: ${file}`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const timestampMatch = file.match(/^(\d{14})\D/);
|
|
196
|
+
if (!timestampMatch) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`Migration file name should start with 14 digit version, received ${file}`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
path: path.join(migrationsPath, file),
|
|
204
|
+
version: timestampMatch[1],
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export const sortAsc = (arr: string[]) => arr.sort();
|
|
210
|
+
|
|
211
|
+
export const sortDesc = (arr: string[]) => arr.sort((a, b) => (a > b ? -1 : 1));
|
|
212
|
+
|
|
213
|
+
export const joinWords = (...words: string[]) => {
|
|
214
|
+
return words
|
|
215
|
+
.slice(1)
|
|
216
|
+
.reduce(
|
|
217
|
+
(acc, word) => acc + word[0].toUpperCase() + word.slice(1),
|
|
218
|
+
words[0],
|
|
219
|
+
);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export const joinColumns = (columns: string[]) => {
|
|
223
|
+
return columns.map((column) => `"${column}"`).join(', ');
|
|
224
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Migration } from './migration';
|
|
2
|
+
|
|
3
|
+
let currentMigration: Migration | undefined;
|
|
4
|
+
let currentPromise: Promise<void> | undefined;
|
|
5
|
+
let currentUp = true;
|
|
6
|
+
|
|
7
|
+
export const change = (fn: (db: Migration, up: boolean) => Promise<void>) => {
|
|
8
|
+
if (!currentMigration) throw new Error('Database instance is not set');
|
|
9
|
+
currentPromise = fn(currentMigration, currentUp);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const setCurrentMigration = (db: Migration) => {
|
|
13
|
+
currentMigration = db;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const setCurrentMigrationUp = (up: boolean) => {
|
|
17
|
+
currentUp = up;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const getCurrentPromise = () => currentPromise;
|