remult 0.13.27 → 0.13.31

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remult",
3
- "version": "0.13.27",
3
+ "version": "0.13.31",
4
4
  "description": "A CRUD framework for full-stack TypeScript",
5
5
  "homepage": "https://remult.dev/",
6
6
  "repository": {
package/readme.md CHANGED
@@ -21,6 +21,9 @@
21
21
  <a href="https://discord.gg/GXHk7ZfuG5">
22
22
  <img alt="chat on discord" src="https://img.shields.io/badge/chat-on%20discord-blueviolet"/>
23
23
  </a>
24
+ <a href="https://twitter.com/remultjs">
25
+ <img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/remultjs?style=social">
26
+ </a>
24
27
  </div>
25
28
 
26
29
  ## What is Remult?
@@ -114,24 +117,29 @@ static async increasePriceOfTofu(priceIncrease: number, remult?: Remult) {
114
117
  ### :ballot_box_with_check: Data validation and constraints - defined once
115
118
 
116
119
  ```ts
117
- import { Entity, Fields } from 'remult';
120
+ import { Entity, Fields, Validators } from 'remult';
118
121
 
119
122
  @Entity('products', {
120
123
  allowApiCrud: true
121
124
  })
122
125
  export class Product {
126
+ @Fields.string({
127
+ validate: Validators.required
128
+ })
129
+ name = '';
130
+
123
131
  @Fields.string<Product>({
124
132
  validate: product => {
125
- if (product.name.trim().length == 0)
126
- throw 'required';
133
+ if (product.description.trim().length < 50)
134
+ throw "too short";
127
135
  }
128
136
  })
129
- name = '';
137
+ description = '';
130
138
 
131
139
  @Fields.number({
132
140
  validate: (_, field) => {
133
141
  if (field.value < 0)
134
- throw "must not be less than 0";
142
+ field.error = "must not be less than 0"; // or: throw "must not be less than 0";
135
143
  }
136
144
  })
137
145
  unitPrice = 0;
@@ -181,7 +189,11 @@ export class Article {
181
189
  [CRM demo](https://github.com/remult/crm-demo) with a React + [MUI](https://mui.com) front-end and Postgres database.
182
190
 
183
191
  ## Contributing
184
- Contributions are welcome. See [CONTRIBUTING](CONTRIBUTING.md).
192
+ Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
193
+
194
+ * :speech_balloon: Any feedback or suggestions? Start a [discussion](https://github.com/remult/remult/discussions).
195
+ * :muscle: Want to help out? Look for "help wanted" labeled [issues](https://github.com/remult/remult/issues).
196
+ * :star: Give this repo a star.
185
197
 
186
198
  ## License
187
199
  Remult is [MIT Licensed](LICENSE).
@@ -0,0 +1,2 @@
1
+ import { RemultMiddlewareOptions } from './server/expressBridge';
2
+ export declare function remultMiddleware(options?: RemultMiddlewareOptions): import("./server/expressBridge").RemultExpressBridge;
@@ -0,0 +1,64 @@
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
+ console.log(req.path);
62
+ next();
63
+ }
64
+ }
@@ -37,6 +37,7 @@ declare class ExpressBridge {
37
37
  dataProvider: DataProvider | Promise<DataProvider>;
38
38
  openApiDoc(options: {
39
39
  title: string;
40
+ version?: string;
40
41
  }): any;
41
42
  backendMethodsOpenApi: {
42
43
  path: string;
@@ -61,7 +61,9 @@ function remultExpress(options) {
61
61
  var bridge = new ExpressBridge(app, new inProcessQueueHandler(options.queueStorage), options.initRequest, dataProvider);
62
62
  if (options.logApiEndPoints !== undefined)
63
63
  bridge.logApiEndPoints = options.logApiEndPoints;
64
- var apiArea = bridge.addArea((options === null || options === void 0 ? void 0 : options.rootPath) || __1.Remult.apiBaseUrl);
64
+ if (options.rootPath === undefined)
65
+ options.rootPath = __1.Remult.apiBaseUrl;
66
+ var apiArea = bridge.addArea(options.rootPath);
65
67
  if (!options.disableAutoApi) {
66
68
  var actions = [];
67
69
  if (options.entities)
@@ -91,11 +93,14 @@ var ExpressBridge = /** @class */ (function () {
91
93
  ExpressBridge.prototype.openApiDoc = function (options) {
92
94
  var e_1, _a, e_2, _b;
93
95
  var r = new __1.Remult();
96
+ if (!options.version)
97
+ options.version = "1.0.0";
94
98
  var spec = {
95
- info: { title: options.title },
99
+ info: { title: options.title, version: options.version },
96
100
  openapi: "3.0.0",
97
101
  //swagger: "2.0",
98
102
  "components": {
103
+ "schemas": {},
99
104
  "securitySchemes": {
100
105
  "bearerAuth": {
101
106
  "scheme": "bearer",
@@ -104,8 +109,7 @@ var ExpressBridge = /** @class */ (function () {
104
109
  }
105
110
  }
106
111
  },
107
- paths: {},
108
- definitions: {}
112
+ paths: {}
109
113
  };
110
114
  var validationError = {
111
115
  "400": {
@@ -113,7 +117,7 @@ var ExpressBridge = /** @class */ (function () {
113
117
  "content": {
114
118
  "application/json": {
115
119
  "schema": {
116
- "$ref": "#/definitions/InvalidResponse"
120
+ "$ref": "#/components/schemas/InvalidResponse"
117
121
  }
118
122
  }
119
123
  }
@@ -180,12 +184,12 @@ var ExpressBridge = /** @class */ (function () {
180
184
  }
181
185
  finally { if (e_3) throw e_3.error; }
182
186
  }
183
- spec.definitions[key] = {
187
+ spec.components.schemas[key] = {
184
188
  type: "object",
185
189
  properties: properties
186
190
  };
187
191
  var definition = {
188
- "$ref": "#/definitions/" + key
192
+ "$ref": "#/components/schemas/" + key
189
193
  };
190
194
  var secure = function (condition, def, item) {
191
195
  item.tags = [meta.key];
@@ -204,28 +208,28 @@ var ExpressBridge = /** @class */ (function () {
204
208
  "description": "limit the number of returned rows, default 100",
205
209
  "required": false,
206
210
  "style": "simple",
207
- "type": "int"
211
+ "schema": { "type": "integer" }
208
212
  },
209
213
  {
210
214
  "name": "_page",
211
215
  "in": "query",
212
216
  "description": "to be used for paging",
213
217
  "required": false,
214
- "type": "int"
218
+ "schema": { "type": "integer" }
215
219
  },
216
220
  {
217
221
  "name": "_sort",
218
222
  "in": "query",
219
223
  "description": "the columns to sort on",
220
224
  "required": false,
221
- "type": "string"
225
+ "schema": { "type": "string" }
222
226
  },
223
227
  {
224
228
  "name": "_order",
225
229
  "in": "query",
226
230
  "description": "the sort order to user for the columns in `_sort`",
227
231
  "required": false,
228
- "type": "string"
232
+ "schema": { "type": "string" }
229
233
  }], parameters),
230
234
  responses: {
231
235
  "200": {
@@ -246,14 +250,14 @@ var ExpressBridge = /** @class */ (function () {
246
250
  "in": "path",
247
251
  "description": "id of " + key,
248
252
  "required": true,
249
- "type": "string"
253
+ "schema": { "type": "string" },
250
254
  };
251
255
  var itemInBody = {
252
256
  "requestBody": {
253
257
  "content": {
254
258
  "application/json": {
255
259
  "schema": {
256
- "$ref": "#/definitions/" + key
260
+ "$ref": "#/components/schemas/" + key
257
261
  }
258
262
  }
259
263
  }
@@ -267,7 +271,7 @@ var ExpressBridge = /** @class */ (function () {
267
271
  "content": {
268
272
  "application/json": {
269
273
  "schema": {
270
- "$ref": "#/definitions/" + key
274
+ "$ref": "#/components/schemas/" + key
271
275
  }
272
276
  }
273
277
  }
@@ -280,7 +284,7 @@ var ExpressBridge = /** @class */ (function () {
280
284
  "content": {
281
285
  "application/json": {
282
286
  "schema": {
283
- "$ref": "#/definitions/" + key
287
+ "$ref": "#/components/schemas/" + key
284
288
  }
285
289
  }
286
290
  }
@@ -297,7 +301,7 @@ var ExpressBridge = /** @class */ (function () {
297
301
  "content": {
298
302
  "application/json": {
299
303
  "schema": {
300
- "$ref": "#/definitions/" + key
304
+ "$ref": "#/components/schemas/" + key
301
305
  }
302
306
  }
303
307
  }
@@ -376,7 +380,7 @@ var ExpressBridge = /** @class */ (function () {
376
380
  }
377
381
  finally { if (e_2) throw e_2.error; }
378
382
  }
379
- spec.definitions["InvalidResponse"] = {
383
+ spec.components.schemas["InvalidResponse"] = {
380
384
  "type": "object",
381
385
  properties: {
382
386
  "message": {
@@ -856,3 +860,4 @@ var JobsInQueueEntity = /** @class */ (function (_super) {
856
860
  return JobsInQueueEntity;
857
861
  }(__1.IdEntity));
858
862
  exports.JobsInQueueEntity = JobsInQueueEntity;
863
+ context_1.allEntities.splice(context_1.allEntities.indexOf(JobsInQueueEntity), 1);
@@ -14,18 +14,18 @@ export interface FieldOptions<entityType = any, valueType = any> {
14
14
  allowApiUpdate?: AllowedForInstance<entityType>;
15
15
  /** An arrow function that'll be used to perform validations on it
16
16
  * @example
17
- * .@Fields.string({
17
+ * @Fields.string({
18
18
  * validate: Validators.required
19
19
  * })
20
20
  * @example
21
- * .@Fields.string<Task>({
21
+ * @Fields.string<Task>({
22
22
  * validate: task=>{
23
23
  * if (task.title.length<3)
24
24
  * throw "Too Short";
25
25
  * }
26
26
  * })
27
27
  * @example
28
- * .@Fields.string({
28
+ * @Fields.string({
29
29
  * validate: (_, fieldRef)=>{
30
30
  * if (fieldRef.value.length<3)
31
31
  * fieldRef.error = "Too Short";
package/src/context.js CHANGED
@@ -41,28 +41,28 @@ var HttpProviderBridgeToRestDataProviderHttpProvider = /** @class */ (function (
41
41
  }());
42
42
  exports.HttpProviderBridgeToRestDataProviderHttpProvider = HttpProviderBridgeToRestDataProviderHttpProvider;
43
43
  function retry(what) {
44
- var _a, _b, _c;
44
+ var _a, _b, _c, _d;
45
45
  return tslib_1.__awaiter(this, void 0, void 0, function () {
46
46
  var err_1;
47
- return tslib_1.__generator(this, function (_d) {
48
- switch (_d.label) {
47
+ return tslib_1.__generator(this, function (_e) {
48
+ switch (_e.label) {
49
49
  case 0:
50
50
  if (!true) return [3 /*break*/, 7];
51
- _d.label = 1;
51
+ _e.label = 1;
52
52
  case 1:
53
- _d.trys.push([1, 3, , 6]);
53
+ _e.trys.push([1, 3, , 6]);
54
54
  return [4 /*yield*/, what()];
55
- case 2: return [2 /*return*/, _d.sent()];
55
+ case 2: return [2 /*return*/, _e.sent()];
56
56
  case 3:
57
- err_1 = _d.sent();
58
- if (!(((_a = err_1.message) === null || _a === void 0 ? void 0 : _a.startsWith("Error occurred while trying to proxy")) || ((_b = err_1.message) === null || _b === void 0 ? void 0 : _b.startsWith("Error occured while trying to proxy")) || ((_c = err_1.message) === null || _c === void 0 ? void 0 : _c.startsWith("Gateway Timeout")))) return [3 /*break*/, 5];
57
+ err_1 = _e.sent();
58
+ if (!(((_a = err_1.message) === null || _a === void 0 ? void 0 : _a.startsWith("Error occurred while trying to proxy")) || ((_b = err_1.message) === null || _b === void 0 ? void 0 : _b.startsWith("Error occured while trying to proxy")) || ((_c = err_1.message) === null || _c === void 0 ? void 0 : _c.includes("http proxy error")) || ((_d = err_1.message) === null || _d === void 0 ? void 0 : _d.startsWith("Gateway Timeout")))) return [3 /*break*/, 5];
59
59
  return [4 /*yield*/, new Promise(function (res, req) {
60
60
  setTimeout(function () {
61
61
  res({});
62
62
  }, 250);
63
63
  })];
64
64
  case 4:
65
- _d.sent();
65
+ _e.sent();
66
66
  return [3 /*break*/, 0];
67
67
  case 5: throw err_1;
68
68
  case 6: return [3 /*break*/, 0];
@@ -4,7 +4,6 @@ exports.ArrayEntityDataProvider = void 0;
4
4
  var tslib_1 = require("tslib");
5
5
  var filter_interfaces_1 = require("../filter/filter-interfaces");
6
6
  var column_1 = require("../column");
7
- var sort_1 = require("../sort");
8
7
  var ArrayEntityDataProvider = /** @class */ (function () {
9
8
  function ArrayEntityDataProvider(entity, rows) {
10
9
  var e_1, _a;
@@ -220,34 +219,38 @@ var ArrayEntityDataProvider = /** @class */ (function () {
220
219
  ArrayEntityDataProvider.prototype.insert = function (data) {
221
220
  var _a;
222
221
  return tslib_1.__awaiter(this, void 0, void 0, function () {
223
- var idf, lastRow, j;
224
- return tslib_1.__generator(this, function (_b) {
225
- switch (_b.label) {
226
- case 0:
227
- idf = this.entity.idMetadata.field;
228
- if (!!(idf instanceof column_1.CompoundIdField)) return [3 /*break*/, 3];
229
- if (!(((_a = idf.options.valueConverter) === null || _a === void 0 ? void 0 : _a.fieldTypeInDb) === 'autoincrement')) return [3 /*break*/, 2];
230
- return [4 /*yield*/, this.find({ orderBy: new sort_1.Sort({ field: idf, isDescending: true }) })];
231
- case 1:
232
- lastRow = _b.sent();
233
- if (lastRow.length > 0)
234
- data[idf.key] = lastRow[0][idf.key] + 1;
235
- else
236
- data[idf.key] = 1;
237
- _b.label = 2;
238
- case 2:
239
- if (data[idf.key])
240
- this.rows.forEach(function (i) {
241
- if (data[idf.key] == i[idf.key])
242
- throw Error("id already exists");
243
- });
244
- _b.label = 3;
245
- case 3:
246
- j = this.translateToJson(data);
247
- this.verifyThatRowHasAllNotNullColumns(j);
248
- this.rows.push(j);
249
- return [2 /*return*/, Promise.resolve(this.translateFromJson(j))];
222
+ var idf, _b, _c, row, j;
223
+ var e_6, _d;
224
+ return tslib_1.__generator(this, function (_e) {
225
+ idf = this.entity.idMetadata.field;
226
+ if (!(idf instanceof column_1.CompoundIdField)) {
227
+ if (((_a = idf.options.valueConverter) === null || _a === void 0 ? void 0 : _a.fieldTypeInDb) === 'autoincrement') {
228
+ data[idf.key] = 1;
229
+ try {
230
+ for (_b = tslib_1.__values(this.rows), _c = _b.next(); !_c.done; _c = _b.next()) {
231
+ row = _c.value;
232
+ if (row[idf.key] >= data[idf.key])
233
+ data[idf.key] = row[idf.key] + 1;
234
+ }
235
+ }
236
+ catch (e_6_1) { e_6 = { error: e_6_1 }; }
237
+ finally {
238
+ try {
239
+ if (_c && !_c.done && (_d = _b.return)) _d.call(_b);
240
+ }
241
+ finally { if (e_6) throw e_6.error; }
242
+ }
243
+ }
244
+ if (data[idf.key])
245
+ this.rows.forEach(function (i) {
246
+ if (data[idf.key] == i[idf.key])
247
+ throw Error("id already exists");
248
+ });
250
249
  }
250
+ j = this.translateToJson(data);
251
+ this.verifyThatRowHasAllNotNullColumns(j);
252
+ this.rows.push(j);
253
+ return [2 /*return*/, Promise.resolve(this.translateFromJson(j))];
251
254
  });
252
255
  });
253
256
  };
@@ -287,7 +290,7 @@ var FilterConsumerBridgeToObject = /** @class */ (function () {
287
290
  throw new Error('Custom Filter should be translated before it gets here');
288
291
  };
289
292
  FilterConsumerBridgeToObject.prototype.or = function (orElements) {
290
- var e_6, _a;
293
+ var e_7, _a;
291
294
  try {
292
295
  for (var orElements_1 = tslib_1.__values(orElements), orElements_1_1 = orElements_1.next(); !orElements_1_1.done; orElements_1_1 = orElements_1.next()) {
293
296
  var element = orElements_1_1.value;
@@ -298,12 +301,12 @@ var FilterConsumerBridgeToObject = /** @class */ (function () {
298
301
  }
299
302
  }
300
303
  }
301
- catch (e_6_1) { e_6 = { error: e_6_1 }; }
304
+ catch (e_7_1) { e_7 = { error: e_7_1 }; }
302
305
  finally {
303
306
  try {
304
307
  if (orElements_1_1 && !orElements_1_1.done && (_a = orElements_1.return)) _a.call(orElements_1);
305
308
  }
306
- finally { if (e_6) throw e_6.error; }
309
+ finally { if (e_7) throw e_7.error; }
307
310
  }
308
311
  this.ok = false;
309
312
  };
@@ -316,7 +319,7 @@ var FilterConsumerBridgeToObject = /** @class */ (function () {
316
319
  this.ok = false;
317
320
  };
318
321
  FilterConsumerBridgeToObject.prototype.isIn = function (col, val) {
319
- var e_7, _a;
322
+ var e_8, _a;
320
323
  try {
321
324
  for (var val_1 = tslib_1.__values(val), val_1_1 = val_1.next(); !val_1_1.done; val_1_1 = val_1.next()) {
322
325
  var v = val_1_1.value;
@@ -325,12 +328,12 @@ var FilterConsumerBridgeToObject = /** @class */ (function () {
325
328
  }
326
329
  }
327
330
  }
328
- catch (e_7_1) { e_7 = { error: e_7_1 }; }
331
+ catch (e_8_1) { e_8 = { error: e_8_1 }; }
329
332
  finally {
330
333
  try {
331
334
  if (val_1_1 && !val_1_1.done && (_a = val_1.return)) _a.call(val_1);
332
335
  }
333
- finally { if (e_7) throw e_7.error; }
336
+ finally { if (e_8) throw e_8.error; }
334
337
  }
335
338
  this.ok = false;
336
339
  };
@@ -109,15 +109,16 @@ var WebSqlDataProvider = /** @class */ (function () {
109
109
  };
110
110
  WebSqlDataProvider.prototype.addColumnSqlSyntax = function (x, dbName) {
111
111
  var result = dbName;
112
+ var nullNumber = x.allowNull ? "" : " default 0 not null";
112
113
  if (x.valueType == Date)
113
114
  result += " integer";
114
115
  else if (x.valueType == Boolean)
115
- result += " integer default 0 not null";
116
+ result += " integer " + nullNumber;
116
117
  else if (x.valueType == Number) {
117
118
  if (!x.valueConverter.fieldTypeInDb)
118
- result += ' real default 0 not null';
119
+ result += ' real ' + nullNumber;
119
120
  else
120
- result += ' ' + x.valueConverter.fieldTypeInDb + ' default 0 not null';
121
+ result += ' ' + x.valueConverter.fieldTypeInDb + ' ' + nullNumber;
121
122
  }
122
123
  else
123
124
  result += " text" + (x.allowNull ? " " : " default '' not null ");
package/src/entity.d.ts CHANGED
@@ -29,7 +29,7 @@ export interface EntityOptions<entityType = any> {
29
29
  apiPrefilter?: EntityFilter<entityType> | (() => EntityFilter<entityType> | Promise<EntityFilter<entityType>>);
30
30
  /** A filter that will be used for all queries from this entity both from the API and from within the backend.
31
31
  * @example
32
- * fixedWhereFilter: { archive:false }
32
+ * backendPrefilter: { archive:false }
33
33
  * @see [EntityFilter](http://remult.dev/docs/entityFilter.html)
34
34
  */
35
35
  backendPrefilter?: EntityFilter<entityType> | (() => EntityFilter<entityType> | Promise<EntityFilter<entityType>>);
@@ -45,7 +45,7 @@ export interface EntityOptions<entityType = any> {
45
45
  * If the `error` property of the entity's ref or any of it's fields will be set, the save will be aborted and an exception will be thrown.
46
46
  * this is the place to run logic that we want to run in any case before an entity is saved.
47
47
  * @example
48
- * .@Entity<Task>("tasks", {
48
+ * @Entity<Task>("tasks", {
49
49
  * saving: async task => {
50
50
  * task.lastUpdated = new Date()
51
51
  * }
@@ -68,8 +68,15 @@ export interface EntityOptions<entityType = any> {
68
68
  dbName?: string;
69
69
  /** For entities that are based on SQL expressions instead of a physical table or view*/
70
70
  sqlExpression?: string | ((entity: FieldsMetadata<entityType>) => string | Promise<string>);
71
- /** An arrow function that identifies the `id` column to use for this entity */
72
- id?: (entity: FieldsMetadata<entityType>) => FieldMetadata;
71
+ /** An arrow function that identifies the `id` column to use for this entity
72
+ * @example
73
+ * //Single column id
74
+ * @Entity<Products>("products", { id:p=>p.productCode })
75
+ * @example
76
+ * //Multiple columns id
77
+ * @Entity<OrderDetails>("orderDetails", { id:od=> [od.orderId, od.productCode] })
78
+ */
79
+ id?: (entity: FieldsMetadata<entityType>) => FieldMetadata | FieldMetadata[];
73
80
  entityRefInit?: (ref: EntityRef<entityType>, row: entityType) => void;
74
81
  apiRequireId?: Allowed;
75
82
  }
@@ -256,11 +256,11 @@ export declare function getValueList<T>(type: ClassType<T>): T[];
256
256
  * FieldOptions can be set in two ways:
257
257
  * @example
258
258
  * // as an object
259
- * .@Fields.string({ includeInApi:false })
259
+ * @Fields.string({ includeInApi:false })
260
260
  * title='';
261
261
  * @example
262
262
  * // as an arrow function that receives `remult` as a parameter
263
- * .@Fields.string((options,remult) => options.includeInApi = true)
263
+ * @Fields.string((options,remult) => options.includeInApi = true)
264
264
  * title='';
265
265
  */
266
266
  export declare function Field<entityType = any, valueType = any>(valueType: () => ClassType<valueType>, ...options: (FieldOptions<entityType, valueType> | ((options: FieldOptions<entityType, valueType>, remult: Remult) => void))[]): (target: any, key: any, c?: any) => void;
@@ -289,13 +289,14 @@ export declare function BuildEntity<entityType>(c: ClassType<entityType>, key: s
289
289
  * @Fields.boolean()
290
290
  * completed = false;
291
291
  * }
292
- * *EntityOptions can be set in two ways:*
292
+ * @note
293
+ * EntityOptions can be set in two ways:
293
294
  * @example
294
295
  * // as an object
295
- * .@Entity("tasks",{ allowApiCrud:true })
296
+ * @Entity("tasks",{ allowApiCrud:true })
296
297
  * @example
297
298
  * // as an arrow function that receives `remult` as a parameter
298
- * .@Entity("tasks", (options,remult) => options.allowApiCrud = true)
299
+ * @Entity("tasks", (options,remult) => options.allowApiCrud = true)
299
300
  */
300
301
  export declare function Entity<entityType>(key: string, ...options: (EntityOptions<entityType> | ((options: EntityOptions<entityType>, remult: Remult) => void))[]): (target: any) => any;
301
302
  export declare class EntityBase {
@@ -2181,7 +2181,15 @@ var EntityFullInfo = /** @class */ (function () {
2181
2181
  this.fields = r;
2182
2182
  this.caption = buildCaption(entityInfo.caption, this.key, remult);
2183
2183
  if (entityInfo.id) {
2184
- this.idMetadata.field = entityInfo.id(this.fields);
2184
+ var r_2 = entityInfo.id(this.fields);
2185
+ if (Array.isArray(r_2)) {
2186
+ if (r_2.length > 1)
2187
+ this.idMetadata.field = new (column_1.CompoundIdField.bind.apply(column_1.CompoundIdField, tslib_1.__spread([void 0], r_2)))();
2188
+ else if (r_2.length == 1)
2189
+ this.idMetadata.field = r_2[0];
2190
+ }
2191
+ else
2192
+ this.idMetadata.field = r_2;
2185
2193
  }
2186
2194
  else {
2187
2195
  if (this.fields["id"])
@@ -2331,8 +2339,8 @@ var Fields = /** @class */ (function () {
2331
2339
  }());
2332
2340
  exports.Fields = Fields;
2333
2341
  function isAutoIncrement(f) {
2334
- var _a;
2335
- return ((_a = f.options.valueConverter) === null || _a === void 0 ? void 0 : _a.fieldTypeInDb) === 'autoincrement';
2342
+ var _a, _b;
2343
+ return ((_b = (_a = f.options) === null || _a === void 0 ? void 0 : _a.valueConverter) === null || _b === void 0 ? void 0 : _b.fieldTypeInDb) === 'autoincrement';
2336
2344
  }
2337
2345
  exports.isAutoIncrement = isAutoIncrement;
2338
2346
  function ValueListFieldType() {
@@ -2467,11 +2475,11 @@ exports.getValueList = getValueList;
2467
2475
  * FieldOptions can be set in two ways:
2468
2476
  * @example
2469
2477
  * // as an object
2470
- * .@Fields.string({ includeInApi:false })
2478
+ * @Fields.string({ includeInApi:false })
2471
2479
  * title='';
2472
2480
  * @example
2473
2481
  * // as an arrow function that receives `remult` as a parameter
2474
- * .@Fields.string((options,remult) => options.includeInApi = true)
2482
+ * @Fields.string((options,remult) => options.includeInApi = true)
2475
2483
  * title='';
2476
2484
  */
2477
2485
  function Field(valueType) {
@@ -2637,13 +2645,14 @@ exports.BuildEntity = BuildEntity;
2637
2645
  * @Fields.boolean()
2638
2646
  * completed = false;
2639
2647
  * }
2640
- * *EntityOptions can be set in two ways:*
2648
+ * @note
2649
+ * EntityOptions can be set in two ways:
2641
2650
  * @example
2642
2651
  * // as an object
2643
- * .@Entity("tasks",{ allowApiCrud:true })
2652
+ * @Entity("tasks",{ allowApiCrud:true })
2644
2653
  * @example
2645
2654
  * // as an arrow function that receives `remult` as a parameter
2646
- * .@Entity("tasks", (options,remult) => options.allowApiCrud = true)
2655
+ * @Entity("tasks", (options,remult) => options.allowApiCrud = true)
2647
2656
  */
2648
2657
  function Entity(key) {
2649
2658
  var options = [];
@@ -112,7 +112,12 @@ export declare type OmitEB<T> = Omit<T, keyof import('./RepositoryImplementation
112
112
  export interface Repository<entityType> {
113
113
  /** returns a result array based on the provided options */
114
114
  find(options?: FindOptions<entityType>): Promise<entityType[]>;
115
- /** returns the first item that matchers the `where` condition */
115
+ /** returns the first item that matchers the `where` condition
116
+ * @example
117
+ * await taskRepo.findFirst({ completed:false })
118
+ * @example
119
+ * await taskRepo.findFirst({ completed:false },{ createIfNotFound: true })
120
+ * */
116
121
  findFirst(where?: EntityFilter<entityType>, options?: FindFirstOptions<entityType>): Promise<entityType>;
117
122
  /** returns the items that matches the idm the result is cached unless specified differently in the `options` parameter */
118
123
  findId(id: entityType extends {
@@ -285,7 +290,7 @@ export interface QueryResult<entityType> {
285
290
  [Symbol.asyncIterator](): {
286
291
  next: () => Promise<IteratorResult<entityType, entityType>>;
287
292
  };
288
- /** returns the number of rows that match the query critiria */
293
+ /** returns the number of rows that match the query criteria */
289
294
  count(): Promise<number>;
290
295
  /** Returns a `Paginator` object that is used for efficient paging */
291
296
  paginator(): Promise<Paginator<entityType>>;
@@ -300,7 +305,7 @@ export interface Paginator<entityType> {
300
305
  items: entityType[];
301
306
  /** True if next page exists */
302
307
  hasNextPage: boolean;
303
- /** Get's the next page in the query's result set */
308
+ /** Gets the next page in the `query`'s result set */
304
309
  nextPage(): Promise<Paginator<entityType>>;
305
310
  /** the count of the total items in the `query`'s result */
306
311
  count(): Promise<number>;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  var tslib_1 = require("tslib");
4
+ var sql_database_1 = require("../data-providers/sql-database");
4
5
  var remult3_1 = require("../remult3");
5
6
  var db_tests_setup_1 = require("./db-tests-setup");
6
7
  var testNull = /** @class */ (function (_super) {
@@ -8,6 +9,7 @@ var testNull = /** @class */ (function (_super) {
8
9
  function testNull() {
9
10
  var _this = _super !== null && _super.apply(this, arguments) || this;
10
11
  _this.id = 0;
12
+ _this.numberWithNull = null;
11
13
  return _this;
12
14
  }
13
15
  tslib_1.__decorate([
@@ -18,6 +20,10 @@ var testNull = /** @class */ (function (_super) {
18
20
  remult3_1.Field(undefined, { allowNull: true }),
19
21
  tslib_1.__metadata("design:type", Object)
20
22
  ], testNull.prototype, "myCol", void 0);
23
+ tslib_1.__decorate([
24
+ remult3_1.Fields.integer({ allowNull: true }),
25
+ tslib_1.__metadata("design:type", Number)
26
+ ], testNull.prototype, "numberWithNull", void 0);
21
27
  testNull = tslib_1.__decorate([
22
28
  remult3_1.Entity('testNull', { allowApiCrud: true })
23
29
  ], testNull);
@@ -49,3 +55,35 @@ db_tests_setup_1.testAll("test that update null works", function (_a) {
49
55
  });
50
56
  });
51
57
  }, false);
58
+ db_tests_setup_1.testAll("test number with null", function (_a) {
59
+ var createEntity = _a.createEntity;
60
+ return tslib_1.__awaiter(void 0, void 0, void 0, function () {
61
+ var r, i, _b, _c;
62
+ return tslib_1.__generator(this, function (_d) {
63
+ switch (_d.label) {
64
+ case 0: return [4 /*yield*/, createEntity(testNull)];
65
+ case 1:
66
+ r = _d.sent();
67
+ return [4 /*yield*/, r.insert({ id: 1, myCol: { value: 'abc' }, numberWithNull: null })];
68
+ case 2:
69
+ i = _d.sent();
70
+ expect(i.numberWithNull).toBe(null);
71
+ _b = expect;
72
+ return [4 /*yield*/, r.count({ numberWithNull: null })];
73
+ case 3:
74
+ _b.apply(void 0, [_d.sent()]).toBe(1);
75
+ i.numberWithNull = 0;
76
+ return [4 /*yield*/, i.save()];
77
+ case 4:
78
+ _d.sent();
79
+ expect(i.numberWithNull).toBe(0);
80
+ sql_database_1.SqlDatabase.LogToConsole = true;
81
+ _c = expect;
82
+ return [4 /*yield*/, r.count({ numberWithNull: null })];
83
+ case 5:
84
+ _c.apply(void 0, [_d.sent()]).toBe(0);
85
+ return [2 /*return*/];
86
+ }
87
+ });
88
+ });
89
+ }, false);
@@ -86,6 +86,8 @@ var ValueConverters = /** @class */ (function () {
86
86
  return undefined;
87
87
  return val.replace(/-/g, '');
88
88
  }, fromDb: function (val) {
89
+ if (val === null)
90
+ return null;
89
91
  if (!val)
90
92
  return undefined;
91
93
  return new Date(val.substring(0, 4) + '-' + val.substring(4, 6) + '-' + val.substring(6, 8));
@@ -110,6 +112,8 @@ var ValueConverters = /** @class */ (function () {
110
112
  };
111
113
  ValueConverters.Number = {
112
114
  fromDb: function (value) {
115
+ if (value === null)
116
+ return null;
113
117
  if (value !== undefined)
114
118
  return +value;
115
119
  return undefined;