remult 0.25.7-exp.4 → 0.25.8-exp.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 +6 -12
- package/esm/migrations/compare-migration-snapshots.js +39 -0
- package/esm/migrations/generate-migrations.js +47 -0
- package/esm/migrations/index.js +2 -0
- package/esm/migrations/migrate.js +55 -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 +12 -0
- package/esm/postgres/schema-builder.js +35 -26
- package/esm/remult-knex/index.js +112 -28
- package/esm/remult-sql-js.js +2 -2
- package/esm/src/context.js +14 -8
- package/esm/src/data-providers/sql-database.js +10 -3
- 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 +20 -13
- package/migrations/compare-migration-snapshots.d.ts +26 -0
- package/migrations/compare-migration-snapshots.js +101 -0
- package/migrations/generate-migrations.d.ts +8 -0
- package/migrations/generate-migrations.js +82 -0
- package/migrations/index.d.ts +3 -0
- package/migrations/index.js +7 -0
- package/migrations/migrate.d.ts +7 -0
- package/migrations/migrate.js +147 -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 +3 -1
- package/postgres/postgres-data-provider.js +34 -0
- package/postgres/schema-builder.js +81 -48
- package/remult-knex/index.d.ts +10 -3
- package/remult-knex/index.js +170 -56
- package/remult-sql-js.js +1 -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 +5 -2
- package/src/data-providers/sql-database.js +8 -3
- 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 +20 -13
- package/src/sql-command.d.ts +4 -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,47 @@
|
|
|
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
|
+
export async function generateMigrations(options) {
|
|
8
|
+
const migrationDir = options.migrationsDir || path.join(process.cwd(), 'src/server/migrations');
|
|
9
|
+
const snapshotFileName = options.snapshotFileName ||
|
|
10
|
+
path.join(migrationDir, 'migrations-snapshot.json');
|
|
11
|
+
const migrationsTSFilename = options.migrationsTSFilename || path.join(migrationDir, 'migrations.ts');
|
|
12
|
+
const dataProvider = await options.dataProvider;
|
|
13
|
+
for (const p of [snapshotFileName, migrationsTSFilename]) {
|
|
14
|
+
if (!fs.existsSync(path.dirname(p))) {
|
|
15
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
let snapshot = emptySnapshot();
|
|
19
|
+
if (fs.existsSync(snapshotFileName)) {
|
|
20
|
+
snapshot = JSON.parse(fs.readFileSync(snapshotFileName).toString());
|
|
21
|
+
}
|
|
22
|
+
let hasSql = false;
|
|
23
|
+
let steps = [];
|
|
24
|
+
const code = {
|
|
25
|
+
//https://marketplace.visualstudio.com/items?itemName=qufiwefefwoyn.inline-sql-syntax
|
|
26
|
+
addSql: (sql) => {
|
|
27
|
+
steps.push('await sql(`--sql\n' + sql + '`)');
|
|
28
|
+
hasSql = true;
|
|
29
|
+
},
|
|
30
|
+
addTypescriptCode: (code) => steps.push(code),
|
|
31
|
+
addComment: (comment) => steps.push(comment.indexOf('\n') >= 0 ? `/*${comment}*/` : `// ${comment}`),
|
|
32
|
+
};
|
|
33
|
+
let migrationBuilder = new DefaultMigrationBuilder(code);
|
|
34
|
+
if (isOfType(dataProvider, 'provideMigrationBuilder')) {
|
|
35
|
+
migrationBuilder = dataProvider.provideMigrationBuilder(code);
|
|
36
|
+
}
|
|
37
|
+
snapshot = await compareMigrationSnapshot({
|
|
38
|
+
entities: options.entities,
|
|
39
|
+
snapshot,
|
|
40
|
+
migrationBuilder,
|
|
41
|
+
});
|
|
42
|
+
if (steps.length)
|
|
43
|
+
await updateMigrationsFile(migrationsTSFilename, [
|
|
44
|
+
`async (${hasSql ? '{sql}' : ''})=>{\n${steps.join('\n')}\n}`,
|
|
45
|
+
]);
|
|
46
|
+
fs.writeFileSync(snapshotFileName, JSON.stringify(snapshot, null, 2));
|
|
47
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import { Entity, IdEntity, Fields, remult, Remult, } from '../index.js';
|
|
3
|
+
import { doTransaction } from '../src/context.js';
|
|
4
|
+
import { cast } from '../src/isOfType.js';
|
|
5
|
+
export async function migrate(options) {
|
|
6
|
+
let migrationTableName = options.migrationTableName || '__remult_migration_version';
|
|
7
|
+
const dataProvider = options.dataProvider
|
|
8
|
+
? await options.dataProvider
|
|
9
|
+
: remult.dataProvider;
|
|
10
|
+
const prev = remult.dataProvider;
|
|
11
|
+
remult.dataProvider = dataProvider;
|
|
12
|
+
try {
|
|
13
|
+
let VersionInfo = class VersionInfo extends IdEntity {
|
|
14
|
+
version = -1;
|
|
15
|
+
};
|
|
16
|
+
__decorate([
|
|
17
|
+
Fields.number(),
|
|
18
|
+
__metadata("design:type", Object)
|
|
19
|
+
], VersionInfo.prototype, "version", void 0);
|
|
20
|
+
VersionInfo = __decorate([
|
|
21
|
+
Entity(migrationTableName)
|
|
22
|
+
], VersionInfo);
|
|
23
|
+
const repo = new Remult(dataProvider).repo(VersionInfo);
|
|
24
|
+
if (dataProvider.ensureSchema) {
|
|
25
|
+
await dataProvider.ensureSchema([repo.metadata]);
|
|
26
|
+
}
|
|
27
|
+
let v = await repo.findFirst();
|
|
28
|
+
if (!v) {
|
|
29
|
+
v = repo.create();
|
|
30
|
+
v.version = -1;
|
|
31
|
+
}
|
|
32
|
+
const steps = Object.entries(options.migrations).sort(([a], [b]) => parseInt(a) - parseInt(b));
|
|
33
|
+
for (const [stepText, action] of steps) {
|
|
34
|
+
const step = parseInt(stepText);
|
|
35
|
+
if (step < 0)
|
|
36
|
+
throw new Error('Migration step number must be a non-negative integer');
|
|
37
|
+
if (v.version >= step)
|
|
38
|
+
continue;
|
|
39
|
+
console.info('Executing migration step', step);
|
|
40
|
+
await doTransaction(remult, async (dp) => {
|
|
41
|
+
const utils = {
|
|
42
|
+
sql: async (sql) => {
|
|
43
|
+
await cast(dp, 'execute').execute(sql);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
await action(utils);
|
|
47
|
+
});
|
|
48
|
+
v.version = step;
|
|
49
|
+
await v.save();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
remult.dataProvider = prev;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -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 = 'MigrationSteps';
|
|
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
|
+
}
|
|
@@ -35,6 +35,18 @@ export class PostgresDataProvider {
|
|
|
35
35
|
this.pool = new PostgresSchemaWrapper(pool, options.schema);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
provideMigrationBuilder(builder) {
|
|
39
|
+
var db = new SqlDatabase(this);
|
|
40
|
+
var sb = new PostgresSchemaBuilder(db, this.options?.schema);
|
|
41
|
+
return {
|
|
42
|
+
addColumn: async (meta, field) => {
|
|
43
|
+
builder.addSql(await sb.getAddColumnScript(meta, field));
|
|
44
|
+
},
|
|
45
|
+
createTable: async (meta) => {
|
|
46
|
+
builder.addSql(await sb.createTableScript(meta));
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
38
50
|
wrapIdentifier = (name) => name
|
|
39
51
|
.split('.')
|
|
40
52
|
.map((name) => name.startsWith('"') ? name : '"' + name.replace(/"/g, '""') + '"')
|
|
@@ -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,30 @@ export class KnexDataProvider {
|
|
|
13
13
|
constructor(knex) {
|
|
14
14
|
this.knex = knex;
|
|
15
15
|
}
|
|
16
|
+
provideMigrationBuilder(builder) {
|
|
17
|
+
var sb = new KnexSchemaBuilder(this.knex);
|
|
18
|
+
return {
|
|
19
|
+
async createTable(entity) {
|
|
20
|
+
let e = await dbNamesOf(entity, (x) => x);
|
|
21
|
+
sb.createTableKnexCommand(entity, e)
|
|
22
|
+
.toSQL()
|
|
23
|
+
.forEach((sql) => builder.addSql(sql.sql));
|
|
24
|
+
},
|
|
25
|
+
async addColumn(entity, field) {
|
|
26
|
+
let e = await dbNamesOf(entity, (x) => x);
|
|
27
|
+
await sb
|
|
28
|
+
.createColumnKnexCommand(e, field, e.$dbNameOf(field))
|
|
29
|
+
.toSQL()
|
|
30
|
+
.forEach((sql) => builder.addSql(sql.sql));
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
createCommand() {
|
|
35
|
+
return new KnexBridgeToSQLCommand(this.knex);
|
|
36
|
+
}
|
|
37
|
+
execute(sql) {
|
|
38
|
+
return this.createCommand().execute(sql);
|
|
39
|
+
}
|
|
16
40
|
static getDb(remult) {
|
|
17
41
|
const r = (remult || remultContext).dataProvider;
|
|
18
42
|
if (!r.knex)
|
|
@@ -350,11 +374,9 @@ export class KnexSchemaBuilder {
|
|
|
350
374
|
for (const entity of entities) {
|
|
351
375
|
let e = await dbNamesOf(entity, (x) => x);
|
|
352
376
|
try {
|
|
353
|
-
if (
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
await this.verifyAllColumns(entity);
|
|
357
|
-
}
|
|
377
|
+
if (shouldCreateEntity(entity, e)) {
|
|
378
|
+
await this.createIfNotExist(entity);
|
|
379
|
+
await this.verifyAllColumns(entity);
|
|
358
380
|
}
|
|
359
381
|
}
|
|
360
382
|
catch (err) {
|
|
@@ -366,27 +388,30 @@ export class KnexSchemaBuilder {
|
|
|
366
388
|
async createIfNotExist(entity) {
|
|
367
389
|
const e = await dbNamesOf(entity, (x) => x);
|
|
368
390
|
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
|
-
|
|
391
|
+
await logSql(this.createTableKnexCommand(entity, e));
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
createTableKnexCommand(entity, e) {
|
|
395
|
+
let cols = new Map();
|
|
396
|
+
for (const f of entity.fields) {
|
|
397
|
+
cols.set(f, {
|
|
398
|
+
name: e.$dbNameOf(f),
|
|
399
|
+
readonly: shouldNotCreateField(f, e),
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
return this.knex.schema.createTable(e.$entityName, (b) => {
|
|
403
|
+
for (const x of entity.fields) {
|
|
404
|
+
if (!cols.get(x).readonly || isAutoIncrement(x)) {
|
|
405
|
+
if (isAutoIncrement(x))
|
|
406
|
+
b.increments(cols.get(x).name);
|
|
407
|
+
else {
|
|
408
|
+
buildColumn(x, cols.get(x).name, b, supportsJsonDataStorage(this.knex));
|
|
409
|
+
if (x == entity.idMetadata.field)
|
|
410
|
+
b.primary([cols.get(x).name]);
|
|
386
411
|
}
|
|
387
412
|
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
413
|
+
}
|
|
414
|
+
});
|
|
390
415
|
}
|
|
391
416
|
async addColumnIfNotExist(entity, c) {
|
|
392
417
|
let e = await dbNamesOf(entity, (x) => x);
|
|
@@ -395,11 +420,14 @@ export class KnexSchemaBuilder {
|
|
|
395
420
|
let col = c(entity);
|
|
396
421
|
let colName = e.$dbNameOf(col);
|
|
397
422
|
if (!(await this.knex.schema.hasColumn(e.$entityName, colName))) {
|
|
398
|
-
await logSql(this.
|
|
399
|
-
buildColumn(col, colName, b, supportsJsonDataStorage(this.knex));
|
|
400
|
-
}));
|
|
423
|
+
await logSql(this.createColumnKnexCommand(e, col, colName));
|
|
401
424
|
}
|
|
402
425
|
}
|
|
426
|
+
createColumnKnexCommand(e, col, colName) {
|
|
427
|
+
return this.knex.schema.alterTable(e.$entityName, (b) => {
|
|
428
|
+
buildColumn(col, colName, b, supportsJsonDataStorage(this.knex));
|
|
429
|
+
});
|
|
430
|
+
}
|
|
403
431
|
async verifyAllColumns(entity) {
|
|
404
432
|
let e = await dbNamesOf(entity, (x) => x);
|
|
405
433
|
try {
|
|
@@ -502,3 +530,59 @@ function translateValueAndHandleArrayAndHandleArray(field, val) {
|
|
|
502
530
|
return JSON.stringify(result);
|
|
503
531
|
return result;
|
|
504
532
|
}
|
|
533
|
+
class KnexBridgeToSQLCommand {
|
|
534
|
+
source;
|
|
535
|
+
constructor(source) {
|
|
536
|
+
this.source = source;
|
|
537
|
+
}
|
|
538
|
+
values = [];
|
|
539
|
+
addParameterAndReturnSqlToken(val) {
|
|
540
|
+
return this.param(val);
|
|
541
|
+
}
|
|
542
|
+
param(val) {
|
|
543
|
+
if (Array.isArray(val))
|
|
544
|
+
val = JSON.stringify(val);
|
|
545
|
+
this.values.push(val);
|
|
546
|
+
return '?';
|
|
547
|
+
}
|
|
548
|
+
async execute(sql) {
|
|
549
|
+
return await this.source.raw(sql, this.values).then((r) => {
|
|
550
|
+
switch (this.source.client.config.client) {
|
|
551
|
+
case 'mysql':
|
|
552
|
+
case 'mysql2':
|
|
553
|
+
return new KnexPostgresBridgeToSQLQueryResult({
|
|
554
|
+
fields: r[1],
|
|
555
|
+
rows: r[0],
|
|
556
|
+
});
|
|
557
|
+
case 'pg':
|
|
558
|
+
return new KnexPostgresBridgeToSQLQueryResult(r);
|
|
559
|
+
default:
|
|
560
|
+
case 'better-sqlite3':
|
|
561
|
+
case 'mssql':
|
|
562
|
+
return new KnexPostgresBridgeToSQLQueryResult({
|
|
563
|
+
rows: r,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
class KnexPostgresBridgeToSQLQueryResult {
|
|
570
|
+
r;
|
|
571
|
+
getColumnKeyInResultForIndexInSelect(index) {
|
|
572
|
+
if (this.r.fields)
|
|
573
|
+
return this.r.fields[index].name;
|
|
574
|
+
if (this.rows.length == 0)
|
|
575
|
+
throw Error('No rows');
|
|
576
|
+
let i = 0;
|
|
577
|
+
for (let m in this.rows[0]) {
|
|
578
|
+
if (i++ == index)
|
|
579
|
+
return m;
|
|
580
|
+
}
|
|
581
|
+
throw Error('index not found');
|
|
582
|
+
}
|
|
583
|
+
constructor(r) {
|
|
584
|
+
this.r = r;
|
|
585
|
+
this.rows = r.rows;
|
|
586
|
+
}
|
|
587
|
+
rows;
|
|
588
|
+
}
|
package/esm/remult-sql-js.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SqlDatabase } from './src/data-providers/sql-database.js';
|
|
2
|
-
import { dbNamesOf,
|
|
2
|
+
import { dbNamesOf, shouldNotCreateField, } from './src/filter/filter-consumer-bridge-to-sql-request.js';
|
|
3
3
|
import { isAutoIncrement } from './src/remult3/RepositoryImplementation.js';
|
|
4
4
|
export class SqlJsDataProvider {
|
|
5
5
|
db;
|
|
@@ -52,7 +52,7 @@ export class SqlJsDataProvider {
|
|
|
52
52
|
let result = '';
|
|
53
53
|
let e = await dbNamesOf(entity);
|
|
54
54
|
for (const x of entity.fields) {
|
|
55
|
-
if (!
|
|
55
|
+
if (!shouldNotCreateField(x, e) || isAutoIncrement(x)) {
|
|
56
56
|
if (result.length != 0)
|
|
57
57
|
result += ',';
|
|
58
58
|
result += '\r\n ';
|
package/esm/src/context.js
CHANGED
|
@@ -233,14 +233,20 @@ export class EventSource {
|
|
|
233
233
|
export async function doTransaction(remult, what) {
|
|
234
234
|
const trans = new transactionLiveQueryPublisher(remult.liveQueryPublisher);
|
|
235
235
|
let ok = true;
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
remult.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
236
|
+
const prev = remult.dataProvider;
|
|
237
|
+
try {
|
|
238
|
+
await remult.dataProvider.transaction(async (ds) => {
|
|
239
|
+
remult.dataProvider = ds;
|
|
240
|
+
remult.liveQueryPublisher = trans;
|
|
241
|
+
await what(ds);
|
|
242
|
+
ok = true;
|
|
243
|
+
});
|
|
244
|
+
if (ok)
|
|
245
|
+
await trans.flush();
|
|
246
|
+
}
|
|
247
|
+
finally {
|
|
248
|
+
remult.dataProvider = prev;
|
|
249
|
+
}
|
|
244
250
|
}
|
|
245
251
|
class transactionLiveQueryPublisher {
|
|
246
252
|
orig;
|