remult 3.3.19-next.3 → 3.4.0-next.10
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/SseSubscriptionServer.d.ts +4 -0
- package/SseSubscriptionServer.js +44 -2
- package/esm/SseSubscriptionServer.js +16 -1
- package/esm/internals.js +2 -0
- package/esm/live-query/data-provider-live-query-storage.js +3 -1
- package/esm/remult-knex/index.js +46 -73
- package/esm/remult-mongo.js +18 -12
- package/esm/server/remult-api-server.js +21 -15
- package/esm/src/data-api.js +61 -38
- package/esm/src/data-providers/sql-database.js +197 -134
- package/esm/src/filter/filter-consumer-bridge-to-sql-request.js +35 -34
- package/esm/src/filter/walk-filter.js +29 -0
- package/esm/src/live-query/LiveQueryClient.js +192 -53
- package/esm/src/live-query/SseSubscriptionClient.js +130 -43
- package/esm/src/live-query/SubscriptionChannel.js +61 -3
- package/esm/src/live-query/SubscriptionServer.js +11 -3
- package/esm/src/remult3/Fields.js +12 -2
- package/esm/src/remult3/RepositoryImplementation.js +81 -21
- package/esm/src/remult3/remult3.js +6 -0
- package/esm/src/sort.js +0 -3
- package/esm/src/valueConverters.js +11 -0
- package/index.d.ts +1 -0
- package/internals.d.ts +4 -0
- package/internals.js +6 -1
- package/live-query/data-provider-live-query-storage.d.ts +4 -1
- package/live-query/data-provider-live-query-storage.js +18 -15
- package/package.json +1 -1
- package/remult-knex/index.d.ts +3 -2
- package/remult-knex/index.js +118 -164
- package/remult-mongo.js +23 -16
- package/server/remult-api-server.d.ts +12 -0
- package/server/remult-api-server.js +32 -48
- package/src/column-interfaces.d.ts +5 -1
- package/src/data-api.d.ts +6 -5
- package/src/data-api.js +153 -121
- package/src/data-providers/sql-database.d.ts +57 -5
- package/src/data-providers/sql-database.js +386 -255
- package/src/entity.d.ts +12 -0
- package/src/filter/filter-consumer-bridge-to-sql-request.d.ts +8 -3
- package/src/filter/filter-consumer-bridge-to-sql-request.js +40 -47
- package/src/filter/walk-filter.d.ts +3 -0
- package/src/filter/walk-filter.js +56 -0
- package/src/live-query/LiveQueryClient.js +267 -99
- package/src/live-query/SseSubscriptionClient.js +175 -84
- package/src/live-query/SubscriptionChannel.d.ts +20 -0
- package/src/live-query/SubscriptionChannel.js +73 -10
- package/src/live-query/SubscriptionServer.d.ts +11 -2
- package/src/live-query/SubscriptionServer.js +20 -10
- package/src/remult3/Fields.js +6 -0
- package/src/remult3/RepositoryImplementation.d.ts +9 -2
- package/src/remult3/RepositoryImplementation.js +299 -232
- package/src/remult3/remult3.d.ts +8 -13
- package/src/remult3/remult3.js +6 -0
- package/src/remult3/repository-internals.d.ts +4 -2
- package/src/sort.js +0 -3
- package/src/sql-command.d.ts +5 -0
- package/src/valueConverters.d.ts +4 -1
- package/src/valueConverters.js +13 -0
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { GenericResponse } from './server/index.js';
|
|
2
|
+
import type { SubscriptionServerRouteApi } from './server/remult-api-server.js';
|
|
2
3
|
import type { Remult } from './src/context.js';
|
|
3
4
|
import type { SubscriptionServer } from './src/live-query/SubscriptionServer.js';
|
|
5
|
+
/** Keep below typical proxy idle (30s) and Heroku H15 (55s). */
|
|
6
|
+
export declare const ssePingMs = 15000;
|
|
4
7
|
export declare class SseSubscriptionServer implements SubscriptionServer {
|
|
5
8
|
private canUserConnectToChannel?;
|
|
9
|
+
initApiServer(api: SubscriptionServerRouteApi): void;
|
|
6
10
|
constructor(canUserConnectToChannel?: ((channel: string, remult: Remult) => boolean) | undefined);
|
|
7
11
|
publishMessage<T>(channel: string, message: any): Promise<void>;
|
|
8
12
|
}
|
package/SseSubscriptionServer.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.clientConnection = exports.SseSubscriptionServer = void 0;
|
|
3
|
+
exports.clientConnection = exports.SseSubscriptionServer = exports.ssePingMs = void 0;
|
|
4
4
|
var tslib_1 = require("tslib");
|
|
5
5
|
var SseSubscriptionClient_js_1 = require("./src/live-query/SseSubscriptionClient.js");
|
|
6
|
+
var SubscriptionChannel_js_1 = require("./src/live-query/SubscriptionChannel.js");
|
|
7
|
+
/** Keep below typical proxy idle (30s) and Heroku H15 (55s). */
|
|
8
|
+
exports.ssePingMs = 15000;
|
|
6
9
|
var SseSubscriptionServer = /** @class */ (function () {
|
|
7
10
|
function SseSubscriptionServer(canUserConnectToChannel) {
|
|
8
11
|
this.canUserConnectToChannel = canUserConnectToChannel;
|
|
@@ -16,6 +19,45 @@ var SseSubscriptionServer = /** @class */ (function () {
|
|
|
16
19
|
this.canUserConnectToChannel = function () { return true; };
|
|
17
20
|
}
|
|
18
21
|
}
|
|
22
|
+
SseSubscriptionServer.prototype.initApiServer = function (api) {
|
|
23
|
+
var _this = this;
|
|
24
|
+
var streamPath = '/' + SubscriptionChannel_js_1.streamUrl;
|
|
25
|
+
api.addRoute(streamPath, 'get', function (_a) { return tslib_1.__awaiter(_this, [_a], void 0, function (_b) {
|
|
26
|
+
var req = _b.req, origRes = _b.origRes;
|
|
27
|
+
return tslib_1.__generator(this, function (_c) {
|
|
28
|
+
this.openHttpServerStream(req, origRes);
|
|
29
|
+
return [2 /*return*/];
|
|
30
|
+
});
|
|
31
|
+
}); });
|
|
32
|
+
api.addRoute(streamPath + '/subscribe', 'post', function (_a) { return tslib_1.__awaiter(_this, [_a], void 0, function (_b) {
|
|
33
|
+
var _c;
|
|
34
|
+
var remult = _b.remult, res = _b.res, getBody = _b.getBody;
|
|
35
|
+
return tslib_1.__generator(this, function (_d) {
|
|
36
|
+
switch (_d.label) {
|
|
37
|
+
case 0:
|
|
38
|
+
_c = this.subscribeToChannel;
|
|
39
|
+
return [4 /*yield*/, getBody()];
|
|
40
|
+
case 1:
|
|
41
|
+
_c.apply(this, [_d.sent(), res, remult]);
|
|
42
|
+
return [2 /*return*/];
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}); });
|
|
46
|
+
api.addRoute(streamPath + '/unsubscribe', 'post', function (_a) { return tslib_1.__awaiter(_this, [_a], void 0, function (_b) {
|
|
47
|
+
var _c;
|
|
48
|
+
var remult = _b.remult, res = _b.res, getBody = _b.getBody;
|
|
49
|
+
return tslib_1.__generator(this, function (_d) {
|
|
50
|
+
switch (_d.label) {
|
|
51
|
+
case 0:
|
|
52
|
+
_c = this.subscribeToChannel;
|
|
53
|
+
return [4 /*yield*/, getBody()];
|
|
54
|
+
case 1:
|
|
55
|
+
_c.apply(this, [_d.sent(), res, remult, true]);
|
|
56
|
+
return [2 /*return*/];
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}); });
|
|
60
|
+
};
|
|
19
61
|
//@internal
|
|
20
62
|
SseSubscriptionServer.prototype.subscribeToChannel = function (_a, res, remult, remove) {
|
|
21
63
|
var e_1, _b;
|
|
@@ -137,7 +179,7 @@ var clientConnection = /** @class */ (function () {
|
|
|
137
179
|
this.write('', 'keep-alive');
|
|
138
180
|
this.timeOutRef = setTimeout(function () {
|
|
139
181
|
_this.sendLiveMessage();
|
|
140
|
-
},
|
|
182
|
+
}, exports.ssePingMs);
|
|
141
183
|
};
|
|
142
184
|
return clientConnection;
|
|
143
185
|
}());
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { ConnectionNotFoundError } from './src/live-query/SseSubscriptionClient.js';
|
|
2
|
+
import { streamUrl } from './src/live-query/SubscriptionChannel.js';
|
|
3
|
+
/** Keep below typical proxy idle (30s) and Heroku H15 (55s). */
|
|
4
|
+
export const ssePingMs = 15_000;
|
|
2
5
|
export class SseSubscriptionServer {
|
|
3
6
|
canUserConnectToChannel;
|
|
7
|
+
initApiServer(api) {
|
|
8
|
+
const streamPath = '/' + streamUrl;
|
|
9
|
+
api.addRoute(streamPath, 'get', async ({ req, origRes }) => {
|
|
10
|
+
this.openHttpServerStream(req, origRes);
|
|
11
|
+
});
|
|
12
|
+
api.addRoute(streamPath + '/subscribe', 'post', async ({ remult, res, getBody }) => {
|
|
13
|
+
this.subscribeToChannel(await getBody(), res, remult);
|
|
14
|
+
});
|
|
15
|
+
api.addRoute(streamPath + '/unsubscribe', 'post', async ({ remult, res, getBody }) => {
|
|
16
|
+
this.subscribeToChannel(await getBody(), res, remult, true);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
4
19
|
//@internal
|
|
5
20
|
subscribeToChannel({ channel, clientId }, res, remult, remove = false) {
|
|
6
21
|
for (const c of this.connections) {
|
|
@@ -101,6 +116,6 @@ export class clientConnection {
|
|
|
101
116
|
this.write('', 'keep-alive');
|
|
102
117
|
this.timeOutRef = setTimeout(() => {
|
|
103
118
|
this.sendLiveMessage();
|
|
104
|
-
},
|
|
119
|
+
}, ssePingMs);
|
|
105
120
|
}
|
|
106
121
|
}
|
package/esm/internals.js
CHANGED
|
@@ -4,8 +4,10 @@ export { getRelationInfo, getRelationFieldInfo, } from './src/remult3/relationIn
|
|
|
4
4
|
export { decorateColumnSettings, controllerRefImpl, getControllerRef, fieldOptionsEnricher, } from './src/remult3/RepositoryImplementation.js';
|
|
5
5
|
export { getEntitySettings } from './src/remult3/getEntityRef.js';
|
|
6
6
|
export { __updateEntityBasedOnWhere } from './src/filter/filter-interfaces.js';
|
|
7
|
+
export { omitFilterField, walkFilter } from './src/filter/walk-filter.js';
|
|
7
8
|
export const actionInfo = remultStatic.actionInfo;
|
|
8
9
|
export { isOfType } from './src/isOfType.js';
|
|
9
10
|
export { flags } from './src/remult3/remult3.js';
|
|
11
|
+
export { SseSubscriptionClient } from './src/live-query/SseSubscriptionClient.js';
|
|
10
12
|
export { DataProviderPromiseWrapper } from './src/data-interfaces.js';
|
|
11
13
|
export { pagedQueryResult } from './src/remult3/pagedQueryResult.js';
|
|
@@ -52,12 +52,14 @@ export class DataProviderLiveQueryStorage {
|
|
|
52
52
|
}
|
|
53
53
|
async keepAliveAndReturnUnknownQueryIds(queryIds) {
|
|
54
54
|
const repo = await this.repo;
|
|
55
|
+
const versions = {};
|
|
55
56
|
for (const query of await repo.find({ where: { id: queryIds } })) {
|
|
56
57
|
query.lastUsedIso = new Date().toISOString();
|
|
57
58
|
await repo.save(query);
|
|
59
|
+
versions[query.id] = query.data?.version ?? 0;
|
|
58
60
|
queryIds = queryIds.filter((x) => x !== query.id);
|
|
59
61
|
}
|
|
60
|
-
return queryIds;
|
|
62
|
+
return { unknownQueryIds: queryIds, versions };
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
65
|
let LiveQueryStorageEntity = class LiveQueryStorageEntity extends EntityBase {
|
package/esm/remult-knex/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { CustomSqlFilterBuilder, dbNamesOfWithForceSqlExpression, } from '../src/filter/filter-consumer-bridge-to-sql-request.js';
|
|
1
|
+
import { CustomSqlFilterBuilder, dbNamesOfWithForceSqlExpression, escapeLike, LIKE_ESCAPE_SQL, } from '../src/filter/filter-consumer-bridge-to-sql-request.js';
|
|
2
2
|
import { dbNamesOf, isDbReadonly, shouldCreateEntity, shouldNotCreateField, } from '../src/filter/filter-consumer-bridge-to-sql-request.js';
|
|
3
3
|
import { customDatabaseFilterToken, Filter, } from '../src/filter/filter-interfaces.js';
|
|
4
4
|
import { remult as remultContext } from '../src/remult-proxy.js';
|
|
5
5
|
import { getRepository, isAutoIncrement, } from '../src/remult3/RepositoryImplementation.js';
|
|
6
6
|
import { Sort } from '../src/sort.js';
|
|
7
|
-
import { ValueConverters } from '../src/valueConverters.js';
|
|
7
|
+
import { fromDbColumn, toDbColumn, ValueConverters, } from '../src/valueConverters.js';
|
|
8
8
|
import { getRepositoryInternals } from '../src/remult3/repository-internals.js';
|
|
9
|
-
import { groupByImpl, getRowAfterUpdate, } from '../src/data-providers/sql-database.js';
|
|
9
|
+
import { groupByImpl, getRowAfterUpdate, jsonColumnStorageOf, } from '../src/data-providers/sql-database.js';
|
|
10
10
|
export class KnexDataProvider {
|
|
11
11
|
knex;
|
|
12
12
|
constructor(knex) {
|
|
@@ -46,29 +46,14 @@ export class KnexDataProvider {
|
|
|
46
46
|
return r.knex;
|
|
47
47
|
}
|
|
48
48
|
wrapIdentifier = (x) => this.knex.ref(x) + '';
|
|
49
|
-
|
|
49
|
+
get jsonColumnStorage() {
|
|
50
50
|
if (!supportsJsonLoadingAndSaving(this.knex))
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
fromDb: ValueConverters.JsonString.fromDb,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
else if (this.knex.client.config.client === 'mysql2') {
|
|
62
|
-
for (const f of entity.fields.toArray()) {
|
|
63
|
-
if (f.valueConverter.fieldTypeInDb === 'json') {
|
|
64
|
-
//@ts-ignore
|
|
65
|
-
f.valueConverter = {
|
|
66
|
-
...f.valueConverter,
|
|
67
|
-
toDb: ValueConverters.JsonString.toDb,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
51
|
+
return 'text';
|
|
52
|
+
if (this.knex.client.config.client === 'mysql2')
|
|
53
|
+
return 'textOnWrite';
|
|
54
|
+
return 'native';
|
|
55
|
+
}
|
|
56
|
+
getEntityDataProvider(entity) {
|
|
72
57
|
return new KnexEntityDataProvider(entity, this.knex, this.wrapIdentifier, this);
|
|
73
58
|
}
|
|
74
59
|
async transaction(action) {
|
|
@@ -93,7 +78,7 @@ export class KnexDataProvider {
|
|
|
93
78
|
if (!wrapIdentifier)
|
|
94
79
|
wrapIdentifier = (x) => x;
|
|
95
80
|
const repo = getRepository(entity);
|
|
96
|
-
var b = new FilterConsumerBridgeToKnexRequest(await dbNamesOfWithForceSqlExpression(repo.metadata, wrapIdentifier), wrapIdentifier ?? wrapIdentifier);
|
|
81
|
+
var b = new FilterConsumerBridgeToKnexRequest(await dbNamesOfWithForceSqlExpression(repo.metadata, wrapIdentifier), wrapIdentifier ?? wrapIdentifier, jsonColumnStorageOf(repo));
|
|
97
82
|
b._addWhere = false;
|
|
98
83
|
await (await getRepositoryInternals(repo)._translateWhereToFilter(condition)).__applyToConsumer(b);
|
|
99
84
|
let r = await b.resolveWhere();
|
|
@@ -119,7 +104,7 @@ class KnexEntityDataProvider {
|
|
|
119
104
|
async groupBy(options) {
|
|
120
105
|
return groupByImpl(options, await dbNamesOf(this.entity, this.dp.wrapIdentifier), this.dp.createCommand(), false, (limit, offset) => this.knex.client.config.client?.includes('mssql')
|
|
121
106
|
? ' offset ' + offset + ' rows fetch next ' + limit + ' rows only'
|
|
122
|
-
: ' limit ' + limit + ' offset ' + offset);
|
|
107
|
+
: ' limit ' + limit + ' offset ' + offset, this.dp.jsonColumnStorage);
|
|
123
108
|
}
|
|
124
109
|
getEntityFrom(e) {
|
|
125
110
|
if (this.entity.options.sqlExpression)
|
|
@@ -128,7 +113,7 @@ class KnexEntityDataProvider {
|
|
|
128
113
|
}
|
|
129
114
|
async count(where) {
|
|
130
115
|
const e = await this.init();
|
|
131
|
-
const br = new FilterConsumerBridgeToKnexRequest(e, this.rawSqlWrapIdentifier);
|
|
116
|
+
const br = new FilterConsumerBridgeToKnexRequest(e, this.rawSqlWrapIdentifier, this.dp.jsonColumnStorage);
|
|
132
117
|
where.__applyToConsumer(br);
|
|
133
118
|
let r = await br.resolveWhere();
|
|
134
119
|
const result = await this.getEntityFrom(e)
|
|
@@ -162,7 +147,7 @@ class KnexEntityDataProvider {
|
|
|
162
147
|
}
|
|
163
148
|
let query = this.getEntityFrom(e).select(cols);
|
|
164
149
|
if (options?.where) {
|
|
165
|
-
const br = new FilterConsumerBridgeToKnexRequest(e, this.rawSqlWrapIdentifier);
|
|
150
|
+
const br = new FilterConsumerBridgeToKnexRequest(e, this.rawSqlWrapIdentifier, this.dp.jsonColumnStorage);
|
|
166
151
|
options.where.__applyToConsumer(br);
|
|
167
152
|
let r = await br.resolveWhere();
|
|
168
153
|
query.where((b) => r.forEach((y) => y(b)));
|
|
@@ -188,7 +173,7 @@ class KnexEntityDataProvider {
|
|
|
188
173
|
for (let m in y) {
|
|
189
174
|
let field = colKeys[i++];
|
|
190
175
|
try {
|
|
191
|
-
result[field.key] = field
|
|
176
|
+
result[field.key] = fromDbColumn(field, y[m], this.dp.jsonColumnStorage);
|
|
192
177
|
}
|
|
193
178
|
catch (err) {
|
|
194
179
|
throw new Error('Failed to load from db:' + field.key + '\r\n' + err);
|
|
@@ -212,14 +197,14 @@ class KnexEntityDataProvider {
|
|
|
212
197
|
}
|
|
213
198
|
async update(id, data, options) {
|
|
214
199
|
const e = await this.init();
|
|
215
|
-
let f = new FilterConsumerBridgeToKnexRequest(e, this.rawSqlWrapIdentifier);
|
|
200
|
+
let f = new FilterConsumerBridgeToKnexRequest(e, this.rawSqlWrapIdentifier, this.dp.jsonColumnStorage);
|
|
216
201
|
Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(id)).__applyToConsumer(f);
|
|
217
202
|
let updateObject = {};
|
|
218
203
|
for (const x of this.entity.fields) {
|
|
219
204
|
if (isDbReadonly(x, e)) {
|
|
220
205
|
}
|
|
221
206
|
else if (data[x.key] !== undefined) {
|
|
222
|
-
let v = translateValueAndHandleArrayAndHandleArray(x, data[x.key]);
|
|
207
|
+
let v = translateValueAndHandleArrayAndHandleArray(x, data[x.key], this.dp.jsonColumnStorage);
|
|
223
208
|
if (v !== undefined) {
|
|
224
209
|
let key = await e.$dbNameOf(x);
|
|
225
210
|
updateObject[key] = v;
|
|
@@ -238,7 +223,7 @@ class KnexEntityDataProvider {
|
|
|
238
223
|
if (ids.length === 0)
|
|
239
224
|
return;
|
|
240
225
|
const e = await this.init();
|
|
241
|
-
let f = new FilterConsumerBridgeToKnexRequest(e, this.rawSqlWrapIdentifier);
|
|
226
|
+
let f = new FilterConsumerBridgeToKnexRequest(e, this.rawSqlWrapIdentifier, this.dp.jsonColumnStorage);
|
|
242
227
|
Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(...ids)).__applyToConsumer(f);
|
|
243
228
|
let where = await f.resolveWhere();
|
|
244
229
|
await this.getEntityFrom(e)
|
|
@@ -257,7 +242,7 @@ class KnexEntityDataProvider {
|
|
|
257
242
|
return result;
|
|
258
243
|
}
|
|
259
244
|
const e = await this.init();
|
|
260
|
-
const batches = splitByMaxParameters(data, (row) => countKnexInsertBindParameters(this.entity, e, row), knexMaxParametersInOneSqlStatement(this.knex));
|
|
245
|
+
const batches = splitByMaxParameters(data, (row) => countKnexInsertBindParameters(this.entity, e, row, this.dp.jsonColumnStorage), knexMaxParametersInOneSqlStatement(this.knex));
|
|
261
246
|
const result = [];
|
|
262
247
|
for (const batch of batches)
|
|
263
248
|
result.push(...(await this.insertBatch(batch, e, options)));
|
|
@@ -268,15 +253,14 @@ class KnexEntityDataProvider {
|
|
|
268
253
|
for (const x of this.entity.fields) {
|
|
269
254
|
if (isDbReadonly(x, e))
|
|
270
255
|
continue;
|
|
271
|
-
if (batch.some((row) => translateValueAndHandleArrayAndHandleArray(x, row[x.key]) !=
|
|
272
|
-
undefined))
|
|
256
|
+
if (batch.some((row) => translateValueAndHandleArrayAndHandleArray(x, row[x.key], this.dp.jsonColumnStorage) != undefined))
|
|
273
257
|
cols.push(x);
|
|
274
258
|
}
|
|
275
259
|
const insertObjects = [];
|
|
276
260
|
for (const row of batch) {
|
|
277
261
|
const insertObject = {};
|
|
278
262
|
for (const x of cols) {
|
|
279
|
-
const v = translateValueAndHandleArrayAndHandleArray(x, row[x.key]);
|
|
263
|
+
const v = translateValueAndHandleArrayAndHandleArray(x, row[x.key], this.dp.jsonColumnStorage);
|
|
280
264
|
const key = await e.$dbNameOf(x);
|
|
281
265
|
if (v != undefined)
|
|
282
266
|
insertObject[key] = v;
|
|
@@ -291,9 +275,7 @@ class KnexEntityDataProvider {
|
|
|
291
275
|
const wantRows = options?.select !== 'none';
|
|
292
276
|
if (isAutoIncrement(this.entity.idMetadata.field) && wantRows) {
|
|
293
277
|
const result = await insert.returning(this.entity.idMetadata.field.dbName);
|
|
294
|
-
const ids = result.map((r) => r && typeof r === 'object'
|
|
295
|
-
? r[this.entity.idMetadata.field.dbName]
|
|
296
|
-
: r);
|
|
278
|
+
const ids = result.map((r) => r && typeof r === 'object' ? r[this.entity.idMetadata.field.dbName] : r);
|
|
297
279
|
return this.loadRowsByIds(ids);
|
|
298
280
|
}
|
|
299
281
|
await insert;
|
|
@@ -321,7 +303,7 @@ class KnexEntityDataProvider {
|
|
|
321
303
|
if (isDbReadonly(x, e)) {
|
|
322
304
|
}
|
|
323
305
|
else {
|
|
324
|
-
let v = translateValueAndHandleArrayAndHandleArray(x, data[x.key]);
|
|
306
|
+
let v = translateValueAndHandleArrayAndHandleArray(x, data[x.key], this.dp.jsonColumnStorage);
|
|
325
307
|
if (v != undefined) {
|
|
326
308
|
let key = await e.$dbNameOf(x);
|
|
327
309
|
insertObject[key] = v;
|
|
@@ -357,6 +339,7 @@ class KnexEntityDataProvider {
|
|
|
357
339
|
class FilterConsumerBridgeToKnexRequest {
|
|
358
340
|
innerNameProvider;
|
|
359
341
|
rawSqlWrapIdentifier;
|
|
342
|
+
jsonColumnStorage;
|
|
360
343
|
_addWhere = true;
|
|
361
344
|
promises = [];
|
|
362
345
|
result = [];
|
|
@@ -370,9 +353,10 @@ class FilterConsumerBridgeToKnexRequest {
|
|
|
370
353
|
}
|
|
371
354
|
return this.result;
|
|
372
355
|
}
|
|
373
|
-
constructor(innerNameProvider, rawSqlWrapIdentifier) {
|
|
356
|
+
constructor(innerNameProvider, rawSqlWrapIdentifier, jsonColumnStorage) {
|
|
374
357
|
this.innerNameProvider = innerNameProvider;
|
|
375
358
|
this.rawSqlWrapIdentifier = rawSqlWrapIdentifier;
|
|
359
|
+
this.jsonColumnStorage = jsonColumnStorage;
|
|
376
360
|
}
|
|
377
361
|
custom(key, customItem) {
|
|
378
362
|
throw new Error('Custom filter should be translated before it gets here');
|
|
@@ -381,7 +365,7 @@ class FilterConsumerBridgeToKnexRequest {
|
|
|
381
365
|
this.promises.push((async () => {
|
|
382
366
|
const result = [];
|
|
383
367
|
for (const element of orElements) {
|
|
384
|
-
let f = new FilterConsumerBridgeToKnexRequest(this.innerNameProvider, this.rawSqlWrapIdentifier);
|
|
368
|
+
let f = new FilterConsumerBridgeToKnexRequest(this.innerNameProvider, this.rawSqlWrapIdentifier, this.jsonColumnStorage);
|
|
385
369
|
f._addWhere = false;
|
|
386
370
|
element.__applyToConsumer(f);
|
|
387
371
|
let where = await f.resolveWhere();
|
|
@@ -402,7 +386,7 @@ class FilterConsumerBridgeToKnexRequest {
|
|
|
402
386
|
}
|
|
403
387
|
not(element) {
|
|
404
388
|
this.promises.push((async () => {
|
|
405
|
-
let f = new FilterConsumerBridgeToKnexRequest(this.innerNameProvider, this.rawSqlWrapIdentifier);
|
|
389
|
+
let f = new FilterConsumerBridgeToKnexRequest(this.innerNameProvider, this.rawSqlWrapIdentifier, this.jsonColumnStorage);
|
|
406
390
|
f._addWhere = false;
|
|
407
391
|
element.__applyToConsumer(f);
|
|
408
392
|
let where = await f.resolveWhere();
|
|
@@ -424,7 +408,7 @@ class FilterConsumerBridgeToKnexRequest {
|
|
|
424
408
|
this.result.push((b) => b.whereNotNull(this.innerNameProvider.$dbNameOf(col)));
|
|
425
409
|
}
|
|
426
410
|
isIn(col, val) {
|
|
427
|
-
this.result.push((knex) => knex.whereIn(this.innerNameProvider.$dbNameOf(col), val.map((x) => translateValueAndHandleArrayAndHandleArray(col, x))));
|
|
411
|
+
this.result.push((knex) => knex.whereIn(this.innerNameProvider.$dbNameOf(col), val.map((x) => translateValueAndHandleArrayAndHandleArray(col, x, this.jsonColumnStorage))));
|
|
428
412
|
}
|
|
429
413
|
isEqualTo(col, val) {
|
|
430
414
|
this.add(col, val, '=');
|
|
@@ -445,39 +429,27 @@ class FilterConsumerBridgeToKnexRequest {
|
|
|
445
429
|
this.add(col, val, '<');
|
|
446
430
|
}
|
|
447
431
|
containsCaseInsensitive(col, val) {
|
|
448
|
-
this.
|
|
449
|
-
b.client.ref(this.innerNameProvider.$dbNameOf(col)) +
|
|
450
|
-
") like lower ('%" +
|
|
451
|
-
val.replace(/'/g, "''") +
|
|
452
|
-
"%')"));
|
|
453
|
-
this.promises.push((async () => { })());
|
|
432
|
+
this.like(col, '%' + escapeLike(val) + '%');
|
|
454
433
|
}
|
|
455
434
|
notContainsCaseInsensitive(col, val) {
|
|
456
|
-
this.
|
|
457
|
-
b.client.ref(this.innerNameProvider.$dbNameOf(col)) +
|
|
458
|
-
") like lower ('%" +
|
|
459
|
-
val.replace(/'/g, "''") +
|
|
460
|
-
"%')"));
|
|
461
|
-
this.promises.push((async () => { })());
|
|
435
|
+
this.like(col, '%' + escapeLike(val) + '%', 'not ');
|
|
462
436
|
}
|
|
463
437
|
startsWithCaseInsensitive(col, val) {
|
|
464
|
-
this.
|
|
465
|
-
b.client.ref(this.innerNameProvider.$dbNameOf(col)) +
|
|
466
|
-
") like lower ('" +
|
|
467
|
-
val.replace(/'/g, "''") +
|
|
468
|
-
"%')"));
|
|
469
|
-
this.promises.push((async () => { })());
|
|
438
|
+
this.like(col, escapeLike(val) + '%');
|
|
470
439
|
}
|
|
471
440
|
endsWithCaseInsensitive(col, val) {
|
|
472
|
-
this.
|
|
441
|
+
this.like(col, '%' + escapeLike(val));
|
|
442
|
+
}
|
|
443
|
+
like(col, pattern, not = '') {
|
|
444
|
+
this.result.push((b) => b.whereRaw(not +
|
|
445
|
+
'lower (' +
|
|
473
446
|
b.client.ref(this.innerNameProvider.$dbNameOf(col)) +
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
"')"));
|
|
447
|
+
') like lower (?)' +
|
|
448
|
+
LIKE_ESCAPE_SQL, [pattern]));
|
|
477
449
|
this.promises.push((async () => { })());
|
|
478
450
|
}
|
|
479
451
|
add(col, val, operator) {
|
|
480
|
-
this.result.push((b) => b.where(this.innerNameProvider.$dbNameOf(col), operator, translateValueAndHandleArrayAndHandleArray(col, val)));
|
|
452
|
+
this.result.push((b) => b.where(this.innerNameProvider.$dbNameOf(col), operator, translateValueAndHandleArrayAndHandleArray(col, val, this.jsonColumnStorage)));
|
|
481
453
|
}
|
|
482
454
|
databaseCustom(databaseCustom) {
|
|
483
455
|
this.promises.push((async () => {
|
|
@@ -486,7 +458,7 @@ class FilterConsumerBridgeToKnexRequest {
|
|
|
486
458
|
}
|
|
487
459
|
if (databaseCustom?.buildSql) {
|
|
488
460
|
let r = new KnexCommandHelper();
|
|
489
|
-
const item = new CustomSqlFilterBuilder(r, this.rawSqlWrapIdentifier);
|
|
461
|
+
const item = new CustomSqlFilterBuilder(r, this.rawSqlWrapIdentifier, this.jsonColumnStorage);
|
|
490
462
|
let sql = await databaseCustom.buildSql(item);
|
|
491
463
|
if (typeof sql !== 'string')
|
|
492
464
|
sql = item.sql;
|
|
@@ -691,18 +663,19 @@ function splitByMaxParameters(items, parametersInItem, maxParameters) {
|
|
|
691
663
|
batches.push(batch);
|
|
692
664
|
return batches;
|
|
693
665
|
}
|
|
694
|
-
function countKnexInsertBindParameters(entity, e, row) {
|
|
666
|
+
function countKnexInsertBindParameters(entity, e, row, storage) {
|
|
695
667
|
let n = 0;
|
|
696
668
|
for (const x of entity.fields) {
|
|
697
669
|
if (isDbReadonly(x, e))
|
|
698
670
|
continue;
|
|
699
|
-
if (translateValueAndHandleArrayAndHandleArray(x, row[x.key]) !=
|
|
671
|
+
if (translateValueAndHandleArrayAndHandleArray(x, row[x.key], storage) !=
|
|
672
|
+
undefined)
|
|
700
673
|
n++;
|
|
701
674
|
}
|
|
702
675
|
return n;
|
|
703
676
|
}
|
|
704
|
-
function translateValueAndHandleArrayAndHandleArray(field, val) {
|
|
705
|
-
let result = field
|
|
677
|
+
function translateValueAndHandleArrayAndHandleArray(field, val, storage) {
|
|
678
|
+
let result = toDbColumn(field, val, storage);
|
|
706
679
|
if (Array.isArray(result))
|
|
707
680
|
return JSON.stringify(result);
|
|
708
681
|
return result;
|
package/esm/remult-mongo.js
CHANGED
|
@@ -119,6 +119,8 @@ class MongoEntityDataProvider {
|
|
|
119
119
|
translateToDb(row, nameProvider) {
|
|
120
120
|
let result = {};
|
|
121
121
|
for (const col of this.entity.fields) {
|
|
122
|
+
if (col.dbReadOnly || col.isServerExpression)
|
|
123
|
+
continue;
|
|
122
124
|
let val = toDb(col, row[col.key]);
|
|
123
125
|
if (val === null)
|
|
124
126
|
val = NULL;
|
|
@@ -255,7 +257,7 @@ class MongoEntityDataProvider {
|
|
|
255
257
|
for (const f of this.entity.fields) {
|
|
256
258
|
if (!f.dbReadOnly && !f.isServerExpression) {
|
|
257
259
|
if (keys.includes(f.key)) {
|
|
258
|
-
newR[f
|
|
260
|
+
newR[e.$dbNameOf(f)] = toDb(f, data[f.key]);
|
|
259
261
|
}
|
|
260
262
|
}
|
|
261
263
|
}
|
|
@@ -386,23 +388,27 @@ class FilterConsumerBridgeToMongo {
|
|
|
386
388
|
this.add(col, val, '$lt');
|
|
387
389
|
}
|
|
388
390
|
containsCaseInsensitive(col, val) {
|
|
389
|
-
this.
|
|
391
|
+
this.addRegex(col, val);
|
|
390
392
|
}
|
|
391
393
|
notContainsCaseInsensitive(col, val) {
|
|
392
|
-
this.
|
|
393
|
-
[this.nameProvider.$dbNameOf(col)]: {
|
|
394
|
-
$not: {
|
|
395
|
-
$regex: isNull(val) ? val : toDb(col, val),
|
|
396
|
-
$options: 'i',
|
|
397
|
-
},
|
|
398
|
-
},
|
|
399
|
-
}));
|
|
394
|
+
this.addRegex(col, val, '', '', true);
|
|
400
395
|
}
|
|
401
396
|
startsWithCaseInsensitive(col, val) {
|
|
402
|
-
this.
|
|
397
|
+
this.addRegex(col, val, '^');
|
|
403
398
|
}
|
|
404
399
|
endsWithCaseInsensitive(col, val) {
|
|
405
|
-
this.
|
|
400
|
+
this.addRegex(col, val, '', '$');
|
|
401
|
+
}
|
|
402
|
+
addRegex(col, val, start = '', end = '', negate = false) {
|
|
403
|
+
this.result.push(() => {
|
|
404
|
+
const regex = {
|
|
405
|
+
$regex: start + toDb(col, val).replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + end,
|
|
406
|
+
$options: 'i',
|
|
407
|
+
};
|
|
408
|
+
return {
|
|
409
|
+
[this.nameProvider.$dbNameOf(col)]: negate ? { $not: regex } : regex,
|
|
410
|
+
};
|
|
411
|
+
});
|
|
406
412
|
}
|
|
407
413
|
add(col, val, operator, moreOptions) {
|
|
408
414
|
this.result.push(() => ({
|
|
@@ -2,7 +2,7 @@ import { __decorate, __metadata } from "tslib";
|
|
|
2
2
|
import { SseSubscriptionServer } from '../SseSubscriptionServer.js';
|
|
3
3
|
import { Remult, RemultAsyncLocalStorage, withRemult } from '../src/context.js';
|
|
4
4
|
import { DataApi, serializeError } from '../src/data-api.js';
|
|
5
|
-
import { liveQueryKeepAliveRoute
|
|
5
|
+
import { liveQueryKeepAliveRoute } from '../src/live-query/SubscriptionChannel.js';
|
|
6
6
|
import { InMemoryLiveQueryStorage, LiveQueryPublisher, } from '../src/live-query/SubscriptionServer.js';
|
|
7
7
|
import { IdEntity } from '../src/remult3/IdEntity.js';
|
|
8
8
|
import { Fields } from '../src/remult3/Fields.js';
|
|
@@ -266,22 +266,28 @@ export class RemultServerImplementation {
|
|
|
266
266
|
r.route(this.options.rootPath + '/admin').get(admin());
|
|
267
267
|
}
|
|
268
268
|
r.route(this.options.rootPath + '/me').get(this.process(async (remult, req, res) => res.success(remult.user ?? null)));
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
269
|
+
const subscriptionServer = this.options.subscriptionServer;
|
|
270
|
+
if (subscriptionServer?.initApiServer) {
|
|
271
|
+
subscriptionServer.initApiServer({
|
|
272
|
+
rootPath: this.options.rootPath,
|
|
273
|
+
addRoute: (relativePath, method, handler) => {
|
|
274
|
+
r.route(this.options.rootPath + relativePath)[method](this.process(async (remult, _, res, req, origRes, origReq) => handler({
|
|
275
|
+
remult,
|
|
276
|
+
res,
|
|
277
|
+
req,
|
|
278
|
+
origRes,
|
|
279
|
+
getBody: () => this.coreOptions.getRequestBody(origReq),
|
|
280
|
+
})));
|
|
281
|
+
},
|
|
282
|
+
});
|
|
282
283
|
}
|
|
283
284
|
r.route(this.options.rootPath + '/' + liveQueryKeepAliveRoute).post(this.process(async (remult, req, res, reqInfo, origRes, origReq) => {
|
|
284
|
-
|
|
285
|
+
const body = await this.coreOptions.getRequestBody(origReq);
|
|
286
|
+
const queryIds = Array.isArray(body)
|
|
287
|
+
? body
|
|
288
|
+
: (body?.queryIds ?? []);
|
|
289
|
+
const result = await remult.liveQueryStorage.keepAliveAndReturnUnknownQueryIds(queryIds);
|
|
290
|
+
res.success(Array.isArray(body) ? result.unknownQueryIds : result);
|
|
285
291
|
}));
|
|
286
292
|
}
|
|
287
293
|
this.entities.forEach((e) => {
|