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.
Files changed (58) hide show
  1. package/SseSubscriptionServer.d.ts +4 -0
  2. package/SseSubscriptionServer.js +44 -2
  3. package/esm/SseSubscriptionServer.js +16 -1
  4. package/esm/internals.js +2 -0
  5. package/esm/live-query/data-provider-live-query-storage.js +3 -1
  6. package/esm/remult-knex/index.js +46 -73
  7. package/esm/remult-mongo.js +18 -12
  8. package/esm/server/remult-api-server.js +21 -15
  9. package/esm/src/data-api.js +61 -38
  10. package/esm/src/data-providers/sql-database.js +197 -134
  11. package/esm/src/filter/filter-consumer-bridge-to-sql-request.js +35 -34
  12. package/esm/src/filter/walk-filter.js +29 -0
  13. package/esm/src/live-query/LiveQueryClient.js +192 -53
  14. package/esm/src/live-query/SseSubscriptionClient.js +130 -43
  15. package/esm/src/live-query/SubscriptionChannel.js +61 -3
  16. package/esm/src/live-query/SubscriptionServer.js +11 -3
  17. package/esm/src/remult3/Fields.js +12 -2
  18. package/esm/src/remult3/RepositoryImplementation.js +81 -21
  19. package/esm/src/remult3/remult3.js +6 -0
  20. package/esm/src/sort.js +0 -3
  21. package/esm/src/valueConverters.js +11 -0
  22. package/index.d.ts +1 -0
  23. package/internals.d.ts +4 -0
  24. package/internals.js +6 -1
  25. package/live-query/data-provider-live-query-storage.d.ts +4 -1
  26. package/live-query/data-provider-live-query-storage.js +18 -15
  27. package/package.json +1 -1
  28. package/remult-knex/index.d.ts +3 -2
  29. package/remult-knex/index.js +118 -164
  30. package/remult-mongo.js +23 -16
  31. package/server/remult-api-server.d.ts +12 -0
  32. package/server/remult-api-server.js +32 -48
  33. package/src/column-interfaces.d.ts +5 -1
  34. package/src/data-api.d.ts +6 -5
  35. package/src/data-api.js +153 -121
  36. package/src/data-providers/sql-database.d.ts +57 -5
  37. package/src/data-providers/sql-database.js +386 -255
  38. package/src/entity.d.ts +12 -0
  39. package/src/filter/filter-consumer-bridge-to-sql-request.d.ts +8 -3
  40. package/src/filter/filter-consumer-bridge-to-sql-request.js +40 -47
  41. package/src/filter/walk-filter.d.ts +3 -0
  42. package/src/filter/walk-filter.js +56 -0
  43. package/src/live-query/LiveQueryClient.js +267 -99
  44. package/src/live-query/SseSubscriptionClient.js +175 -84
  45. package/src/live-query/SubscriptionChannel.d.ts +20 -0
  46. package/src/live-query/SubscriptionChannel.js +73 -10
  47. package/src/live-query/SubscriptionServer.d.ts +11 -2
  48. package/src/live-query/SubscriptionServer.js +20 -10
  49. package/src/remult3/Fields.js +6 -0
  50. package/src/remult3/RepositoryImplementation.d.ts +9 -2
  51. package/src/remult3/RepositoryImplementation.js +299 -232
  52. package/src/remult3/remult3.d.ts +8 -13
  53. package/src/remult3/remult3.js +6 -0
  54. package/src/remult3/repository-internals.d.ts +4 -2
  55. package/src/sort.js +0 -3
  56. package/src/sql-command.d.ts +5 -0
  57. package/src/valueConverters.d.ts +4 -1
  58. 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
  }
@@ -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
- }, 45000);
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
- }, 45000);
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 {
@@ -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
- getEntityDataProvider(entity) {
49
+ get jsonColumnStorage() {
50
50
  if (!supportsJsonLoadingAndSaving(this.knex))
51
- for (const f of entity.fields.toArray()) {
52
- if (f.valueConverter.fieldTypeInDb === 'json') {
53
- //@ts-ignore
54
- f.valueConverter = {
55
- ...f.valueConverter,
56
- toDb: ValueConverters.JsonString.toDb,
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.valueConverter.fromDb(y[m]);
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.result.push((b) => b.whereRaw('lower (' +
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.result.push((b) => b.whereRaw('not lower (' +
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.result.push((b) => b.whereRaw('lower (' +
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.result.push((b) => b.whereRaw('lower (' +
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
- ") like lower ('%" +
475
- val.replace(/'/g, "''") +
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]) != undefined)
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.valueConverter.toDb(val);
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;
@@ -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.key] = toDb(f, data[f.key]);
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.add(col, val, '$regex', { $options: 'i' });
391
+ this.addRegex(col, val);
390
392
  }
391
393
  notContainsCaseInsensitive(col, val) {
392
- this.result.push(() => ({
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.add(col, `^${val}`, '$regex', { $options: 'i' });
397
+ this.addRegex(col, val, '^');
403
398
  }
404
399
  endsWithCaseInsensitive(col, val) {
405
- this.add(col, `${val}$`, '$regex', { $options: 'i' });
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, streamUrl, } from '../src/live-query/SubscriptionChannel.js';
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
- if (this.options.subscriptionServer instanceof SseSubscriptionServer) {
270
- const streamPath = this.options.rootPath + '/' + streamUrl;
271
- r.route(streamPath).get(this.process(async (remult, req, res, origReq, origRes) => {
272
- ;
273
- remult.subscriptionServer.openHttpServerStream(origReq, origRes);
274
- }));
275
- r.route(streamPath + '/subscribe').post(this.process(async (remult, _2, res, _, _1, origReq) => {
276
- const body = remult.subscriptionServer.subscribeToChannel(await this.coreOptions.getRequestBody(origReq), res, remult);
277
- }));
278
- r.route(streamPath + '/unsubscribe').post(this.process(async (remult, req, res, reqInfo, origRes, origReq) => {
279
- ;
280
- remult.subscriptionServer.subscribeToChannel(await this.coreOptions.getRequestBody(origReq), res, remult, true);
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
- res.success(await remult.liveQueryStorage.keepAliveAndReturnUnknownQueryIds(await this.coreOptions.getRequestBody(origReq)));
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) => {