remult 0.25.8-exp.1 → 0.25.8-exp.3
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/esm/graphql.js +2 -2
- package/esm/migrations/generate-migrations.js +12 -0
- package/esm/migrations/migrate.js +10 -0
- package/esm/postgres/postgres-data-provider.js +2 -2
- package/esm/remult-knex/index.js +2 -2
- package/esm/remult-mongo.js +2 -2
- package/esm/server/expressBridge.js +130 -29
- package/esm/src/data-api.js +41 -24
- package/esm/src/data-providers/rest-data-provider.js +17 -15
- package/esm/src/data-providers/sql-database.js +2 -2
- package/esm/src/filter/filter-interfaces.js +31 -0
- package/esm/src/remult3/RepositoryImplementation.js +2 -0
- package/esm/src/remult3/remult3.js +5 -27
- package/esm/src/server-action.js +2 -2
- package/esm/src/valueConverters.js +1 -1
- package/graphql.js +2 -4
- package/migrations/generate-migrations.d.ts +12 -0
- package/migrations/generate-migrations.js +12 -0
- package/migrations/migrate.d.ts +10 -0
- package/migrations/migrate.js +10 -0
- package/migrations/migration-types.d.ts +20 -0
- package/package.json +1 -1
- package/postgres/postgres-data-provider.d.ts +2 -2
- package/postgres/postgres-data-provider.js +2 -2
- package/remult-knex/index.d.ts +1 -1
- package/remult-knex/index.js +2 -2
- package/remult-mongo.d.ts +2 -2
- package/remult-mongo.js +2 -2
- package/server/expressBridge.d.ts +21 -0
- package/server/expressBridge.js +140 -32
- package/src/context.d.ts +2 -0
- package/src/data-api.d.ts +12 -8
- package/src/data-api.js +93 -71
- package/src/data-providers/rest-data-provider.js +26 -15
- package/src/data-providers/sql-database.d.ts +1 -2
- package/src/data-providers/sql-database.js +2 -2
- package/src/filter/filter-interfaces.js +64 -12
- package/src/remult3/RepositoryImplementation.js +2 -0
- package/src/remult3/remult3.js +5 -27
- package/src/server-action.js +2 -2
- package/src/valueConverters.js +1 -1
package/esm/graphql.js
CHANGED
|
@@ -904,7 +904,7 @@ export function translateWhereToRestBody(fields, { where }) {
|
|
|
904
904
|
}
|
|
905
905
|
}
|
|
906
906
|
if (where.OR) {
|
|
907
|
-
result.OR = where.OR.map((where) => translateWhereToRestBody(fields, { where }));
|
|
907
|
+
result.OR = where.OR.map((where) => translateWhereToRestBody(fields, { where }).where);
|
|
908
908
|
}
|
|
909
|
-
return result;
|
|
909
|
+
return { where: result };
|
|
910
910
|
}
|
|
@@ -5,6 +5,18 @@ import { updateMigrationsFile } from './update-migrations-ts-file.js';
|
|
|
5
5
|
import { DefaultMigrationBuilder, } from './migration-types.js';
|
|
6
6
|
import { isOfType } from '../src/isOfType.js';
|
|
7
7
|
import { initDataProvider } from '../server/initDataProvider.js';
|
|
8
|
+
/**
|
|
9
|
+
* Generates migration scripts based on changes in entities.
|
|
10
|
+
*
|
|
11
|
+
* @param options - Configuration options for generating migrations.
|
|
12
|
+
* @param options.entities - An array of entity classes to be included in the migration.
|
|
13
|
+
* @param options.dataProvider - The data provider instance or a function returning a promise of the data provider.
|
|
14
|
+
* @param options.migrationsFolder - (Optional) The path to the folder where migration scripts will be stored. Default is 'src/server/migrations'.
|
|
15
|
+
* @param options.snapshotFile - (Optional) The path to the file where the snapshot of the last known state will be stored. Default is 'migrations-snapshot.json' in the `migrationsFolder`.
|
|
16
|
+
* @param options.migrationsTSFile - (Optional) The path to the TypeScript file where the generated migrations will be written. Default is 'migrations.ts' in the `migrationsFolder`.
|
|
17
|
+
* @param options.endConnection - (Optional) Determines whether to close the database connection after generating migrations. Default is true.
|
|
18
|
+
* @see [Migrations](http://remult.dev/docs/migrations.html)
|
|
19
|
+
*/
|
|
8
20
|
export async function generateMigrations(options) {
|
|
9
21
|
const migrationDir = options.migrationsFolder ||
|
|
10
22
|
path.join(process.cwd(), 'src/server/migrations');
|
|
@@ -3,6 +3,16 @@ import { Entity, IdEntity, Fields, remult, Remult, } from '../index.js';
|
|
|
3
3
|
import { initDataProvider } from '../server/initDataProvider.js';
|
|
4
4
|
import { doTransaction } from '../src/context.js';
|
|
5
5
|
import { cast, isOfType } from '../src/isOfType.js';
|
|
6
|
+
/**
|
|
7
|
+
* Applies migration scripts to update the database schema.
|
|
8
|
+
*
|
|
9
|
+
* @param options - Configuration options for applying migrations.
|
|
10
|
+
* @param options.migrations - An object containing the migration scripts, each keyed by a unique identifier.
|
|
11
|
+
* @param options.dataProvider - The data provider instance or a function returning a promise of the data provider.
|
|
12
|
+
* @param options.migrationsTable - (Optional) The name of the table that tracks applied migrations. Default is '__remult_migrations_version'.
|
|
13
|
+
* @param options.endConnection - (Optional) Determines whether to close the database connection after applying migrations. Default is true.
|
|
14
|
+
* @see [Migrations](http://remult.dev/docs/migrations.html)
|
|
15
|
+
*/
|
|
6
16
|
export async function migrate(options) {
|
|
7
17
|
let migrationTableName = options.migrationsTable || '__remult_migrations_version';
|
|
8
18
|
const dataProvider = await initDataProvider(options.dataProvider);
|
|
@@ -8,8 +8,8 @@ export class PostgresDataProvider {
|
|
|
8
8
|
pool;
|
|
9
9
|
options;
|
|
10
10
|
supportsJsonColumnType = true;
|
|
11
|
-
static getDb(
|
|
12
|
-
const r = (
|
|
11
|
+
static getDb(dataProvider) {
|
|
12
|
+
const r = (dataProvider || defaultRemult.dataProvider);
|
|
13
13
|
if (!r._getSourceSql)
|
|
14
14
|
throw 'the data provider is not an SqlDatabase';
|
|
15
15
|
const me = r._getSourceSql();
|
package/esm/remult-knex/index.js
CHANGED
|
@@ -41,8 +41,8 @@ export class KnexDataProvider {
|
|
|
41
41
|
execute(sql) {
|
|
42
42
|
return this.createCommand().execute(sql);
|
|
43
43
|
}
|
|
44
|
-
static getDb(
|
|
45
|
-
const r = (
|
|
44
|
+
static getDb(dataProvider) {
|
|
45
|
+
const r = (dataProvider || remultContext.dataProvider);
|
|
46
46
|
if (!r.knex)
|
|
47
47
|
throw 'the data provider is not an KnexDataProvider';
|
|
48
48
|
return r.knex;
|
package/esm/remult-mongo.js
CHANGED
|
@@ -16,8 +16,8 @@ export class MongoDataProvider {
|
|
|
16
16
|
}
|
|
17
17
|
session;
|
|
18
18
|
disableTransactions = false;
|
|
19
|
-
static getDb(
|
|
20
|
-
const r = (
|
|
19
|
+
static getDb(dataProvider) {
|
|
20
|
+
const r = (dataProvider || remultContext.dataProvider);
|
|
21
21
|
if (!r.db)
|
|
22
22
|
throw 'the data provider is not a MongoDataProvider';
|
|
23
23
|
return { db: r.db, session: r.session };
|
|
@@ -13,6 +13,7 @@ import { serverActionField } from '../src/server-action-info.js';
|
|
|
13
13
|
import { initDataProvider } from './initDataProvider.js';
|
|
14
14
|
import { remultStatic } from '../src/remult-static.js';
|
|
15
15
|
import remultAdminHtml from './remult-admin.js';
|
|
16
|
+
import { isOfType } from '../src/isOfType.js';
|
|
16
17
|
export function createRemultServerCore(options, serverCoreOptions) {
|
|
17
18
|
if (!options) {
|
|
18
19
|
options = {};
|
|
@@ -249,8 +250,8 @@ export class RemultServerImplementation {
|
|
|
249
250
|
console.info('[remult] ' + myRoute);
|
|
250
251
|
r.route(myRoute)
|
|
251
252
|
.get(this.process((c, req, res, orig) => dataApiFactory(c).httpGet(res, req, () => this.serializeContext(c))))
|
|
252
|
-
.put(this.process(async (c, req, res, _, __, orig) => dataApiFactory(c).
|
|
253
|
-
.delete(this.process(async (c, req, res, orig) => dataApiFactory(c).
|
|
253
|
+
.put(this.process(async (c, req, res, _, __, orig) => dataApiFactory(c).updateMany(res, req, await this.coreOptions.getRequestBody(orig))))
|
|
254
|
+
.delete(this.process(async (c, req, res, _, __, orig) => dataApiFactory(c).deleteMany(res, req, undefined)))
|
|
254
255
|
.post(this.process(async (c, req, res, _, __, orig) => dataApiFactory(c).httpPost(res, req, await this.coreOptions.getRequestBody(orig), () => this.serializeContext(c))));
|
|
255
256
|
r.route(myRoute + '/:id')
|
|
256
257
|
//@ts-ignore
|
|
@@ -285,7 +286,7 @@ export class RemultServerImplementation {
|
|
|
285
286
|
genReq.params = req['_tempParams'];
|
|
286
287
|
}
|
|
287
288
|
let myReq = new ExpressRequestBridgeToDataApiRequest(genReq);
|
|
288
|
-
let myRes = new ExpressResponseBridgeToDataApiResponse(origRes, req);
|
|
289
|
+
let myRes = new ExpressResponseBridgeToDataApiResponse(origRes, req, this.options.error);
|
|
289
290
|
try {
|
|
290
291
|
await this.runWithRemult(async (remult) => {
|
|
291
292
|
if (req) {
|
|
@@ -315,7 +316,7 @@ export class RemultServerImplementation {
|
|
|
315
316
|
});
|
|
316
317
|
}
|
|
317
318
|
catch (err) {
|
|
318
|
-
myRes.error(
|
|
319
|
+
myRes.error(err, undefined);
|
|
319
320
|
}
|
|
320
321
|
};
|
|
321
322
|
}
|
|
@@ -466,6 +467,17 @@ export class RemultServerImplementation {
|
|
|
466
467
|
return secureBase(condition, def, item);
|
|
467
468
|
};
|
|
468
469
|
let apiPath = (spec.paths[this.options.rootPath + '/' + key] = {});
|
|
470
|
+
let itemInBody = {
|
|
471
|
+
requestBody: {
|
|
472
|
+
content: {
|
|
473
|
+
'application/json': {
|
|
474
|
+
schema: {
|
|
475
|
+
$ref: '#/components/schemas/' + mutationKey,
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
};
|
|
469
481
|
let apiPathWithId = (spec.paths[this.options.rootPath + '/' + key + '/{id}'] = {});
|
|
470
482
|
//https://github.com/2fd/open-api.d.ts
|
|
471
483
|
apiPath.get = secure(meta.options.allowApiRead, true, {
|
|
@@ -518,6 +530,73 @@ export class RemultServerImplementation {
|
|
|
518
530
|
},
|
|
519
531
|
},
|
|
520
532
|
});
|
|
533
|
+
apiPath.delete = secure(meta.options.allowApiDelete, true, {
|
|
534
|
+
description: 'deletes row of ' +
|
|
535
|
+
key +
|
|
536
|
+
'. supports filter operators. For more info on filtering [see this article](https://remult.dev/docs/rest-api.html#filter)',
|
|
537
|
+
parameters: [...parameters],
|
|
538
|
+
responses: {
|
|
539
|
+
'400': {
|
|
540
|
+
description: 'Error: Bad Request',
|
|
541
|
+
content: {
|
|
542
|
+
'application/json': {
|
|
543
|
+
schema: {
|
|
544
|
+
$ref: '#/components/schemas/InvalidResponse',
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
'200': {
|
|
550
|
+
description: 'returns the number of deleted rows ' + key,
|
|
551
|
+
content: {
|
|
552
|
+
'application/json': {
|
|
553
|
+
schema: {
|
|
554
|
+
type: 'object',
|
|
555
|
+
properties: {
|
|
556
|
+
deleted: {
|
|
557
|
+
type: 'number',
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
});
|
|
566
|
+
apiPath.put = secure(meta.options.allowApiDelete, true, {
|
|
567
|
+
description: 'updates row of ' +
|
|
568
|
+
key +
|
|
569
|
+
'. supports filter operators. For more info on filtering [see this article](https://remult.dev/docs/rest-api.html#filter)',
|
|
570
|
+
parameters: [...parameters],
|
|
571
|
+
...itemInBody,
|
|
572
|
+
responses: {
|
|
573
|
+
'400': {
|
|
574
|
+
description: 'Error: Bad Request',
|
|
575
|
+
content: {
|
|
576
|
+
'application/json': {
|
|
577
|
+
schema: {
|
|
578
|
+
$ref: '#/components/schemas/InvalidResponse',
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
'200': {
|
|
584
|
+
description: 'returns the number of updated rows ' + key,
|
|
585
|
+
content: {
|
|
586
|
+
'application/json': {
|
|
587
|
+
schema: {
|
|
588
|
+
type: 'object',
|
|
589
|
+
properties: {
|
|
590
|
+
updated: {
|
|
591
|
+
type: 'number',
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
},
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
},
|
|
599
|
+
});
|
|
521
600
|
let idParameter = {
|
|
522
601
|
name: 'id',
|
|
523
602
|
in: 'path',
|
|
@@ -525,17 +604,6 @@ export class RemultServerImplementation {
|
|
|
525
604
|
required: true,
|
|
526
605
|
schema: { type: 'string' },
|
|
527
606
|
};
|
|
528
|
-
let itemInBody = {
|
|
529
|
-
requestBody: {
|
|
530
|
-
content: {
|
|
531
|
-
'application/json': {
|
|
532
|
-
schema: {
|
|
533
|
-
$ref: '#/components/schemas/' + mutationKey,
|
|
534
|
-
},
|
|
535
|
-
},
|
|
536
|
-
},
|
|
537
|
-
},
|
|
538
|
-
};
|
|
539
607
|
apiPath.post = secure(meta.options.allowApiInsert, false, {
|
|
540
608
|
//"summary": "insert a " + key,
|
|
541
609
|
//"description": "insert a " + key,
|
|
@@ -673,15 +741,17 @@ class ExpressRequestBridgeToDataApiRequest {
|
|
|
673
741
|
class ExpressResponseBridgeToDataApiResponse {
|
|
674
742
|
r;
|
|
675
743
|
req;
|
|
744
|
+
handleError;
|
|
676
745
|
forbidden() {
|
|
677
|
-
this.
|
|
746
|
+
this.error({ message: 'Forbidden' }, undefined, 403);
|
|
678
747
|
}
|
|
679
748
|
setStatus(status) {
|
|
680
749
|
return this.r.status(status);
|
|
681
750
|
}
|
|
682
|
-
constructor(r, req) {
|
|
751
|
+
constructor(r, req, handleError) {
|
|
683
752
|
this.r = r;
|
|
684
753
|
this.req = req;
|
|
754
|
+
this.handleError = handleError;
|
|
685
755
|
}
|
|
686
756
|
progress(progress) { }
|
|
687
757
|
success(data) {
|
|
@@ -694,22 +764,53 @@ class ExpressResponseBridgeToDataApiResponse {
|
|
|
694
764
|
this.setStatus(204).end();
|
|
695
765
|
}
|
|
696
766
|
notFound() {
|
|
697
|
-
this.
|
|
767
|
+
this.error({ message: 'Forbidden' }, undefined, 404);
|
|
698
768
|
}
|
|
699
|
-
error(
|
|
700
|
-
data = serializeError(
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
769
|
+
async error(exception, entity, httpStatusCode) {
|
|
770
|
+
let data = serializeError(exception);
|
|
771
|
+
if (!httpStatusCode) {
|
|
772
|
+
if (data.httpStatusCode) {
|
|
773
|
+
httpStatusCode = data.httpStatusCode;
|
|
774
|
+
}
|
|
775
|
+
else if (isOfType(exception, 'isForbiddenError') &&
|
|
776
|
+
exception.isForbiddenError) {
|
|
777
|
+
httpStatusCode = 403;
|
|
778
|
+
}
|
|
779
|
+
else if (data.modelState) {
|
|
780
|
+
httpStatusCode = 400;
|
|
781
|
+
}
|
|
782
|
+
else {
|
|
783
|
+
httpStatusCode = 500;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
let responseSent = false;
|
|
787
|
+
const sendError = (httpStatusCode, body) => {
|
|
788
|
+
if (responseSent) {
|
|
789
|
+
throw Error('Error response already sent');
|
|
790
|
+
}
|
|
791
|
+
responseSent = true;
|
|
792
|
+
console.error({
|
|
793
|
+
message: body.message,
|
|
794
|
+
httpStatusCode,
|
|
795
|
+
stack: data.stack?.split('\n'),
|
|
796
|
+
url: this.req?.url,
|
|
797
|
+
method: this.req?.method,
|
|
798
|
+
});
|
|
799
|
+
this.setStatus(httpStatusCode).json(body);
|
|
800
|
+
};
|
|
801
|
+
await this.handleError?.({
|
|
802
|
+
httpStatusCode,
|
|
803
|
+
req: this.req,
|
|
804
|
+
entity,
|
|
805
|
+
exception,
|
|
806
|
+
responseBody: data,
|
|
807
|
+
sendError,
|
|
706
808
|
});
|
|
707
|
-
|
|
809
|
+
if (!responseSent) {
|
|
810
|
+
sendError(httpStatusCode, data);
|
|
811
|
+
}
|
|
708
812
|
}
|
|
709
813
|
}
|
|
710
|
-
function throwError() {
|
|
711
|
-
throw 'Invalid';
|
|
712
|
-
}
|
|
713
814
|
class inProcessQueueHandler {
|
|
714
815
|
storage;
|
|
715
816
|
constructor(storage) {
|
package/esm/src/data-api.js
CHANGED
|
@@ -33,7 +33,7 @@ export class DataApi {
|
|
|
33
33
|
case 'deleteMany':
|
|
34
34
|
return this.deleteMany(res, req, body);
|
|
35
35
|
case 'updateMany':
|
|
36
|
-
return this.
|
|
36
|
+
return this.updateManyImplementation(res, req, body);
|
|
37
37
|
case 'endLiveQuery':
|
|
38
38
|
await this.remult.liveQueryStorage.remove(body.id);
|
|
39
39
|
res.success('ok');
|
|
@@ -50,26 +50,28 @@ export class DataApi {
|
|
|
50
50
|
}
|
|
51
51
|
await this.doOnId(response, id, async (row) => response.success(this.repository.getEntityRef(row).toApiJson()));
|
|
52
52
|
}
|
|
53
|
-
async count(response, request,
|
|
53
|
+
async count(response, request, body) {
|
|
54
54
|
if (!this.repository.metadata.apiReadAllowed) {
|
|
55
55
|
response.forbidden();
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
58
|
try {
|
|
59
59
|
response.success({
|
|
60
|
-
count: +(await this.repository.count(await this.buildWhere(request,
|
|
60
|
+
count: +(await this.repository.count(await this.buildWhere(request, body))),
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
catch (err) {
|
|
64
|
-
response.error(err);
|
|
64
|
+
response.error(err, this.repository.metadata);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
async deleteMany(response, request,
|
|
67
|
+
async deleteMany(response, request, body) {
|
|
68
68
|
try {
|
|
69
|
+
let deleted = 0;
|
|
70
|
+
let where = await this.buildWhere(request, body);
|
|
71
|
+
Filter.throwErrorIfFilterIsEmpty(where, 'deleteMany');
|
|
69
72
|
return await doTransaction(this.remult, async () => {
|
|
70
|
-
let deleted = 0;
|
|
71
73
|
for await (const x of this.repository.query({
|
|
72
|
-
where
|
|
74
|
+
where,
|
|
73
75
|
include: this.includeNone(),
|
|
74
76
|
})) {
|
|
75
77
|
await this.actualDelete(x);
|
|
@@ -79,15 +81,15 @@ export class DataApi {
|
|
|
79
81
|
});
|
|
80
82
|
}
|
|
81
83
|
catch (err) {
|
|
82
|
-
response.error(err);
|
|
84
|
+
response.error(err, this.repository.metadata);
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
|
-
async getArrayImpl(response, request,
|
|
87
|
+
async getArrayImpl(response, request, body) {
|
|
86
88
|
let findOptions = {
|
|
87
89
|
load: () => [],
|
|
88
90
|
include: this.includeNone(),
|
|
89
91
|
};
|
|
90
|
-
findOptions.where = await this.buildWhere(request,
|
|
92
|
+
findOptions.where = await this.buildWhere(request, body);
|
|
91
93
|
if (request) {
|
|
92
94
|
let sort = request.get('_sort');
|
|
93
95
|
if (sort != undefined) {
|
|
@@ -146,29 +148,29 @@ export class DataApi {
|
|
|
146
148
|
}
|
|
147
149
|
return include;
|
|
148
150
|
}
|
|
149
|
-
async getArray(response, request,
|
|
151
|
+
async getArray(response, request, body) {
|
|
150
152
|
if (!this.repository.metadata.apiReadAllowed) {
|
|
151
153
|
response.forbidden();
|
|
152
154
|
return;
|
|
153
155
|
}
|
|
154
156
|
try {
|
|
155
|
-
const { r } = await this.getArrayImpl(response, request,
|
|
157
|
+
const { r } = await this.getArrayImpl(response, request, body);
|
|
156
158
|
response.success(r);
|
|
157
159
|
}
|
|
158
160
|
catch (err) {
|
|
159
161
|
if (err.isForbiddenError)
|
|
160
162
|
response.forbidden();
|
|
161
163
|
else
|
|
162
|
-
response.error(err);
|
|
164
|
+
response.error(err, this.repository.metadata);
|
|
163
165
|
}
|
|
164
166
|
}
|
|
165
|
-
async liveQuery(response, request,
|
|
167
|
+
async liveQuery(response, request, body, serializeContext, queryChannel) {
|
|
166
168
|
if (!this.repository.metadata.apiReadAllowed) {
|
|
167
169
|
response.forbidden();
|
|
168
170
|
return;
|
|
169
171
|
}
|
|
170
172
|
try {
|
|
171
|
-
const r = await this.getArrayImpl(response, request,
|
|
173
|
+
const r = await this.getArrayImpl(response, request, body);
|
|
172
174
|
const data = {
|
|
173
175
|
requestJson: await serializeContext(),
|
|
174
176
|
findOptionsJson: findOptionsToJson(r.findOptions, this.repository.metadata),
|
|
@@ -185,10 +187,10 @@ export class DataApi {
|
|
|
185
187
|
if (err.isForbiddenError)
|
|
186
188
|
response.forbidden();
|
|
187
189
|
else
|
|
188
|
-
response.error(err);
|
|
190
|
+
response.error(err, this.repository.metadata);
|
|
189
191
|
}
|
|
190
192
|
}
|
|
191
|
-
async buildWhere(request,
|
|
193
|
+
async buildWhere(request, body) {
|
|
192
194
|
var where = [];
|
|
193
195
|
if (this.repository.metadata.options.apiPrefilter) {
|
|
194
196
|
if (typeof this.repository.metadata.options.apiPrefilter === 'function')
|
|
@@ -208,8 +210,8 @@ export class DataApi {
|
|
|
208
210
|
},
|
|
209
211
|
}));
|
|
210
212
|
}
|
|
211
|
-
if (
|
|
212
|
-
where.push(Filter.entityFilterFromJson(this.repository.metadata,
|
|
213
|
+
if (body?.where)
|
|
214
|
+
where.push(Filter.entityFilterFromJson(this.repository.metadata, body.where));
|
|
213
215
|
return { $and: where };
|
|
214
216
|
}
|
|
215
217
|
async doOnId(response, id, what) {
|
|
@@ -232,21 +234,36 @@ export class DataApi {
|
|
|
232
234
|
if (r.length == 0)
|
|
233
235
|
response.notFound();
|
|
234
236
|
else if (r.length > 1)
|
|
235
|
-
response.error({
|
|
237
|
+
response.error({
|
|
238
|
+
message: `id "${id}" is not unique for entity ` +
|
|
239
|
+
this.repository.metadata.key,
|
|
240
|
+
}, this.repository.metadata, 500);
|
|
236
241
|
else
|
|
237
242
|
await what(r[0]);
|
|
238
243
|
});
|
|
239
244
|
}
|
|
240
245
|
catch (err) {
|
|
241
|
-
response.error(err);
|
|
246
|
+
response.error(err, this.repository.metadata);
|
|
242
247
|
}
|
|
243
248
|
}
|
|
244
249
|
async updateMany(response, request, body) {
|
|
250
|
+
const action = request?.get('__action');
|
|
251
|
+
if (action == 'emptyId') {
|
|
252
|
+
return this.put(response, '', body);
|
|
253
|
+
}
|
|
254
|
+
let where = await this.buildWhere(request, body);
|
|
255
|
+
Filter.throwErrorIfFilterIsEmpty(where, 'updateMany');
|
|
256
|
+
return this.updateManyImplementation(response, request, {
|
|
257
|
+
where: undefined,
|
|
258
|
+
set: body,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
async updateManyImplementation(response, request, body) {
|
|
245
262
|
try {
|
|
246
263
|
return await doTransaction(this.remult, async () => {
|
|
247
264
|
let updated = 0;
|
|
248
265
|
for await (const x of this.repository.query({
|
|
249
|
-
where: await this.buildWhere(request, body
|
|
266
|
+
where: await this.buildWhere(request, body),
|
|
250
267
|
include: this.includeNone(),
|
|
251
268
|
})) {
|
|
252
269
|
await this.actualUpdate(x, body.set);
|
|
@@ -256,7 +273,7 @@ export class DataApi {
|
|
|
256
273
|
});
|
|
257
274
|
}
|
|
258
275
|
catch (err) {
|
|
259
|
-
response.error(err);
|
|
276
|
+
response.error(err, this.repository.metadata);
|
|
260
277
|
}
|
|
261
278
|
}
|
|
262
279
|
async actualUpdate(row, body) {
|
|
@@ -313,7 +330,7 @@ export class DataApi {
|
|
|
313
330
|
if (err.isForbiddenError)
|
|
314
331
|
response.forbidden();
|
|
315
332
|
else
|
|
316
|
-
response.error(err);
|
|
333
|
+
response.error(err, this.repository.metadata);
|
|
317
334
|
}
|
|
318
335
|
}
|
|
319
336
|
}
|
|
@@ -120,26 +120,27 @@ export class RestEntityDataProvider {
|
|
|
120
120
|
return run('count').then((r) => +r.count);
|
|
121
121
|
}
|
|
122
122
|
async deleteMany(where) {
|
|
123
|
-
const { run } = this.buildFindRequest({ where },
|
|
123
|
+
const { run } = this.buildFindRequest({ where }, 'delete');
|
|
124
124
|
return run('deleteMany').then((r) => +r.deleted);
|
|
125
125
|
}
|
|
126
126
|
async updateMany(where, data) {
|
|
127
|
-
const { run } = this.buildFindRequest({ where },
|
|
128
|
-
return run('updateMany',
|
|
127
|
+
const { run } = this.buildFindRequest({ where }, 'put');
|
|
128
|
+
return run('updateMany', this.toJsonOfIncludedKeys(data)).then((r) => +r.updated);
|
|
129
129
|
}
|
|
130
130
|
find(options) {
|
|
131
131
|
let { run } = this.buildFindRequest(options);
|
|
132
132
|
return run().then((x) => x.map((y) => this.translateFromJson(y)));
|
|
133
133
|
}
|
|
134
134
|
//@internal
|
|
135
|
-
buildFindRequest(options,
|
|
135
|
+
buildFindRequest(options, method) {
|
|
136
|
+
if (!method)
|
|
137
|
+
method = 'get';
|
|
136
138
|
let url = new UrlBuilder(this.url());
|
|
137
139
|
let filterObject;
|
|
138
140
|
if (options) {
|
|
139
141
|
if (options.where) {
|
|
140
142
|
filterObject = options.where.toJson(); // options.where.__applyToConsumer(new FilterConsumnerBridgeToUrlBuilder(url));
|
|
141
|
-
if (
|
|
142
|
-
addFilterToUrlAndReturnTrueIfSuccessful(filterObject, url))
|
|
143
|
+
if (addFilterToUrlAndReturnTrueIfSuccessful(filterObject, url))
|
|
143
144
|
filterObject = undefined;
|
|
144
145
|
}
|
|
145
146
|
if (options.orderBy && options.orderBy.Segments) {
|
|
@@ -173,15 +174,12 @@ export class RestEntityDataProvider {
|
|
|
173
174
|
}
|
|
174
175
|
if (action)
|
|
175
176
|
u.add('__action', action);
|
|
176
|
-
if (filterObject
|
|
177
|
-
|
|
178
|
-
body = filterObject;
|
|
179
|
-
else
|
|
180
|
-
body = { ...body, where: filterObject };
|
|
177
|
+
if (filterObject) {
|
|
178
|
+
body = { set: body, where: filterObject };
|
|
181
179
|
return this.http().post(u.url, body);
|
|
182
180
|
}
|
|
183
181
|
else
|
|
184
|
-
return this.http()
|
|
182
|
+
return this.http()[method](u.url, body);
|
|
185
183
|
};
|
|
186
184
|
return {
|
|
187
185
|
createKey: () => JSON.stringify({ url, filterObject }),
|
|
@@ -201,7 +199,8 @@ export class RestEntityDataProvider {
|
|
|
201
199
|
}
|
|
202
200
|
update(id, data) {
|
|
203
201
|
return this.http()
|
|
204
|
-
.put(this.url() +
|
|
202
|
+
.put(this.url() +
|
|
203
|
+
(id != '' ? '/' + encodeURIComponent(id) : '?__action=emptyId'), this.toJsonOfIncludedKeys(data))
|
|
205
204
|
.then((y) => this.translateFromJson(y));
|
|
206
205
|
}
|
|
207
206
|
toJsonOfIncludedKeys(data) {
|
|
@@ -213,8 +212,11 @@ export class RestEntityDataProvider {
|
|
|
213
212
|
}
|
|
214
213
|
return result;
|
|
215
214
|
}
|
|
216
|
-
delete(id) {
|
|
217
|
-
|
|
215
|
+
async delete(id) {
|
|
216
|
+
if (id == '')
|
|
217
|
+
await this.deleteMany(Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(id)));
|
|
218
|
+
else
|
|
219
|
+
return this.http().delete(this.url() + '/' + encodeURIComponent(id));
|
|
218
220
|
}
|
|
219
221
|
insert(data) {
|
|
220
222
|
return this.http()
|
|
@@ -10,8 +10,8 @@ import { isOfType } from '../isOfType.js';
|
|
|
10
10
|
// @dynamic
|
|
11
11
|
export class SqlDatabase {
|
|
12
12
|
sql;
|
|
13
|
-
static getDb(
|
|
14
|
-
const r = (
|
|
13
|
+
static getDb(dataProvider) {
|
|
14
|
+
const r = (dataProvider || defaultRemult.dataProvider);
|
|
15
15
|
if (isOfType(r, 'createCommand'))
|
|
16
16
|
return r;
|
|
17
17
|
else
|
|
@@ -6,6 +6,37 @@ import { getRelationFieldInfo } from '../remult3/relationInfoMember.js';
|
|
|
6
6
|
*/
|
|
7
7
|
export class Filter {
|
|
8
8
|
apply;
|
|
9
|
+
//@internal
|
|
10
|
+
static throwErrorIfFilterIsEmpty(where, methodName) {
|
|
11
|
+
if (Filter.isFilterEmpty(where)) {
|
|
12
|
+
throw {
|
|
13
|
+
message: `${methodName}: requires a filter to protect against accidental delete/update of all rows`,
|
|
14
|
+
httpStatusCode: 400,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//@internal
|
|
19
|
+
static isFilterEmpty(where) {
|
|
20
|
+
if (where.$and) {
|
|
21
|
+
for (const a of where.$and) {
|
|
22
|
+
if (!Filter.isFilterEmpty(a)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (where.$or) {
|
|
28
|
+
for (const a of where.$or) {
|
|
29
|
+
if (Filter.isFilterEmpty(a)) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
if (Object.keys(where).filter((x) => !['$or', '$and'].includes(x)).length == 0) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
9
40
|
static createCustom(rawFilterTranslator, key = '') {
|
|
10
41
|
let rawFilterInfo = { key: key, rawFilterTranslator };
|
|
11
42
|
return Object.assign((x) => {
|
|
@@ -270,6 +270,7 @@ export class RepositoryImplementation {
|
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
async updateMany(where, set) {
|
|
273
|
+
Filter.throwErrorIfFilterIsEmpty(where, 'updateMany');
|
|
273
274
|
if (this._dataProvider.isProxy) {
|
|
274
275
|
return this._edp.updateMany(await this._translateWhereToFilter(where), set);
|
|
275
276
|
}
|
|
@@ -636,6 +637,7 @@ export class RepositoryImplementation {
|
|
|
636
637
|
return this._edp.count(await this._translateWhereToFilter(where));
|
|
637
638
|
}
|
|
638
639
|
async deleteMany(where) {
|
|
640
|
+
Filter.throwErrorIfFilterIsEmpty(where, 'deleteMany');
|
|
639
641
|
if (this._dataProvider.isProxy) {
|
|
640
642
|
return this._edp.deleteMany(await this._translateWhereToFilter(where));
|
|
641
643
|
}
|