remult 0.16.3-exp.4 → 0.17.0-exp.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/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/server/expressBridge.d.ts +44 -8
- package/esm/server/expressBridge.js +27 -14
- 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/EventSourceLiveQuery.d.ts +5 -0
- package/esm/src/live-query/EventSourceLiveQuery.js +48 -0
- package/esm/src/live-query/EventSourceLiveQueryProvider.d.ts +6 -0
- package/esm/src/live-query/EventSourceLiveQueryProvider.js +24 -0
- package/esm/src/live-query/LiveQuery.d.ts +68 -0
- package/esm/src/live-query/LiveQuery.js +227 -0
- package/esm/src/live-query/LiveQueryManager.d.ts +33 -0
- package/esm/src/live-query/LiveQueryManager.js +97 -0
- package/esm/src/remult-proxy.d.ts +1 -1
- package/esm/src/remult-proxy.js +11 -5
- package/esm/src/remult3/RepositoryImplementation.d.ts +6 -3
- package/esm/src/remult3/RepositoryImplementation.js +33 -12
- package/esm/src/remult3/remult3.d.ts +10 -6
- package/esm/src/server-action.js +4 -3
- package/esm/src/sort.d.ts +1 -0
- package/esm/src/sort.js +18 -0
- 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/server/expressBridge.d.ts +44 -8
- package/server/expressBridge.js +37 -14
- 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/EventSourceLiveQuery.d.ts +5 -0
- package/src/live-query/EventSourceLiveQuery.js +62 -0
- package/src/live-query/EventSourceLiveQueryProvider.d.ts +6 -0
- package/src/live-query/EventSourceLiveQueryProvider.js +32 -0
- package/src/live-query/LiveQuery.d.ts +68 -0
- package/src/live-query/LiveQuery.js +310 -0
- package/src/live-query/LiveQueryManager.d.ts +33 -0
- package/src/live-query/LiveQueryManager.js +196 -0
- package/src/remult-proxy.d.ts +1 -1
- package/src/remult-proxy.js +19 -5
- package/src/remult3/RepositoryImplementation.d.ts +6 -3
- package/src/remult3/RepositoryImplementation.js +63 -32
- package/src/remult3/remult3.d.ts +10 -6
- package/src/server-action.js +3 -2
- package/src/sort.d.ts +1 -0
- package/src/sort.js +18 -0
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 { LiveQueryManager } 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 LiveQueryManager(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
|
|
@@ -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 { LiveQueryManager } from '../src/live-query/LiveQueryManager';
|
|
7
9
|
export interface RemultServerOptions<RequestType extends GenericRequest> {
|
|
8
10
|
/** Sets a database connection for Remult.
|
|
9
11
|
*
|
|
@@ -57,12 +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
|
-
|
|
65
|
-
|
|
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: LiveQueryManager;
|
|
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>;
|
|
66
95
|
}
|
|
67
96
|
export interface queuedJobInfo {
|
|
68
97
|
info: queuedJobInfoResponse;
|
|
@@ -88,6 +117,12 @@ export declare class EntityQueueStorage implements QueueStorage {
|
|
|
88
117
|
getJobInfo(queuedJobId: string): Promise<queuedJobInfo>;
|
|
89
118
|
createJob(url: string, userId: string): Promise<string>;
|
|
90
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
|
+
}
|
|
91
126
|
export declare class JobsInQueueEntity extends IdEntity {
|
|
92
127
|
userId: string;
|
|
93
128
|
url: string;
|
|
@@ -98,3 +133,4 @@ export declare class JobsInQueueEntity extends IdEntity {
|
|
|
98
133
|
error: boolean;
|
|
99
134
|
progress: number;
|
|
100
135
|
}
|
|
136
|
+
export {};
|
|
@@ -30,11 +30,30 @@ export function createRemultServer(options) {
|
|
|
30
30
|
return dp;
|
|
31
31
|
return new (await import('./JsonEntityFileStorage')).JsonFileDataProvider('./db');
|
|
32
32
|
});
|
|
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
|
+
}
|
|
33
52
|
if (options.initApi) {
|
|
34
53
|
dataProvider = dataProvider.then(async (dp) => {
|
|
35
54
|
var remult = new Remult(dp);
|
|
36
55
|
await new Promise((res) => {
|
|
37
|
-
|
|
56
|
+
remultObjectStorage.run(remult, async () => {
|
|
38
57
|
await options.initApi(remult);
|
|
39
58
|
res({});
|
|
40
59
|
});
|
|
@@ -59,7 +78,7 @@ export function createRemultServer(options) {
|
|
|
59
78
|
return bridge;
|
|
60
79
|
}
|
|
61
80
|
;
|
|
62
|
-
|
|
81
|
+
class RemultAsyncLocalStorage {
|
|
63
82
|
constructor(remultObjectStorage) {
|
|
64
83
|
this.remultObjectStorage = remultObjectStorage;
|
|
65
84
|
}
|
|
@@ -71,20 +90,13 @@ export class RemultAsyncLocalStorage {
|
|
|
71
90
|
}
|
|
72
91
|
getRemult() {
|
|
73
92
|
if (!this.remultObjectStorage) {
|
|
74
|
-
throw
|
|
93
|
+
throw "can't use static remult in this environment, `async_hooks` were not found";
|
|
75
94
|
}
|
|
76
95
|
return this.remultObjectStorage.getStore();
|
|
77
96
|
}
|
|
78
97
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const r = RemultAsyncLocalStorage.instance.getRemult();
|
|
82
|
-
if (r)
|
|
83
|
-
return r;
|
|
84
|
-
else
|
|
85
|
-
throw new Error("remult object was requested outside of a valid context, try running it within initApi or a remult request cycle");
|
|
86
|
-
};
|
|
87
|
-
class RemultServerImplementation {
|
|
98
|
+
let remultObjectStorage;
|
|
99
|
+
export class RemultServerImplementation {
|
|
88
100
|
constructor(queue, options, dataProvider) {
|
|
89
101
|
this.queue = queue;
|
|
90
102
|
this.options = options;
|
|
@@ -143,7 +155,7 @@ class RemultServerImplementation {
|
|
|
143
155
|
let key = getEntityKey(e);
|
|
144
156
|
if (key != undefined)
|
|
145
157
|
this.add(key, c => {
|
|
146
|
-
return new DataApi(c.repo(e), c);
|
|
158
|
+
return new DataApi(c.repo(e), c, this.liveQueryManager);
|
|
147
159
|
}, r);
|
|
148
160
|
});
|
|
149
161
|
}
|
|
@@ -168,9 +180,10 @@ class RemultServerImplementation {
|
|
|
168
180
|
let myReq = new ExpressRequestBridgeToDataApiRequest(req);
|
|
169
181
|
let myRes = new ExpressResponseBridgeToDataApiResponse(res, req);
|
|
170
182
|
let remult = new Remult();
|
|
183
|
+
remult._changeListener = this.liveQueryManager;
|
|
171
184
|
remult.dataProvider = (await this.dataProvider);
|
|
172
185
|
await new Promise(res => {
|
|
173
|
-
|
|
186
|
+
remultObjectStorage.run(remult, async () => {
|
|
174
187
|
if (req) {
|
|
175
188
|
let user;
|
|
176
189
|
if (this.options.getUser)
|
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;
|
package/esm/src/data-api.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { customUrlToken, buildFilterFromRequestParameters } from './filter/filter-interfaces';
|
|
2
2
|
import { Filter } from './filter/filter-interfaces';
|
|
3
3
|
export class DataApi {
|
|
4
|
-
constructor(repository, remult) {
|
|
4
|
+
constructor(repository, remult, liveQueryProvider) {
|
|
5
5
|
this.repository = repository;
|
|
6
6
|
this.remult = remult;
|
|
7
|
+
this.liveQueryProvider = liveQueryProvider;
|
|
7
8
|
}
|
|
8
9
|
httpGet(res, req) {
|
|
9
10
|
if (req.get("__action") == "count") {
|
|
@@ -13,7 +14,10 @@ export class DataApi {
|
|
|
13
14
|
return this.getArray(res, req);
|
|
14
15
|
}
|
|
15
16
|
httpPost(res, req, body) {
|
|
16
|
-
|
|
17
|
+
const action = req?.get("__action");
|
|
18
|
+
if (action?.startsWith("subscribe"))
|
|
19
|
+
return this.getArray(res, req, body);
|
|
20
|
+
switch (action) {
|
|
17
21
|
case "get":
|
|
18
22
|
return this.getArray(res, req, body);
|
|
19
23
|
case "count":
|
|
@@ -89,10 +93,19 @@ export class DataApi {
|
|
|
89
93
|
findOptions.limit = limit;
|
|
90
94
|
findOptions.page = +request.get("_page");
|
|
91
95
|
}
|
|
92
|
-
|
|
96
|
+
const action = request?.get("__action");
|
|
97
|
+
const r = await this.repository.find(findOptions)
|
|
93
98
|
.then(async (r) => {
|
|
94
|
-
|
|
99
|
+
return await Promise.all(r.map(async (y) => this.repository.getEntityRef(y).toApiJson()));
|
|
95
100
|
});
|
|
101
|
+
if (this.liveQueryProvider && action?.startsWith("subscribe")) {
|
|
102
|
+
response.success({
|
|
103
|
+
id: this.liveQueryProvider.subscribe(this.repository, action.split('|')[1], findOptions, this.remult, r.map(y => this.repository.getEntityRef(y).getId())),
|
|
104
|
+
result: r
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
response.success(r);
|
|
96
109
|
}
|
|
97
110
|
catch (err) {
|
|
98
111
|
response.error(err);
|
|
@@ -66,22 +66,7 @@ export class ArrayEntityDataProvider {
|
|
|
66
66
|
}
|
|
67
67
|
if (options.orderBy) {
|
|
68
68
|
rows = rows.sort((a, b) => {
|
|
69
|
-
|
|
70
|
-
for (let i = 0; i < options.orderBy.Segments.length; i++) {
|
|
71
|
-
let seg = options.orderBy.Segments[i];
|
|
72
|
-
let left = a[seg.field.key];
|
|
73
|
-
let right = b[seg.field.key];
|
|
74
|
-
if (left > right)
|
|
75
|
-
r = 1;
|
|
76
|
-
else if (left < right)
|
|
77
|
-
r = -1;
|
|
78
|
-
if (r != 0) {
|
|
79
|
-
if (seg.isDescending)
|
|
80
|
-
r *= -1;
|
|
81
|
-
return r;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return r;
|
|
69
|
+
return options.orderBy.compare(a, b);
|
|
85
70
|
});
|
|
86
71
|
}
|
|
87
72
|
rows = pageArray(rows, options);
|
|
@@ -19,6 +19,10 @@ export declare class RestEntityDataProvider implements EntityDataProvider {
|
|
|
19
19
|
translateToJson(row: any): {};
|
|
20
20
|
count(where: Filter): Promise<number>;
|
|
21
21
|
find(options: EntityDataProviderFindOptions): Promise<Array<any>>;
|
|
22
|
+
buildFindRequest(options: EntityDataProviderFindOptions): {
|
|
23
|
+
filterObject: any;
|
|
24
|
+
url: UrlBuilder;
|
|
25
|
+
};
|
|
22
26
|
update(id: any, data: any): Promise<any>;
|
|
23
27
|
delete(id: any): Promise<void>;
|
|
24
28
|
insert(data: any): Promise<any>;
|
|
@@ -55,6 +55,16 @@ export class RestEntityDataProvider {
|
|
|
55
55
|
return this.http().get(url.url).then(r => +(r.count));
|
|
56
56
|
}
|
|
57
57
|
find(options) {
|
|
58
|
+
let { filterObject, url } = this.buildFindRequest(options);
|
|
59
|
+
if (filterObject) {
|
|
60
|
+
url.add("__action", "get");
|
|
61
|
+
return this.http().post(url.url, filterObject).then(x => x.map(y => this.translateFromJson(y)));
|
|
62
|
+
}
|
|
63
|
+
else
|
|
64
|
+
return this.http().get(url.url).then(x => x.map(y => this.translateFromJson(y)));
|
|
65
|
+
;
|
|
66
|
+
}
|
|
67
|
+
buildFindRequest(options) {
|
|
58
68
|
let url = new UrlBuilder(this.url());
|
|
59
69
|
let filterObject;
|
|
60
70
|
if (options) {
|
|
@@ -82,13 +92,7 @@ export class RestEntityDataProvider {
|
|
|
82
92
|
if (options.page)
|
|
83
93
|
url.add('_page', options.page);
|
|
84
94
|
}
|
|
85
|
-
|
|
86
|
-
url.add("__action", "get");
|
|
87
|
-
return this.http().post(url.url, filterObject).then(x => x.map(y => this.translateFromJson(y)));
|
|
88
|
-
}
|
|
89
|
-
else
|
|
90
|
-
return this.http().get(url.url).then(x => x.map(y => this.translateFromJson(y)));
|
|
91
|
-
;
|
|
95
|
+
return { filterObject, url };
|
|
92
96
|
}
|
|
93
97
|
update(id, data) {
|
|
94
98
|
let result = {};
|