remult 0.25.7 → 0.25.8-exp.1
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 +6 -12
- package/esm/migrations/compare-migration-snapshots.js +39 -0
- package/esm/migrations/generate-migrations.js +51 -0
- package/esm/migrations/index.js +2 -0
- package/esm/migrations/migrate.js +66 -0
- package/esm/migrations/migration-types.js +12 -0
- package/esm/migrations/update-migrations-ts-file.js +112 -0
- package/esm/postgres/postgres-data-provider.js +23 -2
- package/esm/postgres/schema-builder.js +35 -26
- package/esm/remult-knex/index.js +116 -28
- package/esm/remult-sql-js.js +7 -2
- package/esm/src/context.js +14 -8
- package/esm/src/data-providers/sql-database.js +14 -3
- package/esm/src/data-providers/web-sql-data-provider.js +1 -9
- package/esm/src/filter/filter-consumer-bridge-to-sql-request.js +4 -0
- package/esm/src/isOfType.js +9 -0
- package/esm/src/remult3/remult3.js +24 -11
- package/migrations/compare-migration-snapshots.d.ts +26 -0
- package/migrations/compare-migration-snapshots.js +101 -0
- package/migrations/generate-migrations.d.ts +9 -0
- package/migrations/generate-migrations.js +88 -0
- package/migrations/index.d.ts +3 -0
- package/migrations/index.js +7 -0
- package/migrations/migrate.d.ts +8 -0
- package/migrations/migrate.js +159 -0
- package/migrations/migration-types.d.ts +23 -0
- package/migrations/migration-types.js +27 -0
- package/migrations/update-migrations-ts-file.d.ts +1 -0
- package/migrations/update-migrations-ts-file.js +135 -0
- package/package.json +6 -1
- package/postgres/postgres-data-provider.d.ts +6 -1
- package/postgres/postgres-data-provider.js +45 -2
- package/postgres/schema-builder.js +81 -48
- package/remult-knex/index.d.ts +10 -3
- package/remult-knex/index.js +174 -56
- package/remult-sql-js.d.ts +2 -0
- package/remult-sql-js.js +15 -1
- package/server/expressBridge.d.ts +9 -1
- package/src/context.d.ts +1 -1
- package/src/context.js +16 -8
- package/src/data-providers/sql-database.d.ts +4 -2
- package/src/data-providers/sql-database.js +11 -3
- package/src/data-providers/web-sql-data-provider.d.ts +1 -2
- package/src/data-providers/web-sql-data-provider.js +4 -8
- package/src/filter/filter-consumer-bridge-to-sql-request.d.ts +1 -0
- package/src/filter/filter-consumer-bridge-to-sql-request.js +6 -1
- package/src/isOfType.d.ts +2 -0
- package/src/isOfType.js +14 -0
- package/src/remult3/remult3.js +24 -11
- package/src/sql-command.d.ts +5 -0
package/README.md
CHANGED
|
@@ -5,23 +5,17 @@
|
|
|
5
5
|
<h1>Remult</h1>
|
|
6
6
|
<p>Full-stack CRUD, simplified, with SSOT TypeScript entities</p>
|
|
7
7
|
<a href="https://circleci.com/gh/remult/remult/tree/master" rel="nofollow">
|
|
8
|
-
<img alt="CircleCI" src="https://circleci.com/gh/remult/remult/tree/master.svg?style=shield">
|
|
9
|
-
</a>
|
|
8
|
+
<img alt="CircleCI" src="https://circleci.com/gh/remult/remult/tree/master.svg?style=shield"></a>
|
|
10
9
|
<a href="https://codecov.io/gh/remult/remult" rel="nofollow">
|
|
11
|
-
<img src="https://codecov.io/gh/remult/remult/branch/master/graph/badge.svg?token=LYWQRUN3D1"
|
|
12
|
-
</a>
|
|
10
|
+
<img src="https://codecov.io/gh/remult/remult/branch/master/graph/badge.svg?token=LYWQRUN3D1"/></a>
|
|
13
11
|
<a href="https://raw.githubusercontent.com/remult/remult/master/LICENSE" rel="nofollow">
|
|
14
|
-
<img alt="GitHub license" src="https://img.shields.io/badge/license-MIT-blue.svg">
|
|
15
|
-
</a>
|
|
12
|
+
<img alt="GitHub license" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
|
|
16
13
|
<a href="https://www.npmjs.com/package/remult" rel="nofollow">
|
|
17
|
-
<img alt="npm version" src="https://badge.fury.io/js/remult.svg">
|
|
18
|
-
</a>
|
|
14
|
+
<img alt="npm version" src="https://badge.fury.io/js/remult.svg"></a>
|
|
19
15
|
<a href="https://www.npmjs.com/package/remult" rel="nofollow">
|
|
20
|
-
<img alt="npm downloads" src="https://img.shields.io/npm/dm/remult">
|
|
21
|
-
</a>
|
|
16
|
+
<img alt="npm downloads" src="https://img.shields.io/npm/dm/remult"></a>
|
|
22
17
|
<a href="https://discord.gg/GXHk7ZfuG5" rel="nofollow">
|
|
23
|
-
<img alt="Join Discord" src="https://badgen.net/discord/
|
|
24
|
-
</a>
|
|
18
|
+
<img alt="Join Discord" src="https://badgen.net/discord/members/GXHk7ZfuG5?icon=discord&label=Discord"/></a>
|
|
25
19
|
</div>
|
|
26
20
|
|
|
27
21
|
<br/>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { repo } from '../index.js';
|
|
2
|
+
import { dbNamesOf, shouldCreateEntity, shouldNotCreateField, } from '../src/filter/filter-consumer-bridge-to-sql-request.js';
|
|
3
|
+
export async function compareMigrationSnapshot({ entities, snapshot, migrationBuilder: migrationBuilder, }) {
|
|
4
|
+
snapshot = JSON.parse(JSON.stringify(snapshot));
|
|
5
|
+
for (const entity of entities) {
|
|
6
|
+
const meta = repo(entity).metadata;
|
|
7
|
+
const e = await dbNamesOf(meta, (x) => x);
|
|
8
|
+
if (shouldCreateEntity(meta, e)) {
|
|
9
|
+
let entitySnapshot = snapshot.entities[e.$entityName];
|
|
10
|
+
let createColumns = true;
|
|
11
|
+
if (!entitySnapshot) {
|
|
12
|
+
createColumns = false;
|
|
13
|
+
await migrationBuilder.createTable(meta);
|
|
14
|
+
entitySnapshot = snapshot.entities[e.$entityName] = {
|
|
15
|
+
key: meta.key,
|
|
16
|
+
className: meta.entityType.name,
|
|
17
|
+
columns: {},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
for (const field of meta.fields) {
|
|
21
|
+
if (!shouldNotCreateField(field, e)) {
|
|
22
|
+
const column = entitySnapshot.columns[e.$dbNameOf(field)];
|
|
23
|
+
if (!column) {
|
|
24
|
+
if (createColumns)
|
|
25
|
+
await migrationBuilder.addColumn(meta, field);
|
|
26
|
+
entitySnapshot.columns[e.$dbNameOf(field)] = { key: field.key };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return snapshot;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function emptySnapshot() {
|
|
35
|
+
return {
|
|
36
|
+
version: 1,
|
|
37
|
+
entities: {},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { compareMigrationSnapshot, emptySnapshot, } from './compare-migration-snapshots.js';
|
|
4
|
+
import { updateMigrationsFile } from './update-migrations-ts-file.js';
|
|
5
|
+
import { DefaultMigrationBuilder, } from './migration-types.js';
|
|
6
|
+
import { isOfType } from '../src/isOfType.js';
|
|
7
|
+
import { initDataProvider } from '../server/initDataProvider.js';
|
|
8
|
+
export async function generateMigrations(options) {
|
|
9
|
+
const migrationDir = options.migrationsFolder ||
|
|
10
|
+
path.join(process.cwd(), 'src/server/migrations');
|
|
11
|
+
const snapshotFileName = options.snapshotFile || path.join(migrationDir, 'migrations-snapshot.json');
|
|
12
|
+
const migrationsTSFilename = options.migrationsTSFile || path.join(migrationDir, 'migrations.ts');
|
|
13
|
+
const dataProvider = await initDataProvider(options.dataProvider);
|
|
14
|
+
for (const p of [snapshotFileName, migrationsTSFilename]) {
|
|
15
|
+
if (!fs.existsSync(path.dirname(p))) {
|
|
16
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
let snapshot = emptySnapshot();
|
|
20
|
+
if (fs.existsSync(snapshotFileName)) {
|
|
21
|
+
snapshot = JSON.parse(fs.readFileSync(snapshotFileName).toString());
|
|
22
|
+
}
|
|
23
|
+
let hasSql = false;
|
|
24
|
+
let steps = [];
|
|
25
|
+
const code = {
|
|
26
|
+
//https://marketplace.visualstudio.com/items?itemName=qufiwefefwoyn.inline-sql-syntax
|
|
27
|
+
addSql: (sql) => {
|
|
28
|
+
steps.push('await sql(`--sql\n' + sql + '`)');
|
|
29
|
+
hasSql = true;
|
|
30
|
+
},
|
|
31
|
+
addTypescriptCode: (code) => steps.push(code),
|
|
32
|
+
addComment: (comment) => steps.push(comment.indexOf('\n') >= 0 ? `/*${comment}*/` : `// ${comment}`),
|
|
33
|
+
};
|
|
34
|
+
let migrationBuilder = new DefaultMigrationBuilder(code);
|
|
35
|
+
if (isOfType(dataProvider, 'provideMigrationBuilder')) {
|
|
36
|
+
migrationBuilder = dataProvider.provideMigrationBuilder(code);
|
|
37
|
+
}
|
|
38
|
+
snapshot = await compareMigrationSnapshot({
|
|
39
|
+
entities: options.entities,
|
|
40
|
+
snapshot,
|
|
41
|
+
migrationBuilder,
|
|
42
|
+
});
|
|
43
|
+
if (steps.length)
|
|
44
|
+
await updateMigrationsFile(migrationsTSFilename, [
|
|
45
|
+
`async (${hasSql ? '{sql}' : ''})=>{\n${steps.join('\n')}\n}`,
|
|
46
|
+
]);
|
|
47
|
+
fs.writeFileSync(snapshotFileName, JSON.stringify(snapshot, null, 2));
|
|
48
|
+
if (options.endConnection !== false && isOfType(dataProvider, 'end')) {
|
|
49
|
+
await dataProvider.end();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import { Entity, IdEntity, Fields, remult, Remult, } from '../index.js';
|
|
3
|
+
import { initDataProvider } from '../server/initDataProvider.js';
|
|
4
|
+
import { doTransaction } from '../src/context.js';
|
|
5
|
+
import { cast, isOfType } from '../src/isOfType.js';
|
|
6
|
+
export async function migrate(options) {
|
|
7
|
+
let migrationTableName = options.migrationsTable || '__remult_migrations_version';
|
|
8
|
+
const dataProvider = await initDataProvider(options.dataProvider);
|
|
9
|
+
const prev = remult.dataProvider;
|
|
10
|
+
remult.dataProvider = dataProvider;
|
|
11
|
+
try {
|
|
12
|
+
let VersionInfo = class VersionInfo extends IdEntity {
|
|
13
|
+
version = -1;
|
|
14
|
+
};
|
|
15
|
+
__decorate([
|
|
16
|
+
Fields.number(),
|
|
17
|
+
__metadata("design:type", Object)
|
|
18
|
+
], VersionInfo.prototype, "version", void 0);
|
|
19
|
+
VersionInfo = __decorate([
|
|
20
|
+
Entity(migrationTableName)
|
|
21
|
+
], VersionInfo);
|
|
22
|
+
const repo = new Remult(dataProvider).repo(VersionInfo);
|
|
23
|
+
if (dataProvider.ensureSchema) {
|
|
24
|
+
await dataProvider.ensureSchema([repo.metadata]);
|
|
25
|
+
}
|
|
26
|
+
let v = await repo.findFirst();
|
|
27
|
+
if (!v) {
|
|
28
|
+
v = repo.create();
|
|
29
|
+
v.version = -1;
|
|
30
|
+
}
|
|
31
|
+
const steps = Object.entries(options.migrations).sort(([a], [b]) => parseInt(a) - parseInt(b));
|
|
32
|
+
for (const [stepText, action] of steps) {
|
|
33
|
+
const step = parseInt(stepText);
|
|
34
|
+
if (step < 0)
|
|
35
|
+
throw new Error('Migration step number must be a non-negative integer');
|
|
36
|
+
if (v.version >= step)
|
|
37
|
+
continue;
|
|
38
|
+
console.info('Executing migration step ' + step);
|
|
39
|
+
console.time('Completed migration step ' + step);
|
|
40
|
+
try {
|
|
41
|
+
await doTransaction(remult, async (dp) => {
|
|
42
|
+
const utils = {
|
|
43
|
+
sql: async (sql) => {
|
|
44
|
+
await cast(dp, 'execute').execute(sql);
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
await action(utils);
|
|
48
|
+
});
|
|
49
|
+
console.timeEnd('Completed migration step ' + step);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
console.error('Failed to execute migration step ' + step);
|
|
53
|
+
console.error(err);
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
v.version = step;
|
|
57
|
+
await v.save();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
remult.dataProvider = prev;
|
|
62
|
+
}
|
|
63
|
+
if (options.endConnection !== false && isOfType(dataProvider, 'end')) {
|
|
64
|
+
await dataProvider.end();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export class DefaultMigrationBuilder {
|
|
2
|
+
code;
|
|
3
|
+
constructor(code) {
|
|
4
|
+
this.code = code;
|
|
5
|
+
}
|
|
6
|
+
async createTable(meta) {
|
|
7
|
+
this.code.addComment('create table ' + meta.entityType.name);
|
|
8
|
+
}
|
|
9
|
+
async addColumn(meta, field) {
|
|
10
|
+
this.code.addComment('add column ' + meta.entityType.name + '.' + field.key);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// consider replacing with https://www.npmjs.com/package/ts-morph
|
|
2
|
+
import * as ts from 'typescript';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
export async function updateMigrationsFile(filePath, steps) {
|
|
5
|
+
let maxStep = -1;
|
|
6
|
+
function buildSteps() {
|
|
7
|
+
return steps.map((step) => ts.factory.createPropertyAssignment(ts.factory.createIdentifier((++maxStep).toString()), ts.factory.createIdentifier(step)));
|
|
8
|
+
}
|
|
9
|
+
if (!fs.existsSync(filePath)) {
|
|
10
|
+
fs.writeFileSync(filePath, '');
|
|
11
|
+
}
|
|
12
|
+
const importPath = 'remult/migrations';
|
|
13
|
+
const importName = 'Migrations';
|
|
14
|
+
// Load the file
|
|
15
|
+
const sourceFile = ts.createSourceFile(filePath, fs.readFileSync(filePath).toString(), ts.ScriptTarget.Latest, true);
|
|
16
|
+
let migrationsExportFound = false;
|
|
17
|
+
// Find the "migrations" named export and add the new member
|
|
18
|
+
let updatedSourceFile = ts.transform(sourceFile, [
|
|
19
|
+
(context) => {
|
|
20
|
+
return (node) => {
|
|
21
|
+
const visitor = (node) => {
|
|
22
|
+
if (ts.isVariableStatement(node) &&
|
|
23
|
+
node.declarationList.declarations.length > 0) {
|
|
24
|
+
const declaration = node.declarationList.declarations[0];
|
|
25
|
+
if (ts.isIdentifier(declaration.name) &&
|
|
26
|
+
declaration.name.text === 'migrations' &&
|
|
27
|
+
declaration.initializer &&
|
|
28
|
+
ts.isObjectLiteralExpression(declaration.initializer)) {
|
|
29
|
+
migrationsExportFound = true;
|
|
30
|
+
declaration.initializer.properties.forEach((property) => {
|
|
31
|
+
if (ts.isPropertyAssignment(property)) {
|
|
32
|
+
if (ts.isNumericLiteral(property.name)) {
|
|
33
|
+
const step = parseInt(property.name.text);
|
|
34
|
+
if (step > maxStep) {
|
|
35
|
+
maxStep = step;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
let steps = buildSteps();
|
|
41
|
+
// Found the "migrations" array
|
|
42
|
+
// Update the initializer with the new steps
|
|
43
|
+
const updatedInitializer = ts.factory.updateObjectLiteralExpression(declaration.initializer, [...declaration.initializer.properties, ...steps]);
|
|
44
|
+
// Update the declaration with the new initializer
|
|
45
|
+
const updatedDeclaration = ts.factory.updateVariableDeclaration(declaration, declaration.name, undefined, declaration.type, // Preserve the original type annotation if present
|
|
46
|
+
updatedInitializer);
|
|
47
|
+
return ts.factory.updateVariableStatement(node, node.modifiers, ts.factory.updateVariableDeclarationList(node.declarationList, [
|
|
48
|
+
updatedDeclaration,
|
|
49
|
+
]));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return ts.visitEachChild(node, visitor, context);
|
|
53
|
+
};
|
|
54
|
+
return ts.visitNode(node, visitor);
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
]).transformed[0];
|
|
58
|
+
if (!migrationsExportFound) {
|
|
59
|
+
let importFound = false;
|
|
60
|
+
// Check if the import statement already exists
|
|
61
|
+
sourceFile.statements.forEach((statement) => {
|
|
62
|
+
if (ts.isImportDeclaration(statement) &&
|
|
63
|
+
ts.isStringLiteral(statement.moduleSpecifier) &&
|
|
64
|
+
statement.moduleSpecifier.text === `${importPath}`) {
|
|
65
|
+
statement.importClause?.namedBindings?.forEachChild((namedBinding) => {
|
|
66
|
+
if (ts.isImportSpecifier(namedBinding) &&
|
|
67
|
+
namedBinding.name.text === importName) {
|
|
68
|
+
importFound = true;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
// If the import statement doesn't exist, create it
|
|
74
|
+
if (!importFound) {
|
|
75
|
+
updatedSourceFile = ts.factory.updateSourceFile(updatedSourceFile, [
|
|
76
|
+
ts.factory.createExpressionStatement(ts.factory.createIdentifier(`import type { ${importName} } from '${importPath}'`)),
|
|
77
|
+
...updatedSourceFile.statements,
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
// Create a new export for migrations if it doesn't exist
|
|
81
|
+
const newMigrationsExport = ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
|
|
82
|
+
ts.factory.createVariableDeclaration('migrations', undefined, // You can add a type annotation here if needed
|
|
83
|
+
ts.factory.createTypeReferenceNode(importName, undefined), ts.factory.createObjectLiteralExpression(buildSteps(), false)),
|
|
84
|
+
], ts.NodeFlags.Const));
|
|
85
|
+
// Add the new export to the end of the file
|
|
86
|
+
updatedSourceFile = ts.factory.updateSourceFile(updatedSourceFile, [
|
|
87
|
+
...updatedSourceFile.statements,
|
|
88
|
+
newMigrationsExport,
|
|
89
|
+
]);
|
|
90
|
+
}
|
|
91
|
+
let code = ts.createPrinter().printFile(updatedSourceFile);
|
|
92
|
+
try {
|
|
93
|
+
let prettier;
|
|
94
|
+
try {
|
|
95
|
+
prettier = await import('prettier' + '');
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
console.warn('error importing prettier,for better formatting run `npm i -D prettier`. Writing raw code to file.');
|
|
99
|
+
}
|
|
100
|
+
if (prettier) {
|
|
101
|
+
let options = await prettier.resolveConfig();
|
|
102
|
+
code = await prettier.format(code, {
|
|
103
|
+
...options,
|
|
104
|
+
parser: 'typescript',
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
console.log('error formatting code, writing raw code to file.');
|
|
110
|
+
}
|
|
111
|
+
fs.writeFileSync(filePath, code);
|
|
112
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { remult as defaultRemult } from '../src/remult-proxy.js';
|
|
1
2
|
import pg from 'pg';
|
|
2
3
|
const { Pool } = pg;
|
|
3
4
|
import { Remult } from '../src/context.js';
|
|
@@ -8,8 +9,10 @@ export class PostgresDataProvider {
|
|
|
8
9
|
options;
|
|
9
10
|
supportsJsonColumnType = true;
|
|
10
11
|
static getDb(remult) {
|
|
11
|
-
const
|
|
12
|
-
|
|
12
|
+
const r = (remult || defaultRemult).dataProvider;
|
|
13
|
+
if (!r._getSourceSql)
|
|
14
|
+
throw 'the data provider is not an SqlDatabase';
|
|
15
|
+
const me = r._getSourceSql();
|
|
13
16
|
if (!me.pool) {
|
|
14
17
|
throw 'the data provider is not a PostgresDataProvider';
|
|
15
18
|
}
|
|
@@ -35,6 +38,21 @@ export class PostgresDataProvider {
|
|
|
35
38
|
this.pool = new PostgresSchemaWrapper(pool, options.schema);
|
|
36
39
|
}
|
|
37
40
|
}
|
|
41
|
+
end() {
|
|
42
|
+
return this.pool.end();
|
|
43
|
+
}
|
|
44
|
+
provideMigrationBuilder(builder) {
|
|
45
|
+
var db = new SqlDatabase(this);
|
|
46
|
+
var sb = new PostgresSchemaBuilder(db, this.options?.schema);
|
|
47
|
+
return {
|
|
48
|
+
addColumn: async (meta, field) => {
|
|
49
|
+
builder.addSql(await sb.getAddColumnScript(meta, field));
|
|
50
|
+
},
|
|
51
|
+
createTable: async (meta) => {
|
|
52
|
+
builder.addSql(await sb.createTableScript(meta));
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
38
56
|
wrapIdentifier = (name) => name
|
|
39
57
|
.split('.')
|
|
40
58
|
.map((name) => name.startsWith('"') ? name : '"' + name.replace(/"/g, '""') + '"')
|
|
@@ -172,4 +190,7 @@ export class PostgresSchemaWrapper {
|
|
|
172
190
|
c.release();
|
|
173
191
|
}
|
|
174
192
|
}
|
|
193
|
+
end() {
|
|
194
|
+
return this.pool.end();
|
|
195
|
+
}
|
|
175
196
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { dbNamesOf, shouldNotCreateField, } from '../src/filter/filter-consumer-bridge-to-sql-request.js';
|
|
1
|
+
import { dbNamesOf, shouldCreateEntity, shouldNotCreateField, } from '../src/filter/filter-consumer-bridge-to-sql-request.js';
|
|
2
2
|
import { remult as defaultRemult } from '../src/remult-proxy.js';
|
|
3
3
|
import { remultStatic } from '../src/remult-static.js';
|
|
4
4
|
import { isAutoIncrement } from '../src/remult3/RepositoryImplementation.js';
|
|
@@ -19,7 +19,7 @@ export function postgresColumnSyntax(x, dbName) {
|
|
|
19
19
|
if (x.valueConverter == ValueConverters.DateOnly)
|
|
20
20
|
result += ' date';
|
|
21
21
|
else
|
|
22
|
-
result += '
|
|
22
|
+
result += ' timestamptz';
|
|
23
23
|
else
|
|
24
24
|
result += ' ' + x.valueConverter.fieldTypeInDb;
|
|
25
25
|
}
|
|
@@ -109,11 +109,9 @@ export class PostgresSchemaBuilder {
|
|
|
109
109
|
for (const entity of entities) {
|
|
110
110
|
let e = await dbNamesOf(entity, this.pool.wrapIdentifier);
|
|
111
111
|
try {
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
await this.verifyAllColumns(entity);
|
|
116
|
-
}
|
|
112
|
+
if (shouldCreateEntity(entity, e)) {
|
|
113
|
+
await this.createIfNotExist(entity);
|
|
114
|
+
await this.verifyAllColumns(entity);
|
|
117
115
|
}
|
|
118
116
|
}
|
|
119
117
|
catch (err) {
|
|
@@ -130,29 +128,41 @@ export class PostgresSchemaBuilder {
|
|
|
130
128
|
`${this.whereTableAndSchema(c, e)}`)
|
|
131
129
|
.then(async (r) => {
|
|
132
130
|
if (r.rows.length == 0) {
|
|
133
|
-
|
|
134
|
-
for (const x of entity.fields) {
|
|
135
|
-
if (!shouldNotCreateField(x, e) || isAutoIncrement(x)) {
|
|
136
|
-
if (result.length != 0)
|
|
137
|
-
result += ',';
|
|
138
|
-
result += '\r\n ';
|
|
139
|
-
if (isAutoIncrement(x))
|
|
140
|
-
result += e.$dbNameOf(x) + ' serial';
|
|
141
|
-
else {
|
|
142
|
-
result += postgresColumnSyntax(x, e.$dbNameOf(x));
|
|
143
|
-
}
|
|
144
|
-
if (x == entity.idMetadata.field)
|
|
145
|
-
result += ' primary key';
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
let sql = `CREATE SCHEMA IF NOT EXISTS ${this.schemaOnly(e)};
|
|
149
|
-
CREATE table ${this.schemaAndName(e)} (${result}\r\n)`;
|
|
131
|
+
const sql = await this.createTableScript(entity);
|
|
150
132
|
if (PostgresSchemaBuilder.logToConsole)
|
|
151
133
|
console.info(sql);
|
|
152
134
|
await this.pool.execute(sql);
|
|
153
135
|
}
|
|
154
136
|
});
|
|
155
137
|
}
|
|
138
|
+
/* @internal*/
|
|
139
|
+
async getAddColumnScript(entity, field) {
|
|
140
|
+
let e = await dbNamesOf(entity, this.pool.wrapIdentifier);
|
|
141
|
+
return (`ALTER table ${this.schemaAndName(e)} ` +
|
|
142
|
+
`ADD column ${postgresColumnSyntax(field, e.$dbNameOf(field))}`);
|
|
143
|
+
}
|
|
144
|
+
/* @internal*/
|
|
145
|
+
async createTableScript(entity) {
|
|
146
|
+
let result = '';
|
|
147
|
+
let e = await dbNamesOf(entity, this.pool.wrapIdentifier);
|
|
148
|
+
for (const x of entity.fields) {
|
|
149
|
+
if (!shouldNotCreateField(x, e) || isAutoIncrement(x)) {
|
|
150
|
+
if (result.length != 0)
|
|
151
|
+
result += ',';
|
|
152
|
+
result += '\r\n ';
|
|
153
|
+
if (isAutoIncrement(x))
|
|
154
|
+
result += e.$dbNameOf(x) + ' serial';
|
|
155
|
+
else {
|
|
156
|
+
result += postgresColumnSyntax(x, e.$dbNameOf(x));
|
|
157
|
+
}
|
|
158
|
+
if (x == entity.idMetadata.field)
|
|
159
|
+
result += ' primary key';
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
let sql = `CREATE SCHEMA IF NOT EXISTS ${this.schemaOnly(e)};
|
|
163
|
+
CREATE table ${this.schemaAndName(e)} (${result}\r\n)`;
|
|
164
|
+
return sql;
|
|
165
|
+
}
|
|
156
166
|
async addColumnIfNotExist(entity, c) {
|
|
157
167
|
let e = await dbNamesOf(entity, this.pool.wrapIdentifier);
|
|
158
168
|
if (shouldNotCreateField(c(entity), e))
|
|
@@ -163,8 +173,7 @@ CREATE table ${this.schemaAndName(e)} (${result}\r\n)`;
|
|
|
163
173
|
if ((await cmd.execute(`SELECT 1 FROM information_schema.columns WHERE ` +
|
|
164
174
|
`${this.whereTableAndSchema(cmd, e)} ` +
|
|
165
175
|
`AND column_name=${cmd.param(colName.toLocaleLowerCase())}`)).rows.length == 0) {
|
|
166
|
-
let sql =
|
|
167
|
-
`ADD column ${postgresColumnSyntax(c(entity), colName)}`;
|
|
176
|
+
let sql = await this.getAddColumnScript(entity, c(entity));
|
|
168
177
|
if (PostgresSchemaBuilder.logToConsole)
|
|
169
178
|
console.info(sql);
|
|
170
179
|
await this.pool.execute(sql);
|
package/esm/remult-knex/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { dbNamesOf, isDbReadonly, shouldNotCreateField, } from '../src/filter/filter-consumer-bridge-to-sql-request.js';
|
|
1
|
+
import { dbNamesOf, isDbReadonly, shouldCreateEntity, shouldNotCreateField, } from '../src/filter/filter-consumer-bridge-to-sql-request.js';
|
|
2
2
|
import { customDatabaseFilterToken, Filter, } from '../src/filter/filter-interfaces.js';
|
|
3
3
|
import { CompoundIdField } from '../src/CompoundIdField.js';
|
|
4
4
|
import { remult as remultContext } from '../src/remult-proxy.js';
|
|
@@ -13,6 +13,34 @@ export class KnexDataProvider {
|
|
|
13
13
|
constructor(knex) {
|
|
14
14
|
this.knex = knex;
|
|
15
15
|
}
|
|
16
|
+
end() {
|
|
17
|
+
return this.knex.destroy();
|
|
18
|
+
}
|
|
19
|
+
/* @internal */
|
|
20
|
+
provideMigrationBuilder(builder) {
|
|
21
|
+
var sb = new KnexSchemaBuilder(this.knex);
|
|
22
|
+
return {
|
|
23
|
+
async createTable(entity) {
|
|
24
|
+
let e = await dbNamesOf(entity, (x) => x);
|
|
25
|
+
sb.createTableKnexCommand(entity, e)
|
|
26
|
+
.toSQL()
|
|
27
|
+
.forEach((sql) => builder.addSql(sql.sql));
|
|
28
|
+
},
|
|
29
|
+
async addColumn(entity, field) {
|
|
30
|
+
let e = await dbNamesOf(entity, (x) => x);
|
|
31
|
+
await sb
|
|
32
|
+
.createColumnKnexCommand(e, field, e.$dbNameOf(field))
|
|
33
|
+
.toSQL()
|
|
34
|
+
.forEach((sql) => builder.addSql(sql.sql));
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
createCommand() {
|
|
39
|
+
return new KnexBridgeToSQLCommand(this.knex);
|
|
40
|
+
}
|
|
41
|
+
execute(sql) {
|
|
42
|
+
return this.createCommand().execute(sql);
|
|
43
|
+
}
|
|
16
44
|
static getDb(remult) {
|
|
17
45
|
const r = (remult || remultContext).dataProvider;
|
|
18
46
|
if (!r.knex)
|
|
@@ -350,11 +378,9 @@ export class KnexSchemaBuilder {
|
|
|
350
378
|
for (const entity of entities) {
|
|
351
379
|
let e = await dbNamesOf(entity, (x) => x);
|
|
352
380
|
try {
|
|
353
|
-
if (
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
await this.verifyAllColumns(entity);
|
|
357
|
-
}
|
|
381
|
+
if (shouldCreateEntity(entity, e)) {
|
|
382
|
+
await this.createIfNotExist(entity);
|
|
383
|
+
await this.verifyAllColumns(entity);
|
|
358
384
|
}
|
|
359
385
|
}
|
|
360
386
|
catch (err) {
|
|
@@ -366,27 +392,30 @@ export class KnexSchemaBuilder {
|
|
|
366
392
|
async createIfNotExist(entity) {
|
|
367
393
|
const e = await dbNamesOf(entity, (x) => x);
|
|
368
394
|
if (!(await this.knex.schema.hasTable(e.$entityName))) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
395
|
+
await logSql(this.createTableKnexCommand(entity, e));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
createTableKnexCommand(entity, e) {
|
|
399
|
+
let cols = new Map();
|
|
400
|
+
for (const f of entity.fields) {
|
|
401
|
+
cols.set(f, {
|
|
402
|
+
name: e.$dbNameOf(f),
|
|
403
|
+
readonly: shouldNotCreateField(f, e),
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
return this.knex.schema.createTable(e.$entityName, (b) => {
|
|
407
|
+
for (const x of entity.fields) {
|
|
408
|
+
if (!cols.get(x).readonly || isAutoIncrement(x)) {
|
|
409
|
+
if (isAutoIncrement(x))
|
|
410
|
+
b.increments(cols.get(x).name);
|
|
411
|
+
else {
|
|
412
|
+
buildColumn(x, cols.get(x).name, b, supportsJsonDataStorage(this.knex));
|
|
413
|
+
if (x == entity.idMetadata.field)
|
|
414
|
+
b.primary([cols.get(x).name]);
|
|
386
415
|
}
|
|
387
416
|
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
417
|
+
}
|
|
418
|
+
});
|
|
390
419
|
}
|
|
391
420
|
async addColumnIfNotExist(entity, c) {
|
|
392
421
|
let e = await dbNamesOf(entity, (x) => x);
|
|
@@ -395,11 +424,14 @@ export class KnexSchemaBuilder {
|
|
|
395
424
|
let col = c(entity);
|
|
396
425
|
let colName = e.$dbNameOf(col);
|
|
397
426
|
if (!(await this.knex.schema.hasColumn(e.$entityName, colName))) {
|
|
398
|
-
await logSql(this.
|
|
399
|
-
buildColumn(col, colName, b, supportsJsonDataStorage(this.knex));
|
|
400
|
-
}));
|
|
427
|
+
await logSql(this.createColumnKnexCommand(e, col, colName));
|
|
401
428
|
}
|
|
402
429
|
}
|
|
430
|
+
createColumnKnexCommand(e, col, colName) {
|
|
431
|
+
return this.knex.schema.alterTable(e.$entityName, (b) => {
|
|
432
|
+
buildColumn(col, colName, b, supportsJsonDataStorage(this.knex));
|
|
433
|
+
});
|
|
434
|
+
}
|
|
403
435
|
async verifyAllColumns(entity) {
|
|
404
436
|
let e = await dbNamesOf(entity, (x) => x);
|
|
405
437
|
try {
|
|
@@ -502,3 +534,59 @@ function translateValueAndHandleArrayAndHandleArray(field, val) {
|
|
|
502
534
|
return JSON.stringify(result);
|
|
503
535
|
return result;
|
|
504
536
|
}
|
|
537
|
+
class KnexBridgeToSQLCommand {
|
|
538
|
+
source;
|
|
539
|
+
constructor(source) {
|
|
540
|
+
this.source = source;
|
|
541
|
+
}
|
|
542
|
+
values = [];
|
|
543
|
+
addParameterAndReturnSqlToken(val) {
|
|
544
|
+
return this.param(val);
|
|
545
|
+
}
|
|
546
|
+
param(val) {
|
|
547
|
+
if (Array.isArray(val))
|
|
548
|
+
val = JSON.stringify(val);
|
|
549
|
+
this.values.push(val);
|
|
550
|
+
return '?';
|
|
551
|
+
}
|
|
552
|
+
async execute(sql) {
|
|
553
|
+
return await this.source.raw(sql, this.values).then((r) => {
|
|
554
|
+
switch (this.source.client.config.client) {
|
|
555
|
+
case 'mysql':
|
|
556
|
+
case 'mysql2':
|
|
557
|
+
return new KnexPostgresBridgeToSQLQueryResult({
|
|
558
|
+
fields: r[1],
|
|
559
|
+
rows: r[0],
|
|
560
|
+
});
|
|
561
|
+
case 'pg':
|
|
562
|
+
return new KnexPostgresBridgeToSQLQueryResult(r);
|
|
563
|
+
default:
|
|
564
|
+
case 'better-sqlite3':
|
|
565
|
+
case 'mssql':
|
|
566
|
+
return new KnexPostgresBridgeToSQLQueryResult({
|
|
567
|
+
rows: r,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
class KnexPostgresBridgeToSQLQueryResult {
|
|
574
|
+
r;
|
|
575
|
+
getColumnKeyInResultForIndexInSelect(index) {
|
|
576
|
+
if (this.r.fields)
|
|
577
|
+
return this.r.fields[index].name;
|
|
578
|
+
if (this.rows.length == 0)
|
|
579
|
+
throw Error('No rows');
|
|
580
|
+
let i = 0;
|
|
581
|
+
for (let m in this.rows[0]) {
|
|
582
|
+
if (i++ == index)
|
|
583
|
+
return m;
|
|
584
|
+
}
|
|
585
|
+
throw Error('index not found');
|
|
586
|
+
}
|
|
587
|
+
constructor(r) {
|
|
588
|
+
this.r = r;
|
|
589
|
+
this.rows = r.rows;
|
|
590
|
+
}
|
|
591
|
+
rows;
|
|
592
|
+
}
|