remult 0.14.7 → 0.14.8
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/LICENSE +21 -0
- package/esm/assign.d.ts +1 -0
- package/esm/assign.js +5 -0
- package/esm/classType.d.ts +3 -0
- package/esm/classType.js +1 -0
- package/esm/graphql.d.ts +5 -0
- package/esm/graphql.js +102 -0
- package/esm/index.d.ts +36 -0
- package/esm/index.js +30 -0
- package/esm/inputTypes.d.ts +9 -0
- package/esm/inputTypes.js +9 -0
- package/esm/postgres/index.d.ts +2 -0
- package/esm/postgres/index.js +2 -0
- package/esm/postgres/postgres-data-provider.d.ts +28 -0
- package/esm/postgres/postgres-data-provider.js +97 -0
- package/esm/postgres/schema-builder.d.ts +15 -0
- package/esm/postgres/schema-builder.js +128 -0
- package/esm/remult-express.d.ts +5 -0
- package/esm/remult-express.js +16 -0
- package/esm/remult-knex/index.d.ts +51 -0
- package/esm/remult-knex/index.js +381 -0
- package/esm/remult-middleware.d.ts +2 -0
- package/esm/remult-middleware.js +63 -0
- package/esm/remult-mongo.d.ts +9 -0
- package/esm/remult-mongo.js +220 -0
- package/esm/server/JsonEntityFileStorage.d.ts +7 -0
- package/esm/server/JsonEntityFileStorage.js +20 -0
- package/esm/server/expressBridge.d.ts +140 -0
- package/esm/server/expressBridge.js +668 -0
- package/esm/server/index.d.ts +4 -0
- package/esm/server/index.js +4 -0
- package/esm/server/register-actions-on-server.d.ts +3 -0
- package/esm/server/register-actions-on-server.js +24 -0
- package/esm/server/register-entities-on-server.d.ts +3 -0
- package/esm/server/register-entities-on-server.js +15 -0
- package/esm/src/__EntityValueProvider.d.ts +10 -0
- package/esm/src/__EntityValueProvider.js +1 -0
- package/esm/src/__RowsOfDataForTesting.d.ts +3 -0
- package/esm/src/__RowsOfDataForTesting.js +1 -0
- package/esm/src/backend-tests/backend-database-test-setup.backend-spec.d.ts +5 -0
- package/esm/src/backend-tests/backend-database-test-setup.backend-spec.js +113 -0
- package/esm/src/backend-tests/my-test.backend-spec.d.ts +1 -0
- package/esm/src/backend-tests/my-test.backend-spec.js +49 -0
- package/esm/src/column-interfaces.d.ts +95 -0
- package/esm/src/column-interfaces.js +7 -0
- package/esm/src/column.d.ts +49 -0
- package/esm/src/column.js +133 -0
- package/esm/src/context.d.ts +124 -0
- package/esm/src/context.js +285 -0
- package/esm/src/data-api.d.ts +33 -0
- package/esm/src/data-api.js +204 -0
- package/esm/src/data-interfaces.d.ts +37 -0
- package/esm/src/data-interfaces.js +12 -0
- package/esm/src/data-providers/array-entity-data-provider.d.ts +22 -0
- package/esm/src/data-providers/array-entity-data-provider.js +264 -0
- package/esm/src/data-providers/in-memory-database.d.ts +9 -0
- package/esm/src/data-providers/in-memory-database.js +16 -0
- package/esm/src/data-providers/json-data-provider.d.ts +12 -0
- package/esm/src/data-providers/json-data-provider.js +52 -0
- package/esm/src/data-providers/rest-data-provider.d.ts +35 -0
- package/esm/src/data-providers/rest-data-provider.js +188 -0
- package/esm/src/data-providers/sql-database.d.ts +16 -0
- package/esm/src/data-providers/sql-database.js +261 -0
- package/esm/src/data-providers/web-sql-data-provider.d.ts +18 -0
- package/esm/src/data-providers/web-sql-data-provider.js +121 -0
- package/esm/src/entity.d.ts +82 -0
- package/esm/src/entity.js +1 -0
- package/esm/src/filter/filter-consumer-bridge-to-sql-request.d.ts +50 -0
- package/esm/src/filter/filter-consumer-bridge-to-sql-request.js +139 -0
- package/esm/src/filter/filter-consumer-bridge-to-url-builder.d.ts +1 -0
- package/esm/src/filter/filter-consumer-bridge-to-url-builder.js +1 -0
- package/esm/src/filter/filter-interfaces.d.ts +85 -0
- package/esm/src/filter/filter-interfaces.js +467 -0
- package/esm/src/id-entity.d.ts +4 -0
- package/esm/src/id-entity.js +8 -0
- package/esm/src/remult3/RepositoryImplementation.d.ts +326 -0
- package/esm/src/remult3/RepositoryImplementation.js +1806 -0
- package/esm/src/remult3/index.d.ts +2 -0
- package/esm/src/remult3/index.js +2 -0
- package/esm/src/remult3/remult3.d.ts +312 -0
- package/esm/src/remult3/remult3.js +1 -0
- package/esm/src/server-action.d.ts +70 -0
- package/esm/src/server-action.js +365 -0
- package/esm/src/shared-tests/db-tests-setup.d.ts +23 -0
- package/esm/src/shared-tests/db-tests-setup.js +66 -0
- package/esm/src/shared-tests/db-tests.db-tests.d.ts +26 -0
- package/esm/src/shared-tests/db-tests.db-tests.js +723 -0
- package/esm/src/shared-tests/deleteAll.d.ts +2 -0
- package/esm/src/shared-tests/deleteAll.js +4 -0
- package/esm/src/shared-tests/entityWithValidations.d.ts +14 -0
- package/esm/src/shared-tests/entityWithValidations.js +53 -0
- package/esm/src/shared-tests/index.d.ts +4 -0
- package/esm/src/shared-tests/index.js +4 -0
- package/esm/src/shared-tests/test-custom-id-column.spec.d.ts +1 -0
- package/esm/src/shared-tests/test-custom-id-column.spec.js +62 -0
- package/esm/src/shared-tests/test-special-value.spec.d.ts +10 -0
- package/esm/src/shared-tests/test-special-value.spec.js +88 -0
- package/esm/src/shared-tests/test-update-with-null.spec.d.ts +1 -0
- package/esm/src/shared-tests/test-update-with-null.spec.js +47 -0
- package/esm/src/sort.d.ts +15 -0
- package/esm/src/sort.js +82 -0
- package/esm/src/sql-command.d.ts +15 -0
- package/esm/src/sql-command.js +1 -0
- package/esm/src/tests/Done.d.ts +5 -0
- package/esm/src/tests/Done.js +11 -0
- package/esm/src/tests/TestDataApiResponse.d.ts +10 -0
- package/esm/src/tests/TestDataApiResponse.js +22 -0
- package/esm/src/tests/c.d.ts +8 -0
- package/esm/src/tests/c.js +22 -0
- package/esm/src/tests/createData.d.ts +7 -0
- package/esm/src/tests/createData.js +22 -0
- package/esm/src/tests/d.d.ts +6 -0
- package/esm/src/tests/d.js +19 -0
- package/esm/src/tests/dWithPrefilter.d.ts +7 -0
- package/esm/src/tests/dWithPrefilter.js +32 -0
- package/esm/src/tests/entityForCustomFilter.d.ts +16 -0
- package/esm/src/tests/entityForCustomFilter.js +44 -0
- package/esm/src/tests/entityWithValidationsOnColumn.d.ts +5 -0
- package/esm/src/tests/entityWithValidationsOnColumn.js +21 -0
- package/esm/src/tests/frontend-database-tests-setup.spec.d.ts +4 -0
- package/esm/src/tests/frontend-database-tests-setup.spec.js +38 -0
- package/esm/src/tests/h.d.ts +6 -0
- package/esm/src/tests/h.js +35 -0
- package/esm/src/tests/remult-3-entities.d.ts +23 -0
- package/esm/src/tests/remult-3-entities.js +72 -0
- package/esm/src/tests/tasks.d.ts +6 -0
- package/esm/src/tests/tasks.js +24 -0
- package/esm/src/tests/testHelper.spec.d.ts +34 -0
- package/esm/src/tests/testHelper.spec.js +217 -0
- package/esm/src/tests/testModel/models.d.ts +16 -0
- package/esm/src/tests/testModel/models.js +31 -0
- package/esm/src/validators.d.ts +14 -0
- package/esm/src/validators.js +33 -0
- package/esm/urlBuilder.d.ts +6 -0
- package/esm/urlBuilder.js +19 -0
- package/esm/valueConverters.d.ts +12 -0
- package/esm/valueConverters.js +169 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +22 -1
- package/postgres/index.d.ts +1 -0
- package/postgres/index.js +1 -0
- package/postgres/postgres-data-provider.d.ts +1 -13
- package/postgres/postgres-data-provider.js +28 -262
- package/postgres/schema-builder.d.ts +15 -0
- package/postgres/schema-builder.js +270 -0
- package/readme.md +68 -30
- package/src/data-providers/web-sql-data-provider.js +4 -3
- package/src/entity.d.ts +9 -2
- package/src/remult3/RepositoryImplementation.js +11 -3
- package/src/shared-tests/test-update-with-null.spec.js +38 -0
- package/valueConverters.js +4 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import knex from 'knex';
|
|
2
|
+
import { customDatabaseFilterToken, Filter } from "../src/filter/filter-interfaces";
|
|
3
|
+
import { getDbNameProvider } from "../src/filter/filter-consumer-bridge-to-sql-request";
|
|
4
|
+
import { allEntities, Remult } from "../src/context";
|
|
5
|
+
import { isAutoIncrement } from "../src/remult3";
|
|
6
|
+
import { ValueConverters } from "../valueConverters";
|
|
7
|
+
import { CompoundIdField } from '../src/column';
|
|
8
|
+
export class KnexDataProvider {
|
|
9
|
+
constructor(knex) {
|
|
10
|
+
this.knex = knex;
|
|
11
|
+
}
|
|
12
|
+
getEntityDataProvider(entity) {
|
|
13
|
+
return new KnexEntityDataProvider(entity, this.knex);
|
|
14
|
+
}
|
|
15
|
+
async transaction(action) {
|
|
16
|
+
let t = await this.knex.transaction();
|
|
17
|
+
try {
|
|
18
|
+
await action(new KnexDataProvider(t));
|
|
19
|
+
await t.commit();
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
await t.rollback();
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
static customFilter(build) {
|
|
27
|
+
return {
|
|
28
|
+
[customDatabaseFilterToken]: {
|
|
29
|
+
buildKnex: build
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
class KnexEntityDataProvider {
|
|
35
|
+
constructor(entity, knex) {
|
|
36
|
+
this.entity = entity;
|
|
37
|
+
this.knex = knex;
|
|
38
|
+
}
|
|
39
|
+
async count(where) {
|
|
40
|
+
const e = await this.init();
|
|
41
|
+
const br = new FilterConsumerBridgeToKnexRequest(e);
|
|
42
|
+
where.__applyToConsumer(br);
|
|
43
|
+
let r = await br.resolveWhere();
|
|
44
|
+
const result = (await this.knex(e.entityName)
|
|
45
|
+
.count().where(b => r.forEach(w => w(b))));
|
|
46
|
+
var row = result[0];
|
|
47
|
+
for (const key in row) {
|
|
48
|
+
if (Object.prototype.hasOwnProperty.call(row, key)) {
|
|
49
|
+
const element = row[key];
|
|
50
|
+
return +element;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async find(options) {
|
|
55
|
+
const e = await this.init();
|
|
56
|
+
let cols = [];
|
|
57
|
+
let colKeys = [];
|
|
58
|
+
for (const x of this.entity.fields) {
|
|
59
|
+
if (x.isServerExpression) {
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
cols.push(e.nameOf(x));
|
|
63
|
+
colKeys.push(x);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
let query = this.knex(e.entityName).select(cols);
|
|
67
|
+
if (options?.where) {
|
|
68
|
+
const br = new FilterConsumerBridgeToKnexRequest(e);
|
|
69
|
+
options.where.__applyToConsumer(br);
|
|
70
|
+
let r = await br.resolveWhere();
|
|
71
|
+
query.where(b => r.forEach(y => y(b)));
|
|
72
|
+
}
|
|
73
|
+
if (options.orderBy) {
|
|
74
|
+
query = query.orderBy(options.orderBy.Segments.map(s => ({
|
|
75
|
+
column: e.nameOf(s.field),
|
|
76
|
+
order: s.isDescending ? "desc" : "asc"
|
|
77
|
+
})));
|
|
78
|
+
}
|
|
79
|
+
if (options.limit) {
|
|
80
|
+
query = query.limit(options.limit);
|
|
81
|
+
if (options.page)
|
|
82
|
+
query = query.offset((options.page - 1) * options.limit);
|
|
83
|
+
}
|
|
84
|
+
const r = await query;
|
|
85
|
+
return r.map(y => {
|
|
86
|
+
let result = {};
|
|
87
|
+
let i = 0;
|
|
88
|
+
for (let m in y) {
|
|
89
|
+
let field = colKeys[i++];
|
|
90
|
+
try {
|
|
91
|
+
result[field.key] = field.valueConverter.fromDb(y[m]);
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
throw new Error("Failed to load from db:" + field.key + "\r\n" + err);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async init() {
|
|
101
|
+
return await getDbNameProvider(this.entity);
|
|
102
|
+
}
|
|
103
|
+
async update(id, data) {
|
|
104
|
+
const e = await this.init();
|
|
105
|
+
let f = new FilterConsumerBridgeToKnexRequest(e);
|
|
106
|
+
Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(id)).__applyToConsumer(f);
|
|
107
|
+
let resultFilter = this.entity.idMetadata.getIdFilter(id);
|
|
108
|
+
if (data.id != undefined)
|
|
109
|
+
resultFilter = this.entity.idMetadata.getIdFilter(data.id);
|
|
110
|
+
let updateObject = {};
|
|
111
|
+
for (const x of this.entity.fields) {
|
|
112
|
+
if (e.isDbReadonly(x)) { }
|
|
113
|
+
else if (data[x.key] !== undefined) {
|
|
114
|
+
let v = x.valueConverter.toDb(data[x.key]);
|
|
115
|
+
if (v !== undefined) {
|
|
116
|
+
let key = await e.nameOf(x);
|
|
117
|
+
updateObject[key] = v;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
let where = await f.resolveWhere();
|
|
122
|
+
await this.knex(e.entityName).update(updateObject).where(b => where.forEach(w => w(b)));
|
|
123
|
+
return this.find({ where: Filter.fromEntityFilter(this.entity, resultFilter) }).then(y => y[0]);
|
|
124
|
+
}
|
|
125
|
+
async delete(id) {
|
|
126
|
+
const e = await this.init();
|
|
127
|
+
let f = new FilterConsumerBridgeToKnexRequest(e);
|
|
128
|
+
Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(id)).__applyToConsumer(f);
|
|
129
|
+
let where = await f.resolveWhere();
|
|
130
|
+
await this.knex(e.entityName).delete().where(b => where.forEach(w => w(b)));
|
|
131
|
+
}
|
|
132
|
+
async insert(data) {
|
|
133
|
+
const e = await this.init();
|
|
134
|
+
let resultFilter;
|
|
135
|
+
if (this.entity.idMetadata.field instanceof CompoundIdField)
|
|
136
|
+
resultFilter = this.entity.idMetadata.field.resultIdFilter(undefined, data);
|
|
137
|
+
else
|
|
138
|
+
resultFilter = Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(data[this.entity.idMetadata.field.key]));
|
|
139
|
+
let insertObject = {};
|
|
140
|
+
for (const x of this.entity.fields) {
|
|
141
|
+
if (e.isDbReadonly(x)) { }
|
|
142
|
+
else {
|
|
143
|
+
let v = x.valueConverter.toDb(data[x.key]);
|
|
144
|
+
if (v != undefined) {
|
|
145
|
+
let key = await e.nameOf(x);
|
|
146
|
+
insertObject[key] = v;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
let insert = this.knex(e.entityName).insert(insertObject);
|
|
151
|
+
if (isAutoIncrement(this.entity.idMetadata.field)) {
|
|
152
|
+
let result = await insert.returning(this.entity.idMetadata.field.key);
|
|
153
|
+
let newId = result[0].id;
|
|
154
|
+
resultFilter = new Filter(x => x.isEqualTo(this.entity.idMetadata.field, newId));
|
|
155
|
+
}
|
|
156
|
+
else
|
|
157
|
+
await insert;
|
|
158
|
+
return this.find({ where: resultFilter }).then(y => {
|
|
159
|
+
return y[0];
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
export class FilterConsumerBridgeToKnexRequest {
|
|
164
|
+
constructor(nameProvider) {
|
|
165
|
+
this.nameProvider = nameProvider;
|
|
166
|
+
this._addWhere = true;
|
|
167
|
+
this.promises = [];
|
|
168
|
+
this.result = [];
|
|
169
|
+
}
|
|
170
|
+
async resolveWhere() {
|
|
171
|
+
while (this.promises.length > 0) {
|
|
172
|
+
let p = this.promises;
|
|
173
|
+
this.promises = [];
|
|
174
|
+
for (const pr of p) {
|
|
175
|
+
await pr;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return this.result;
|
|
179
|
+
}
|
|
180
|
+
custom(key, customItem) {
|
|
181
|
+
throw new Error("Custom filter should be translated before it gets here");
|
|
182
|
+
}
|
|
183
|
+
or(orElements) {
|
|
184
|
+
this.promises.push((async () => {
|
|
185
|
+
const result = [];
|
|
186
|
+
for (const element of orElements) {
|
|
187
|
+
let f = new FilterConsumerBridgeToKnexRequest(this.nameProvider);
|
|
188
|
+
f._addWhere = false;
|
|
189
|
+
element.__applyToConsumer(f);
|
|
190
|
+
let where = await f.resolveWhere();
|
|
191
|
+
if (where.length > 0) {
|
|
192
|
+
result.push(b => {
|
|
193
|
+
b.orWhere(b => {
|
|
194
|
+
where.forEach(x => x(b));
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
else
|
|
199
|
+
return; //empty or means all rows
|
|
200
|
+
}
|
|
201
|
+
if (result.length > 0) {
|
|
202
|
+
this.result.push(b => b.where(x => result.find(y => y(x))));
|
|
203
|
+
}
|
|
204
|
+
})());
|
|
205
|
+
}
|
|
206
|
+
isNull(col) {
|
|
207
|
+
this.result.push(b => b.whereNull(this.nameProvider.nameOf(col)));
|
|
208
|
+
}
|
|
209
|
+
isNotNull(col) {
|
|
210
|
+
this.result.push(b => b.whereNotNull(this.nameProvider.nameOf(col)));
|
|
211
|
+
}
|
|
212
|
+
isIn(col, val) {
|
|
213
|
+
this.result.push(knex => knex.whereIn(this.nameProvider.nameOf(col), val.map(x => col.valueConverter.toDb(x))));
|
|
214
|
+
}
|
|
215
|
+
isEqualTo(col, val) {
|
|
216
|
+
this.add(col, val, "=");
|
|
217
|
+
}
|
|
218
|
+
isDifferentFrom(col, val) {
|
|
219
|
+
this.add(col, val, "<>");
|
|
220
|
+
}
|
|
221
|
+
isGreaterOrEqualTo(col, val) {
|
|
222
|
+
this.add(col, val, ">=");
|
|
223
|
+
}
|
|
224
|
+
isGreaterThan(col, val) {
|
|
225
|
+
this.add(col, val, ">");
|
|
226
|
+
}
|
|
227
|
+
isLessOrEqualTo(col, val) {
|
|
228
|
+
this.add(col, val, "<=");
|
|
229
|
+
}
|
|
230
|
+
isLessThan(col, val) {
|
|
231
|
+
this.add(col, val, "<");
|
|
232
|
+
}
|
|
233
|
+
containsCaseInsensitive(col, val) {
|
|
234
|
+
this.result.push(b => b.whereRaw('lower (' + this.nameProvider.nameOf(col) + ") like lower ('%" + val.replace(/'/g, '\'\'') + "%')"));
|
|
235
|
+
this.promises.push((async () => {
|
|
236
|
+
})());
|
|
237
|
+
}
|
|
238
|
+
add(col, val, operator) {
|
|
239
|
+
this.result.push(b => b.where(this.nameProvider.nameOf(col), operator, col.valueConverter.toDb(val)));
|
|
240
|
+
}
|
|
241
|
+
databaseCustom(databaseCustom) {
|
|
242
|
+
this.promises.push((async () => {
|
|
243
|
+
if (databaseCustom?.buildKnex) {
|
|
244
|
+
this.result.push(await databaseCustom.buildKnex());
|
|
245
|
+
}
|
|
246
|
+
})());
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
export class KnexSchemaBuilder {
|
|
250
|
+
constructor(knex) {
|
|
251
|
+
this.knex = knex;
|
|
252
|
+
this.additionalWhere = '';
|
|
253
|
+
}
|
|
254
|
+
async verifyStructureOfAllEntities(remult) {
|
|
255
|
+
console.log("start verify structure");
|
|
256
|
+
for (const entity of allEntities) {
|
|
257
|
+
let metadata = remult.repo(entity).metadata;
|
|
258
|
+
try {
|
|
259
|
+
if (!metadata.options.sqlExpression) {
|
|
260
|
+
if ((await metadata.getDbName()).toLowerCase().indexOf('from ') < 0) {
|
|
261
|
+
await this.createIfNotExist(metadata);
|
|
262
|
+
await this.verifyAllColumns(metadata);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
console.log("failed verify structure of " + await metadata.getDbName() + " ", err);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
async createIfNotExist(entity) {
|
|
272
|
+
const e = await getDbNameProvider(entity);
|
|
273
|
+
if (!await this.knex.schema.hasTable(e.entityName)) {
|
|
274
|
+
let cols = new Map();
|
|
275
|
+
for (const f of entity.fields) {
|
|
276
|
+
cols.set(f, {
|
|
277
|
+
name: e.nameOf(f),
|
|
278
|
+
readonly: e.isDbReadonly(f)
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
await logSql(this.knex.schema.createTable(e.entityName, b => {
|
|
282
|
+
for (const x of entity.fields) {
|
|
283
|
+
if (!cols.get(x).readonly || isAutoIncrement(x)) {
|
|
284
|
+
if (isAutoIncrement(x))
|
|
285
|
+
b.increments(cols.get(x).name);
|
|
286
|
+
else {
|
|
287
|
+
buildColumn(x, cols.get(x).name, b);
|
|
288
|
+
if (x == entity.idMetadata.field)
|
|
289
|
+
b.primary([cols.get(x).name]);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async addColumnIfNotExist(entity, c) {
|
|
297
|
+
let e = await getDbNameProvider(entity);
|
|
298
|
+
if (e.isDbReadonly(c(entity)))
|
|
299
|
+
return;
|
|
300
|
+
let col = c(entity);
|
|
301
|
+
let colName = e.nameOf(col);
|
|
302
|
+
if (!await this.knex.schema.hasColumn(e.entityName, colName)) {
|
|
303
|
+
await this.knex.schema.alterTable(e.entityName, b => {
|
|
304
|
+
buildColumn(col, colName, b);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async verifyAllColumns(entity) {
|
|
309
|
+
let e = await getDbNameProvider(entity);
|
|
310
|
+
try {
|
|
311
|
+
for (const col of entity.fields) {
|
|
312
|
+
if (!e.isDbReadonly(col)) {
|
|
313
|
+
await this.addColumnIfNotExist(entity, () => col);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
catch (err) {
|
|
318
|
+
console.log(err);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
export function buildColumn(x, dbName, b) {
|
|
323
|
+
if (x.valueType == Number) {
|
|
324
|
+
if (!x.valueConverter.fieldTypeInDb) {
|
|
325
|
+
let c = b.decimal(dbName);
|
|
326
|
+
if (!x.allowNull) {
|
|
327
|
+
c.defaultTo(0).notNullable();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
else if (x.valueConverter.fieldTypeInDb == "integer") {
|
|
331
|
+
let c = b.integer(dbName);
|
|
332
|
+
if (!x.allowNull) {
|
|
333
|
+
c.defaultTo(0).notNullable();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
else
|
|
337
|
+
b.specificType(dbName, x.valueConverter.fieldTypeInDb);
|
|
338
|
+
}
|
|
339
|
+
else if (x.valueType == Date) {
|
|
340
|
+
if (!x.valueConverter.fieldTypeInDb)
|
|
341
|
+
if (x.valueConverter == ValueConverters.DateOnly)
|
|
342
|
+
b.date(dbName);
|
|
343
|
+
else
|
|
344
|
+
b.dateTime(dbName);
|
|
345
|
+
else if (x.valueConverter.fieldTypeInDb == "date")
|
|
346
|
+
b.date(dbName);
|
|
347
|
+
else
|
|
348
|
+
b.specificType(dbName, x.valueConverter.fieldTypeInDb);
|
|
349
|
+
}
|
|
350
|
+
else if (x.valueType == Boolean) {
|
|
351
|
+
let c = b.boolean(dbName);
|
|
352
|
+
if (!x.allowNull)
|
|
353
|
+
c.defaultTo(false).notNullable();
|
|
354
|
+
}
|
|
355
|
+
else if (x.valueConverter.fieldTypeInDb) {
|
|
356
|
+
if (x.valueConverter.fieldTypeInDb == "integer") {
|
|
357
|
+
let c = b.integer(dbName);
|
|
358
|
+
if (!x.allowNull) {
|
|
359
|
+
c.defaultTo(0).notNullable();
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else
|
|
363
|
+
b.specificType(dbName, x.valueConverter.fieldTypeInDb);
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
let c = b.string(dbName, x.options.maxLength);
|
|
367
|
+
if (!x.allowNull)
|
|
368
|
+
c.defaultTo('').notNullable();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function logSql(who) {
|
|
372
|
+
//console.log(who.toSQL().sql);
|
|
373
|
+
return who;
|
|
374
|
+
}
|
|
375
|
+
export async function createKnexDataProvider(config, autoCreateTables = true) {
|
|
376
|
+
let k = knex(config);
|
|
377
|
+
let result = new KnexDataProvider(k);
|
|
378
|
+
if (autoCreateTables)
|
|
379
|
+
await new KnexSchemaBuilder(k).verifyStructureOfAllEntities(new Remult(result));
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { remultMiddlewareBase } from './server/expressBridge';
|
|
2
|
+
export function remultMiddleware(options) {
|
|
3
|
+
const m = new middleware();
|
|
4
|
+
let app = Object.assign(m, (req, res, next) => m.handleRequest(req, res, next));
|
|
5
|
+
return remultMiddlewareBase(app, options);
|
|
6
|
+
}
|
|
7
|
+
class middleware {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.map = new Map();
|
|
10
|
+
}
|
|
11
|
+
route(path) {
|
|
12
|
+
//consider using:
|
|
13
|
+
//* https://raw.githubusercontent.com/cmorten/opine/main/src/utils/pathToRegex.ts
|
|
14
|
+
//* https://github.com/pillarjs/path-to-regexp
|
|
15
|
+
let r = path.toLowerCase();
|
|
16
|
+
let m = new Map();
|
|
17
|
+
this.map.set(r, m);
|
|
18
|
+
const route = {
|
|
19
|
+
get: (h) => {
|
|
20
|
+
m.set("get", h);
|
|
21
|
+
return route;
|
|
22
|
+
},
|
|
23
|
+
put: (h) => {
|
|
24
|
+
m.set("put", h);
|
|
25
|
+
return route;
|
|
26
|
+
},
|
|
27
|
+
post: (h) => {
|
|
28
|
+
m.set("post", h);
|
|
29
|
+
return route;
|
|
30
|
+
},
|
|
31
|
+
delete: (h) => {
|
|
32
|
+
m.set("delete", h);
|
|
33
|
+
return route;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
return route;
|
|
37
|
+
}
|
|
38
|
+
handleRequest(req, res, next) {
|
|
39
|
+
let lowerPath = req.path.toLowerCase();
|
|
40
|
+
let m = this.map.get(lowerPath);
|
|
41
|
+
if (m) {
|
|
42
|
+
let h = m.get(req.method.toLowerCase());
|
|
43
|
+
if (h) {
|
|
44
|
+
h(req, res, next);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
let idPosition = req.path.lastIndexOf('/');
|
|
49
|
+
if (idPosition >= 0) {
|
|
50
|
+
lowerPath = req.path.substring(0, idPosition) + '/:id';
|
|
51
|
+
m = this.map.get(lowerPath);
|
|
52
|
+
if (m) {
|
|
53
|
+
let h = m.get(req.method.toLowerCase());
|
|
54
|
+
if (h) {
|
|
55
|
+
req.params.id = req.path.substring(idPosition + 1);
|
|
56
|
+
h(req, res, next);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
next();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { MongoClient, Db } from 'mongodb';
|
|
2
|
+
import { DataProvider, EntityDataProvider, EntityMetadata } from '.';
|
|
3
|
+
export declare class MongoDataProvider implements DataProvider {
|
|
4
|
+
private db;
|
|
5
|
+
private client;
|
|
6
|
+
constructor(db: Db, client: MongoClient);
|
|
7
|
+
getEntityDataProvider(entity: EntityMetadata<any>): EntityDataProvider;
|
|
8
|
+
transaction(action: (dataProvider: DataProvider) => Promise<void>): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { CompoundIdField, Filter } from '.';
|
|
2
|
+
import { getDbNameProvider } from './src/filter/filter-consumer-bridge-to-sql-request';
|
|
3
|
+
export class MongoDataProvider {
|
|
4
|
+
constructor(db, client) {
|
|
5
|
+
this.db = db;
|
|
6
|
+
this.client = client;
|
|
7
|
+
}
|
|
8
|
+
getEntityDataProvider(entity) {
|
|
9
|
+
return new MongoEntityDataProvider(this.db, entity);
|
|
10
|
+
}
|
|
11
|
+
async transaction(action) {
|
|
12
|
+
let session = await this.client.startSession();
|
|
13
|
+
session.startTransaction();
|
|
14
|
+
try {
|
|
15
|
+
await action(new MongoDataProvider(this.db, undefined));
|
|
16
|
+
await session.commitTransaction();
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
await session.abortTransaction();
|
|
20
|
+
throw err;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const NULL = { $null: "$null" };
|
|
25
|
+
function isNull(x) {
|
|
26
|
+
return x?.$null === NULL.$null;
|
|
27
|
+
}
|
|
28
|
+
class MongoEntityDataProvider {
|
|
29
|
+
constructor(db, entity) {
|
|
30
|
+
this.db = db;
|
|
31
|
+
this.entity = entity;
|
|
32
|
+
}
|
|
33
|
+
translateFromJson(row, nameProvider) {
|
|
34
|
+
let result = {};
|
|
35
|
+
for (const col of this.entity.fields) {
|
|
36
|
+
result[col.key] = col.valueConverter.fromDb(row[nameProvider.nameOf(col)]);
|
|
37
|
+
if (isNull(result[col.key]))
|
|
38
|
+
result[col.key] = null;
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
translateToJson(row, nameProvider) {
|
|
43
|
+
let result = {};
|
|
44
|
+
for (const col of this.entity.fields) {
|
|
45
|
+
let val = col.valueConverter.toDb(row[col.key]);
|
|
46
|
+
if (val === null)
|
|
47
|
+
val = NULL;
|
|
48
|
+
result[nameProvider.nameOf(col)] = val;
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
async count(where) {
|
|
53
|
+
const { collection, e } = await this.collection();
|
|
54
|
+
let x = new FilterConsumerBridgeToMongo(e);
|
|
55
|
+
where.__applyToConsumer(x);
|
|
56
|
+
let w = await x.resolveWhere();
|
|
57
|
+
return await collection.countDocuments(w);
|
|
58
|
+
}
|
|
59
|
+
async find(options) {
|
|
60
|
+
let { collection, e } = await this.collection();
|
|
61
|
+
let x = new FilterConsumerBridgeToMongo(e);
|
|
62
|
+
if (options?.where)
|
|
63
|
+
options.where.__applyToConsumer(x);
|
|
64
|
+
let where = await x.resolveWhere();
|
|
65
|
+
let op = {};
|
|
66
|
+
if (options.limit) {
|
|
67
|
+
op.limit = options.limit;
|
|
68
|
+
if (options.page) {
|
|
69
|
+
op.skip = (options.page - 1) * options.limit;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (options.orderBy) {
|
|
73
|
+
op.sort = {};
|
|
74
|
+
for (const s of options.orderBy.Segments) {
|
|
75
|
+
op.sort[e.nameOf(s.field)] = s.isDescending ? -1 : 1;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return await Promise.all(await collection.find(where, op).map(x => this.translateFromJson(x, e)).toArray());
|
|
79
|
+
}
|
|
80
|
+
async update(id, data) {
|
|
81
|
+
let { collection, e } = await this.collection();
|
|
82
|
+
let f = new FilterConsumerBridgeToMongo(e);
|
|
83
|
+
Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(id)).__applyToConsumer(f);
|
|
84
|
+
let resultFilter = this.entity.idMetadata.getIdFilter(id);
|
|
85
|
+
if (data.id != undefined)
|
|
86
|
+
resultFilter = this.entity.idMetadata.getIdFilter(data.id);
|
|
87
|
+
for (const x of this.entity.fields) {
|
|
88
|
+
if (x instanceof CompoundIdField) {
|
|
89
|
+
resultFilter = x.resultIdFilter(id, data);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
let newR = {};
|
|
93
|
+
let keys = Object.keys(data);
|
|
94
|
+
for (const f of this.entity.fields) {
|
|
95
|
+
if (!f.dbReadOnly && !f.isServerExpression) {
|
|
96
|
+
if (keys.includes(f.key)) {
|
|
97
|
+
newR[f.key] = f.valueConverter.toJson(data[f.key]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
let r = await collection.updateOne(await f.resolveWhere(), {
|
|
102
|
+
$set: newR
|
|
103
|
+
});
|
|
104
|
+
return this.find({ where: Filter.fromEntityFilter(this.entity, resultFilter) }).then(y => y[0]);
|
|
105
|
+
}
|
|
106
|
+
async delete(id) {
|
|
107
|
+
const { e, collection } = await this.collection();
|
|
108
|
+
let f = new FilterConsumerBridgeToMongo(e);
|
|
109
|
+
Filter.fromEntityFilter(this.entity, this.entity.idMetadata.getIdFilter(id)).__applyToConsumer(f);
|
|
110
|
+
collection.deleteOne(await f.resolveWhere());
|
|
111
|
+
}
|
|
112
|
+
async insert(data) {
|
|
113
|
+
let { collection, e } = await this.collection();
|
|
114
|
+
let r = await collection.insertOne(await this.translateToJson(data, e));
|
|
115
|
+
return await this.translateFromJson(await collection.findOne({ _id: r.insertedId }), e);
|
|
116
|
+
}
|
|
117
|
+
async collection() {
|
|
118
|
+
const e = await getDbNameProvider(this.entity);
|
|
119
|
+
const collection = this.db.collection(e.entityName);
|
|
120
|
+
return { e, collection };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
class FilterConsumerBridgeToMongo {
|
|
124
|
+
constructor(nameProvider) {
|
|
125
|
+
this.nameProvider = nameProvider;
|
|
126
|
+
this._addWhere = true;
|
|
127
|
+
this.promises = [];
|
|
128
|
+
this.result = [];
|
|
129
|
+
}
|
|
130
|
+
async resolveWhere() {
|
|
131
|
+
while (this.promises.length > 0) {
|
|
132
|
+
let p = this.promises;
|
|
133
|
+
this.promises = [];
|
|
134
|
+
for (const pr of p) {
|
|
135
|
+
await pr;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (this.result.length > 0)
|
|
139
|
+
return { $and: this.result.map(x => x()) };
|
|
140
|
+
else
|
|
141
|
+
return {};
|
|
142
|
+
}
|
|
143
|
+
custom(key, customItem) {
|
|
144
|
+
throw new Error("Custom filter should be translated before it gets here");
|
|
145
|
+
}
|
|
146
|
+
or(orElements) {
|
|
147
|
+
this.promises.push((async () => {
|
|
148
|
+
let result = [];
|
|
149
|
+
for (const element of orElements) {
|
|
150
|
+
let f = new FilterConsumerBridgeToMongo(this.nameProvider);
|
|
151
|
+
f._addWhere = false;
|
|
152
|
+
element.__applyToConsumer(f);
|
|
153
|
+
let where = await f.resolveWhere();
|
|
154
|
+
if (where?.$and?.length > 0) {
|
|
155
|
+
result.push(where);
|
|
156
|
+
}
|
|
157
|
+
else
|
|
158
|
+
return; //since empty or is all rows;
|
|
159
|
+
}
|
|
160
|
+
this.result.push(() => ({
|
|
161
|
+
$or: result
|
|
162
|
+
}));
|
|
163
|
+
})());
|
|
164
|
+
}
|
|
165
|
+
isNull(col) {
|
|
166
|
+
this.add(col, NULL, "$eq");
|
|
167
|
+
}
|
|
168
|
+
isNotNull(col) {
|
|
169
|
+
this.add(col, NULL, "$ne");
|
|
170
|
+
}
|
|
171
|
+
isIn(col, val) {
|
|
172
|
+
this.result.push(() => ({
|
|
173
|
+
[this.nameProvider.nameOf(col)]: {
|
|
174
|
+
$in: val.map(x => col.valueConverter.toDb(x))
|
|
175
|
+
}
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
isEqualTo(col, val) {
|
|
179
|
+
this.add(col, val, "$eq");
|
|
180
|
+
}
|
|
181
|
+
isDifferentFrom(col, val) {
|
|
182
|
+
this.add(col, val, "$ne");
|
|
183
|
+
}
|
|
184
|
+
isGreaterOrEqualTo(col, val) {
|
|
185
|
+
this.add(col, val, "$gte");
|
|
186
|
+
}
|
|
187
|
+
isGreaterThan(col, val) {
|
|
188
|
+
this.add(col, val, "$gt");
|
|
189
|
+
}
|
|
190
|
+
isLessOrEqualTo(col, val) {
|
|
191
|
+
this.add(col, val, "$lte");
|
|
192
|
+
}
|
|
193
|
+
isLessThan(col, val) {
|
|
194
|
+
this.add(col, val, "$lt");
|
|
195
|
+
}
|
|
196
|
+
containsCaseInsensitive(col, val) {
|
|
197
|
+
throw "containsCaseInsensitive not yet implemented";
|
|
198
|
+
// this.promises.push(col.getDbName().then(colName => {
|
|
199
|
+
// this.result.push(b => b.whereRaw(
|
|
200
|
+
// 'lower (' + colName + ") like lower ('%" + val.replace(/'/g, '\'\'') + "%')"));
|
|
201
|
+
// }));
|
|
202
|
+
}
|
|
203
|
+
add(col, val, operator) {
|
|
204
|
+
this.result.push(() => ({
|
|
205
|
+
[this.nameProvider.nameOf(col)]: { [operator]: isNull(val) ? val : col.valueConverter.toDb(val) }
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
databaseCustom(databaseCustom) {
|
|
209
|
+
throw "error";
|
|
210
|
+
// this.promises.push((async () => {
|
|
211
|
+
// if (databaseCustom?.buildSql) {
|
|
212
|
+
// let item = new CustomSqlFilterBuilder(this.knex);
|
|
213
|
+
// await databaseCustom.buildSql(item);
|
|
214
|
+
// if (item.sql) {
|
|
215
|
+
// this.addToWhere("(" + item.sql + ")");
|
|
216
|
+
// }
|
|
217
|
+
// }
|
|
218
|
+
// })());
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { JsonEntityStorage } from '../index';
|
|
2
|
+
export declare class JsonEntityFileStorage implements JsonEntityStorage {
|
|
3
|
+
private folderPath;
|
|
4
|
+
getItem(entityDbName: string): string;
|
|
5
|
+
setItem(entityDbName: string, json: string): void;
|
|
6
|
+
constructor(folderPath: string);
|
|
7
|
+
}
|