remult 3.4.0-next.4 → 3.4.0-next.6
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 +2 -0
- package/SseSubscriptionServer.js +40 -0
- package/esm/SseSubscriptionServer.js +13 -0
- package/esm/index.js +7 -6
- package/esm/remult-d1.js +1 -0
- package/esm/remult-knex/index.js +112 -2
- package/esm/remult-mongo.js +35 -20
- package/esm/remult-sqlite-core.js +1 -0
- package/esm/server/remult-api-server.js +15 -14
- package/esm/src/data-api.js +19 -8
- package/esm/src/data-interfaces.js +4 -4
- package/esm/src/data-providers/array-entity-data-provider.js +14 -2
- package/esm/src/data-providers/in-memory-database.js +12 -1
- package/esm/src/data-providers/indexed-db-data-provider.js +844 -0
- package/esm/src/data-providers/json-data-provider.js +2 -2
- package/esm/src/data-providers/rest-data-provider.js +11 -12
- package/esm/src/data-providers/sql-database.js +275 -149
- package/esm/src/remult3/RepositoryImplementation.js +137 -60
- package/index.d.ts +32 -17
- package/index.js +5 -2
- package/internals.d.ts +2 -0
- package/package.json +1 -1
- package/remult-d1.d.ts +1 -0
- package/remult-d1.js +1 -0
- package/remult-knex/index.js +329 -44
- package/remult-mongo.js +103 -52
- package/remult-sqlite-core.d.ts +1 -0
- package/remult-sqlite-core.js +1 -0
- package/server/remult-api-server.d.ts +12 -0
- package/server/remult-api-server.js +19 -39
- package/src/data-api.d.ts +2 -5
- package/src/data-api.js +118 -90
- package/src/data-interfaces.d.ts +9 -3
- package/src/data-interfaces.js +4 -4
- package/src/data-providers/array-entity-data-provider.d.ts +2 -2
- package/src/data-providers/array-entity-data-provider.js +92 -14
- package/src/data-providers/in-memory-database.d.ts +5 -2
- package/src/data-providers/in-memory-database.js +22 -1
- package/src/data-providers/indexed-db-data-provider.d.ts +61 -0
- package/src/data-providers/indexed-db-data-provider.js +1459 -0
- package/src/data-providers/json-data-provider.js +2 -2
- package/src/data-providers/rest-data-provider.d.ts +2 -3
- package/src/data-providers/rest-data-provider.js +18 -17
- package/src/data-providers/sql-database.d.ts +46 -2
- package/src/data-providers/sql-database.js +544 -273
- package/src/entity.d.ts +12 -0
- package/src/live-query/SubscriptionServer.d.ts +4 -0
- package/src/remult3/RepositoryImplementation.d.ts +7 -4
- package/src/remult3/RepositoryImplementation.js +523 -326
- package/src/remult3/remult3.d.ts +6 -4
- package/src/remult3/repository-internals.d.ts +4 -2
- package/src/sql-command.d.ts +2 -0
- package/src/standard-schema/index.d.ts +1 -1
|
@@ -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
|
}
|
package/SseSubscriptionServer.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
36
|
-
export { SubscriptionChannel
|
|
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/remult-d1.js
CHANGED
package/esm/remult-knex/index.js
CHANGED
|
@@ -234,16 +234,87 @@ class KnexEntityDataProvider {
|
|
|
234
234
|
return undefined;
|
|
235
235
|
return getRowAfterUpdate(this.entity, this, data, id, 'update');
|
|
236
236
|
}
|
|
237
|
-
async delete(
|
|
237
|
+
async delete(ids) {
|
|
238
|
+
if (ids.length === 0)
|
|
239
|
+
return;
|
|
238
240
|
const e = await this.init();
|
|
239
241
|
let f = new FilterConsumerBridgeToKnexRequest(e, this.rawSqlWrapIdentifier);
|
|
240
|
-
Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(
|
|
242
|
+
Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(...ids)).__applyToConsumer(f);
|
|
241
243
|
let where = await f.resolveWhere();
|
|
242
244
|
await this.getEntityFrom(e)
|
|
243
245
|
.delete()
|
|
244
246
|
.where((b) => where.forEach((w) => w(b)));
|
|
245
247
|
}
|
|
246
248
|
async insert(data, options) {
|
|
249
|
+
if (data.length === 0)
|
|
250
|
+
return [];
|
|
251
|
+
if (isMysqlKnex(this.knex) &&
|
|
252
|
+
isAutoIncrement(this.entity.idMetadata.field) &&
|
|
253
|
+
options?.select !== 'none') {
|
|
254
|
+
const result = [];
|
|
255
|
+
for (const row of data)
|
|
256
|
+
result.push(await this.insertOne(row, options));
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
const e = await this.init();
|
|
260
|
+
const batches = splitByMaxParameters(data, (row) => countKnexInsertBindParameters(this.entity, e, row), knexMaxParametersInOneSqlStatement(this.knex));
|
|
261
|
+
const result = [];
|
|
262
|
+
for (const batch of batches)
|
|
263
|
+
result.push(...(await this.insertBatch(batch, e, options)));
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
async insertBatch(batch, e, options) {
|
|
267
|
+
const cols = [];
|
|
268
|
+
for (const x of this.entity.fields) {
|
|
269
|
+
if (isDbReadonly(x, e))
|
|
270
|
+
continue;
|
|
271
|
+
if (batch.some((row) => translateValueAndHandleArrayAndHandleArray(x, row[x.key]) !=
|
|
272
|
+
undefined))
|
|
273
|
+
cols.push(x);
|
|
274
|
+
}
|
|
275
|
+
const insertObjects = [];
|
|
276
|
+
for (const row of batch) {
|
|
277
|
+
const insertObject = {};
|
|
278
|
+
for (const x of cols) {
|
|
279
|
+
const v = translateValueAndHandleArrayAndHandleArray(x, row[x.key]);
|
|
280
|
+
const key = await e.$dbNameOf(x);
|
|
281
|
+
if (v != undefined)
|
|
282
|
+
insertObject[key] = v;
|
|
283
|
+
else if (x.allowNull)
|
|
284
|
+
insertObject[key] = null;
|
|
285
|
+
else
|
|
286
|
+
insertObject[key] = this.knex.raw('DEFAULT');
|
|
287
|
+
}
|
|
288
|
+
insertObjects.push(insertObject);
|
|
289
|
+
}
|
|
290
|
+
let insert = this.getEntityFrom(e).insert(insertObjects);
|
|
291
|
+
const wantRows = options?.select !== 'none';
|
|
292
|
+
if (isAutoIncrement(this.entity.idMetadata.field) && wantRows) {
|
|
293
|
+
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);
|
|
297
|
+
return this.loadRowsByIds(ids);
|
|
298
|
+
}
|
|
299
|
+
await insert;
|
|
300
|
+
if (!wantRows)
|
|
301
|
+
return batch.map(() => undefined);
|
|
302
|
+
return this.loadRowsByIds(batch.map((row) => this.entity.idMetadata.getId(row)));
|
|
303
|
+
}
|
|
304
|
+
async loadRowsByIds(ids) {
|
|
305
|
+
const found = await this.find({
|
|
306
|
+
where: Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(...ids)),
|
|
307
|
+
});
|
|
308
|
+
const byId = new Map(found.map((row) => [this.entity.idMetadata.getId(row) + '', row]));
|
|
309
|
+
return ids.map((id) => {
|
|
310
|
+
const row = byId.get(id + '');
|
|
311
|
+
if (!row)
|
|
312
|
+
throw new Error(`Failed to insert row - result contained ${found.length} rows`);
|
|
313
|
+
return row;
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
//@internal
|
|
317
|
+
async insertOne(data, options) {
|
|
247
318
|
const e = await this.init();
|
|
248
319
|
let insertObject = {};
|
|
249
320
|
for (const x of this.entity.fields) {
|
|
@@ -591,6 +662,45 @@ export async function createKnexDataProvider(config) {
|
|
|
591
662
|
let result = new KnexDataProvider(k);
|
|
592
663
|
return result;
|
|
593
664
|
}
|
|
665
|
+
const defaultMaxParametersInOneSqlStatement = 2000;
|
|
666
|
+
function isMysqlKnex(knex) {
|
|
667
|
+
const client = knex.client.config.client;
|
|
668
|
+
return client === 'mysql2' || client === 'mysql';
|
|
669
|
+
}
|
|
670
|
+
function knexMaxParametersInOneSqlStatement(knex) {
|
|
671
|
+
const client = String(knex.client.config.client ?? '');
|
|
672
|
+
if (client.includes('sqlite'))
|
|
673
|
+
return 999;
|
|
674
|
+
return defaultMaxParametersInOneSqlStatement;
|
|
675
|
+
}
|
|
676
|
+
function splitByMaxParameters(items, parametersInItem, maxParameters) {
|
|
677
|
+
const batches = [];
|
|
678
|
+
let batch = [];
|
|
679
|
+
let params = 0;
|
|
680
|
+
for (const item of items) {
|
|
681
|
+
const n = parametersInItem(item);
|
|
682
|
+
if (batch.length > 0 && params + n > maxParameters) {
|
|
683
|
+
batches.push(batch);
|
|
684
|
+
batch = [];
|
|
685
|
+
params = 0;
|
|
686
|
+
}
|
|
687
|
+
batch.push(item);
|
|
688
|
+
params += n;
|
|
689
|
+
}
|
|
690
|
+
if (batch.length)
|
|
691
|
+
batches.push(batch);
|
|
692
|
+
return batches;
|
|
693
|
+
}
|
|
694
|
+
function countKnexInsertBindParameters(entity, e, row) {
|
|
695
|
+
let n = 0;
|
|
696
|
+
for (const x of entity.fields) {
|
|
697
|
+
if (isDbReadonly(x, e))
|
|
698
|
+
continue;
|
|
699
|
+
if (translateValueAndHandleArrayAndHandleArray(x, row[x.key]) != undefined)
|
|
700
|
+
n++;
|
|
701
|
+
}
|
|
702
|
+
return n;
|
|
703
|
+
}
|
|
594
704
|
function translateValueAndHandleArrayAndHandleArray(field, val) {
|
|
595
705
|
let result = field.valueConverter.toDb(val);
|
|
596
706
|
if (Array.isArray(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
|
}
|
|
@@ -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(
|
|
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(
|
|
273
|
-
await collection.
|
|
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
|
-
|
|
280
|
-
|
|
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
|
-
|
|
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.
|
|
391
|
+
this.addRegex(col, val);
|
|
381
392
|
}
|
|
382
393
|
notContainsCaseInsensitive(col, val) {
|
|
383
|
-
this.
|
|
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.
|
|
397
|
+
this.addRegex(col, val, '^');
|
|
394
398
|
}
|
|
395
399
|
endsWithCaseInsensitive(col, val) {
|
|
396
|
-
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
|
+
});
|
|
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
|
}
|
|
@@ -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,19 +266,20 @@ 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);
|
package/esm/src/data-api.js
CHANGED
|
@@ -12,7 +12,7 @@ export class DataApi {
|
|
|
12
12
|
this.repository = repository;
|
|
13
13
|
this.remult = remult;
|
|
14
14
|
}
|
|
15
|
-
httpGet(res, req, serializeContext) {
|
|
15
|
+
async httpGet(res, req, serializeContext) {
|
|
16
16
|
try {
|
|
17
17
|
const action = req?.get('__action');
|
|
18
18
|
if (action?.startsWith(liveQueryAction))
|
|
@@ -22,13 +22,13 @@ export class DataApi {
|
|
|
22
22
|
case 'count':
|
|
23
23
|
return this.count(res, req, undefined);
|
|
24
24
|
case 'groupBy':
|
|
25
|
-
return res.success(this.groupBy(req, undefined));
|
|
25
|
+
return res.success(await this.groupBy(req, undefined));
|
|
26
26
|
}
|
|
27
27
|
return this.getArray(res, req, undefined);
|
|
28
28
|
}
|
|
29
29
|
catch (err) {
|
|
30
30
|
if (err.isForbiddenError)
|
|
31
|
-
res.forbidden(err);
|
|
31
|
+
res.forbidden(err.message);
|
|
32
32
|
else
|
|
33
33
|
res.error(err, this.repository.metadata);
|
|
34
34
|
}
|
|
@@ -70,7 +70,7 @@ export class DataApi {
|
|
|
70
70
|
res.success('ok');
|
|
71
71
|
return;
|
|
72
72
|
case 'query':
|
|
73
|
-
return
|
|
73
|
+
return this.query(res, req, body);
|
|
74
74
|
default:
|
|
75
75
|
return res.created(await this.post(body, req));
|
|
76
76
|
}
|
|
@@ -124,10 +124,10 @@ export class DataApi {
|
|
|
124
124
|
this.getArrayImpl(request, rest),
|
|
125
125
|
this.groupBy(aggregateRequest, { ...aggregate, where: body.where }),
|
|
126
126
|
]);
|
|
127
|
-
return {
|
|
127
|
+
return response.success({
|
|
128
128
|
items: r,
|
|
129
129
|
aggregates,
|
|
130
|
-
};
|
|
130
|
+
});
|
|
131
131
|
}
|
|
132
132
|
catch (err) {
|
|
133
133
|
if (err.isForbiddenError)
|
|
@@ -513,11 +513,22 @@ export class DataApi {
|
|
|
513
513
|
return this.repository.getEntityRef(newr).toApiJson();
|
|
514
514
|
};
|
|
515
515
|
if (Array.isArray(body)) {
|
|
516
|
-
|
|
516
|
+
let result = [];
|
|
517
517
|
await doTransaction(this.remult, async () => {
|
|
518
|
+
const items = [];
|
|
518
519
|
for (const item of body) {
|
|
519
|
-
|
|
520
|
+
let newr = this.repository.create();
|
|
521
|
+
await this.repository.getEntityRef(newr)._updateEntityBasedOnApi(item);
|
|
522
|
+
if (!this.repository.getEntityRef(newr).apiInsertAllowed) {
|
|
523
|
+
throw new ForbiddenError();
|
|
524
|
+
}
|
|
525
|
+
items.push(newr);
|
|
520
526
|
}
|
|
527
|
+
const inserted = await this.repository.insert(items, options);
|
|
528
|
+
if (options?.select === 'none')
|
|
529
|
+
result = items.map(() => undefined);
|
|
530
|
+
else
|
|
531
|
+
result = inserted.map((row) => this.repository.getEntityRef(row).toApiJson());
|
|
521
532
|
});
|
|
522
533
|
return result;
|
|
523
534
|
}
|
|
@@ -40,11 +40,11 @@ class EntityDataProviderPromiseWrapper {
|
|
|
40
40
|
constructor(dataProvider) {
|
|
41
41
|
this.dataProvider = dataProvider;
|
|
42
42
|
}
|
|
43
|
-
delete(
|
|
44
|
-
return this.dataProvider.then((dp) => dp.delete(
|
|
43
|
+
delete(ids) {
|
|
44
|
+
return this.dataProvider.then((dp) => dp.delete(ids));
|
|
45
45
|
}
|
|
46
|
-
insert(data) {
|
|
47
|
-
return this.dataProvider.then((dp) => dp.insert(data));
|
|
46
|
+
insert(data, options) {
|
|
47
|
+
return this.dataProvider.then((dp) => dp.insert(data, options));
|
|
48
48
|
}
|
|
49
49
|
count(where) {
|
|
50
50
|
return this.dataProvider.then((dp) => dp.count(where));
|
|
@@ -318,18 +318,30 @@ export class ArrayEntityDataProvider {
|
|
|
318
318
|
}
|
|
319
319
|
throw new Error(`ArrayEntityDataProvider: Couldn't find row with id "${id}" in entity "${this.entity.key}" to update`);
|
|
320
320
|
}
|
|
321
|
-
async delete(
|
|
321
|
+
async delete(ids) {
|
|
322
|
+
for (const id of ids)
|
|
323
|
+
await this.deleteOne(id);
|
|
324
|
+
}
|
|
325
|
+
//@internal
|
|
326
|
+
async deleteOne(id) {
|
|
322
327
|
const names = await this.init();
|
|
323
328
|
let idMatches = this.idMatches(id, names);
|
|
324
329
|
for (let i = 0; i < this.rows().length; i++) {
|
|
325
330
|
if (idMatches(this.rows()[i])) {
|
|
326
331
|
this.rows().splice(i, 1);
|
|
327
|
-
return
|
|
332
|
+
return;
|
|
328
333
|
}
|
|
329
334
|
}
|
|
330
335
|
throw new Error(`ArrayEntityDataProvider: Couldn't find row with id "${id}" in entity "${this.entity.key}" to delete`);
|
|
331
336
|
}
|
|
332
337
|
async insert(data) {
|
|
338
|
+
const result = [];
|
|
339
|
+
for (const row of data)
|
|
340
|
+
result.push(await this.insertOne(row));
|
|
341
|
+
return result;
|
|
342
|
+
}
|
|
343
|
+
//@internal
|
|
344
|
+
async insertOne(data) {
|
|
333
345
|
const names = await this.init();
|
|
334
346
|
let j = this.translateToJson(data, names);
|
|
335
347
|
let idf = this.entity.idMetadata.field;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getEntityMetadata, } from '../remult3/RepositoryImplementation.js';
|
|
1
2
|
import { ArrayEntityDataProvider } from './array-entity-data-provider.js';
|
|
2
3
|
export class InMemoryDataProvider {
|
|
3
4
|
async transaction(action) {
|
|
@@ -15,7 +16,17 @@ export class InMemoryDataProvider {
|
|
|
15
16
|
let name = entity.dbName;
|
|
16
17
|
if (!this.rows[name])
|
|
17
18
|
this.rows[name] = [];
|
|
18
|
-
return new ArrayEntityDataProvider(entity, () =>
|
|
19
|
+
return new ArrayEntityDataProvider(entity, () => {
|
|
20
|
+
if (!this.rows[name])
|
|
21
|
+
this.rows[name] = [];
|
|
22
|
+
return this.rows[name];
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
async dropDatabase() {
|
|
26
|
+
this.rows = {};
|
|
27
|
+
}
|
|
28
|
+
async dropTable(entity) {
|
|
29
|
+
delete this.rows[getEntityMetadata(entity).dbName];
|
|
19
30
|
}
|
|
20
31
|
toString() {
|
|
21
32
|
return 'InMemoryDataProvider';
|