remult 0.13.21 → 0.13.24

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,7 +1,7 @@
1
1
  {
2
2
  "name": "remult",
3
- "version": "0.13.21",
4
- "description": "remult core lib",
3
+ "version": "0.13.24",
4
+ "description": "A CRUD framework for full-stack TypeScript",
5
5
  "homepage": "https://remult.dev/",
6
6
  "repository": {
7
7
  "type": "git",
@@ -13,7 +13,6 @@
13
13
  },
14
14
  "dependencies": {
15
15
  "uuid": "^8.3.2",
16
- "body-parser": "^1.18.3",
17
16
  "reflect-metadata": "^0.1.13",
18
17
  "tslib": "^2.3.1"
19
18
  }
package/readme.md CHANGED
@@ -4,7 +4,7 @@
4
4
  </a>
5
5
  <h1>Remult</h1>
6
6
  <a href="https://circleci.com/gh/remult/remult/tree/master">
7
- <img alt="CircleCI" src="https://circleci.com/gh/remult/remult/tree/master.svg?style=svg">
7
+ <img alt="CircleCI" src="https://circleci.com/gh/remult/remult/tree/master.svg?style=shield">
8
8
  </a>
9
9
  <a href="https://codecov.io/gh/remult/remult">
10
10
  <img src="https://codecov.io/gh/remult/remult/branch/master/graph/badge.svg?token=LYWQRUN3D1"/>
@@ -18,33 +18,36 @@
18
18
  <a href="https://www.npmjs.com/package/remult">
19
19
  <img alt="npm downloads" src="https://img.shields.io/npm/dm/remult">
20
20
  </a>
21
- <a href="https://snyk.io/test/npm/remult">
22
- <img alt="snyk" src="https://snyk.io/test/npm/remult/badge.svg"/>
23
- </a>
24
- <a href="https://github.com/remult/remult/pulse" alt="Activity">
25
- <img src="https://img.shields.io/github/commit-activity/m/remult/remult" /></a>
21
+ <a href="https://discord.gg/GXHk7ZfuG5">
22
+ <img alt="chat on discord" src="https://img.shields.io/badge/chat-on%20discord-blueviolet"/>
23
+ </a>
26
24
  </div>
27
25
 
28
26
  ## What is Remult?
29
27
 
30
- **Remult** is a fullstack CRUD framework that uses your TypeScript model types to provide:
28
+ **Remult** is a full-stack CRUD framework that uses your TypeScript model types to provide:
31
29
 
32
30
  * Secure REST API (highly configurable)
33
31
  * Type-safe frontend API client
34
32
  * Type-safe backend query builder
35
33
 
36
- #### Remult :heart: Monorepos
34
+ #### Remult :heart: Code Sharing
37
35
 
38
- Using a `monorepo` approach, with model types shared between frontend and backend code, Remult can enforce data validation and constraints, defined once, on both frontend and REST API levels.
36
+ With model types shared between frontend and backend code, Remult can **enforce data validation and constraints, defined once, both in the front-end and witin back-end API routes**.
39
37
 
40
38
  ## Getting started
41
- The best way to learn Remult is by following a tutorial of a simple Todo app with a Node.js Express backend. There's one [using a React frontend](https://remult.dev/tutorials/tutorial-react.html) and one [using Angular](https://remult.dev/tutorials/tutorial-angular.html).
39
+ The best way to learn Remult is by following a tutorial of a simple Todo web app with a Node.js Express backend.
40
+
41
+ * [Tutorial with React](https://remult.dev/tutorials/react/)
42
+ * [Tutorial with Angular](https://remult.dev/tutorials/angular/)
42
43
 
43
44
  ## Installation
44
45
  ```sh
45
46
  npm i remult
46
47
  ```
47
48
 
49
+ ## Usage
50
+
48
51
  ### Setup API backend using a Node.js Express middleware
49
52
  ```ts
50
53
  import express from 'express';
@@ -60,14 +63,14 @@ app.listen(port, () => {
60
63
  });
61
64
  ```
62
65
 
63
- ## Define model classes
66
+ ### Define model classes
64
67
  ```ts
65
- import { Entity, EntityBase, Fields } from 'remult';
68
+ import { Entity, Fields } from 'remult';
66
69
 
67
70
  @Entity('products', {
68
71
  allowApiCrud: true
69
72
  })
70
- export class Product extends EntityBase {
73
+ export class Product {
71
74
  @Fields.string()
72
75
  name = '';
73
76
 
@@ -83,13 +86,14 @@ export class Product extends EntityBase {
83
86
  [{"name":"Tofu","unitPrice":5}]
84
87
  ```
85
88
 
86
- ## Find and manipulate data in type-safe frontend code
89
+ ### Find and manipulate data in type-safe frontend code
87
90
  ```ts
88
91
  async function increasePriceOfTofu(priceIncrease: number) {
89
- const product = await remult.repo(Product).findFirst({ name: 'Tofu' });
92
+ const productsRepo = remult.repo(Product);
90
93
 
94
+ const product = await productsRepo.findFirst({ name: 'Tofu' });
91
95
  product.unitPrice += priceIncrease;
92
- product.save();
96
+ productsRepo.save(product);
93
97
  }
94
98
  ```
95
99
 
@@ -97,26 +101,27 @@ async function increasePriceOfTofu(priceIncrease: number) {
97
101
  ```ts
98
102
  @BackendMethod({ allowed: Allow.authenticated })
99
103
  static async increasePriceOfTofu(priceIncrease: number, remult?: Remult) {
100
- const product = await remult!.repo(Product).findFirst({ name: 'Tofu' });
104
+ const productsRepo = remult!.repo(Product);
101
105
 
106
+ const product = await productsRepo.findFirst({ name: 'Tofu' });
102
107
  product.unitPrice += priceIncrease;
103
- product.save();
108
+ productsRepo.save(product);
104
109
  }
105
110
  ```
106
111
 
107
- ## :ballot_box_with_check: Data validation and constraints - defined once
112
+ ### :ballot_box_with_check: Data validation and constraints - defined once
108
113
 
109
114
  ```ts
110
- import { Entity, EntityBase, Fields } from 'remult';
115
+ import { Entity, Fields } from 'remult';
111
116
 
112
117
  @Entity('products', {
113
118
  allowApiCrud: true
114
119
  })
115
- export class Product extends EntityBase {
120
+ export class Product {
116
121
  @Fields.string<Product>({
117
122
  validate: product => {
118
123
  if (product.name.trim().length == 0)
119
- product.$.name.error = 'required';
124
+ throw 'required';
120
125
  }
121
126
  })
122
127
  name = '';
@@ -124,7 +129,7 @@ export class Product extends EntityBase {
124
129
  @Fields.number({
125
130
  validate: (_, field) => {
126
131
  if (field.value < 0)
127
- field.error = "must not be less than 0";
132
+ throw "must not be less than 0";
128
133
  }
129
134
  })
130
135
  unitPrice = 0;
@@ -133,31 +138,31 @@ export class Product extends EntityBase {
133
138
 
134
139
  ### Enforced in frontend:
135
140
  ```ts
136
- const product = remult.repo(Product).create();
141
+ const product = productsRepo.create();
137
142
 
138
143
  try {
139
- await product.save();
144
+ await productsRepo.save(product);
140
145
  }
141
- catch {
142
- console.error(product._.error); // Browser console will display - "Name: required"
146
+ catch (e: any) {
147
+ console.error(e.message); // Browser console will display - "Name: required"
143
148
  }
144
149
  ```
145
150
 
146
151
  ### Enforced in backend:
147
152
  ```sh
148
- > curl -d "{""unitPrice"":-1}" -H "Content-Type: application/json" -X POST http://localhost:3001/api/products
153
+ > curl http://localhost:3001/api/products -H "Content-Type: application/json" -d "{""unitPrice"":-1}"
149
154
 
150
155
  {"modelState":{"unitPrice":"must not be less than 0","name":"required"},"message":"Name: required"}
151
156
  ```
152
157
 
153
- ## :lock: Secure the API with fine-grained authorization
158
+ ### :lock: Secure the API with fine-grained authorization
154
159
  ```ts
155
160
  @Entity<Article>('Articles', {
156
161
  allowApiRead: true,
157
162
  allowApiInsert: remult => remult.authenticated(),
158
163
  allowApiUpdate: (remult, article) => article.author.id == remult.user.id
159
164
  })
160
- export class Article extends EntityBase {
165
+ export class Article {
161
166
  @Fields.string({ allowApiUpdate: false })
162
167
  slug = '';
163
168
 
@@ -168,3 +173,13 @@ export class Article extends EntityBase {
168
173
  content = '';
169
174
  }
170
175
  ```
176
+
177
+ ## Example App
178
+
179
+ [CRM demo](https://github.com/remult/crm-demo) with a React + [MUI](https://mui.com) front-end and Postgres database.
180
+
181
+ ## Contributing
182
+ Contributions are welcome. See [CONTRIBUTING](CONTRIBUTING.md).
183
+
184
+ ## License
185
+ Remult is [MIT Licensed](LICENSE).
@@ -6,10 +6,10 @@ import { DataApi, DataApiRequest, DataApiResponse } from '../src/data-api';
6
6
  import { AllowedForInstance } from '../src/context';
7
7
  import { ClassType } from '../classType';
8
8
  export declare function remultExpress(options?: {
9
- /** Determines the Data Provider
10
-
11
- * @see [Optional Databases](https://remult.dev/docs/databases.html)
12
- */
9
+ /** Sets a database connection for Remult.
10
+ *
11
+ * @see [Connecting to a Database](https://remult.dev/docs/databases.html).
12
+ */
13
13
  dataProvider?: DataProvider | Promise<DataProvider> | (() => Promise<DataProvider | undefined>);
14
14
  bodySizeLimit?: string;
15
15
  disableAutoApi?: boolean;
@@ -20,6 +20,7 @@ export declare function remultExpress(options?: {
20
20
  defaultGetLimit?: number;
21
21
  entities?: ClassType<any>[];
22
22
  controllers?: ClassType<any>[];
23
+ bodyParser?: boolean;
23
24
  }): RemultExpressBridge;
24
25
  export interface RemultExpressBridge extends express.RequestHandler {
25
26
  getRemult(req: express.Request): Promise<Remult>;
@@ -4,7 +4,6 @@ exports.JobsInQueueEntity = exports.EntityQueueStorage = exports.ExpressRequestB
4
4
  var tslib_1 = require("tslib");
5
5
  var __1 = require("../");
6
6
  var express = require("express");
7
- var bodyParser = require("body-parser");
8
7
  var register_actions_on_server_1 = require("./register-actions-on-server");
9
8
  var register_entities_on_server_1 = require("./register-entities-on-server");
10
9
  var JsonEntityFileStorage_1 = require("./JsonEntityFileStorage");
@@ -29,8 +28,10 @@ function remultExpress(options) {
29
28
  if (!options.queueStorage) {
30
29
  options.queueStorage = new InMemoryQueueStorage();
31
30
  }
32
- app.use(bodyParser.json({ limit: options.bodySizeLimit }));
33
- app.use(bodyParser.urlencoded({ extended: true, limit: options.bodySizeLimit }));
31
+ if ((options === null || options === void 0 ? void 0 : options.bodyParser) !== false) {
32
+ app.use(express.json({ limit: options.bodySizeLimit }));
33
+ app.use(express.urlencoded({ extended: true, limit: options.bodySizeLimit }));
34
+ }
34
35
  var dataProvider;
35
36
  if (typeof options.dataProvider === "function") {
36
37
  dataProvider = options.dataProvider();
@@ -62,8 +63,13 @@ function remultExpress(options) {
62
63
  bridge.logApiEndPoints = options.logApiEndPoints;
63
64
  var apiArea = bridge.addArea(__1.Remult.apiBaseUrl);
64
65
  if (!options.disableAutoApi) {
65
- register_actions_on_server_1.registerActionsOnServer(apiArea);
66
- register_entities_on_server_1.registerEntitiesOnServer(apiArea);
66
+ var actions = [];
67
+ if (options.entities)
68
+ actions.push.apply(actions, tslib_1.__spread(options.entities));
69
+ if (options.controllers)
70
+ actions.push.apply(actions, tslib_1.__spread(options.controllers));
71
+ register_actions_on_server_1.registerActionsOnServer(apiArea, actions);
72
+ register_entities_on_server_1.registerEntitiesOnServer(apiArea, options.entities);
67
73
  }
68
74
  return Object.assign(app, {
69
75
  getRemult: function (req) { return bridge.getRemult(req); },
@@ -1,2 +1,3 @@
1
1
  import { SiteArea } from "./expressBridge";
2
- export declare function registerActionsOnServer(area: SiteArea): void;
2
+ import { ClassType } from "../classType";
3
+ export declare function registerActionsOnServer(area: SiteArea, controllers?: ClassType<any>[]): void;
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.registerActionsOnServer = void 0;
4
+ var tslib_1 = require("tslib");
4
5
  var server_action_1 = require("../src/server-action");
5
- function registerActionsOnServer(area) {
6
+ function registerActionsOnServer(area, controllers) {
7
+ var e_1, _a, e_2, _b;
6
8
  var addAction = function (a) {
7
9
  var x = a[server_action_1.serverActionField];
8
10
  if (!x) {
@@ -11,8 +13,38 @@ function registerActionsOnServer(area) {
11
13
  area.addAction(x);
12
14
  };
13
15
  server_action_1.actionInfo.runningOnServer = true;
14
- server_action_1.actionInfo.allActions.forEach(function (a) {
15
- addAction(a);
16
- });
16
+ if (controllers && controllers.length > 0) {
17
+ try {
18
+ for (var controllers_1 = tslib_1.__values(controllers), controllers_1_1 = controllers_1.next(); !controllers_1_1.done; controllers_1_1 = controllers_1.next()) {
19
+ var c = controllers_1_1.value;
20
+ var z = c[server_action_1.classBackendMethodsArray];
21
+ if (z)
22
+ try {
23
+ for (var z_1 = (e_2 = void 0, tslib_1.__values(z)), z_1_1 = z_1.next(); !z_1_1.done; z_1_1 = z_1.next()) {
24
+ var a = z_1_1.value;
25
+ addAction(a);
26
+ }
27
+ }
28
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
29
+ finally {
30
+ try {
31
+ if (z_1_1 && !z_1_1.done && (_b = z_1.return)) _b.call(z_1);
32
+ }
33
+ finally { if (e_2) throw e_2.error; }
34
+ }
35
+ }
36
+ }
37
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
38
+ finally {
39
+ try {
40
+ if (controllers_1_1 && !controllers_1_1.done && (_a = controllers_1.return)) _a.call(controllers_1);
41
+ }
42
+ finally { if (e_1) throw e_1.error; }
43
+ }
44
+ }
45
+ else
46
+ server_action_1.actionInfo.allActions.forEach(function (a) {
47
+ addAction(a);
48
+ });
17
49
  }
18
50
  exports.registerActionsOnServer = registerActionsOnServer;
@@ -1,2 +1,3 @@
1
1
  import { SiteArea } from "./expressBridge";
2
- export declare function registerEntitiesOnServer(area: SiteArea): void;
2
+ import { ClassType } from "../classType";
3
+ export declare function registerEntitiesOnServer(area: SiteArea, entities?: ClassType<any>[]): void;
@@ -4,9 +4,11 @@ exports.registerEntitiesOnServer = void 0;
4
4
  var remult3_1 = require("../src/remult3");
5
5
  var context_1 = require("../src/context");
6
6
  var data_api_1 = require("../src/data-api");
7
- function registerEntitiesOnServer(area) {
7
+ function registerEntitiesOnServer(area, entities) {
8
+ if (!entities)
9
+ entities = context_1.allEntities;
8
10
  //add Api Entries
9
- context_1.allEntities.forEach(function (e) {
11
+ entities.forEach(function (e) {
10
12
  var key = remult3_1.getEntityKey(e);
11
13
  if (key != undefined)
12
14
  area.add(key, function (c) {
@@ -6,7 +6,7 @@ export declare class WebSqlDataProvider implements SqlImplementation, __RowsOfDa
6
6
  rows: {
7
7
  [tableName: string]: any;
8
8
  };
9
- constructor(databaseName: string);
9
+ constructor(databaseName: string, databaseSize?: number);
10
10
  getLimitSqlSyntax(limit: number, offset: number): string;
11
11
  entityIsUsedForTheFirstTime(entity: EntityMetadata): Promise<void>;
12
12
  dropTable(entity: EntityMetadata): Promise<void>;
@@ -7,10 +7,11 @@ var sql_database_1 = require("./sql-database");
7
7
  var filter_consumer_bridge_to_sql_request_1 = require("../filter/filter-consumer-bridge-to-sql-request");
8
8
  //SqlDatabase.LogToConsole = true;
9
9
  var WebSqlDataProvider = /** @class */ (function () {
10
- function WebSqlDataProvider(databaseName) {
10
+ function WebSqlDataProvider(databaseName, databaseSize) {
11
+ if (databaseSize === void 0) { databaseSize = 2 * 1024 * 1024; }
11
12
  this.databaseName = databaseName;
12
13
  //@ts-ignore
13
- this.db = window.openDatabase(databaseName, '1.0', databaseName, 2 * 1024 * 1024);
14
+ this.db = window.openDatabase(databaseName, '1.0', databaseName, databaseSize);
14
15
  }
15
16
  WebSqlDataProvider.prototype.getLimitSqlSyntax = function (limit, offset) {
16
17
  return ' limit ' + limit + ' offset ' + offset;
@@ -224,7 +224,7 @@ export declare function isAutoIncrement(f: FieldMetadata): boolean;
224
224
  export interface StringFieldOptions<entityType = any> extends FieldOptions<entityType, string> {
225
225
  maxLength?: number;
226
226
  }
227
- export declare function ValueListFieldType<entityType = any, valueType extends ValueListItem = any>(...options: (ValueListFieldOptions<entityType, valueType> | ((options: FieldOptions<entityType, valueType>, remult: Remult) => void))[]): (type: ClassType<valueType>) => void;
227
+ export declare function ValueListFieldType<valueType extends ValueListItem = any>(...options: (ValueListFieldOptions<any, valueType> | ((options: FieldOptions<any, valueType>, remult: Remult) => void))[]): (type: ClassType<valueType>) => void;
228
228
  export interface ValueListFieldOptions<entityType, valueType> extends FieldOptions<entityType, valueType> {
229
229
  getValues?: () => valueType[];
230
230
  }
@@ -82,7 +82,7 @@ export interface FieldRef<entityType = any, valueType = any> extends Subscribabl
82
82
  valueChanged(): boolean;
83
83
  entityRef: EntityRef<entityType>;
84
84
  container: entityType;
85
- metadata: FieldMetadata<entityType>;
85
+ metadata: FieldMetadata<valueType>;
86
86
  load(): Promise<valueType>;
87
87
  valueIsNull(): boolean;
88
88
  originalValueIsNull(): boolean;
@@ -245,7 +245,7 @@ export interface FindOptionsBase<entityType> extends LoadOptions<entityType> {
245
245
  /** filters the data
246
246
  * @example
247
247
  * await taskRepo.find({where: { completed:false }})
248
- * @see For more usage examples see [EntityFilter](https://remult.github.io/guide/entityFilter.html)
248
+ * @see For more usage examples see [EntityFilter](https://remult.dev/docs/entityFilter.html)
249
249
  */
250
250
  where?: EntityFilter<entityType>;
251
251
  /** Determines the order of items returned .
@@ -66,4 +66,5 @@ export declare class ProgressListener {
66
66
  }
67
67
  export declare function prepareArgsToSend(types: any[], args: any[]): any[];
68
68
  export declare function prepareReceivedArgs(types: any[], args: any[], remult: Remult, ds: DataProvider, res: DataApiResponse): Promise<any[]>;
69
+ export declare const classBackendMethodsArray: unique symbol;
69
70
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.prepareReceivedArgs = exports.prepareArgsToSend = exports.ProgressListener = exports.BackendMethod = exports.Controller = exports.serverActionField = exports.actionInfo = exports.myServerAction = exports.Action = void 0;
3
+ exports.classBackendMethodsArray = exports.prepareReceivedArgs = exports.prepareArgsToSend = exports.ProgressListener = exports.BackendMethod = exports.Controller = exports.serverActionField = exports.actionInfo = exports.myServerAction = exports.Action = void 0;
4
4
  var tslib_1 = require("tslib");
5
5
  require("reflect-metadata");
6
6
  var context_1 = require("./context");
@@ -216,7 +216,7 @@ function BackendMethod(options) {
216
216
  });
217
217
  });
218
218
  };
219
- exports.actionInfo.allActions.push(descriptor.value);
219
+ registerAction(target, descriptor);
220
220
  descriptor.value[exports.serverActionField] = serverAction_1;
221
221
  return descriptor;
222
222
  }
@@ -479,7 +479,7 @@ function BackendMethod(options) {
479
479
  });
480
480
  });
481
481
  };
482
- exports.actionInfo.allActions.push(descriptor.value);
482
+ registerAction(target.constructor, descriptor);
483
483
  descriptor.value[exports.serverActionField] = serverAction;
484
484
  return descriptor;
485
485
  };
@@ -488,6 +488,10 @@ exports.BackendMethod = BackendMethod;
488
488
  var customUndefined = {
489
489
  _isUndefined: true
490
490
  };
491
+ function registerAction(target, descriptor) {
492
+ (target[exports.classBackendMethodsArray] || (target[exports.classBackendMethodsArray] = [])).push(descriptor.value);
493
+ exports.actionInfo.allActions.push(descriptor.value);
494
+ }
491
495
  function isCustomUndefined(x) {
492
496
  return x && x._isUndefined;
493
497
  }
@@ -592,3 +596,4 @@ function prepareReceivedArgs(types, args, remult, ds, res) {
592
596
  });
593
597
  }
594
598
  exports.prepareReceivedArgs = prepareReceivedArgs;
599
+ exports.classBackendMethodsArray = Symbol('classBackendMethodsArray');