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.
Files changed (50) hide show
  1. package/README.md +6 -12
  2. package/esm/migrations/compare-migration-snapshots.js +39 -0
  3. package/esm/migrations/generate-migrations.js +51 -0
  4. package/esm/migrations/index.js +2 -0
  5. package/esm/migrations/migrate.js +66 -0
  6. package/esm/migrations/migration-types.js +12 -0
  7. package/esm/migrations/update-migrations-ts-file.js +112 -0
  8. package/esm/postgres/postgres-data-provider.js +23 -2
  9. package/esm/postgres/schema-builder.js +35 -26
  10. package/esm/remult-knex/index.js +116 -28
  11. package/esm/remult-sql-js.js +7 -2
  12. package/esm/src/context.js +14 -8
  13. package/esm/src/data-providers/sql-database.js +14 -3
  14. package/esm/src/data-providers/web-sql-data-provider.js +1 -9
  15. package/esm/src/filter/filter-consumer-bridge-to-sql-request.js +4 -0
  16. package/esm/src/isOfType.js +9 -0
  17. package/esm/src/remult3/remult3.js +24 -11
  18. package/migrations/compare-migration-snapshots.d.ts +26 -0
  19. package/migrations/compare-migration-snapshots.js +101 -0
  20. package/migrations/generate-migrations.d.ts +9 -0
  21. package/migrations/generate-migrations.js +88 -0
  22. package/migrations/index.d.ts +3 -0
  23. package/migrations/index.js +7 -0
  24. package/migrations/migrate.d.ts +8 -0
  25. package/migrations/migrate.js +159 -0
  26. package/migrations/migration-types.d.ts +23 -0
  27. package/migrations/migration-types.js +27 -0
  28. package/migrations/update-migrations-ts-file.d.ts +1 -0
  29. package/migrations/update-migrations-ts-file.js +135 -0
  30. package/package.json +6 -1
  31. package/postgres/postgres-data-provider.d.ts +6 -1
  32. package/postgres/postgres-data-provider.js +45 -2
  33. package/postgres/schema-builder.js +81 -48
  34. package/remult-knex/index.d.ts +10 -3
  35. package/remult-knex/index.js +174 -56
  36. package/remult-sql-js.d.ts +2 -0
  37. package/remult-sql-js.js +15 -1
  38. package/server/expressBridge.d.ts +9 -1
  39. package/src/context.d.ts +1 -1
  40. package/src/context.js +16 -8
  41. package/src/data-providers/sql-database.d.ts +4 -2
  42. package/src/data-providers/sql-database.js +11 -3
  43. package/src/data-providers/web-sql-data-provider.d.ts +1 -2
  44. package/src/data-providers/web-sql-data-provider.js +4 -8
  45. package/src/filter/filter-consumer-bridge-to-sql-request.d.ts +1 -0
  46. package/src/filter/filter-consumer-bridge-to-sql-request.js +6 -1
  47. package/src/isOfType.d.ts +2 -0
  48. package/src/isOfType.js +14 -0
  49. package/src/remult3/remult3.js +24 -11
  50. 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/online-members/GXHk7ZfuG5?icon=discord&label=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,2 @@
1
+ export { generateMigrations } from './generate-migrations.js';
2
+ export { migrate } from './migrate.js';
@@ -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 sql = SqlDatabase.getDb(remult);
12
- const me = sql._getSourceSql();
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 += ' timestamp';
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 (!entity.options.sqlExpression) {
113
- if (e.$entityName.toLowerCase().indexOf('from ') < 0) {
114
- await this.createIfNotExist(entity);
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
- let result = '';
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 = `ALTER table ${this.schemaAndName(e)} ` +
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);
@@ -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 (!entity.options.sqlExpression) {
354
- if (e.$entityName.toLowerCase().indexOf('from ') < 0) {
355
- await this.createIfNotExist(entity);
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
- let cols = new Map();
370
- for (const f of entity.fields) {
371
- cols.set(f, {
372
- name: e.$dbNameOf(f),
373
- readonly: shouldNotCreateField(f, e),
374
- });
375
- }
376
- await logSql(this.knex.schema.createTable(e.$entityName, (b) => {
377
- for (const x of entity.fields) {
378
- if (!cols.get(x).readonly || isAutoIncrement(x)) {
379
- if (isAutoIncrement(x))
380
- b.increments(cols.get(x).name);
381
- else {
382
- buildColumn(x, cols.get(x).name, b, supportsJsonDataStorage(this.knex));
383
- if (x == entity.idMetadata.field)
384
- b.primary([cols.get(x).name]);
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.knex.schema.alterTable(e.$entityName, (b) => {
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
+ }