remult 0.16.5 → 0.17.0-exp.2
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/README.md +5 -27
- package/esm/index.d.ts +2 -1
- package/esm/remult-express.d.ts +32 -0
- package/esm/remult-express.js +130 -2
- package/esm/remult-fastify.js +0 -2
- package/esm/remult-knex/index.js +2 -9
- package/esm/server/expressBridge.d.ts +44 -10
- package/esm/server/expressBridge.js +27 -20
- package/esm/src/backend-tests/backend-database-test-setup.backend-spec.d.ts +0 -1
- package/esm/src/backend-tests/backend-database-test-setup.backend-spec.js +0 -10
- package/esm/src/backend-tests/my-test.backend-spec.js +0 -48
- package/esm/src/column.d.ts +3 -3
- package/esm/src/context.d.ts +2 -0
- package/esm/src/context.js +11 -0
- package/esm/src/data-api.d.ts +6 -2
- package/esm/src/data-api.js +17 -4
- package/esm/src/data-providers/array-entity-data-provider.js +1 -16
- package/esm/src/data-providers/rest-data-provider.d.ts +4 -0
- package/esm/src/data-providers/rest-data-provider.js +11 -7
- package/esm/src/live-query/LiveQuery.js +7 -5
- package/esm/src/live-query/LiveQueryManager.d.ts +1 -2
- package/esm/src/live-query/LiveQueryManager.js +18 -16
- package/esm/src/remult-proxy.d.ts +1 -1
- package/esm/src/remult-proxy.js +11 -9
- package/esm/src/remult3/RepositoryImplementation.d.ts +6 -3
- package/esm/src/remult3/RepositoryImplementation.js +39 -22
- package/esm/src/remult3/getId.d.ts +2 -0
- package/esm/src/remult3/getId.js +7 -0
- package/esm/src/remult3/remult3.d.ts +10 -6
- package/esm/src/server-action.js +4 -3
- package/esm/src/shared-tests/db-tests-setup.d.ts +1 -1
- package/esm/src/shared-tests/db-tests-setup.js +0 -1
- package/esm/src/sort.d.ts +1 -0
- package/esm/src/sort.js +18 -0
- package/esm/src/tests/testHelper.spec.d.ts +2 -0
- package/esm/src/tests/testHelper.spec.js +60 -57
- package/esm/src/valueConverters.js +0 -2
- package/index.d.ts +2 -1
- package/package.json +1 -1
- package/remult-express.d.ts +32 -0
- package/remult-express.js +193 -3
- package/remult-fastify.js +0 -2
- package/remult-knex/index.js +8 -16
- package/server/expressBridge.d.ts +44 -10
- package/server/expressBridge.js +37 -20
- package/src/backend-tests/backend-database-test-setup.backend-spec.d.ts +0 -1
- package/src/backend-tests/backend-database-test-setup.backend-spec.js +1 -11
- package/src/backend-tests/my-test.backend-spec.js +0 -57
- package/src/column.d.ts +3 -3
- package/src/context.d.ts +2 -0
- package/src/context.js +11 -0
- package/src/data-api.d.ts +6 -2
- package/src/data-api.js +23 -15
- package/src/data-providers/array-entity-data-provider.js +1 -16
- package/src/data-providers/rest-data-provider.d.ts +4 -0
- package/src/data-providers/rest-data-provider.js +11 -7
- package/src/live-query/LiveQuery.js +6 -4
- package/src/live-query/LiveQueryManager.d.ts +1 -2
- package/src/live-query/LiveQueryManager.js +64 -62
- package/src/remult-proxy.d.ts +1 -1
- package/src/remult-proxy.js +19 -9
- package/src/remult3/RepositoryImplementation.d.ts +6 -3
- package/src/remult3/RepositoryImplementation.js +69 -42
- package/src/remult3/getId.d.ts +2 -0
- package/src/remult3/getId.js +11 -0
- package/src/remult3/remult3.d.ts +10 -6
- package/src/server-action.js +3 -2
- package/src/shared-tests/db-tests-setup.d.ts +1 -1
- package/src/shared-tests/db-tests-setup.js +0 -1
- package/src/sort.d.ts +1 -0
- package/src/sort.js +18 -0
- package/src/tests/testHelper.spec.d.ts +2 -0
- package/src/tests/testHelper.spec.js +99 -95
- package/src/valueConverters.js +0 -2
package/README.md
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
<img src="https://github.com/remult/remult/raw/master/docs/.vuepress/public/logo.png" width="140" height="140">
|
|
4
4
|
</a>
|
|
5
5
|
<h1>Remult</h1>
|
|
6
|
-
<p>A CRUD framework for full-stack TypeScript</p>
|
|
7
6
|
<a href="https://circleci.com/gh/remult/remult/tree/master" rel="nofollow">
|
|
8
7
|
<img alt="CircleCI" src="https://circleci.com/gh/remult/remult/tree/master.svg?style=shield">
|
|
9
8
|
</a>
|
|
@@ -25,28 +24,14 @@
|
|
|
25
24
|
<a href="https://twitter.com/intent/tweet?text=Remult%20-%20open-source%20CRUD%20framework%20for%20full-stack%20TypeScript%0A%0A%40remultjs%0A&url=https%3A%2F%2Fgithub.com%2Fremult%2Fremult">
|
|
26
25
|
<img alt="Twitter URL" src="https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Ftwitter.com%2Fintent%2Ftweet%3Ftext%3DRemult%2520-%2520open-source%2520CRUD%2520framework%2520for%2520full-stack%2520TypeScript%250A%250A%2540remultjs%250A%26url%3Dhttps%253A%252F%252Fgithub.com%252Fremult%252Fremult">
|
|
27
26
|
</a>
|
|
27
|
+
<a href="https://twitter.com/remultjs" rel="nofollow">
|
|
28
|
+
<img alt="Twitter Follow" src="https://img.shields.io/twitter/url.svg?label=Follow%20%40remultjs&style=social&url=https%3A%2F%2Ftwitter.com%2FRemultJs">
|
|
29
|
+
</a>
|
|
28
30
|
</div>
|
|
29
|
-
|
|
30
|
-
<br/>
|
|
31
|
-
|
|
32
|
-
<div align="center">
|
|
33
|
-
<a href="#getting-started">Getting Started</a> |
|
|
34
|
-
<a href="#documentation">Documentation</a> |
|
|
35
|
-
<a href="#example-apps">Example Apps</a>
|
|
36
|
-
</div>
|
|
37
|
-
|
|
38
31
|
<hr/>
|
|
39
|
-
<br/>
|
|
40
32
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
<img src="https://github.com/remult/remult/raw/master/static/images/video-thumbnail.jpg" alt="Video thumbnail" width="320" />
|
|
44
|
-
</p>
|
|
45
|
-
</a>
|
|
46
|
-
|
|
47
|
-
<a href="https://www.youtube.com/watch?v=rEoScmSVNUE" target="_blank">
|
|
48
|
-
<p align="center">Watch code demo on YouTube here</p>
|
|
49
|
-
</a>
|
|
33
|
+
[Getting Started](#getting-started) | [Documentation](#documentation) |
|
|
34
|
+
[Example Apps](#example-apps)
|
|
50
35
|
|
|
51
36
|
## What is Remult?
|
|
52
37
|
|
|
@@ -54,13 +39,6 @@
|
|
|
54
39
|
as a single source of truth for your API, frontend type-safe API client and
|
|
55
40
|
backend ORM**.
|
|
56
41
|
|
|
57
|
-
* :zap: Zero-boilerplate CRUD API routes with paging, sorting, and filtering for Express / Fastify / Next.js / NestJS / Koa / others...
|
|
58
|
-
* :ok_hand: Fullstack type-safety for API queries, mutations and RPC, without code generation
|
|
59
|
-
* :sparkles: Input validation, defined once, runs both on the backend and on the frontend for best UX
|
|
60
|
-
* :lock: Fine-grained code-based API authorization
|
|
61
|
-
* :relieved: Incrementally adoptable
|
|
62
|
-
* :rocket: Production ready
|
|
63
|
-
|
|
64
42
|
### Status
|
|
65
43
|
|
|
66
44
|
Remult is **production-ready** and, in fact, used in production apps since 2018.
|
package/esm/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { Field, Fields,
|
|
1
|
+
export { Field, Fields, FieldsMetadata, Entity, 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 { StringFieldOptions } from './src/remult3/RepositoryImplementation';
|
|
2
3
|
export { describeClass } from './src/remult3/DecoratorReplacer';
|
|
3
4
|
export { EntityOptions } from './src/entity';
|
|
4
5
|
export { DataProvider, EntityDataProvider, EntityDataProviderFindOptions, ErrorInfo, RestDataProviderHttpProvider } from './src/data-interfaces';
|
package/esm/remult-express.d.ts
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
import * as express from 'express';
|
|
2
2
|
import { RemultServer, RemultServerOptions } from './server/expressBridge';
|
|
3
|
+
import { Remult } from './src/context';
|
|
4
|
+
import { AMessageChannel, ChannelSubscribe } from './src/live-query/LiveQuery';
|
|
5
|
+
import { ServerEventDispatcher, ServerEventMessage } from './src/live-query/LiveQueryManager';
|
|
3
6
|
export declare function remultExpress(options?: RemultServerOptions<express.Request> & {
|
|
4
7
|
bodyParser?: boolean;
|
|
5
8
|
bodySizeLimit?: string;
|
|
9
|
+
messageChannels?: AMessageChannel<any>[];
|
|
6
10
|
}): express.RequestHandler & RemultServer;
|
|
11
|
+
export declare class ServerEventsController implements ServerEventDispatcher {
|
|
12
|
+
private channels;
|
|
13
|
+
private messageHistoryLength;
|
|
14
|
+
consoleInfo(): void;
|
|
15
|
+
subscribeToChannel(remult: Remult, info: ChannelSubscribe, res: import('express').Response): void;
|
|
16
|
+
connections: clientConnection[];
|
|
17
|
+
constructor(channels: AMessageChannel<any>[], messageHistoryLength?: number);
|
|
18
|
+
sendQueryMessage({ message, clientId, queryId }: ServerEventMessage): void;
|
|
19
|
+
sendChannelMessage<T>(channel: string, message: any): void;
|
|
20
|
+
openHttpServerStream(req: import('express').Request, res: import('express').Response): clientConnection;
|
|
21
|
+
messages: {
|
|
22
|
+
id: number;
|
|
23
|
+
message: any;
|
|
24
|
+
eventType: string;
|
|
25
|
+
}[];
|
|
26
|
+
SendMessage(message: any, eventType?: string): void;
|
|
27
|
+
}
|
|
28
|
+
declare class clientConnection {
|
|
29
|
+
response: import('express').Response;
|
|
30
|
+
clientId: string;
|
|
31
|
+
channels: Record<string, boolean>;
|
|
32
|
+
close(): void;
|
|
33
|
+
closed: boolean;
|
|
34
|
+
write(id: number, message: any, eventType: string): void;
|
|
35
|
+
constructor(response: import('express').Response, clientId: string);
|
|
36
|
+
sendLiveMessage(): void;
|
|
37
|
+
}
|
|
38
|
+
export {};
|
package/esm/remult-express.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as express from 'express';
|
|
2
2
|
import { createRemultServer } from './server/expressBridge';
|
|
3
|
-
import {
|
|
3
|
+
import { streamUrl } from './src/live-query/LiveQuery';
|
|
4
|
+
import { LiveQueryPublisher } from './src/live-query/LiveQueryManager';
|
|
5
|
+
import { remult } from './src/remult-proxy';
|
|
4
6
|
export function remultExpress(options) {
|
|
5
7
|
let app = express.Router();
|
|
6
8
|
if (!options) {
|
|
@@ -13,9 +15,21 @@ export function remultExpress(options) {
|
|
|
13
15
|
app.use(express.json({ limit: options.bodySizeLimit }));
|
|
14
16
|
app.use(express.urlencoded({ extended: true, limit: options.bodySizeLimit }));
|
|
15
17
|
}
|
|
16
|
-
initAsyncHooks();
|
|
17
18
|
const server = createRemultServer(options);
|
|
18
19
|
server.registerRouter(app);
|
|
20
|
+
let httpServerEvents = new ServerEventsController(options.messageChannels);
|
|
21
|
+
server.liveQueryManager = new LiveQueryPublisher(httpServerEvents);
|
|
22
|
+
const streamPath = options.rootPath + '/' + streamUrl;
|
|
23
|
+
app.get(streamPath, (req, res) => {
|
|
24
|
+
httpServerEvents.openHttpServerStream(req, res);
|
|
25
|
+
});
|
|
26
|
+
app.post(streamPath, (r, res, next) => server.withRemult(r, res, next), (req, res) => {
|
|
27
|
+
httpServerEvents.subscribeToChannel(remult, req.body, res);
|
|
28
|
+
});
|
|
29
|
+
app.get(streamPath + '/stats', (req, res) => {
|
|
30
|
+
httpServerEvents.consoleInfo();
|
|
31
|
+
res.json("ok");
|
|
32
|
+
});
|
|
19
33
|
return Object.assign(app, {
|
|
20
34
|
getRemult: (req) => server.getRemult(req),
|
|
21
35
|
openApiDoc: (options) => server.openApiDoc(options),
|
|
@@ -23,3 +37,117 @@ export function remultExpress(options) {
|
|
|
23
37
|
withRemult: (...args) => server.withRemult(...args)
|
|
24
38
|
});
|
|
25
39
|
}
|
|
40
|
+
export class ServerEventsController {
|
|
41
|
+
constructor(channels, messageHistoryLength = 1000) {
|
|
42
|
+
this.channels = channels;
|
|
43
|
+
this.messageHistoryLength = messageHistoryLength;
|
|
44
|
+
this.connections = [];
|
|
45
|
+
this.messages = [];
|
|
46
|
+
if (channels) {
|
|
47
|
+
for (const c of channels) {
|
|
48
|
+
c.dispatcher = this;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
consoleInfo() {
|
|
53
|
+
console.log(this.connections.map(x => ({
|
|
54
|
+
client: x.clientId,
|
|
55
|
+
channels: x.channels
|
|
56
|
+
})));
|
|
57
|
+
}
|
|
58
|
+
subscribeToChannel(remult, info, res) {
|
|
59
|
+
let ok = false;
|
|
60
|
+
if (this.channels)
|
|
61
|
+
for (const sc of this.connections) {
|
|
62
|
+
if (sc.clientId === info.clientId) {
|
|
63
|
+
for (const c of this.channels) {
|
|
64
|
+
if (c.userCanSubscribe(info.channel, remult)) {
|
|
65
|
+
sc.channels[info.channel] = !info.remove;
|
|
66
|
+
ok = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (ok)
|
|
72
|
+
res.json("ok");
|
|
73
|
+
else
|
|
74
|
+
res.status(404).json(`Channel "${info.channel}" not found`);
|
|
75
|
+
}
|
|
76
|
+
sendQueryMessage({ message, clientId, queryId }) {
|
|
77
|
+
for (const sc of this.connections) {
|
|
78
|
+
if (sc.clientId === clientId) {
|
|
79
|
+
sc.write(undefined, message, queryId);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
sendChannelMessage(channel, message) {
|
|
84
|
+
for (const sc of this.connections) {
|
|
85
|
+
if (sc.channels[channel]) {
|
|
86
|
+
sc.write(undefined, message, channel);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
openHttpServerStream(req, res) {
|
|
91
|
+
res.writeHead(200, {
|
|
92
|
+
'Content-Type': 'text/event-stream',
|
|
93
|
+
'Cache-Control': 'no-cache',
|
|
94
|
+
'Connection': 'keep-alive'
|
|
95
|
+
});
|
|
96
|
+
const cc = new clientConnection(res, req.headers["client-id"] || req.query['id']);
|
|
97
|
+
const lastEventId = req.headers['last-event-id'];
|
|
98
|
+
if (lastEventId) {
|
|
99
|
+
this.messages.filter(x => x.id > +lastEventId).forEach(m => cc.write(m.id, m.message, m.eventType));
|
|
100
|
+
}
|
|
101
|
+
this.connections.push(cc);
|
|
102
|
+
req.on("close", () => {
|
|
103
|
+
cc.close();
|
|
104
|
+
this.connections = this.connections.filter(s => s !== cc);
|
|
105
|
+
});
|
|
106
|
+
return cc;
|
|
107
|
+
}
|
|
108
|
+
SendMessage(message, eventType = '') {
|
|
109
|
+
let z = this;
|
|
110
|
+
let id = i++;
|
|
111
|
+
z.messages.push({ id, message: message, eventType });
|
|
112
|
+
while (z.messages.length > z.messageHistoryLength)
|
|
113
|
+
z.messages.shift();
|
|
114
|
+
//console.log({ sendingTo: z.connections.length })
|
|
115
|
+
z.connections.forEach(y => y.write(id, message, eventType));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
class clientConnection {
|
|
119
|
+
constructor(response, clientId) {
|
|
120
|
+
this.response = response;
|
|
121
|
+
this.clientId = clientId;
|
|
122
|
+
this.channels = {};
|
|
123
|
+
this.closed = false;
|
|
124
|
+
//console.log("open connection");
|
|
125
|
+
response.write("event:authenticate\ndata:" + "key" + "\n\n");
|
|
126
|
+
this.sendLiveMessage();
|
|
127
|
+
}
|
|
128
|
+
close() {
|
|
129
|
+
//console.log("close connection");
|
|
130
|
+
this.closed = true;
|
|
131
|
+
}
|
|
132
|
+
write(id, message, eventType) {
|
|
133
|
+
let event = "event:message";
|
|
134
|
+
if (id != undefined)
|
|
135
|
+
event += "\nid:" + id;
|
|
136
|
+
this.response.write(event + "\ndata:" + JSON.stringify({ event: eventType, data: message }) + "\n\n");
|
|
137
|
+
let r = this.response;
|
|
138
|
+
if (r.flush)
|
|
139
|
+
r.flush();
|
|
140
|
+
}
|
|
141
|
+
sendLiveMessage() {
|
|
142
|
+
if (this.closed)
|
|
143
|
+
return;
|
|
144
|
+
this.response.write("event:keep-alive\ndata:\n\n");
|
|
145
|
+
let r = this.response;
|
|
146
|
+
if (r.flush)
|
|
147
|
+
r.flush();
|
|
148
|
+
setTimeout(() => {
|
|
149
|
+
this.sendLiveMessage();
|
|
150
|
+
}, 45000);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
let i = 0;
|
package/esm/remult-fastify.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createRemultServer } from './server/expressBridge';
|
|
2
|
-
import { initAsyncHooks } from './server/initAsyncHooks';
|
|
3
2
|
export function remultFastify(options) {
|
|
4
3
|
function fastifyHandler(handler) {
|
|
5
4
|
const response = (req, res) => {
|
|
@@ -19,7 +18,6 @@ export function remultFastify(options) {
|
|
|
19
18
|
};
|
|
20
19
|
return response;
|
|
21
20
|
}
|
|
22
|
-
initAsyncHooks();
|
|
23
21
|
const api = createRemultServer(options);
|
|
24
22
|
const pluginFunction = async (instance, op) => {
|
|
25
23
|
//@ts-ignore
|
package/esm/remult-knex/index.js
CHANGED
|
@@ -153,15 +153,8 @@ class KnexEntityDataProvider {
|
|
|
153
153
|
}
|
|
154
154
|
let insert = this.knex(e.entityName).insert(insertObject);
|
|
155
155
|
if (isAutoIncrement(this.entity.idMetadata.field)) {
|
|
156
|
-
let
|
|
157
|
-
|
|
158
|
-
let result = await insert;
|
|
159
|
-
newId = result[0];
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
let result = await insert.returning(this.entity.idMetadata.field.key);
|
|
163
|
-
newId = result[0].id;
|
|
164
|
-
}
|
|
156
|
+
let result = await insert.returning(this.entity.idMetadata.field.key);
|
|
157
|
+
let newId = result[0].id;
|
|
165
158
|
resultFilter = new Filter(x => x.isEqualTo(this.entity.idMetadata.field, newId));
|
|
166
159
|
}
|
|
167
160
|
else
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { queuedJobInfoResponse } from '../src/server-action';
|
|
1
|
+
import { ActionInterface, queuedJobInfoResponse } from '../src/server-action';
|
|
2
2
|
import { DataProvider } from '../src/data-interfaces';
|
|
3
|
-
import {
|
|
3
|
+
import { DataApi, DataApiRequest, DataApiResponse } from '../src/data-api';
|
|
4
|
+
import { AllowedForInstance, Remult, UserInfo } from '../src/context';
|
|
4
5
|
import { ClassType } from '../classType';
|
|
5
6
|
import { Repository } from '../src/remult3';
|
|
6
7
|
import { IdEntity } from '../src/id-entity';
|
|
8
|
+
import { LiveQueryPublisher } from '../src/live-query/LiveQueryManager';
|
|
7
9
|
export interface RemultServerOptions<RequestType extends GenericRequest> {
|
|
8
10
|
/** Sets a database connection for Remult.
|
|
9
11
|
*
|
|
@@ -57,14 +59,39 @@ export interface GenericResponse {
|
|
|
57
59
|
status(statusCode: number): GenericResponse;
|
|
58
60
|
end(): any;
|
|
59
61
|
}
|
|
60
|
-
export declare class
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
constructor(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
export declare class RemultServerImplementation implements RemultServer {
|
|
63
|
+
queue: inProcessQueueHandler;
|
|
64
|
+
options: RemultServerOptions<GenericRequest>;
|
|
65
|
+
dataProvider: DataProvider | Promise<DataProvider>;
|
|
66
|
+
constructor(queue: inProcessQueueHandler, options: RemultServerOptions<GenericRequest>, dataProvider: DataProvider | Promise<DataProvider>);
|
|
67
|
+
liveQueryManager: LiveQueryPublisher;
|
|
68
|
+
withRemult<T>(req: GenericRequest, res: GenericResponse, next: VoidFunction): void;
|
|
69
|
+
routeImpl: RouteImplementation;
|
|
70
|
+
handle(req: GenericRequest, gRes?: GenericResponse): Promise<ServerHandleResponse>;
|
|
71
|
+
registeredRouter: boolean;
|
|
72
|
+
registerRouter(r: GenericRouter): void;
|
|
73
|
+
add(key: string, dataApiFactory: ((req: Remult) => DataApi), r: GenericRouter): void;
|
|
74
|
+
process(what: (remult: Remult, myReq: DataApiRequest, myRes: DataApiResponse, origReq: GenericRequest) => Promise<void>): (req: GenericRequest, res: GenericResponse) => Promise<void>;
|
|
75
|
+
getRemult(req: GenericRequest): Promise<Remult>;
|
|
76
|
+
hasQueue: boolean;
|
|
77
|
+
addAction(action: ActionInterface, r: GenericRouter): void;
|
|
78
|
+
openApiDoc(options: {
|
|
79
|
+
title: string;
|
|
80
|
+
version?: string;
|
|
81
|
+
}): any;
|
|
82
|
+
backendMethodsOpenApi: {
|
|
83
|
+
path: string;
|
|
84
|
+
allowed: AllowedForInstance<any>;
|
|
85
|
+
tag: string;
|
|
86
|
+
}[];
|
|
87
|
+
}
|
|
88
|
+
declare class inProcessQueueHandler {
|
|
89
|
+
private storage;
|
|
90
|
+
constructor(storage: QueueStorage);
|
|
91
|
+
submitJob(url: string, req: Remult, body: any): Promise<string>;
|
|
92
|
+
mapQueuedAction(url: string, what: (data: any, r: Remult, res: ApiActionResponse) => void): void;
|
|
93
|
+
actions: Map<string, (data: any, r: Remult, res: ApiActionResponse) => void>;
|
|
94
|
+
getJobInfo(queuedJobId: string): Promise<queuedJobInfo>;
|
|
68
95
|
}
|
|
69
96
|
export interface queuedJobInfo {
|
|
70
97
|
info: queuedJobInfoResponse;
|
|
@@ -90,6 +117,12 @@ export declare class EntityQueueStorage implements QueueStorage {
|
|
|
90
117
|
getJobInfo(queuedJobId: string): Promise<queuedJobInfo>;
|
|
91
118
|
createJob(url: string, userId: string): Promise<string>;
|
|
92
119
|
}
|
|
120
|
+
declare class RouteImplementation {
|
|
121
|
+
map: Map<string, Map<string, GenericRequestHandler>>;
|
|
122
|
+
route(path: string): SpecificRoute;
|
|
123
|
+
handle(req: GenericRequest, gRes?: GenericResponse): Promise<ServerHandleResponse | undefined>;
|
|
124
|
+
middleware(req: GenericRequest, res: GenericResponse, next: VoidFunction): void;
|
|
125
|
+
}
|
|
93
126
|
export declare class JobsInQueueEntity extends IdEntity {
|
|
94
127
|
userId: string;
|
|
95
128
|
url: string;
|
|
@@ -100,3 +133,4 @@ export declare class JobsInQueueEntity extends IdEntity {
|
|
|
100
133
|
error: boolean;
|
|
101
134
|
progress: number;
|
|
102
135
|
}
|
|
136
|
+
export {};
|
|
@@ -30,12 +30,30 @@ export function createRemultServer(options) {
|
|
|
30
30
|
return dp;
|
|
31
31
|
return new (await import('./JsonEntityFileStorage')).JsonFileDataProvider('./db');
|
|
32
32
|
});
|
|
33
|
-
|
|
33
|
+
if (!remultObjectStorage) {
|
|
34
|
+
dataProvider = dataProvider.then(async (dp) => {
|
|
35
|
+
try {
|
|
36
|
+
const asyncHooks = await import('async_hooks');
|
|
37
|
+
remultObjectStorage = new RemultAsyncLocalStorage(new asyncHooks.AsyncLocalStorage());
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
remultObjectStorage = new RemultAsyncLocalStorage(undefined);
|
|
41
|
+
}
|
|
42
|
+
remult.remultFactory = () => {
|
|
43
|
+
const r = remultObjectStorage.getRemult();
|
|
44
|
+
if (r)
|
|
45
|
+
return r;
|
|
46
|
+
else
|
|
47
|
+
throw new Error("remult object was requested outside of a valid context, try running it within initApi or a remult request cycle");
|
|
48
|
+
};
|
|
49
|
+
return dp;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
34
52
|
if (options.initApi) {
|
|
35
53
|
dataProvider = dataProvider.then(async (dp) => {
|
|
36
54
|
var remult = new Remult(dp);
|
|
37
55
|
await new Promise((res) => {
|
|
38
|
-
|
|
56
|
+
remultObjectStorage.run(remult, async () => {
|
|
39
57
|
await options.initApi(remult);
|
|
40
58
|
res({});
|
|
41
59
|
});
|
|
@@ -60,22 +78,10 @@ export function createRemultServer(options) {
|
|
|
60
78
|
return bridge;
|
|
61
79
|
}
|
|
62
80
|
;
|
|
63
|
-
|
|
81
|
+
class RemultAsyncLocalStorage {
|
|
64
82
|
constructor(remultObjectStorage) {
|
|
65
83
|
this.remultObjectStorage = remultObjectStorage;
|
|
66
84
|
}
|
|
67
|
-
static enable() {
|
|
68
|
-
remult.remultFactory = () => {
|
|
69
|
-
const r = RemultAsyncLocalStorage.instance.getRemult();
|
|
70
|
-
if (r)
|
|
71
|
-
return r;
|
|
72
|
-
else
|
|
73
|
-
throw new Error("remult object was requested outside of a valid context, try running it within initApi or a remult request cycle");
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
static disable() {
|
|
77
|
-
remult.resetFactory();
|
|
78
|
-
}
|
|
79
85
|
run(remult, callback) {
|
|
80
86
|
if (this.remultObjectStorage)
|
|
81
87
|
this.remultObjectStorage.run(remult, callback);
|
|
@@ -84,13 +90,13 @@ export class RemultAsyncLocalStorage {
|
|
|
84
90
|
}
|
|
85
91
|
getRemult() {
|
|
86
92
|
if (!this.remultObjectStorage) {
|
|
87
|
-
throw
|
|
93
|
+
throw "can't use static remult in this environment, `async_hooks` were not found";
|
|
88
94
|
}
|
|
89
95
|
return this.remultObjectStorage.getStore();
|
|
90
96
|
}
|
|
91
97
|
}
|
|
92
|
-
|
|
93
|
-
class RemultServerImplementation {
|
|
98
|
+
let remultObjectStorage;
|
|
99
|
+
export class RemultServerImplementation {
|
|
94
100
|
constructor(queue, options, dataProvider) {
|
|
95
101
|
this.queue = queue;
|
|
96
102
|
this.options = options;
|
|
@@ -149,7 +155,7 @@ class RemultServerImplementation {
|
|
|
149
155
|
let key = getEntityKey(e);
|
|
150
156
|
if (key != undefined)
|
|
151
157
|
this.add(key, c => {
|
|
152
|
-
return new DataApi(c.repo(e), c);
|
|
158
|
+
return new DataApi(c.repo(e), c, this.liveQueryManager);
|
|
153
159
|
}, r);
|
|
154
160
|
});
|
|
155
161
|
}
|
|
@@ -174,9 +180,10 @@ class RemultServerImplementation {
|
|
|
174
180
|
let myReq = new ExpressRequestBridgeToDataApiRequest(req);
|
|
175
181
|
let myRes = new ExpressResponseBridgeToDataApiResponse(res, req);
|
|
176
182
|
let remult = new Remult();
|
|
183
|
+
remult._changeListener = this.liveQueryManager;
|
|
177
184
|
remult.dataProvider = (await this.dataProvider);
|
|
178
185
|
await new Promise(res => {
|
|
179
|
-
|
|
186
|
+
remultObjectStorage.run(remult, async () => {
|
|
180
187
|
if (req) {
|
|
181
188
|
let user;
|
|
182
189
|
if (this.options.getUser)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { dbTestWhatSignature } from "../shared-tests/db-tests-setup";
|
|
2
2
|
export declare const testKnexPGSqlImpl: (key: string, what: dbTestWhatSignature, focus?: boolean) => void;
|
|
3
|
-
export declare const mySqlTest: import("../shared-tests/db-tests-setup").dbTestMethodSignature;
|
|
4
3
|
export declare function testPostgresImplementation(key: string, what: dbTestWhatSignature, focus?: boolean): void;
|
|
5
4
|
export declare function testMongo(key: string, what: dbTestWhatSignature, focus?: boolean): void;
|
|
6
5
|
import '../shared-tests';
|
|
@@ -53,16 +53,6 @@ if (true)
|
|
|
53
53
|
filename: ":memory:"
|
|
54
54
|
},
|
|
55
55
|
}), "sqlite3"));
|
|
56
|
-
export const mySqlTest = addDatabaseToTest(testKnexSqlImpl(Knex.default({
|
|
57
|
-
client: 'mysql2',
|
|
58
|
-
connection: {
|
|
59
|
-
user: 'root',
|
|
60
|
-
password: 'MASTERKEY',
|
|
61
|
-
host: '127.0.0.1',
|
|
62
|
-
database: 'test'
|
|
63
|
-
}
|
|
64
|
-
//debug: true
|
|
65
|
-
}), "mysql2"));
|
|
66
56
|
let pg = createPostgresConnection({
|
|
67
57
|
autoCreateTables: false
|
|
68
58
|
});
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import { Remult } from "../context";
|
|
2
1
|
import { KnexDataProvider } from '../../remult-knex';
|
|
3
2
|
import { config } from 'dotenv';
|
|
4
3
|
import { testKnexPGSqlImpl, testPostgresImplementation } from "./backend-database-test-setup.backend-spec";
|
|
5
4
|
import { entityWithValidations } from "../shared-tests/entityWithValidations";
|
|
6
5
|
import { SqlDatabase } from "../data-providers/sql-database";
|
|
7
|
-
import { RemultAsyncLocalStorage } from "../../server/expressBridge";
|
|
8
|
-
import { initAsyncHooks } from "../../server/initAsyncHooks";
|
|
9
|
-
import { remult } from "../remult-proxy";
|
|
10
6
|
config();
|
|
11
7
|
testPostgresImplementation("default order by", async ({ createEntity }) => {
|
|
12
8
|
let s = await entityWithValidations.create4RowsInDp(createEntity);
|
|
@@ -61,47 +57,3 @@ testKnexPGSqlImpl("knex filter2", async ({ createEntity }) => {
|
|
|
61
57
|
}
|
|
62
58
|
})).length).toBe(3);
|
|
63
59
|
}, false);
|
|
64
|
-
it("test async hooks and static remult", async () => {
|
|
65
|
-
let gotException = true;
|
|
66
|
-
try {
|
|
67
|
-
RemultAsyncLocalStorage.instance.getRemult();
|
|
68
|
-
gotException = false;
|
|
69
|
-
}
|
|
70
|
-
catch { }
|
|
71
|
-
expect(gotException).toBe(true);
|
|
72
|
-
initAsyncHooks();
|
|
73
|
-
expect(RemultAsyncLocalStorage.instance.getRemult()).toBe(undefined);
|
|
74
|
-
RemultAsyncLocalStorage.enable();
|
|
75
|
-
try {
|
|
76
|
-
remult.isAllowed(false);
|
|
77
|
-
gotException = false;
|
|
78
|
-
}
|
|
79
|
-
catch { }
|
|
80
|
-
expect(gotException).toBe(true);
|
|
81
|
-
const promises = [];
|
|
82
|
-
RemultAsyncLocalStorage.instance.run(new Remult(), () => {
|
|
83
|
-
remult.user = { id: 'noam' };
|
|
84
|
-
promises.push(new Promise(res => {
|
|
85
|
-
setTimeout(() => {
|
|
86
|
-
expect(remult.user.id).toBe('noam');
|
|
87
|
-
res({});
|
|
88
|
-
}, 10);
|
|
89
|
-
}));
|
|
90
|
-
RemultAsyncLocalStorage.instance.run(new Remult(), () => {
|
|
91
|
-
remult.user = { id: 'yoni' };
|
|
92
|
-
promises.push(new Promise(res => {
|
|
93
|
-
setTimeout(() => {
|
|
94
|
-
expect(remult.user.id).toBe('yoni');
|
|
95
|
-
res({});
|
|
96
|
-
}, 10);
|
|
97
|
-
}));
|
|
98
|
-
});
|
|
99
|
-
promises.push(new Promise(res => {
|
|
100
|
-
setTimeout(() => {
|
|
101
|
-
expect(remult.user.id).toBe('noam');
|
|
102
|
-
res({});
|
|
103
|
-
}, 10);
|
|
104
|
-
}));
|
|
105
|
-
});
|
|
106
|
-
await Promise.all(promises);
|
|
107
|
-
});
|
package/esm/src/column.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ClassType } from '../classType';
|
|
2
2
|
import { FieldMetadata, FieldOptions, ValueConverter } from './column-interfaces';
|
|
3
3
|
import { Filter } from './filter/filter-interfaces';
|
|
4
|
-
import { EntityFilter, FindOptions, Repository, RepositoryImplementation } from './remult3';
|
|
4
|
+
import { EntityFilter, FindOptions, idType, Repository, RepositoryImplementation } from './remult3';
|
|
5
5
|
export declare function makeTitle(name: string): string;
|
|
6
6
|
export declare class CompoundIdField implements FieldMetadata<string> {
|
|
7
7
|
fields: FieldMetadata[];
|
|
@@ -25,12 +25,12 @@ export declare class CompoundIdField implements FieldMetadata<string> {
|
|
|
25
25
|
}
|
|
26
26
|
export declare class LookupColumn<T> {
|
|
27
27
|
private repository;
|
|
28
|
-
id:
|
|
28
|
+
id: idType<T>;
|
|
29
29
|
setId(val: any): void;
|
|
30
30
|
waitLoadOf(id: any): Promise<T>;
|
|
31
31
|
get(id: any): any;
|
|
32
32
|
set(item: T): void;
|
|
33
|
-
constructor(repository: RepositoryImplementation<T>, id:
|
|
33
|
+
constructor(repository: RepositoryImplementation<T>, id: idType<T>);
|
|
34
34
|
get item(): T;
|
|
35
35
|
waitLoad(): Promise<T>;
|
|
36
36
|
}
|
package/esm/src/context.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { DataProvider, RestDataProviderHttpProvider } from "./data-interfaces";
|
|
|
2
2
|
import { RestDataProviderHttpProviderUsingFetch } from './data-providers/rest-data-provider';
|
|
3
3
|
import { EntityMetadata, EntityRef, FindOptions, Repository } from "./remult3";
|
|
4
4
|
import { ClassType } from "../classType";
|
|
5
|
+
import { LiveQueryClient } from "./live-query/LiveQuery";
|
|
5
6
|
export interface ExternalHttpProvider {
|
|
6
7
|
post(url: string, data: any): Promise<any> | {
|
|
7
8
|
toPromise(): Promise<any>;
|
|
@@ -65,6 +66,7 @@ export declare class Remult {
|
|
|
65
66
|
call<T extends ((...args: any[]) => Promise<any>)>(backendMethod: T, classInstance?: any, ...args: GetArguments<T>): ReturnType<T>;
|
|
66
67
|
/** The current data provider */
|
|
67
68
|
dataProvider: DataProvider;
|
|
69
|
+
liveQueryProvider: LiveQueryClient;
|
|
68
70
|
/** A helper callback that can be used to debug and trace all find operations. Useful in debugging scenarios */
|
|
69
71
|
static onFind: (metadata: EntityMetadata, options: FindOptions<any>) => void;
|
|
70
72
|
clearAllCache(): any;
|
package/esm/src/context.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { actionInfo, serverActionField } from './server-action';
|
|
2
2
|
import { RestDataProvider, RestDataProviderHttpProviderUsingFetch } from './data-providers/rest-data-provider';
|
|
3
3
|
import { RepositoryImplementation } from "./remult3/RepositoryImplementation";
|
|
4
|
+
import { LiveQueryClient } from "./live-query/LiveQuery";
|
|
5
|
+
import { EventSourceLiveQueryProvider } from "./live-query/EventSourceLiveQueryProvider";
|
|
6
|
+
import { RemultProxy } from "./remult-proxy";
|
|
4
7
|
export class HttpProviderBridgeToRestDataProviderHttpProvider {
|
|
5
8
|
constructor(http) {
|
|
6
9
|
this.http = http;
|
|
@@ -95,8 +98,14 @@ export class Remult {
|
|
|
95
98
|
constructor(provider) {
|
|
96
99
|
/* @internal */
|
|
97
100
|
this.repCache = new Map();
|
|
101
|
+
/*@internal*/
|
|
102
|
+
this._changeListener = {
|
|
103
|
+
saved: (ref) => { },
|
|
104
|
+
deleted: (ref) => { }
|
|
105
|
+
};
|
|
98
106
|
/** The current data provider */
|
|
99
107
|
this.dataProvider = new RestDataProvider(() => this.apiClient);
|
|
108
|
+
this.liveQueryProvider = new LiveQueryClient(new EventSourceLiveQueryProvider());
|
|
100
109
|
this.context = {};
|
|
101
110
|
this.apiClient = {
|
|
102
111
|
url: '/api'
|
|
@@ -187,6 +196,7 @@ export class Remult {
|
|
|
187
196
|
else
|
|
188
197
|
return this.isAllowed(allowed);
|
|
189
198
|
}
|
|
199
|
+
//@ts-ignore // type error of typescript regarding args that doesn't appear in my normal development
|
|
190
200
|
call(backendMethod, classInstance, ...args) {
|
|
191
201
|
const z = (backendMethod[serverActionField]);
|
|
192
202
|
if (!z.doWork)
|
|
@@ -200,6 +210,7 @@ export class Remult {
|
|
|
200
210
|
}
|
|
201
211
|
/** A helper callback that can be used to debug and trace all find operations. Useful in debugging scenarios */
|
|
202
212
|
Remult.onFind = (metadata, options) => { };
|
|
213
|
+
RemultProxy.defaultRemult = new Remult();
|
|
203
214
|
;
|
|
204
215
|
export const allEntities = [];
|
|
205
216
|
export const classHelpers = new Map();
|
package/esm/src/data-api.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { Remult } from './context';
|
|
2
|
-
import { Repository } from './remult3';
|
|
2
|
+
import { FindOptions, Repository } from './remult3';
|
|
3
3
|
import { ErrorInfo } from './data-interfaces';
|
|
4
|
+
export interface LiveQueryProvider {
|
|
5
|
+
subscribe(repo: Repository<any>, id: string, options: FindOptions<any>, remult: Remult, ids: any[]): string;
|
|
6
|
+
}
|
|
4
7
|
export declare class DataApi<T = any> {
|
|
5
8
|
private repository;
|
|
6
9
|
private remult;
|
|
7
|
-
|
|
10
|
+
private liveQueryProvider?;
|
|
11
|
+
constructor(repository: Repository<T>, remult: Remult, liveQueryProvider?: LiveQueryProvider);
|
|
8
12
|
httpGet(res: DataApiResponse, req: DataApiRequest): Promise<void>;
|
|
9
13
|
httpPost(res: DataApiResponse, req: DataApiRequest, body: any): Promise<void>;
|
|
10
14
|
static defaultGetLimit: number;
|