remult 0.13.27 → 0.13.31-esm.1
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/{readme.md → README.md} +86 -36
- 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 +22 -0
- package/esm/index.js +21 -0
- package/esm/inputTypes.d.ts +9 -0
- package/esm/inputTypes.js +9 -0
- package/esm/postgres/index.d.ts +1 -0
- package/esm/postgres/index.js +1 -0
- package/esm/postgres/postgres-data-provider.d.ts +37 -0
- package/esm/postgres/postgres-data-provider.js +194 -0
- package/esm/postgres/postgresColumnSyntax.d.ts +2 -0
- package/esm/postgres/postgresColumnSyntax.js +30 -0
- package/esm/remult-express.d.ts +1 -0
- package/esm/remult-express.js +1 -0
- package/esm/remult-knex/index.d.ts +48 -0
- package/esm/remult-knex/index.js +381 -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 +114 -0
- package/esm/server/expressBridge.js +677 -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/package.json +14 -1
- package/server/expressBridge.d.ts +1 -0
- package/server/expressBridge.js +22 -17
- package/src/column-interfaces.d.ts +3 -3
- package/src/context.js +9 -9
- package/src/data-providers/array-entity-data-provider.js +37 -34
- package/src/data-providers/web-sql-data-provider.js +4 -3
- package/src/entity.d.ts +11 -4
- package/src/remult3/RepositoryImplementation.d.ts +6 -5
- package/src/remult3/RepositoryImplementation.js +17 -8
- package/src/remult3/remult3.d.ts +8 -3
- package/src/shared-tests/test-update-with-null.spec.js +38 -0
- package/valueConverters.js +4 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Noam Honig, Yoni Rapoport and contributors.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/{readme.md → README.md}
RENAMED
|
@@ -21,52 +21,44 @@
|
|
|
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>
|
|
28
|
+
<hr/>
|
|
29
|
+
|
|
30
|
+
[Getting Started](#getting-started) | [Documentation](#documentation) | [Example App](#example-app)
|
|
25
31
|
|
|
26
32
|
## What is Remult?
|
|
27
33
|
|
|
28
|
-
**Remult** is a full-stack CRUD framework that uses your TypeScript
|
|
34
|
+
**Remult** is a full-stack CRUD framework that uses your **TypeScript entities as a single source of truth for your API, frontend type-safe API client and backend ORM**.
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
* Type-safe frontend API client
|
|
32
|
-
* Type-safe backend query builder
|
|
36
|
+
### Status
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
Remult is **production-ready** and, in fact, used in production apps since 2017. However, we’re keeping the major version at zero so we can use community feedback to finalize the v1 API.
|
|
35
39
|
|
|
36
|
-
|
|
40
|
+
## Motivation
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
The best way to learn Remult is by following a tutorial of a simple Todo web app with a Node.js Express backend.
|
|
42
|
+
Full-stack web development is (still) too complicated. **Simple CRUD, a common requirement of any business application, should be simple to build, maintain, and extend** when the need arises.
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
* [Tutorial with Next.js](https://remult.dev/tutorials/react-next/)
|
|
44
|
+
Remult abstracts away repetitive, boilerplate, error-prone, and poorly designed code on the one hand, and enables total flexibility and control on the other. **Remult let you write TypeScript code you can follow and safely refactor**, and fits nicely into any existing or new project by being minimalistic and completely unopinionated regarding the developer’s choice of other frameworks and tools.
|
|
45
|
+
|
|
46
|
+
Other frameworks tend to fall into either too much abstraction (no-code, low-code, BaaS) or partial abstraction (MVC frameworks, GraphQL, ORMs, API generators, code generators), and tend to be opinionated regarding the development tool-chain, deployment environment, configuration/conventions or DSL. Remult attempts to strike a better balance.
|
|
45
47
|
|
|
46
48
|
## Installation
|
|
49
|
+
|
|
50
|
+
The *remult* package is one and the same for both the frontend bundle and the backend. Install it once for a monolith project or per-repo in a monorepo.
|
|
51
|
+
|
|
47
52
|
```sh
|
|
48
53
|
npm i remult
|
|
49
54
|
```
|
|
50
55
|
|
|
51
56
|
## Usage
|
|
52
57
|
|
|
53
|
-
### Setup API backend using a Node.js Express middleware
|
|
54
|
-
```ts
|
|
55
|
-
import express from 'express';
|
|
56
|
-
import { remultExpress } from 'remult/remult-express';
|
|
57
|
-
|
|
58
|
-
const port = 3001;
|
|
59
|
-
const app = express();
|
|
60
|
-
|
|
61
|
-
app.use(remultExpress());
|
|
62
|
-
|
|
63
|
-
app.listen(port, () => {
|
|
64
|
-
console.log(`Example API listening at http://localhost:${port}`);
|
|
65
|
-
});
|
|
66
|
-
```
|
|
67
|
-
|
|
68
58
|
### Define model classes
|
|
69
59
|
```ts
|
|
60
|
+
// shared/product.ts
|
|
61
|
+
|
|
70
62
|
import { Entity, Fields } from 'remult';
|
|
71
63
|
|
|
72
64
|
@Entity('products', {
|
|
@@ -81,6 +73,26 @@ export class Product {
|
|
|
81
73
|
}
|
|
82
74
|
```
|
|
83
75
|
|
|
76
|
+
### Setup API backend using an Express middleware
|
|
77
|
+
```ts
|
|
78
|
+
// backend/index.ts
|
|
79
|
+
|
|
80
|
+
import express from 'express';
|
|
81
|
+
import { remultExpress } from 'remult/remult-express';
|
|
82
|
+
import { Product } from '../shared/product';
|
|
83
|
+
|
|
84
|
+
const port = 3001;
|
|
85
|
+
const app = express();
|
|
86
|
+
|
|
87
|
+
app.use(remultExpress({
|
|
88
|
+
entities: [Product]
|
|
89
|
+
}));
|
|
90
|
+
|
|
91
|
+
app.listen(port, () => {
|
|
92
|
+
console.log(`Example API listening at http://localhost:${port}`);
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
84
96
|
### :rocket: API Ready
|
|
85
97
|
```sh
|
|
86
98
|
> curl http://localhost:3001/api/products
|
|
@@ -90,12 +102,19 @@ export class Product {
|
|
|
90
102
|
|
|
91
103
|
### Find and manipulate data in type-safe frontend code
|
|
92
104
|
```ts
|
|
105
|
+
// frontend/code.ts
|
|
106
|
+
|
|
107
|
+
import { Remult } from 'remult';
|
|
108
|
+
import { Product } from '../shared/product';
|
|
109
|
+
|
|
110
|
+
const remult = new Remult();
|
|
111
|
+
|
|
93
112
|
async function increasePriceOfTofu(priceIncrease: number) {
|
|
94
113
|
const productsRepo = remult.repo(Product);
|
|
95
114
|
|
|
96
|
-
const product = await productsRepo.findFirst({ name: 'Tofu' });
|
|
115
|
+
const product = await productsRepo.findFirst({ name: 'Tofu' }); // filter is passed through API request all the way to the db
|
|
97
116
|
product.unitPrice += priceIncrease;
|
|
98
|
-
productsRepo.save(product);
|
|
117
|
+
productsRepo.save(product); // mutation request updates the db with no boilerplate code
|
|
99
118
|
}
|
|
100
119
|
```
|
|
101
120
|
|
|
@@ -105,7 +124,7 @@ async function increasePriceOfTofu(priceIncrease: number) {
|
|
|
105
124
|
static async increasePriceOfTofu(priceIncrease: number, remult?: Remult) {
|
|
106
125
|
const productsRepo = remult!.repo(Product);
|
|
107
126
|
|
|
108
|
-
const product = await productsRepo.findFirst({ name: 'Tofu' });
|
|
127
|
+
const product = await productsRepo.findFirst({ name: 'Tofu' }); // use Remult in the backend as an ORM
|
|
109
128
|
product.unitPrice += priceIncrease;
|
|
110
129
|
productsRepo.save(product);
|
|
111
130
|
}
|
|
@@ -114,24 +133,29 @@ static async increasePriceOfTofu(priceIncrease: number, remult?: Remult) {
|
|
|
114
133
|
### :ballot_box_with_check: Data validation and constraints - defined once
|
|
115
134
|
|
|
116
135
|
```ts
|
|
117
|
-
import { Entity, Fields } from 'remult';
|
|
136
|
+
import { Entity, Fields, Validators } from 'remult';
|
|
118
137
|
|
|
119
138
|
@Entity('products', {
|
|
120
139
|
allowApiCrud: true
|
|
121
140
|
})
|
|
122
141
|
export class Product {
|
|
142
|
+
@Fields.string({
|
|
143
|
+
validate: Validators.required
|
|
144
|
+
})
|
|
145
|
+
name = '';
|
|
146
|
+
|
|
123
147
|
@Fields.string<Product>({
|
|
124
148
|
validate: product => {
|
|
125
|
-
if (product.
|
|
126
|
-
throw
|
|
149
|
+
if (product.description.trim().length < 50)
|
|
150
|
+
throw "too short";
|
|
127
151
|
}
|
|
128
152
|
})
|
|
129
|
-
|
|
153
|
+
description = '';
|
|
130
154
|
|
|
131
155
|
@Fields.number({
|
|
132
156
|
validate: (_, field) => {
|
|
133
157
|
if (field.value < 0)
|
|
134
|
-
throw "must not be less than 0";
|
|
158
|
+
field.error = "must not be less than 0"; // or: throw "must not be less than 0";
|
|
135
159
|
}
|
|
136
160
|
})
|
|
137
161
|
unitPrice = 0;
|
|
@@ -176,12 +200,38 @@ export class Article {
|
|
|
176
200
|
}
|
|
177
201
|
```
|
|
178
202
|
|
|
203
|
+
## What about complex CRUD?
|
|
204
|
+
|
|
205
|
+
While simple CRUD shouldn’t require any backend coding, using Remult means having the ability to handle any complex scenario by controlling the backend in numerous ways:
|
|
206
|
+
|
|
207
|
+
* Backend computed (read-only) fields - from simple [expressions](https://remult.dev/docs/ref_field.html#serverexpression) to complex data lookups or even direct db access (SQL)
|
|
208
|
+
* Custom side-effects with [entity lifecycle hooks](https://remult.dev/docs/ref_entity.html#saving) (before/after saving/deleting)
|
|
209
|
+
* Backend only updatable fields (e.g. “last updated at”)
|
|
210
|
+
* Many-to-one [relations](https://remult.dev/docs/entity-relations.html) with [lazy/eager loading](https://remult.dev/docs/lazy-loading-of-related-entities.html)
|
|
211
|
+
* Roll-your-own type-safe endpoints with [Backend Methods](https://remult.dev/docs/backendMethods.html)
|
|
212
|
+
* Roll-your-own low-level endpoints (Express, Fastify, koa, others…)
|
|
213
|
+
|
|
214
|
+
## Getting started
|
|
215
|
+
The best way to learn Remult is by following a tutorial of a simple Todo web app with a Node.js Express backend.
|
|
216
|
+
|
|
217
|
+
* [Tutorial with React](https://remult.dev/tutorials/react/)
|
|
218
|
+
* [Tutorial with Angular](https://remult.dev/tutorials/angular/)
|
|
219
|
+
* [Tutorial with Vue](https://remult.dev/tutorials/vue/)
|
|
220
|
+
* [Tutorial with Next.js](https://remult.dev/tutorials/react-next/)
|
|
221
|
+
|
|
222
|
+
## Documentation
|
|
223
|
+
The [documentation](https://remult.dev/docs) covers the main features of Remult. However, it is still a work-in-progress.
|
|
224
|
+
|
|
179
225
|
## Example App
|
|
180
226
|
|
|
181
227
|
[CRM demo](https://github.com/remult/crm-demo) with a React + [MUI](https://mui.com) front-end and Postgres database.
|
|
182
228
|
|
|
183
229
|
## Contributing
|
|
184
|
-
Contributions are welcome. See [CONTRIBUTING](CONTRIBUTING.md).
|
|
230
|
+
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
231
|
+
|
|
232
|
+
* :speech_balloon: Any feedback or suggestions? Start a [discussion](https://github.com/remult/remult/discussions).
|
|
233
|
+
* :muscle: Want to help out? Look for "help wanted" labeled [issues](https://github.com/remult/remult/issues).
|
|
234
|
+
* :star: Give this repo a star.
|
|
185
235
|
|
|
186
236
|
## License
|
|
187
237
|
Remult is [MIT Licensed](LICENSE).
|
package/esm/assign.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function assign<T>(item: T, valuesToSet: Partial<T>): T;
|
package/esm/assign.js
ADDED
package/esm/classType.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/esm/graphql.d.ts
ADDED
package/esm/graphql.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Remult } from ".";
|
|
2
|
+
import { allEntities } from "./src/context";
|
|
3
|
+
import { getEntityKey } from "./src/remult3";
|
|
4
|
+
import { DataApi } from "./src/data-api";
|
|
5
|
+
export function remultGraphql(api) {
|
|
6
|
+
let r = new Remult();
|
|
7
|
+
let types = '';
|
|
8
|
+
let query = '';
|
|
9
|
+
let root = {};
|
|
10
|
+
for (const e of allEntities) {
|
|
11
|
+
let meta = r.repo(e).metadata;
|
|
12
|
+
let fields = '';
|
|
13
|
+
let filterFields = '';
|
|
14
|
+
let key = getEntityKey(e);
|
|
15
|
+
if (key) {
|
|
16
|
+
for (const f of meta.fields) {
|
|
17
|
+
{
|
|
18
|
+
let type = "String";
|
|
19
|
+
switch (f.valueType) {
|
|
20
|
+
case Boolean:
|
|
21
|
+
type = "Boolean";
|
|
22
|
+
case Number:
|
|
23
|
+
{
|
|
24
|
+
if (f.valueConverter?.fieldTypeInDb == 'integer')
|
|
25
|
+
type = "Int";
|
|
26
|
+
else
|
|
27
|
+
type = "Float";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
fields += "\n\t" + f.key + ":" + type;
|
|
31
|
+
for (const operator of ["", "_ne"]) {
|
|
32
|
+
filterFields += "\n\t" + f.key + operator + ":" + type;
|
|
33
|
+
}
|
|
34
|
+
if (f.valueType === String || f.valueType === Number)
|
|
35
|
+
for (const operator of ["_gt", "_gte", "_lt", "_lte"]) {
|
|
36
|
+
filterFields += "\n\t" + f.key + operator + ":" + type;
|
|
37
|
+
}
|
|
38
|
+
if (f.valueType === String)
|
|
39
|
+
for (const operator of ["_st", "_contains"]) {
|
|
40
|
+
filterFields += "\n\t" + f.key + operator + ":" + type;
|
|
41
|
+
}
|
|
42
|
+
if (f.allowNull)
|
|
43
|
+
filterFields += "\n\t" + f.key + "_null:Boolean";
|
|
44
|
+
filterFields += "\n\t" + f.key + "_in:[" + type + "]";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
types += "type " + key + "{" + fields + "\n}\n";
|
|
48
|
+
types += "input " + key + "Filter{" + filterFields + "\n\tOR:[" + key + "Filter]\n}\n";
|
|
49
|
+
query += "\n\t" + key + "(options: options, filter:" + key + "Filter): [" + key + "]";
|
|
50
|
+
root[key] = async ({ options, filter }, req) => {
|
|
51
|
+
let remult = await api.getRemult(req);
|
|
52
|
+
let repo = remult.repo(e);
|
|
53
|
+
let dApi = new DataApi(repo, remult);
|
|
54
|
+
let result;
|
|
55
|
+
let err;
|
|
56
|
+
await dApi.getArray({
|
|
57
|
+
success: x => result = x,
|
|
58
|
+
created: undefined,
|
|
59
|
+
deleted: undefined,
|
|
60
|
+
error: x => err = x,
|
|
61
|
+
forbidden: () => err = 'forbidden',
|
|
62
|
+
notFound: () => err = 'not found',
|
|
63
|
+
progress: undefined
|
|
64
|
+
}, {
|
|
65
|
+
get: key => {
|
|
66
|
+
if (options)
|
|
67
|
+
switch (key) {
|
|
68
|
+
case "_limit":
|
|
69
|
+
return options.limit;
|
|
70
|
+
case "_page":
|
|
71
|
+
return options.page;
|
|
72
|
+
case "_sort":
|
|
73
|
+
return options.sort;
|
|
74
|
+
case "_order":
|
|
75
|
+
return options.order;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}, filter);
|
|
79
|
+
if (err)
|
|
80
|
+
throw err;
|
|
81
|
+
return result;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (query.length > 0) {
|
|
86
|
+
query = `type Query {${query}
|
|
87
|
+
}`;
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
rootValue: root,
|
|
91
|
+
schema: `
|
|
92
|
+
input options{
|
|
93
|
+
limit:Int
|
|
94
|
+
page:Int
|
|
95
|
+
sort:String
|
|
96
|
+
order:String
|
|
97
|
+
}
|
|
98
|
+
${types}
|
|
99
|
+
${query}
|
|
100
|
+
`
|
|
101
|
+
};
|
|
102
|
+
}
|
package/esm/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export { Field, Fields, StringFieldOptions, FieldsMetadata, Entity, BuildEntity, EntityBase, ControllerBase, FieldRef, IdFieldRef, FieldsRef, EntityMetadata, EntityOrderBy, EntityFilter, FindOptions, QueryResult, QueryOptions, Repository, FieldType, FindFirstOptions, ComparisonValueFilter, ValueFilter, IdFilter, ContainsStringValueFilter, getFields, EntityRef, getEntityRef, SortSegments, ValueListFieldType, getValueList, ValueListFieldOptions, ValueListInfo, OmitEB, Paginator, CaptionTransformer } from './src/remult3';
|
|
2
|
+
export { EntityOptions } from './src/entity';
|
|
3
|
+
export { DataProvider, EntityDataProvider, EntityDataProviderFindOptions, ErrorInfo, RestDataProviderHttpProvider } from './src/data-interfaces';
|
|
4
|
+
export { SqlCommand, SqlImplementation, SqlResult } from './src/sql-command';
|
|
5
|
+
export { FieldMetadata, FieldOptions, FieldValidator, ValueConverter, ValueListItem, // reconsider, maybe it should go to remult angular as the abstraction ?
|
|
6
|
+
ValueOrExpression } from './src/column-interfaces';
|
|
7
|
+
export { RestDataProvider } from './src/data-providers/rest-data-provider';
|
|
8
|
+
export { InMemoryDataProvider } from './src/data-providers/in-memory-database';
|
|
9
|
+
export { ArrayEntityDataProvider } from './src/data-providers/array-entity-data-provider';
|
|
10
|
+
export { WebSqlDataProvider } from './src/data-providers/web-sql-data-provider';
|
|
11
|
+
export { SqlDatabase } from './src/data-providers/sql-database';
|
|
12
|
+
export { JsonDataProvider, JsonEntityStorage } from './src/data-providers/json-data-provider';
|
|
13
|
+
export { Controller, BackendMethodOptions, BackendMethod, ProgressListener } from './src/server-action';
|
|
14
|
+
export { Allowed, Allow, Remult, isBackend, AllowedForInstance, EventDispatcher, EventSource, HttpProvider, Unobserve, UserInfo } from './src/context';
|
|
15
|
+
export { IdEntity } from './src/id-entity';
|
|
16
|
+
export { SortSegment, Sort } from './src/sort';
|
|
17
|
+
export { OneToMany, CompoundIdField } from './src/column';
|
|
18
|
+
export { Filter } from './src/filter/filter-interfaces';
|
|
19
|
+
export { FilterConsumerBridgeToSqlRequest } from './src/filter/filter-consumer-bridge-to-sql-request';
|
|
20
|
+
export { UrlBuilder } from './urlBuilder';
|
|
21
|
+
export { Validators } from './src/validators';
|
|
22
|
+
export { ValueConverters } from './valueConverters';
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Public API Surface of remult
|
|
3
|
+
*/
|
|
4
|
+
export { Field, Fields, Entity, BuildEntity, EntityBase, ControllerBase, FieldType, getFields, getEntityRef, ValueListFieldType, getValueList, ValueListInfo, CaptionTransformer } from './src/remult3';
|
|
5
|
+
export { RestDataProvider } from './src/data-providers/rest-data-provider'; //V
|
|
6
|
+
export { InMemoryDataProvider } from './src/data-providers/in-memory-database'; //V
|
|
7
|
+
export { ArrayEntityDataProvider } from './src/data-providers/array-entity-data-provider'; //V
|
|
8
|
+
export { WebSqlDataProvider } from './src/data-providers/web-sql-data-provider'; //V
|
|
9
|
+
export { SqlDatabase } from './src/data-providers/sql-database'; //V
|
|
10
|
+
export { JsonDataProvider } from './src/data-providers/json-data-provider'; //V
|
|
11
|
+
//export * from './src/data-api'; //reconsider if to make internal
|
|
12
|
+
export { Controller, BackendMethod, ProgressListener } from './src/server-action';
|
|
13
|
+
export { Allow, Remult, isBackend, EventSource } from './src/context';
|
|
14
|
+
export { IdEntity } from './src/id-entity';
|
|
15
|
+
export { Sort } from './src/sort';
|
|
16
|
+
export { OneToMany, CompoundIdField } from './src/column';
|
|
17
|
+
export { Filter } from './src/filter/filter-interfaces';
|
|
18
|
+
export { FilterConsumerBridgeToSqlRequest } from './src/filter/filter-consumer-bridge-to-sql-request';
|
|
19
|
+
export { UrlBuilder } from './urlBuilder';
|
|
20
|
+
export { Validators } from './src/validators';
|
|
21
|
+
export { ValueConverters } from './valueConverters';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './postgres-data-provider';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './postgres-data-provider';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { SqlDatabase, SqlCommand, SqlImplementation, EntityMetadata, FieldMetadata } from '../';
|
|
2
|
+
import { PoolConfig, QueryResult } from 'pg';
|
|
3
|
+
import { Remult } from '../src/context';
|
|
4
|
+
export interface PostgresPool extends PostgresCommandSource {
|
|
5
|
+
connect(): Promise<PostgresClient>;
|
|
6
|
+
}
|
|
7
|
+
export interface PostgresClient extends PostgresCommandSource {
|
|
8
|
+
release(): void;
|
|
9
|
+
}
|
|
10
|
+
export declare class PostgresDataProvider implements SqlImplementation {
|
|
11
|
+
private pool;
|
|
12
|
+
entityIsUsedForTheFirstTime(entity: EntityMetadata): Promise<void>;
|
|
13
|
+
getLimitSqlSyntax(limit: number, offset: number): string;
|
|
14
|
+
createCommand(): SqlCommand;
|
|
15
|
+
constructor(pool: PostgresPool);
|
|
16
|
+
transaction(action: (dataProvider: SqlImplementation) => Promise<void>): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export interface PostgresCommandSource {
|
|
19
|
+
query(queryText: string, values?: any[]): Promise<QueryResult>;
|
|
20
|
+
}
|
|
21
|
+
export declare function verifyStructureOfAllEntities(db: SqlDatabase, remult: Remult): Promise<void>;
|
|
22
|
+
export declare class PostgresSchemaBuilder {
|
|
23
|
+
private pool;
|
|
24
|
+
verifyStructureOfAllEntities(remult: Remult): Promise<void>;
|
|
25
|
+
createIfNotExist(entity: EntityMetadata): Promise<void>;
|
|
26
|
+
addColumnIfNotExist<T extends EntityMetadata>(entity: T, c: ((e: T) => FieldMetadata)): Promise<void>;
|
|
27
|
+
verifyAllColumns<T extends EntityMetadata>(entity: T): Promise<void>;
|
|
28
|
+
additionalWhere: string;
|
|
29
|
+
constructor(pool: SqlDatabase, schema?: string);
|
|
30
|
+
}
|
|
31
|
+
export declare function preparePostgresQueueStorage(sql: SqlDatabase): Promise<import("../server/expressBridge").EntityQueueStorage>;
|
|
32
|
+
export declare function createPostgresConnection(options?: {
|
|
33
|
+
connectionString?: string;
|
|
34
|
+
sslInDev?: boolean;
|
|
35
|
+
configuration?: "heroku" | PoolConfig;
|
|
36
|
+
autoCreateTables?: boolean;
|
|
37
|
+
}): Promise<SqlDatabase>;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { SqlDatabase } from '../';
|
|
2
|
+
import { Pool } from 'pg';
|
|
3
|
+
import { allEntities, Remult } from '../src/context';
|
|
4
|
+
import { postgresColumnSyntax } from './postgresColumnSyntax';
|
|
5
|
+
import { getDbNameProvider } from '../src/filter/filter-consumer-bridge-to-sql-request';
|
|
6
|
+
import { isAutoIncrement } from '../src/remult3';
|
|
7
|
+
export class PostgresDataProvider {
|
|
8
|
+
constructor(pool) {
|
|
9
|
+
this.pool = pool;
|
|
10
|
+
}
|
|
11
|
+
async entityIsUsedForTheFirstTime(entity) {
|
|
12
|
+
}
|
|
13
|
+
getLimitSqlSyntax(limit, offset) {
|
|
14
|
+
return ' limit ' + limit + ' offset ' + offset;
|
|
15
|
+
}
|
|
16
|
+
createCommand() {
|
|
17
|
+
return new PostgresBridgeToSQLCommand(this.pool);
|
|
18
|
+
}
|
|
19
|
+
async transaction(action) {
|
|
20
|
+
let client = await this.pool.connect();
|
|
21
|
+
try {
|
|
22
|
+
await client.query('BEGIN');
|
|
23
|
+
await action({
|
|
24
|
+
createCommand: () => new PostgresBridgeToSQLCommand(client),
|
|
25
|
+
entityIsUsedForTheFirstTime: this.entityIsUsedForTheFirstTime,
|
|
26
|
+
transaction: () => { throw "nested transactions not allowed"; },
|
|
27
|
+
getLimitSqlSyntax: this.getLimitSqlSyntax
|
|
28
|
+
});
|
|
29
|
+
await client.query('COMMIT');
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
await client.query('ROLLBACK');
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
await client.release();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
class PostgresBridgeToSQLCommand {
|
|
41
|
+
constructor(source) {
|
|
42
|
+
this.source = source;
|
|
43
|
+
this.values = [];
|
|
44
|
+
}
|
|
45
|
+
addParameterAndReturnSqlToken(val) {
|
|
46
|
+
this.values.push(val);
|
|
47
|
+
return '$' + this.values.length;
|
|
48
|
+
}
|
|
49
|
+
execute(sql) {
|
|
50
|
+
return this.source.query(sql, this.values).then(r => new PostgresBridgeToSQLQueryResult(r));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
class PostgresBridgeToSQLQueryResult {
|
|
54
|
+
constructor(r) {
|
|
55
|
+
this.r = r;
|
|
56
|
+
this.rows = r.rows;
|
|
57
|
+
}
|
|
58
|
+
getColumnKeyInResultForIndexInSelect(index) {
|
|
59
|
+
return this.r.fields[index].name;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export async function verifyStructureOfAllEntities(db, remult) {
|
|
63
|
+
return await new PostgresSchemaBuilder(db).verifyStructureOfAllEntities(remult);
|
|
64
|
+
}
|
|
65
|
+
export class PostgresSchemaBuilder {
|
|
66
|
+
constructor(pool, schema) {
|
|
67
|
+
this.pool = pool;
|
|
68
|
+
this.additionalWhere = '';
|
|
69
|
+
if (schema) {
|
|
70
|
+
this.additionalWhere = ' and table_schema=\'' + schema + '\'';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async verifyStructureOfAllEntities(remult) {
|
|
74
|
+
console.log("start verify structure");
|
|
75
|
+
for (const entityClass of allEntities) {
|
|
76
|
+
let entity = remult.repo(entityClass).metadata;
|
|
77
|
+
let e = await getDbNameProvider(entity);
|
|
78
|
+
try {
|
|
79
|
+
if (!entity.options.sqlExpression) {
|
|
80
|
+
if ((await e.entityName).toLowerCase().indexOf('from ') < 0) {
|
|
81
|
+
await this.createIfNotExist(entity);
|
|
82
|
+
await this.verifyAllColumns(entity);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
console.log("failed verify structure of " + e.entityName + " ", err);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async createIfNotExist(entity) {
|
|
92
|
+
var c = this.pool.createCommand();
|
|
93
|
+
let e = await getDbNameProvider(entity);
|
|
94
|
+
await c.execute("select 1 from information_Schema.tables where table_name=" + c.addParameterAndReturnSqlToken((e.entityName).toLowerCase()) + this.additionalWhere).then(async (r) => {
|
|
95
|
+
if (r.rows.length == 0) {
|
|
96
|
+
let result = '';
|
|
97
|
+
for (const x of entity.fields) {
|
|
98
|
+
if (!e.isDbReadonly(x) || isAutoIncrement(x)) {
|
|
99
|
+
if (result.length != 0)
|
|
100
|
+
result += ',';
|
|
101
|
+
result += '\r\n ';
|
|
102
|
+
if (isAutoIncrement(x))
|
|
103
|
+
result += e.nameOf(x) + ' serial';
|
|
104
|
+
else {
|
|
105
|
+
result += postgresColumnSyntax(x, e.nameOf(x));
|
|
106
|
+
if (x == entity.idMetadata.field)
|
|
107
|
+
result += ' primary key';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
let sql = 'create table ' + e.entityName + ' (' + result + '\r\n)';
|
|
112
|
+
//console.log(sql);
|
|
113
|
+
await this.pool.execute(sql);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async addColumnIfNotExist(entity, c) {
|
|
118
|
+
let e = await getDbNameProvider(entity);
|
|
119
|
+
if (e.isDbReadonly(c(entity)))
|
|
120
|
+
return;
|
|
121
|
+
try {
|
|
122
|
+
let cmd = this.pool.createCommand();
|
|
123
|
+
const colName = e.nameOf(c(entity));
|
|
124
|
+
if ((await cmd.execute(`select 1
|
|
125
|
+
FROM information_schema.columns
|
|
126
|
+
WHERE table_name=${cmd.addParameterAndReturnSqlToken((e.entityName).toLocaleLowerCase())} and column_name=${cmd.addParameterAndReturnSqlToken((colName).toLocaleLowerCase())}` + this.additionalWhere)).rows.length == 0) {
|
|
127
|
+
let sql = `alter table ${e.entityName} add column ${postgresColumnSyntax(c(entity), colName)}`;
|
|
128
|
+
//console.log(sql);
|
|
129
|
+
await this.pool.execute(sql);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
console.log(err);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async verifyAllColumns(entity) {
|
|
137
|
+
try {
|
|
138
|
+
let cmd = this.pool.createCommand();
|
|
139
|
+
let e = await getDbNameProvider(entity);
|
|
140
|
+
let cols = (await cmd.execute(`select column_name
|
|
141
|
+
FROM information_schema.columns
|
|
142
|
+
WHERE table_name=${cmd.addParameterAndReturnSqlToken((e.entityName).toLocaleLowerCase())} ` + this.additionalWhere)).rows.map(x => x.column_name);
|
|
143
|
+
for (const col of entity.fields) {
|
|
144
|
+
if (!e.isDbReadonly(col))
|
|
145
|
+
if (!cols.includes(e.nameOf(col).toLocaleLowerCase())) {
|
|
146
|
+
let sql = `alter table ${e.entityName} add column ${postgresColumnSyntax(col, e.nameOf(col))}`;
|
|
147
|
+
//console.log(sql);
|
|
148
|
+
await this.pool.execute(sql);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
console.log(err);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
export async function preparePostgresQueueStorage(sql) {
|
|
158
|
+
let c = new Remult();
|
|
159
|
+
c.setDataProvider(sql);
|
|
160
|
+
let JobsInQueueEntity = (await import('../server/expressBridge')).JobsInQueueEntity;
|
|
161
|
+
let e = c.repo(JobsInQueueEntity);
|
|
162
|
+
await new PostgresSchemaBuilder(sql).createIfNotExist(e.metadata);
|
|
163
|
+
await new PostgresSchemaBuilder(sql).verifyAllColumns(e.metadata);
|
|
164
|
+
return new (await import('../server/expressBridge')).EntityQueueStorage(c.repo(JobsInQueueEntity));
|
|
165
|
+
}
|
|
166
|
+
export async function createPostgresConnection(options) {
|
|
167
|
+
if (!options)
|
|
168
|
+
options = {};
|
|
169
|
+
let config = {};
|
|
170
|
+
if (options.configuration)
|
|
171
|
+
if (options.configuration == "heroku") {
|
|
172
|
+
config = {
|
|
173
|
+
connectionString: process.env.DATABASE_URL,
|
|
174
|
+
ssl: process.env.NODE_ENV !== "production" && !options.sslInDev ? false : {
|
|
175
|
+
rejectUnauthorized: false
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
else
|
|
180
|
+
config = options.configuration;
|
|
181
|
+
else {
|
|
182
|
+
if (!options.connectionString)
|
|
183
|
+
options.connectionString = process.env.DATABASE_URL;
|
|
184
|
+
}
|
|
185
|
+
if (!config.connectionString && options.connectionString) {
|
|
186
|
+
config.connectionString = options.connectionString;
|
|
187
|
+
}
|
|
188
|
+
const db = new SqlDatabase(new PostgresDataProvider(new Pool(config)));
|
|
189
|
+
let remult = new Remult();
|
|
190
|
+
remult.setDataProvider(db);
|
|
191
|
+
if (options.autoCreateTables === undefined || options.autoCreateTables)
|
|
192
|
+
await verifyStructureOfAllEntities(db, remult);
|
|
193
|
+
return db;
|
|
194
|
+
}
|