remult 0.13.19 → 0.13.22

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/graphql.js CHANGED
@@ -98,15 +98,15 @@ function remultGraphql(api) {
98
98
  root[key] = function (_a, req) {
99
99
  var options = _a.options, filter = _a.filter;
100
100
  return tslib_1.__awaiter(_this, void 0, void 0, function () {
101
- var remult, repo, dapi, result, err;
101
+ var remult, repo, dApi, result, err;
102
102
  return tslib_1.__generator(this, function (_b) {
103
103
  switch (_b.label) {
104
104
  case 0: return [4 /*yield*/, api.getRemult(req)];
105
105
  case 1:
106
106
  remult = _b.sent();
107
107
  repo = remult.repo(e);
108
- dapi = new data_api_1.DataApi(repo, remult);
109
- return [4 /*yield*/, dapi.getArray({
108
+ dApi = new data_api_1.DataApi(repo, remult);
109
+ return [4 /*yield*/, dApi.getArray({
110
110
  success: function (x) { return result = x; },
111
111
  created: undefined,
112
112
  deleted: undefined,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "remult",
3
- "version": "0.13.19",
4
- "description": "remult core lib",
3
+ "version": "0.13.22",
4
+ "description": "A CRUD framework for full-stack TypeScript",
5
5
  "homepage": "https://remult.dev/",
6
6
  "repository": {
7
7
  "type": "git",
package/readme.md CHANGED
@@ -27,7 +27,7 @@
27
27
 
28
28
  ## What is Remult?
29
29
 
30
- **Remult** is a fullstack CRUD framework that uses your TypeScript model types to provide:
30
+ **Remult** is a full-stack CRUD framework that uses your TypeScript model types to provide:
31
31
 
32
32
  * Secure REST API (highly configurable)
33
33
  * Type-safe frontend API client
@@ -38,13 +38,18 @@
38
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.
39
39
 
40
40
  ## 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).
41
+ The best way to learn Remult is by following a tutorial of a simple Todo web app with a Node.js Express backend.
42
+
43
+ * [Tutorial with React](https://remult.dev/tutorials/react/)
44
+ * [Tutorial with Angular](https://remult.dev/tutorials/tutorial-angular.html)
42
45
 
43
46
  ## Installation
44
47
  ```sh
45
48
  npm i remult
46
49
  ```
47
50
 
51
+ ## Usage
52
+
48
53
  ### Setup API backend using a Node.js Express middleware
49
54
  ```ts
50
55
  import express from 'express';
@@ -60,14 +65,14 @@ app.listen(port, () => {
60
65
  });
61
66
  ```
62
67
 
63
- ## Define model classes
68
+ ### Define model classes
64
69
  ```ts
65
- import { Entity, EntityBase, Fields } from 'remult';
70
+ import { Entity, Fields } from 'remult';
66
71
 
67
72
  @Entity('products', {
68
73
  allowApiCrud: true
69
74
  })
70
- export class Product extends EntityBase {
75
+ export class Product {
71
76
  @Fields.string()
72
77
  name = '';
73
78
 
@@ -83,13 +88,14 @@ export class Product extends EntityBase {
83
88
  [{"name":"Tofu","unitPrice":5}]
84
89
  ```
85
90
 
86
- ## Find and manipulate data in type-safe frontend code
91
+ ### Find and manipulate data in type-safe frontend code
87
92
  ```ts
88
93
  async function increasePriceOfTofu(priceIncrease: number) {
89
- const product = await remult.repo(Product).findFirst({ name: 'Tofu' });
94
+ const productsRepo = remult.repo(Product);
90
95
 
96
+ const product = await productsRepo.findFirst({ name: 'Tofu' });
91
97
  product.unitPrice += priceIncrease;
92
- product.save();
98
+ productsRepo.save(product);
93
99
  }
94
100
  ```
95
101
 
@@ -97,22 +103,23 @@ async function increasePriceOfTofu(priceIncrease: number) {
97
103
  ```ts
98
104
  @BackendMethod({ allowed: Allow.authenticated })
99
105
  static async increasePriceOfTofu(priceIncrease: number, remult?: Remult) {
100
- const product = await remult!.repo(Product).findFirst({ name: 'Tofu' });
106
+ const productsRepo = remult!.repo(Product);
101
107
 
108
+ const product = await productsRepo.findFirst({ name: 'Tofu' });
102
109
  product.unitPrice += priceIncrease;
103
- product.save();
110
+ productsRepo.save(product);
104
111
  }
105
112
  ```
106
113
 
107
- ## :ballot_box_with_check: Data validation and constraints - defined once
114
+ ### :ballot_box_with_check: Data validation and constraints - defined once
108
115
 
109
116
  ```ts
110
- import { Entity, EntityBase, Fields } from 'remult';
117
+ import { Entity, Fields } from 'remult';
111
118
 
112
119
  @Entity('products', {
113
120
  allowApiCrud: true
114
121
  })
115
- export class Product extends EntityBase {
122
+ export class Product {
116
123
  @Fields.string<Product>({
117
124
  validate: product => {
118
125
  if (product.name.trim().length == 0)
@@ -133,13 +140,13 @@ export class Product extends EntityBase {
133
140
 
134
141
  ### Enforced in frontend:
135
142
  ```ts
136
- const product = remult.repo(Product).create();
143
+ const product = productsRepo.create();
137
144
 
138
145
  try {
139
- await product.save();
146
+ await productsRepo.save(product);
140
147
  }
141
- catch {
142
- console.error(product._.error); // Browser console will display - "Name: required"
148
+ catch (e: ErrorInfo<Product>) {
149
+ console.error(e.message); // Browser console will display - "Name: required"
143
150
  }
144
151
  ```
145
152
 
@@ -150,14 +157,14 @@ catch {
150
157
  {"modelState":{"unitPrice":"must not be less than 0","name":"required"},"message":"Name: required"}
151
158
  ```
152
159
 
153
- ## :lock: Secure the API with fine-grained authorization
160
+ ### :lock: Secure the API with fine-grained authorization
154
161
  ```ts
155
162
  @Entity<Article>('Articles', {
156
163
  allowApiRead: true,
157
164
  allowApiInsert: remult => remult.authenticated(),
158
165
  allowApiUpdate: (remult, article) => article.author.id == remult.user.id
159
166
  })
160
- export class Article extends EntityBase {
167
+ export class Article {
161
168
  @Fields.string({ allowApiUpdate: false })
162
169
  slug = '';
163
170
 
@@ -168,3 +175,9 @@ export class Article extends EntityBase {
168
175
  content = '';
169
176
  }
170
177
  ```
178
+
179
+ ## Contributing
180
+ Contributions are welcome. See [CONTRIBUTING](CONTRIBUTING.md).
181
+
182
+ ## License
183
+ 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;
@@ -453,6 +453,8 @@ var SiteArea = /** @class */ (function () {
453
453
  _b.apply(_a, [_c.sent()]);
454
454
  if (req) {
455
455
  user = req['user'];
456
+ if (!user)
457
+ user = req['auth'];
456
458
  if (user)
457
459
  remult.setUser(user);
458
460
  }
@@ -2,24 +2,65 @@ import { ClassType } from '../classType';
2
2
  import { Allowed, AllowedForInstance } from './context';
3
3
  import { EntityMetadata, FieldRef } from './remult3';
4
4
  export interface FieldOptions<entityType = any, valueType = any> {
5
- key?: string;
6
- target?: ClassType<entityType>;
7
- valueType?: any;
5
+ /**A human readable name for the field */
8
6
  caption?: string;
9
- displayValue?: (entity: entityType, value: valueType) => string;
10
- defaultValue?: (entity: entityType) => valueType | Promise<valueType>;
7
+ /** If it can store null in the database */
8
+ allowNull?: boolean;
9
+ /** If this field data is included in the api.
10
+ * @see [allowed](http://remult.dev/docs/allowed.html)*/
11
+ includeInApi?: Allowed;
12
+ /** If this field data can be updated in the api.
13
+ * @see [allowed](http://remult.dev/docs/allowed.html)*/
14
+ allowApiUpdate?: AllowedForInstance<entityType>;
15
+ /** An arrow function that'll be used to perform validations on it
16
+ * @example
17
+ * .@Fields.string({
18
+ * validate: Validators.required
19
+ * })
20
+ * @example
21
+ * .@Fields.string<Task>({
22
+ * validate: task=>{
23
+ * if (task.title.length<3)
24
+ * throw "Too Short";
25
+ * }
26
+ * })
27
+ * @example
28
+ * .@Fields.string({
29
+ * validate: (_, fieldRef)=>{
30
+ * if (fieldRef.value.length<3)
31
+ * fieldRef.error = "Too Short";
32
+ * }
33
+ * })
34
+ */
11
35
  validate?: ((entity: entityType, col: FieldRef<entityType, valueType>) => (any | Promise<any>)) | ((entity: entityType, col: FieldRef<entityType, valueType>) => (any | Promise<any>))[];
36
+ /** Will be fired before this field is saved to the server/database */
12
37
  saving?: ((entity: entityType, col: FieldRef<entityType, valueType>) => (any | Promise<any>));
13
- inputType?: string;
14
- allowNull?: boolean;
38
+ /** An expression that will determine this fields value on the backend and be provided to the front end*/
39
+ serverExpression?: (entity: entityType) => valueType | Promise<valueType>;
40
+ /** The name of the column in the database that holds the data for this field. If no name is set, the key will be used instead. */
15
41
  dbName?: string;
42
+ /** Used or fields that are based on an sql expressions, instead of a physical table column */
16
43
  sqlExpression?: string | ((entity: EntityMetadata<entityType>) => string | Promise<string>);
17
- serverExpression?: (entity: entityType) => valueType | Promise<valueType>;
44
+ /** For fields that shouldn't be part of an update or insert statement */
18
45
  dbReadOnly?: boolean;
46
+ /** The value converter to be used when loading and saving this field */
19
47
  valueConverter?: ValueConverter<valueType>;
20
- includeInApi?: Allowed;
21
- allowApiUpdate?: AllowedForInstance<entityType>;
48
+ /** an arrow function that translates the value to a display value */
49
+ displayValue?: (entity: entityType, value: valueType) => string;
50
+ /** an arrow function that determines the default value of the field, when the entity is created using the `repo.create` method */
51
+ defaultValue?: (entity: entityType) => valueType | Promise<valueType>;
52
+ /** The html input type for this field */
53
+ inputType?: string;
54
+ /** Determines if the referenced entity will be loaded immediately or on demand.
55
+ * @see[Lazy loading of related entities](http://remult.dev/docs/lazy-loading-of-related-entities.html)
56
+ */
22
57
  lazy?: boolean;
58
+ /** The value type for this field */
59
+ valueType?: any;
60
+ /** The entity type to which this field belongs */
61
+ target?: ClassType<entityType>;
62
+ /** The key to be used for this field */
63
+ key?: string;
23
64
  }
24
65
  export interface FieldMetadata<valueType = any> {
25
66
  readonly key: string;
package/src/column.d.ts CHANGED
@@ -44,7 +44,6 @@ export declare class OneToMany<T> {
44
44
  private _currentPromise;
45
45
  get lazyItems(): T[];
46
46
  load(): Promise<T[]>;
47
- reload(): Promise<T[]>;
48
47
  private find;
49
48
  create(item?: Partial<T>): T;
50
49
  }
package/src/column.js CHANGED
@@ -131,8 +131,6 @@ var OneToMany = /** @class */ (function () {
131
131
  function OneToMany(provider, settings) {
132
132
  this.provider = provider;
133
133
  this.settings = settings;
134
- if (!this.settings)
135
- this.settings = {};
136
134
  }
137
135
  Object.defineProperty(OneToMany.prototype, "lazyItems", {
138
136
  get: function () {
@@ -159,14 +157,6 @@ var OneToMany = /** @class */ (function () {
159
157
  });
160
158
  });
161
159
  };
162
- OneToMany.prototype.reload = function () {
163
- return tslib_1.__awaiter(this, void 0, void 0, function () {
164
- return tslib_1.__generator(this, function (_a) {
165
- this._currentPromise = undefined;
166
- return [2 /*return*/, this.load()];
167
- });
168
- });
169
- };
170
160
  OneToMany.prototype.find = function () {
171
161
  return tslib_1.__awaiter(this, void 0, void 0, function () {
172
162
  return tslib_1.__generator(this, function (_a) {
@@ -178,8 +168,6 @@ var OneToMany = /** @class */ (function () {
178
168
  var r = this.provider.create();
179
169
  remult3_1.__updateEntityBasedOnWhere(this.provider.metadata, this.settings.where, r);
180
170
  assign_1.assign(r, item);
181
- if (this.settings.create)
182
- this.settings.create(r);
183
171
  return r;
184
172
  };
185
173
  return OneToMany;
package/src/context.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { DataProvider } from "./data-interfaces";
1
+ import { DataProvider, RestDataProviderHttpProvider } from "./data-interfaces";
2
2
  import { EntityMetadata, EntityRef, FindOptions, Repository } from "./remult3";
3
3
  import { ClassType } from "../classType";
4
4
  export interface HttpProvider {
@@ -15,33 +15,76 @@ export interface HttpProvider {
15
15
  toPromise(): Promise<any>;
16
16
  };
17
17
  }
18
+ export declare class HttpProviderBridgeToRestDataProviderHttpProvider implements RestDataProviderHttpProvider {
19
+ private http;
20
+ constructor(http: HttpProvider);
21
+ post(url: string, data: any): Promise<any>;
22
+ delete(url: string): Promise<void>;
23
+ put(url: string, data: any): Promise<any>;
24
+ get(url: string): Promise<any>;
25
+ }
26
+ export declare function retry<T>(what: () => Promise<T>): Promise<T>;
18
27
  export declare function toPromise<T>(p: Promise<T> | {
19
28
  toPromise(): Promise<T>;
20
29
  }): Promise<any>;
21
30
  export declare function processHttpException(ex: any): Promise<any>;
22
31
  export declare function isBackend(): boolean;
23
32
  export declare class Remult {
24
- static onFind: (metadata: EntityMetadata, options: FindOptions<any>) => void;
25
- clearAllCache(): any;
26
- static entityRefInit?: (ref: EntityRef<any>, row: any) => void;
27
- authenticated(): boolean;
28
- constructor(provider?: HttpProvider | DataProvider);
29
- _dataSource: DataProvider;
30
- setDataProvider(dataProvider: DataProvider): void;
31
- protected _user: UserInfo;
33
+ /**Return's a `Repository` of the specific entity type
34
+ * @example
35
+ * const taskRepo = remult.repo(Task);
36
+ * @see [Repository](https://remult.dev/docs/ref_repository.html)
37
+ *
38
+ */
39
+ repo<T>(entity: ClassType<T>, dataProvider?: DataProvider): Repository<T>;
40
+ /** Returns the current user's info */
32
41
  get user(): UserInfo;
33
- private _userChangeEvent;
34
- get userChange(): EventDispatcher;
42
+ /** Set's the current user info */
35
43
  setUser(info: UserInfo | {
36
44
  sub?: string;
37
45
  name?: string;
38
46
  permissions?: string[];
39
47
  }): Promise<void>;
40
- static apiBaseUrl: string;
41
- isAllowedForInstance(instance: any, x: AllowedForInstance<any>): boolean;
48
+ private _user;
49
+ private _userChangeEvent;
50
+ /** Checks if a user was authenticated */
51
+ authenticated(): boolean;
52
+ /** checks if the user has any of the roles specified in the parameters
53
+ * @example
54
+ * remult.isAllowed("admin")
55
+ * @see
56
+ * [Allowed](https://remult.dev/docs/allowed.html)
57
+ */
42
58
  isAllowed(roles: Allowed): boolean;
59
+ /** checks if the user matches the allowedForInstance callback
60
+ * @see
61
+ * [Allowed](https://remult.dev/docs/allowed.html)
62
+ */
63
+ isAllowedForInstance(instance: any, x: AllowedForInstance<any>): boolean;
64
+ /** returns a dispatcher object that fires once a user has changed*/
65
+ get userChange(): EventDispatcher;
43
66
  private repCache;
44
- repo<T>(entity: ClassType<T>, dataProvider?: DataProvider): Repository<T>;
67
+ /** Creates a new instance of the `remult` object.
68
+ *
69
+ * Can receive either an HttpProvider or a DataProvider as a parameter - which will be used to fetch data from.
70
+ *
71
+ * If no provider is specified, `fetch` will be used as an http provider
72
+ */
73
+ constructor(provider?: HttpProvider | DataProvider);
74
+ /** The api Base Url to be used in all remult calls. by default it's set to `/api`.
75
+ *
76
+ * Set this property in case you want to determine a non relative api url
77
+ */
78
+ static apiBaseUrl: string;
79
+ /** The current data provider */
80
+ _dataSource: DataProvider;
81
+ /** sets the current data provider */
82
+ setDataProvider(dataProvider: DataProvider): void;
83
+ /** A helper callback that can be used to debug and trace all find operations. Useful in debugging scenarios */
84
+ static onFind: (metadata: EntityMetadata, options: FindOptions<any>) => void;
85
+ clearAllCache(): any;
86
+ /** A helper callback that is called whenever an entity is created. */
87
+ static entityRefInit?: (ref: EntityRef<any>, row: any) => void;
45
88
  }
46
89
  export declare const allEntities: ClassType<any>[];
47
90
  export interface ControllerOptions {