remult 3.4.0-next.1 → 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 (79) hide show
  1. package/SseSubscriptionServer.d.ts +2 -0
  2. package/SseSubscriptionServer.js +40 -0
  3. package/esm/SseSubscriptionServer.js +13 -0
  4. package/esm/index.js +7 -6
  5. package/esm/internals.js +2 -0
  6. package/esm/remult-d1.js +1 -0
  7. package/esm/remult-knex/index.js +149 -66
  8. package/esm/remult-mongo.js +35 -20
  9. package/esm/remult-sqlite-core.js +1 -0
  10. package/esm/server/remult-api-server.js +15 -14
  11. package/esm/src/data-api.js +60 -38
  12. package/esm/src/data-interfaces.js +4 -4
  13. package/esm/src/data-providers/array-entity-data-provider.js +14 -2
  14. package/esm/src/data-providers/in-memory-database.js +12 -1
  15. package/esm/src/data-providers/indexed-db-data-provider.js +844 -0
  16. package/esm/src/data-providers/json-data-provider.js +2 -2
  17. package/esm/src/data-providers/rest-data-provider.js +11 -12
  18. package/esm/src/data-providers/sql-database.js +302 -172
  19. package/esm/src/filter/filter-consumer-bridge-to-sql-request.js +35 -34
  20. package/esm/src/filter/walk-filter.js +29 -0
  21. package/esm/src/live-query/LiveQueryClient.js +53 -25
  22. package/esm/src/live-query/SseSubscriptionClient.js +49 -20
  23. package/esm/src/live-query/SubscriptionChannel.js +34 -10
  24. package/esm/src/remult3/Fields.js +12 -2
  25. package/esm/src/remult3/RepositoryImplementation.js +169 -71
  26. package/esm/src/remult3/remult3.js +2 -2
  27. package/esm/src/sort.js +0 -3
  28. package/esm/src/valueConverters.js +11 -0
  29. package/index.d.ts +32 -17
  30. package/index.js +5 -2
  31. package/internals.d.ts +4 -0
  32. package/internals.js +6 -1
  33. package/package.json +1 -1
  34. package/remult-d1.d.ts +1 -0
  35. package/remult-d1.js +1 -0
  36. package/remult-knex/index.d.ts +3 -2
  37. package/remult-knex/index.js +381 -142
  38. package/remult-mongo.js +103 -52
  39. package/remult-sqlite-core.d.ts +1 -0
  40. package/remult-sqlite-core.js +1 -0
  41. package/server/remult-api-server.d.ts +12 -0
  42. package/server/remult-api-server.js +19 -39
  43. package/src/column-interfaces.d.ts +5 -1
  44. package/src/data-api.d.ts +6 -5
  45. package/src/data-api.js +152 -121
  46. package/src/data-interfaces.d.ts +9 -3
  47. package/src/data-interfaces.js +4 -4
  48. package/src/data-providers/array-entity-data-provider.d.ts +2 -2
  49. package/src/data-providers/array-entity-data-provider.js +92 -14
  50. package/src/data-providers/in-memory-database.d.ts +5 -2
  51. package/src/data-providers/in-memory-database.js +22 -1
  52. package/src/data-providers/indexed-db-data-provider.d.ts +61 -0
  53. package/src/data-providers/indexed-db-data-provider.js +1459 -0
  54. package/src/data-providers/json-data-provider.js +2 -2
  55. package/src/data-providers/rest-data-provider.d.ts +2 -3
  56. package/src/data-providers/rest-data-provider.js +18 -17
  57. package/src/data-providers/sql-database.d.ts +57 -5
  58. package/src/data-providers/sql-database.js +587 -307
  59. package/src/entity.d.ts +12 -0
  60. package/src/filter/filter-consumer-bridge-to-sql-request.d.ts +8 -3
  61. package/src/filter/filter-consumer-bridge-to-sql-request.js +40 -47
  62. package/src/filter/walk-filter.d.ts +3 -0
  63. package/src/filter/walk-filter.js +56 -0
  64. package/src/live-query/LiveQueryClient.js +87 -37
  65. package/src/live-query/SseSubscriptionClient.js +75 -44
  66. package/src/live-query/SubscriptionChannel.d.ts +12 -0
  67. package/src/live-query/SubscriptionChannel.js +36 -11
  68. package/src/live-query/SubscriptionServer.d.ts +4 -0
  69. package/src/remult3/Fields.js +6 -0
  70. package/src/remult3/RepositoryImplementation.d.ts +12 -5
  71. package/src/remult3/RepositoryImplementation.js +553 -338
  72. package/src/remult3/remult3.d.ts +7 -5
  73. package/src/remult3/remult3.js +2 -2
  74. package/src/remult3/repository-internals.d.ts +4 -2
  75. package/src/sort.js +0 -3
  76. package/src/sql-command.d.ts +7 -0
  77. package/src/standard-schema/index.d.ts +1 -1
  78. package/src/valueConverters.d.ts +4 -1
  79. package/src/valueConverters.js +13 -0
