remult 0.25.8 → 0.26.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 +194 -129
- package/esm/graphql.js +131 -59
- package/esm/index.js +1 -1
- package/esm/migrations/compare-migration-snapshots.js +61 -0
- package/esm/migrations/generate-migrations.js +68 -0
- package/esm/migrations/index.js +2 -0
- package/esm/migrations/migrate.js +90 -0
- package/esm/migrations/migration-types.js +36 -0
- package/esm/migrations/update-migrations-ts-file.js +115 -0
- package/esm/postgres/postgres-data-provider.js +24 -3
- package/esm/postgres/schema-builder.js +35 -26
- package/esm/remult-better-sqlite3.js +47 -0
- package/esm/remult-bun-sqlite.js +41 -0
- package/esm/remult-knex/index.js +180 -51
- package/esm/remult-mongo.js +2 -2
- package/esm/remult-sql-js.js +3 -78
- package/esm/remult-sqlite-core.js +104 -0
- package/esm/server/expressBridge.js +130 -29
- package/esm/server/initAsyncHooks.js +75 -1
- package/esm/src/CompoundIdField.js +8 -2
- package/esm/src/context.js +20 -24
- package/esm/src/data-api.js +71 -30
- package/esm/src/data-providers/rest-data-provider.js +17 -15
- package/esm/src/data-providers/sql-database.js +32 -7
- package/esm/src/data-providers/web-sql-data-provider.js +1 -9
- package/esm/src/filter/filter-consumer-bridge-to-sql-request.js +37 -11
- package/esm/src/filter/filter-interfaces.js +229 -17
- package/esm/src/isOfType.js +9 -0
- package/esm/src/remult3/Fields.js +70 -18
- package/esm/src/remult3/RepositoryImplementation.js +47 -21
- package/esm/src/remult3/addValidator.js +15 -0
- package/esm/src/remult3/remult3.js +28 -57
- package/esm/src/server-action.js +4 -4
- package/esm/src/validators.js +6 -3
- package/esm/src/valueConverters.js +1 -1
- package/graphql.js +156 -71
- package/index.d.ts +4 -4
- package/index.js +2 -1
- package/migrations/compare-migration-snapshots.d.ts +27 -0
- package/migrations/compare-migration-snapshots.js +165 -0
- package/migrations/generate-migrations.d.ts +21 -0
- package/migrations/generate-migrations.js +104 -0
- package/migrations/index.d.ts +3 -0
- package/migrations/index.js +7 -0
- package/migrations/migrate.d.ts +27 -0
- package/migrations/migrate.js +175 -0
- package/migrations/migration-types.d.ts +48 -0
- package/migrations/migration-types.js +88 -0
- package/migrations/update-migrations-ts-file.d.ts +1 -0
- package/migrations/update-migrations-ts-file.js +141 -0
- package/package.json +21 -1
- package/postgres/postgres-data-provider.d.ts +8 -3
- package/postgres/postgres-data-provider.js +46 -3
- package/postgres/schema-builder.js +81 -48
- package/remult-better-sqlite3.d.ts +12 -0
- package/remult-better-sqlite3.js +64 -0
- package/remult-bun-sqlite.d.ts +11 -0
- package/remult-bun-sqlite.js +56 -0
- package/remult-knex/index.d.ts +12 -5
- package/remult-knex/index.js +257 -90
- package/remult-mongo.d.ts +2 -2
- package/remult-mongo.js +2 -2
- package/remult-sql-js.d.ts +2 -15
- package/remult-sql-js.js +11 -152
- package/remult-sqlite-core.d.ts +21 -0
- package/remult-sqlite-core.js +217 -0
- package/server/expressBridge.d.ts +30 -1
- package/server/expressBridge.js +140 -32
- package/server/initAsyncHooks.d.ts +16 -0
- package/server/initAsyncHooks.js +128 -2
- package/src/CompoundIdField.js +8 -2
- package/src/column-interfaces.d.ts +9 -3
- package/src/context.d.ts +8 -6
- package/src/context.js +27 -36
- package/src/data-api.d.ts +13 -9
- package/src/data-api.js +184 -135
- package/src/data-providers/rest-data-provider.js +26 -15
- package/src/data-providers/sql-database.d.ts +17 -5
- package/src/data-providers/sql-database.js +31 -7
- 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/entity.d.ts +64 -6
- package/src/filter/filter-consumer-bridge-to-sql-request.d.ts +31 -7
- package/src/filter/filter-consumer-bridge-to-sql-request.js +46 -21
- package/src/filter/filter-interfaces.d.ts +106 -39
- package/src/filter/filter-interfaces.js +312 -32
- package/src/isOfType.d.ts +2 -0
- package/src/isOfType.js +14 -0
- package/src/remult3/Fields.d.ts +36 -0
- package/src/remult3/Fields.js +81 -19
- package/src/remult3/RepositoryImplementation.d.ts +7 -3
- package/src/remult3/RepositoryImplementation.js +106 -71
- package/src/remult3/addValidator.d.ts +2 -0
- package/src/remult3/addValidator.js +20 -0
- package/src/remult3/remult3.d.ts +6 -2
- package/src/remult3/remult3.js +28 -57
- package/src/server-action.d.ts +1 -1
- package/src/server-action.js +5 -4
- package/src/sql-command.d.ts +6 -1
- package/src/validators.d.ts +1 -0
- package/src/validators.js +10 -4
- package/src/valueConverters.js +1 -1
package/esm/src/data-api.js
CHANGED
|
@@ -23,17 +23,31 @@ export class DataApi {
|
|
|
23
23
|
}
|
|
24
24
|
async httpPost(res, req, body, serializeContext) {
|
|
25
25
|
const action = req?.get('__action');
|
|
26
|
-
|
|
26
|
+
function validateWhereInBody() {
|
|
27
|
+
if (!body?.where) {
|
|
28
|
+
throw {
|
|
29
|
+
message: `POST with action ${action} must have a where clause in the body`,
|
|
30
|
+
httpStatusCode: 400,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (action?.startsWith(liveQueryAction)) {
|
|
35
|
+
validateWhereInBody();
|
|
27
36
|
return this.liveQuery(res, req, body, serializeContext, action.substring(liveQueryAction.length));
|
|
37
|
+
}
|
|
28
38
|
switch (action) {
|
|
29
39
|
case 'get':
|
|
40
|
+
validateWhereInBody();
|
|
30
41
|
return this.getArray(res, req, body);
|
|
31
42
|
case 'count':
|
|
43
|
+
validateWhereInBody();
|
|
32
44
|
return this.count(res, req, body);
|
|
33
45
|
case 'deleteMany':
|
|
46
|
+
validateWhereInBody();
|
|
34
47
|
return this.deleteMany(res, req, body);
|
|
35
48
|
case 'updateMany':
|
|
36
|
-
|
|
49
|
+
validateWhereInBody();
|
|
50
|
+
return this.updateManyImplementation(res, req, body);
|
|
37
51
|
case 'endLiveQuery':
|
|
38
52
|
await this.remult.liveQueryStorage.remove(body.id);
|
|
39
53
|
res.success('ok');
|
|
@@ -50,26 +64,28 @@ export class DataApi {
|
|
|
50
64
|
}
|
|
51
65
|
await this.doOnId(response, id, async (row) => response.success(this.repository.getEntityRef(row).toApiJson()));
|
|
52
66
|
}
|
|
53
|
-
async count(response, request,
|
|
67
|
+
async count(response, request, body) {
|
|
54
68
|
if (!this.repository.metadata.apiReadAllowed) {
|
|
55
69
|
response.forbidden();
|
|
56
70
|
return;
|
|
57
71
|
}
|
|
58
72
|
try {
|
|
59
73
|
response.success({
|
|
60
|
-
count: +(await this.repository.count(await this.buildWhere(request,
|
|
74
|
+
count: +(await this.repository.count(await this.buildWhere(request, body))),
|
|
61
75
|
});
|
|
62
76
|
}
|
|
63
77
|
catch (err) {
|
|
64
|
-
response.error(err);
|
|
78
|
+
response.error(err, this.repository.metadata);
|
|
65
79
|
}
|
|
66
80
|
}
|
|
67
|
-
async deleteMany(response, request,
|
|
81
|
+
async deleteMany(response, request, body) {
|
|
68
82
|
try {
|
|
83
|
+
let deleted = 0;
|
|
84
|
+
let where = await this.buildWhere(request, body);
|
|
85
|
+
Filter.throwErrorIfFilterIsEmpty(where, 'deleteMany');
|
|
69
86
|
return await doTransaction(this.remult, async () => {
|
|
70
|
-
let deleted = 0;
|
|
71
87
|
for await (const x of this.repository.query({
|
|
72
|
-
where
|
|
88
|
+
where,
|
|
73
89
|
include: this.includeNone(),
|
|
74
90
|
})) {
|
|
75
91
|
await this.actualDelete(x);
|
|
@@ -79,15 +95,15 @@ export class DataApi {
|
|
|
79
95
|
});
|
|
80
96
|
}
|
|
81
97
|
catch (err) {
|
|
82
|
-
response.error(err);
|
|
98
|
+
response.error(err, this.repository.metadata);
|
|
83
99
|
}
|
|
84
100
|
}
|
|
85
|
-
async getArrayImpl(response, request,
|
|
101
|
+
async getArrayImpl(response, request, body) {
|
|
86
102
|
let findOptions = {
|
|
87
103
|
load: () => [],
|
|
88
104
|
include: this.includeNone(),
|
|
89
105
|
};
|
|
90
|
-
findOptions.where = await this.buildWhere(request,
|
|
106
|
+
findOptions.where = await this.buildWhere(request, body);
|
|
91
107
|
if (request) {
|
|
92
108
|
let sort = request.get('_sort');
|
|
93
109
|
if (sort != undefined) {
|
|
@@ -146,29 +162,29 @@ export class DataApi {
|
|
|
146
162
|
}
|
|
147
163
|
return include;
|
|
148
164
|
}
|
|
149
|
-
async getArray(response, request,
|
|
165
|
+
async getArray(response, request, body) {
|
|
150
166
|
if (!this.repository.metadata.apiReadAllowed) {
|
|
151
167
|
response.forbidden();
|
|
152
168
|
return;
|
|
153
169
|
}
|
|
154
170
|
try {
|
|
155
|
-
const { r } = await this.getArrayImpl(response, request,
|
|
171
|
+
const { r } = await this.getArrayImpl(response, request, body);
|
|
156
172
|
response.success(r);
|
|
157
173
|
}
|
|
158
174
|
catch (err) {
|
|
159
175
|
if (err.isForbiddenError)
|
|
160
176
|
response.forbidden();
|
|
161
177
|
else
|
|
162
|
-
response.error(err);
|
|
178
|
+
response.error(err, this.repository.metadata);
|
|
163
179
|
}
|
|
164
180
|
}
|
|
165
|
-
async liveQuery(response, request,
|
|
181
|
+
async liveQuery(response, request, body, serializeContext, queryChannel) {
|
|
166
182
|
if (!this.repository.metadata.apiReadAllowed) {
|
|
167
183
|
response.forbidden();
|
|
168
184
|
return;
|
|
169
185
|
}
|
|
170
186
|
try {
|
|
171
|
-
const r = await this.getArrayImpl(response, request,
|
|
187
|
+
const r = await this.getArrayImpl(response, request, body);
|
|
172
188
|
const data = {
|
|
173
189
|
requestJson: await serializeContext(),
|
|
174
190
|
findOptionsJson: findOptionsToJson(r.findOptions, this.repository.metadata),
|
|
@@ -185,10 +201,10 @@ export class DataApi {
|
|
|
185
201
|
if (err.isForbiddenError)
|
|
186
202
|
response.forbidden();
|
|
187
203
|
else
|
|
188
|
-
response.error(err);
|
|
204
|
+
response.error(err, this.repository.metadata);
|
|
189
205
|
}
|
|
190
206
|
}
|
|
191
|
-
async buildWhere(request,
|
|
207
|
+
async buildWhere(request, body) {
|
|
192
208
|
var where = [];
|
|
193
209
|
if (this.repository.metadata.options.apiPrefilter) {
|
|
194
210
|
if (typeof this.repository.metadata.options.apiPrefilter === 'function')
|
|
@@ -197,19 +213,29 @@ export class DataApi {
|
|
|
197
213
|
where.push(this.repository.metadata.options.apiPrefilter);
|
|
198
214
|
}
|
|
199
215
|
if (request) {
|
|
200
|
-
|
|
216
|
+
let f = buildFilterFromRequestParameters(this.repository.metadata, {
|
|
201
217
|
get: (key) => {
|
|
202
|
-
let result =
|
|
218
|
+
let result = body?.where?.[key];
|
|
219
|
+
if (result !== undefined)
|
|
220
|
+
return result;
|
|
221
|
+
result = request.get(key);
|
|
203
222
|
if (key.startsWith(customUrlToken) &&
|
|
204
223
|
result &&
|
|
205
224
|
typeof result === 'string')
|
|
206
225
|
return JSON.parse(result);
|
|
207
226
|
return result;
|
|
208
227
|
},
|
|
209
|
-
})
|
|
228
|
+
});
|
|
229
|
+
if (this.repository.metadata.options.apiPreprocessFilter) {
|
|
230
|
+
f = await this.repository.metadata.options.apiPreprocessFilter(f, {
|
|
231
|
+
metadata: this.repository.metadata,
|
|
232
|
+
getFilterPreciseValues: async (filter) => {
|
|
233
|
+
return Filter.getPreciseValues(this.repository.metadata, filter || f);
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
where.push(f);
|
|
210
238
|
}
|
|
211
|
-
if (filterBody)
|
|
212
|
-
where.push(Filter.entityFilterFromJson(this.repository.metadata, filterBody));
|
|
213
239
|
return { $and: where };
|
|
214
240
|
}
|
|
215
241
|
async doOnId(response, id, what) {
|
|
@@ -232,21 +258,36 @@ export class DataApi {
|
|
|
232
258
|
if (r.length == 0)
|
|
233
259
|
response.notFound();
|
|
234
260
|
else if (r.length > 1)
|
|
235
|
-
response.error({
|
|
261
|
+
response.error({
|
|
262
|
+
message: `id "${id}" is not unique for entity ` +
|
|
263
|
+
this.repository.metadata.key,
|
|
264
|
+
}, this.repository.metadata, 500);
|
|
236
265
|
else
|
|
237
266
|
await what(r[0]);
|
|
238
267
|
});
|
|
239
268
|
}
|
|
240
269
|
catch (err) {
|
|
241
|
-
response.error(err);
|
|
270
|
+
response.error(err, this.repository.metadata);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async updateManyThroughPutRequest(response, request, body) {
|
|
274
|
+
const action = request?.get('__action');
|
|
275
|
+
if (action == 'emptyId') {
|
|
276
|
+
return this.put(response, '', body);
|
|
242
277
|
}
|
|
278
|
+
return this.updateManyImplementation(response, request, {
|
|
279
|
+
where: undefined,
|
|
280
|
+
set: body,
|
|
281
|
+
});
|
|
243
282
|
}
|
|
244
|
-
async
|
|
283
|
+
async updateManyImplementation(response, request, body) {
|
|
245
284
|
try {
|
|
285
|
+
let where = await this.buildWhere(request, body);
|
|
286
|
+
Filter.throwErrorIfFilterIsEmpty(where, 'deleteMany');
|
|
246
287
|
return await doTransaction(this.remult, async () => {
|
|
247
288
|
let updated = 0;
|
|
248
289
|
for await (const x of this.repository.query({
|
|
249
|
-
where
|
|
290
|
+
where,
|
|
250
291
|
include: this.includeNone(),
|
|
251
292
|
})) {
|
|
252
293
|
await this.actualUpdate(x, body.set);
|
|
@@ -256,7 +297,7 @@ export class DataApi {
|
|
|
256
297
|
});
|
|
257
298
|
}
|
|
258
299
|
catch (err) {
|
|
259
|
-
response.error(err);
|
|
300
|
+
response.error(err, this.repository.metadata);
|
|
260
301
|
}
|
|
261
302
|
}
|
|
262
303
|
async actualUpdate(row, body) {
|
|
@@ -311,9 +352,9 @@ export class DataApi {
|
|
|
311
352
|
}
|
|
312
353
|
catch (err) {
|
|
313
354
|
if (err.isForbiddenError)
|
|
314
|
-
response.forbidden();
|
|
355
|
+
response.forbidden(err.message);
|
|
315
356
|
else
|
|
316
|
-
response.error(err);
|
|
357
|
+
response.error(err, this.repository.metadata);
|
|
317
358
|
}
|
|
318
359
|
}
|
|
319
360
|
}
|
|
@@ -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()
|
|
@@ -6,14 +6,16 @@ import { getRepository } from '../remult3/RepositoryImplementation.js';
|
|
|
6
6
|
import { Sort } from '../sort.js';
|
|
7
7
|
import { ValueConverters } from '../valueConverters.js';
|
|
8
8
|
import { getRepositoryInternals } from '../remult3/repository-internals.js';
|
|
9
|
+
import { isOfType } from '../isOfType.js';
|
|
9
10
|
// @dynamic
|
|
10
11
|
export class SqlDatabase {
|
|
11
12
|
sql;
|
|
12
|
-
static getDb(
|
|
13
|
-
const r = (
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
static getDb(dataProvider) {
|
|
14
|
+
const r = (dataProvider || defaultRemult.dataProvider);
|
|
15
|
+
if (isOfType(r, 'createCommand'))
|
|
16
|
+
return r;
|
|
17
|
+
else
|
|
18
|
+
throw 'the data provider is not an SqlCommandFactory';
|
|
17
19
|
}
|
|
18
20
|
createCommand() {
|
|
19
21
|
return new LogSQLCommand(this.sql.createCommand(), SqlDatabase.LogToConsole);
|
|
@@ -74,6 +76,7 @@ export class SqlDatabase {
|
|
|
74
76
|
transaction: (z) => x.transaction(z),
|
|
75
77
|
supportsJsonColumnType: this.sql.supportsJsonColumnType,
|
|
76
78
|
wrapIdentifier: this.wrapIdentifier,
|
|
79
|
+
end: this.end,
|
|
77
80
|
}));
|
|
78
81
|
}
|
|
79
82
|
finally {
|
|
@@ -81,6 +84,16 @@ export class SqlDatabase {
|
|
|
81
84
|
}
|
|
82
85
|
});
|
|
83
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Creates a raw filter for entity filtering.
|
|
89
|
+
* @param {CustomSqlFilterBuilderFunction} build - The custom SQL filter builder function.
|
|
90
|
+
* @returns {EntityFilter<any>} - The entity filter with a custom SQL filter.
|
|
91
|
+
* @example
|
|
92
|
+
* SqlDatabase.rawFilter(({param}) =>
|
|
93
|
+
`"customerId" in (select id from customers where city = ${param(customerCity)})`
|
|
94
|
+
)
|
|
95
|
+
* @see [Leveraging Database Capabilities with Raw SQL in Custom Filters](https://remult.dev/docs/custom-filter.html#leveraging-database-capabilities-with-raw-sql-in-custom-filters)
|
|
96
|
+
*/
|
|
84
97
|
static rawFilter(build) {
|
|
85
98
|
return {
|
|
86
99
|
[customDatabaseFilterToken]: {
|
|
@@ -88,12 +101,12 @@ export class SqlDatabase {
|
|
|
88
101
|
},
|
|
89
102
|
};
|
|
90
103
|
}
|
|
91
|
-
static async filterToRaw(repo, condition, sqlCommand, dbNames) {
|
|
104
|
+
static async filterToRaw(repo, condition, sqlCommand, dbNames, wrapIdentifier = (x) => x) {
|
|
92
105
|
if (!sqlCommand) {
|
|
93
106
|
sqlCommand = new myDummySQLCommand();
|
|
94
107
|
}
|
|
95
108
|
const r = getRepository(repo);
|
|
96
|
-
var b = new FilterConsumerBridgeToSqlRequest(sqlCommand, dbNames || (await dbNamesOf(r.metadata)));
|
|
109
|
+
var b = new FilterConsumerBridgeToSqlRequest(sqlCommand, dbNames || (await dbNamesOf(r.metadata, wrapIdentifier)));
|
|
97
110
|
b._addWhere = false;
|
|
98
111
|
await (await getRepositoryInternals(r)._translateWhereToFilter(condition)).__applyToConsumer(b);
|
|
99
112
|
return await b.resolveWhere();
|
|
@@ -116,8 +129,15 @@ export class SqlDatabase {
|
|
|
116
129
|
this.sql = sql;
|
|
117
130
|
if (sql.wrapIdentifier)
|
|
118
131
|
this.wrapIdentifier = (x) => sql.wrapIdentifier(x);
|
|
132
|
+
if (isOfType(sql, 'provideMigrationBuilder')) {
|
|
133
|
+
this.provideMigrationBuilder = (x) => sql.provideMigrationBuilder(x);
|
|
134
|
+
}
|
|
135
|
+
if (isOfType(sql, 'end'))
|
|
136
|
+
this.end = () => sql.end();
|
|
119
137
|
}
|
|
138
|
+
provideMigrationBuilder;
|
|
120
139
|
createdEntities = [];
|
|
140
|
+
end;
|
|
121
141
|
}
|
|
122
142
|
const icons = new Map([
|
|
123
143
|
// CRUD
|
|
@@ -336,6 +356,11 @@ class ActualSQLServerDataProvider {
|
|
|
336
356
|
statement += ' returning ' + select;
|
|
337
357
|
return r.execute(statement).then((sqlResult) => {
|
|
338
358
|
this.sql._getSourceSql().afterMutation?.();
|
|
359
|
+
if (sqlResult.rows.length != 1)
|
|
360
|
+
throw new Error('Failed to update row with id ' +
|
|
361
|
+
id +
|
|
362
|
+
', rows updated: ' +
|
|
363
|
+
sqlResult.rows.length);
|
|
339
364
|
return this.buildResultRow(colKeys, sqlResult.rows[0], sqlResult);
|
|
340
365
|
});
|
|
341
366
|
}
|
|
@@ -13,15 +13,7 @@ export class WebSqlDataProvider {
|
|
|
13
13
|
//@ts-ignore
|
|
14
14
|
this.db = window.openDatabase(databaseName, '1.0', databaseName, databaseSize);
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
static getDb(remult) {
|
|
18
|
-
const sql = SqlDatabase.getDb(remult);
|
|
19
|
-
const me = sql._getSourceSql();
|
|
20
|
-
if (!me.db) {
|
|
21
|
-
throw 'the data provider is not a WebSqlDataProvider';
|
|
22
|
-
}
|
|
23
|
-
return me.db;
|
|
24
|
-
}
|
|
16
|
+
async end() { }
|
|
25
17
|
getLimitSqlSyntax(limit, offset) {
|
|
26
18
|
return ' limit ' + limit + ' offset ' + offset;
|
|
27
19
|
}
|
|
@@ -126,34 +126,55 @@ export class FilterConsumerBridgeToSqlRequest {
|
|
|
126
126
|
databaseCustom(databaseCustom) {
|
|
127
127
|
this.promises.push((async () => {
|
|
128
128
|
if (databaseCustom?.buildSql) {
|
|
129
|
-
let item = new CustomSqlFilterBuilder(this.r);
|
|
130
|
-
await databaseCustom.buildSql(item);
|
|
131
|
-
if (
|
|
132
|
-
|
|
129
|
+
let item = new CustomSqlFilterBuilder(this.r, this.nameProvider.wrapIdentifier);
|
|
130
|
+
let sql = await databaseCustom.buildSql(item);
|
|
131
|
+
if (typeof sql !== 'string')
|
|
132
|
+
sql = item.sql;
|
|
133
|
+
if (sql) {
|
|
134
|
+
this.addToWhere('(' + sql + ')');
|
|
133
135
|
}
|
|
134
136
|
}
|
|
135
137
|
})());
|
|
136
138
|
}
|
|
137
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Represents a custom SQL filter builder.
|
|
142
|
+
*/
|
|
138
143
|
export class CustomSqlFilterBuilder {
|
|
139
144
|
r;
|
|
140
|
-
|
|
145
|
+
wrapIdentifier;
|
|
146
|
+
constructor(r, wrapIdentifier) {
|
|
141
147
|
this.r = r;
|
|
148
|
+
this.wrapIdentifier = wrapIdentifier;
|
|
149
|
+
this.param.bind(this);
|
|
150
|
+
this.filterToRaw.bind(this);
|
|
142
151
|
}
|
|
143
152
|
sql = '';
|
|
144
|
-
/** @deprecated
|
|
153
|
+
/** @deprecated use `param` instead*/
|
|
145
154
|
addParameterAndReturnSqlToken(val) {
|
|
146
155
|
return this.param(val);
|
|
147
156
|
}
|
|
148
|
-
|
|
157
|
+
/**
|
|
158
|
+
* Adds a parameter value.
|
|
159
|
+
* @param {valueType} val - The value to add as a parameter.
|
|
160
|
+
* @param {FieldMetadata<valueType>} [field] - The field metadata.
|
|
161
|
+
* @returns {string} - The SQL token.
|
|
162
|
+
*/
|
|
163
|
+
param = (val, field) => {
|
|
149
164
|
if (typeof field === 'object' && field.valueConverter.toDb) {
|
|
150
165
|
val = field.valueConverter.toDb(val);
|
|
151
166
|
}
|
|
152
167
|
return this.r.param(val);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
168
|
+
};
|
|
169
|
+
/**
|
|
170
|
+
* Converts an entity filter into a raw SQL condition - and appends to it any `backendPrefilter` and `backendPreprocessFilter`
|
|
171
|
+
* @param {RepositoryOverloads<entityType>} repo - The repository.
|
|
172
|
+
* @param {EntityFilter<entityType>} condition - The entity filter.
|
|
173
|
+
* @returns {Promise<string>} - The raw SQL.
|
|
174
|
+
*/
|
|
175
|
+
filterToRaw = async (repo, condition) => {
|
|
176
|
+
return SqlDatabase.filterToRaw(repo, condition, this, undefined, this.wrapIdentifier);
|
|
177
|
+
};
|
|
157
178
|
}
|
|
158
179
|
export function isDbReadonly(field, dbNames) {
|
|
159
180
|
return (field.dbReadOnly ||
|
|
@@ -164,6 +185,10 @@ export function shouldNotCreateField(field, dbNames) {
|
|
|
164
185
|
return (field.isServerExpression ||
|
|
165
186
|
(field.options.sqlExpression && field.dbName != dbNames.$dbNameOf(field)));
|
|
166
187
|
}
|
|
188
|
+
export function shouldCreateEntity(entity, e) {
|
|
189
|
+
return (!entity.options.sqlExpression &&
|
|
190
|
+
e.$entityName.toLowerCase().indexOf('from ') < 0);
|
|
191
|
+
}
|
|
167
192
|
export async function dbNamesOf(repo, wrapIdentifierOrOptions) {
|
|
168
193
|
let options = typeof wrapIdentifierOrOptions === 'function'
|
|
169
194
|
? { wrapIdentifier: wrapIdentifierOrOptions }
|
|
@@ -185,6 +210,7 @@ export async function dbNamesOf(repo, wrapIdentifierOrOptions) {
|
|
|
185
210
|
key = field.key;
|
|
186
211
|
return result[key];
|
|
187
212
|
},
|
|
213
|
+
wrapIdentifier: options.wrapIdentifier,
|
|
188
214
|
};
|
|
189
215
|
for (const field of meta.fields) {
|
|
190
216
|
let r = await fieldDbName(field, meta, options.wrapIdentifier);
|