@@ -1,10 +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';
4
5
  /** Keep below typical proxy idle (30s) and Heroku H15 (55s). */
5
6
  export declare const ssePingMs = 15000;
6
7
  export declare class SseSubscriptionServer implements SubscriptionServer {
7
8
  private canUserConnectToChannel?;
9
+ initApiServer(api: SubscriptionServerRouteApi): void;
8
10
  constructor(canUserConnectToChannel?: ((channel: string, remult: Remult) => boolean) | undefined);
9
11
  publishMessage<T>(channel: string, message: any): Promise<void>;
10
12
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
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");
6
7
  /** Keep below typical proxy idle (30s) and Heroku H15 (55s). */
7
8
  exports.ssePingMs = 15000;
8
9
  var SseSubscriptionServer = /** @class */ (function () {
@@ -18,6 +19,45 @@ var SseSubscriptionServer = /** @class */ (function () {
18
19
  this.canUserConnectToChannel = function () { return true; };
19
20
  }
20
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
+ };
21
61
  //@internal
22
62
  SseSubscriptionServer.prototype.subscribeToChannel = function (_a, res, remult, remove) {
23
63
  var e_1, _b;
@@ -1,8 +1,21 @@
1
1
  import { ConnectionNotFoundError } from './src/live-query/SseSubscriptionClient.js';
2
+ import { streamUrl } from './src/live-query/SubscriptionChannel.js';
2
3
  /** Keep below typical proxy idle (30s) and Heroku H15 (55s). */
3
4
  export const ssePingMs = 15_000;
4
5
  export class SseSubscriptionServer {
5
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
+ }
6
19
  //@internal
7
20
  subscribeToChannel({ channel, clientId }, res, remult, remove = false) {
8
21
  for (const c of this.connections) {
package/esm/index.js CHANGED
@@ -9,31 +9,32 @@ export { LabelTransformer } from './src/remult3/RepositoryImplementation.js';
9
9
  export const CaptionTransformer = LabelTransformer;
10
10
  export { Entity } from './src/remult3/entity.js';
11
11
  export { getEntityRef } from './src/remult3/getEntityRef.js';
12
- export { Field, Fields, Relations, } from './src/remult3/Fields.js';
12
+ export { Field, Fields, Relations } from './src/remult3/Fields.js';
13
13
  export { IdEntity } from './src/remult3/IdEntity.js';
14
14
  export { describeClass, describeBackendMethods, describeEntity, } from './src/remult3/classDescribers.js';
15
- export { EntityError, } from './src/data-interfaces.js'; //V
15
+ export { EntityError } from './src/data-interfaces.js'; //V
16
16
  export { RestDataProvider } from './src/data-providers/rest-data-provider.js'; //V
17
17
  export { InMemoryDataProvider } from './src/data-providers/in-memory-database.js'; //V
18
18
  export { SqlDatabase } from './src/data-providers/sql-database.js'; //V
19
19
  export { CustomSqlFilterBuilder, dbNamesOf, } from './src/filter/filter-consumer-bridge-to-sql-request.js';
20
- export { JsonDataProvider, } from './src/data-providers/json-data-provider.js'; //V
20
+ export { JsonDataProvider } from './src/data-providers/json-data-provider.js'; //V
21
21
  export { JsonEntityOpfsStorage } from './src/data-providers/json-entity-opfs-storage.js';
22
22
  export { JsonEntityIndexedDbStorage } from './src/data-providers/json-entity-indexed-db-data-provider.js';
23
+ export { IndexedDbDataProvider, IndexedDbIndexBuilder, } from './src/data-providers/indexed-db-data-provider.js';
23
24
  //export * from './src/data-api'; //reconsider if to make internal
24
25
  export { Controller, BackendMethod, ProgressListener, ForbiddenError, } from './src/server-action.js';
25
26
  export { Allow, Remult, withRemult, isBackend, EventSource, } from './src/context.js';
26
27
  export { Sort } from './src/sort.js';
27
28
  export { CompoundIdField } from './src/CompoundIdField.js';
28
- export { Filter, } from './src/filter/filter-interfaces.js';
29
+ export { Filter } from './src/filter/filter-interfaces.js';
29
30
  export { UrlBuilder } from './urlBuilder.js';
30
31
  export { Validators, valueValidator, createValidator, createValidatorWithArgs, createValueValidatorWithArgs, createValueValidator, } from './src/validators.js';
31
32
  export { ValueConverters } from './src/valueConverters.js';
32
33
  export { remult } from './src/remult-proxy.js';
33
34
  import { remult } from './src/remult-proxy.js';
34
35
  //export { getId } from './src/remult3/getId';
35
- export { InMemoryLiveQueryStorage, } from './src/live-query/SubscriptionServer.js';
36
- export { SubscriptionChannel, } from './src/live-query/SubscriptionChannel.js';
36
+ export { InMemoryLiveQueryStorage } from './src/live-query/SubscriptionServer.js';
37
+ export { SubscriptionChannel } from './src/live-query/SubscriptionChannel.js';
37
38
  /**
38
39
  * A convenient shortcut function to quickly obtain a repository for a specific entity type in Remult.
39
40
  *
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';
package/esm/remult-d1.js CHANGED
@@ -6,6 +6,7 @@ export function createD1DataProvider(d1) {
6
6
  }
7
7
  export class D1DataProvider extends SqliteCoreDataProvider {
8
8
  d1;
9
+ maxParametersInOneSqlStatement = 100;
9
10
  /**
10
11
  * For production or local d1 using binding
11
12
  *
@@ -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;
@@ -234,23 +219,91 @@ class KnexEntityDataProvider {
234
219
  return undefined;
235
220
  return getRowAfterUpdate(this.entity, this, data, id, 'update');
236
221
  }
237
- async delete(id) {
222
+ async delete(ids) {
223
+ if (ids.length === 0)
224
+ return;
238
225
  const e = await this.init();
239
- let f = new FilterConsumerBridgeToKnexRequest(e, this.rawSqlWrapIdentifier);
240
- Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(id)).__applyToConsumer(f);
226
+ let f = new FilterConsumerBridgeToKnexRequest(e, this.rawSqlWrapIdentifier, this.dp.jsonColumnStorage);
227
+ Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(...ids)).__applyToConsumer(f);
241
228
  let where = await f.resolveWhere();
242
229
  await this.getEntityFrom(e)
243
230
  .delete()
244
231
  .where((b) => where.forEach((w) => w(b)));
245
232
  }
246
233
  async insert(data, options) {
234
+ if (data.length === 0)
235
+ return [];
236
+ if (isMysqlKnex(this.knex) &&
237
+ isAutoIncrement(this.entity.idMetadata.field) &&
238
+ options?.select !== 'none') {
239
+ const result = [];
240
+ for (const row of data)
241
+ result.push(await this.insertOne(row, options));
242
+ return result;
243
+ }
244
+ const e = await this.init();
245
+ const batches = splitByMaxParameters(data, (row) => countKnexInsertBindParameters(this.entity, e, row, this.dp.jsonColumnStorage), knexMaxParametersInOneSqlStatement(this.knex));
246
+ const result = [];
247
+ for (const batch of batches)
248
+ result.push(...(await this.insertBatch(batch, e, options)));
249
+ return result;
250
+ }
251
+ async insertBatch(batch, e, options) {
252
+ const cols = [];
253
+ for (const x of this.entity.fields) {
254
+ if (isDbReadonly(x, e))
255
+ continue;
256
+ if (batch.some((row) => translateValueAndHandleArrayAndHandleArray(x, row[x.key], this.dp.jsonColumnStorage) != undefined))
257
+ cols.push(x);
258
+ }
259
+ const insertObjects = [];
260
+ for (const row of batch) {
261
+ const insertObject = {};
262
+ for (const x of cols) {
263
+ const v = translateValueAndHandleArrayAndHandleArray(x, row[x.key], this.dp.jsonColumnStorage);
264
+ const key = await e.$dbNameOf(x);
265
+ if (v != undefined)
266
+ insertObject[key] = v;
267
+ else if (x.allowNull)
268
+ insertObject[key] = null;
269
+ else
270
+ insertObject[key] = this.knex.raw('DEFAULT');
271
+ }
272
+ insertObjects.push(insertObject);
273
+ }
274
+ let insert = this.getEntityFrom(e).insert(insertObjects);
275
+ const wantRows = options?.select !== 'none';
276
+ if (isAutoIncrement(this.entity.idMetadata.field) && wantRows) {
277
+ const result = await insert.returning(this.entity.idMetadata.field.dbName);
278
+ const ids = result.map((r) => r && typeof r === 'object' ? r[this.entity.idMetadata.field.dbName] : r);
279
+ return this.loadRowsByIds(ids);
280
+ }
281
+ await insert;
282
+ if (!wantRows)
283
+ return batch.map(() => undefined);
284
+ return this.loadRowsByIds(batch.map((row) => this.entity.idMetadata.getId(row)));
285
+ }
286
+ async loadRowsByIds(ids) {
287
+ const found = await this.find({
288
+ where: Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(...ids)),
289
+ });
290
+ const byId = new Map(found.map((row) => [this.entity.idMetadata.getId(row) + '', row]));
291
+ return ids.map((id) => {
292
+ const row = byId.get(id + '');
293
+ if (!row)
294
+ throw new Error(`Failed to insert row - result contained ${found.length} rows`);
295
+ return row;
296
+ });
297
+ }
298
+ //@internal
299
+ async insertOne(data, options) {
247
300
  const e = await this.init();
248
301
  let insertObject = {};
249
302
  for (const x of this.entity.fields) {
250
303
  if (isDbReadonly(x, e)) {
251
304
  }
252
305
  else {
253
- let v = translateValueAndHandleArrayAndHandleArray(x, data[x.key]);
306
+ let v = translateValueAndHandleArrayAndHandleArray(x, data[x.key], this.dp.jsonColumnStorage);
254
307
  if (v != undefined) {
255
308
  let key = await e.$dbNameOf(x);
256
309
  insertObject[key] = v;
@@ -286,6 +339,7 @@ class KnexEntityDataProvider {
286
339
  class FilterConsumerBridgeToKnexRequest {
287
340
  innerNameProvider;
288
341
  rawSqlWrapIdentifier;
342
+ jsonColumnStorage;
289
343
  _addWhere = true;
290
344
  promises = [];
291
345
  result = [];
@@ -299,9 +353,10 @@ class FilterConsumerBridgeToKnexRequest {
299
353
  }
300
354
  return this.result;
301
355
  }
302
- constructor(innerNameProvider, rawSqlWrapIdentifier) {
356
+ constructor(innerNameProvider, rawSqlWrapIdentifier, jsonColumnStorage) {
303
357
  this.innerNameProvider = innerNameProvider;
304
358
  this.rawSqlWrapIdentifier = rawSqlWrapIdentifier;
359
+ this.jsonColumnStorage = jsonColumnStorage;
305
360
  }
306
361
  custom(key, customItem) {
307
362
  throw new Error('Custom filter should be translated before it gets here');
@@ -310,7 +365,7 @@ class FilterConsumerBridgeToKnexRequest {
310
365
  this.promises.push((async () => {
311
366
  const result = [];
312
367
  for (const element of orElements) {
313
- let f = new FilterConsumerBridgeToKnexRequest(this.innerNameProvider, this.rawSqlWrapIdentifier);
368
+ let f = new FilterConsumerBridgeToKnexRequest(this.innerNameProvider, this.rawSqlWrapIdentifier, this.jsonColumnStorage);
314
369
  f._addWhere = false;
315
370
  element.__applyToConsumer(f);
316
371
  let where = await f.resolveWhere();
@@ -331,7 +386,7 @@ class FilterConsumerBridgeToKnexRequest {
331
386
  }
332
387
  not(element) {
333
388
  this.promises.push((async () => {
334
- let f = new FilterConsumerBridgeToKnexRequest(this.innerNameProvider, this.rawSqlWrapIdentifier);
389
+ let f = new FilterConsumerBridgeToKnexRequest(this.innerNameProvider, this.rawSqlWrapIdentifier, this.jsonColumnStorage);
335
390
  f._addWhere = false;
336
391
  element.__applyToConsumer(f);
337
392
  let where = await f.resolveWhere();
@@ -353,7 +408,7 @@ class FilterConsumerBridgeToKnexRequest {
353
408
  this.result.push((b) => b.whereNotNull(this.innerNameProvider.$dbNameOf(col)));
354
409
  }
355
410
  isIn(col, val) {
356
- 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))));
357
412
  }
358
413
  isEqualTo(col, val) {
359
414
  this.add(col, val, '=');
@@ -374,39 +429,27 @@ class FilterConsumerBridgeToKnexRequest {
374
429
  this.add(col, val, '<');
375
430
  }
376
431
  containsCaseInsensitive(col, val) {
377
- this.result.push((b) => b.whereRaw('lower (' +
378
- b.client.ref(this.innerNameProvider.$dbNameOf(col)) +
379
- ") like lower ('%" +
380
- val.replace(/'/g, "''") +
381
- "%')"));
382
- this.promises.push((async () => { })());
432
+ this.like(col, '%' + escapeLike(val) + '%');
383
433
  }
384
434
  notContainsCaseInsensitive(col, val) {
385
- this.result.push((b) => b.whereRaw('not lower (' +
386
- b.client.ref(this.innerNameProvider.$dbNameOf(col)) +
387
- ") like lower ('%" +
388
- val.replace(/'/g, "''") +
389
- "%')"));
390
- this.promises.push((async () => { })());
435
+ this.like(col, '%' + escapeLike(val) + '%', 'not ');
391
436
  }
392
437
  startsWithCaseInsensitive(col, val) {
393
- this.result.push((b) => b.whereRaw('lower (' +
394
- b.client.ref(this.innerNameProvider.$dbNameOf(col)) +
395
- ") like lower ('" +
396
- val.replace(/'/g, "''") +
397
- "%')"));
398
- this.promises.push((async () => { })());
438
+ this.like(col, escapeLike(val) + '%');
399
439
  }
400
440
  endsWithCaseInsensitive(col, val) {
401
- 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 (' +
402
446
  b.client.ref(this.innerNameProvider.$dbNameOf(col)) +
403
- ") like lower ('%" +
404
- val.replace(/'/g, "''") +
405
- "')"));
447
+ ') like lower (?)' +
448
+ LIKE_ESCAPE_SQL, [pattern]));
406
449
  this.promises.push((async () => { })());
407
450
  }
408
451
  add(col, val, operator) {
409
- 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)));
410
453
  }
411
454
  databaseCustom(databaseCustom) {
412
455
  this.promises.push((async () => {
@@ -415,7 +458,7 @@ class FilterConsumerBridgeToKnexRequest {
415
458
  }
416
459
  if (databaseCustom?.buildSql) {
417
460
  let r = new KnexCommandHelper();
418
- const item = new CustomSqlFilterBuilder(r, this.rawSqlWrapIdentifier);
461
+ const item = new CustomSqlFilterBuilder(r, this.rawSqlWrapIdentifier, this.jsonColumnStorage);
419
462
  let sql = await databaseCustom.buildSql(item);
420
463
  if (typeof sql !== 'string')
421
464
  sql = item.sql;
@@ -591,8 +634,48 @@ export async function createKnexDataProvider(config) {
591
634
  let result = new KnexDataProvider(k);
592
635
  return result;
593
636
  }
594
- function translateValueAndHandleArrayAndHandleArray(field, val) {
595
- let result = field.valueConverter.toDb(val);
637
+ const defaultMaxParametersInOneSqlStatement = 2000;
638
+ function isMysqlKnex(knex) {
639
+ const client = knex.client.config.client;
640
+ return client === 'mysql2' || client === 'mysql';
641
+ }
642
+ function knexMaxParametersInOneSqlStatement(knex) {
643
+ const client = String(knex.client.config.client ?? '');
644
+ if (client.includes('sqlite'))
645
+ return 999;
646
+ return defaultMaxParametersInOneSqlStatement;
647
+ }
648
+ function splitByMaxParameters(items, parametersInItem, maxParameters) {
649
+ const batches = [];
650
+ let batch = [];
651
+ let params = 0;
652
+ for (const item of items) {
653
+ const n = parametersInItem(item);
654
+ if (batch.length > 0 && params + n > maxParameters) {
655
+ batches.push(batch);
656
+ batch = [];
657
+ params = 0;
658
+ }
659
+ batch.push(item);
660
+ params += n;
661
+ }
662
+ if (batch.length)
663
+ batches.push(batch);
664
+ return batches;
665
+ }
666
+ function countKnexInsertBindParameters(entity, e, row, storage) {
667
+ let n = 0;
668
+ for (const x of entity.fields) {
669
+ if (isDbReadonly(x, e))
670
+ continue;
671
+ if (translateValueAndHandleArrayAndHandleArray(x, row[x.key], storage) !=
672
+ undefined)
673
+ n++;
674
+ }
675
+ return n;
676
+ }
677
+ function translateValueAndHandleArrayAndHandleArray(field, val, storage) {
678
+ let result = toDbColumn(field, val, storage);
596
679
  if (Array.isArray(result))
597
680
  return JSON.stringify(result);
598
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
  }
@@ -266,22 +268,31 @@ class MongoEntityDataProvider {
266
268
  return undefined;
267
269
  return getRowAfterUpdate(this.entity, this, data, id, 'update');
268
270
  }
269
- async delete(id) {
271
+ async delete(ids) {
272
+ if (ids.length === 0)
273
+ return;
270
274
  const { e, collection } = await this.collection();
271
275
  let f = new FilterConsumerBridgeToMongo(e);
272
- Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(id)).__applyToConsumer(f);
273
- await collection.deleteOne(await f.resolveWhere(), {
276
+ Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(...ids)).__applyToConsumer(f);
277
+ await collection.deleteMany(await f.resolveWhere(), {
274
278
  session: this.session,
275
279
  });
276
280
  }
277
281
  async insert(data, options) {
282
+ if (data.length === 0)
283
+ return [];
278
284
  let { collection, e } = await this.collection();
279
- let r = await collection.insertOne(await this.translateToDb(data, e), {
280
- session: this.session,
281
- });
285
+ const docs = [];
286
+ for (const row of data)
287
+ docs.push(await this.translateToDb(row, e));
288
+ const r = await collection.insertMany(docs, { session: this.session });
282
289
  if (options?.select === 'none')
283
- return undefined;
284
- return await this.translateFromDb(await collection.findOne({ _id: r.insertedId }, { session: this.session }), e);
290
+ return data.map(() => undefined);
291
+ const result = [];
292
+ for (let i = 0; i < data.length; i++) {
293
+ result.push(await this.translateFromDb(await collection.findOne({ _id: r.insertedIds[i] }, { session: this.session }), e));
294
+ }
295
+ return result;
285
296
  }
286
297
  async collection() {
287
298
  const e = await dbNamesOf(this.entity);
@@ -377,23 +388,27 @@ class FilterConsumerBridgeToMongo {
377
388
  this.add(col, val, '$lt');
378
389
  }
379
390
  containsCaseInsensitive(col, val) {
380
- this.add(col, val, '$regex', { $options: 'i' });
391
+ this.addRegex(col, val);
381
392
  }
382
393
  notContainsCaseInsensitive(col, val) {
383
- this.result.push(() => ({
384
- [this.nameProvider.$dbNameOf(col)]: {
385
- $not: {
386
- $regex: isNull(val) ? val : toDb(col, val),
387
- $options: 'i',
388
- },
389
- },
390
- }));
394
+ this.addRegex(col, val, '', '', true);
391
395
  }
392
396
  startsWithCaseInsensitive(col, val) {
393
- this.add(col, `^${val}`, '$regex', { $options: 'i' });
397
+ this.addRegex(col, val, '^');
394
398
  }
395
399
  endsWithCaseInsensitive(col, val) {
396
- 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
+ });
397
412
  }
398
413
  add(col, val, operator, moreOptions) {
399
414
  this.result.push(() => ({
@@ -13,6 +13,7 @@ export class SqliteCoreDataProvider {
13
13
  this.doesNotSupportReturningSyntaxOnlyForUpdate = doesNotSupportReturningSyntaxOnlyForUpdate;
14
14
  }
15
15
  orderByNullsFirst;
16
+ maxParametersInOneSqlStatement = 999;
16
17
  getLimitSqlSyntax(limit, offset) {
17
18
  return ' limit ' + limit + ' offset ' + offset;
18
19
  }