remult 0.17.3 → 0.18.0
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/SseSubscriptionServer.d.ts +23 -0
- package/SseSubscriptionServer.js +143 -0
- package/ably.d.ts +13 -0
- package/ably.js +59 -0
- package/esm/SseSubscriptionServer.d.ts +23 -0
- package/esm/SseSubscriptionServer.js +102 -0
- package/esm/ably.d.ts +13 -0
- package/esm/ably.js +28 -0
- package/esm/index.d.ts +7 -4
- package/esm/index.js +4 -2
- package/esm/live-query/data-provider-live-query-storage.d.ts +23 -0
- package/esm/live-query/data-provider-live-query-storage.js +78 -0
- package/esm/myLog.d.ts +1 -0
- package/esm/myLog.js +5 -0
- package/esm/postgres/postgres-data-provider.d.ts +4 -2
- package/esm/postgres/postgres-data-provider.js +18 -6
- package/esm/postgres/schema-builder.d.ts +2 -1
- package/esm/postgres/schema-builder.js +49 -29
- package/esm/remult-knex/index.d.ts +10 -32
- package/esm/remult-knex/index.js +75 -51
- package/esm/remult-mongo.d.ts +9 -2
- package/esm/remult-mongo.js +24 -8
- package/esm/remult-next.d.ts +11 -0
- package/esm/remult-next.js +27 -0
- package/esm/server/core.d.ts +2 -0
- package/esm/server/core.js +2 -0
- package/esm/server/expressBridge.d.ts +19 -7
- package/esm/server/expressBridge.js +135 -31
- package/esm/server/index.d.ts +2 -1
- package/esm/server/index.js +2 -1
- package/esm/server/initDataProvider.d.ts +2 -0
- package/esm/server/initDataProvider.js +14 -0
- package/esm/src/buildRestDataProvider.d.ts +31 -0
- package/esm/src/buildRestDataProvider.js +110 -0
- package/esm/src/column-interfaces.d.ts +44 -8
- package/esm/src/column.d.ts +3 -3
- package/esm/src/context.d.ts +39 -36
- package/esm/src/context.js +87 -117
- package/esm/src/data-api.d.ts +9 -3
- package/esm/src/data-api.js +130 -63
- package/esm/src/data-interfaces.d.ts +5 -1
- package/esm/src/data-providers/array-entity-data-provider.d.ts +1 -1
- package/esm/src/data-providers/array-entity-data-provider.js +2 -17
- package/esm/src/data-providers/rest-data-provider.d.ts +4 -3
- package/esm/src/data-providers/rest-data-provider.js +64 -23
- package/esm/src/data-providers/sql-database.d.ts +7 -3
- package/esm/src/data-providers/sql-database.js +60 -17
- package/esm/src/data-providers/web-sql-data-provider.d.ts +3 -0
- package/esm/src/data-providers/web-sql-data-provider.js +23 -9
- package/esm/src/entity.d.ts +2 -2
- package/esm/src/filter/filter-consumer-bridge-to-sql-request.d.ts +16 -16
- package/esm/src/filter/filter-consumer-bridge-to-sql-request.js +29 -21
- package/esm/src/filter/filter-interfaces.d.ts +5 -5
- package/esm/src/filter/filter-interfaces.js +31 -26
- package/esm/src/live-query/LiveQueryClient.d.ts +1 -0
- package/esm/src/live-query/LiveQueryClient.js +184 -0
- package/esm/src/live-query/SseSubscriptionClient.d.ts +5 -0
- package/esm/src/live-query/SseSubscriptionClient.js +86 -0
- package/esm/src/live-query/SubscriptionChannel.d.ts +41 -0
- package/esm/src/live-query/SubscriptionChannel.js +121 -0
- package/esm/src/live-query/SubscriptionServer.d.ts +38 -0
- package/esm/src/live-query/SubscriptionServer.js +108 -0
- package/esm/src/remult-proxy.d.ts +1 -1
- package/esm/src/remult-proxy.js +52 -7
- package/esm/src/remult3/RepositoryImplementation.d.ts +14 -5
- package/esm/src/remult3/RepositoryImplementation.js +124 -44
- package/esm/src/remult3/getId.d.ts +2 -0
- package/esm/src/remult3/getId.js +7 -0
- package/esm/src/remult3/remult3.d.ts +67 -10
- package/esm/src/remult3/remult3.js +1 -0
- package/esm/src/server-action.d.ts +5 -1
- package/esm/src/server-action.js +11 -12
- package/esm/src/shared-tests/db-tests-setup.d.ts +12 -5
- package/esm/src/shared-tests/db-tests-setup.js +14 -6
- package/esm/src/shared-tests/db-tests.db-tests.js +71 -10
- package/esm/src/sort.d.ts +1 -0
- package/esm/src/sort.js +18 -0
- package/esm/src/sql-command.d.ts +5 -2
- package/esm/src/tests/entityForCustomFilter.d.ts +6 -6
- package/esm/src/tests/entityForCustomFilter.js +20 -21
- package/esm/src/tests/frontend-database-tests-setup.spec.js +3 -4
- package/esm/src/tests/testHelper.spec.d.ts +3 -1
- package/esm/src/tests/testHelper.spec.js +63 -60
- package/esm/src/valueConverters.js +15 -7
- package/index.d.ts +7 -4
- package/index.js +7 -3
- package/live-query/data-provider-live-query-storage.d.ts +23 -0
- package/live-query/data-provider-live-query-storage.js +236 -0
- package/myLog.d.ts +1 -0
- package/myLog.js +20 -0
- package/package.json +18 -1
- package/postgres/postgres-data-provider.d.ts +4 -2
- package/postgres/postgres-data-provider.js +54 -39
- package/postgres/schema-builder.d.ts +2 -1
- package/postgres/schema-builder.js +118 -70
- package/remult-express.js +1 -1
- package/remult-knex/index.d.ts +10 -32
- package/remult-knex/index.js +134 -87
- package/remult-mongo.d.ts +9 -2
- package/remult-mongo.js +39 -7
- package/remult-next.d.ts +11 -0
- package/remult-next.js +73 -0
- package/server/JsonEntityFileStorage.js +2 -2
- package/server/core.d.ts +2 -0
- package/server/core.js +5 -1
- package/server/expressBridge.d.ts +19 -7
- package/server/expressBridge.js +283 -69
- package/server/index.d.ts +2 -1
- package/server/index.js +3 -1
- package/server/initDataProvider.d.ts +2 -0
- package/server/initDataProvider.js +26 -0
- package/src/buildRestDataProvider.d.ts +31 -0
- package/src/buildRestDataProvider.js +162 -0
- package/src/column-interfaces.d.ts +44 -8
- package/src/column.d.ts +3 -3
- package/src/context.d.ts +39 -36
- package/src/context.js +171 -166
- package/src/data-api.d.ts +9 -3
- package/src/data-api.js +233 -88
- package/src/data-interfaces.d.ts +5 -1
- package/src/data-providers/array-entity-data-provider.d.ts +1 -1
- package/src/data-providers/array-entity-data-provider.js +2 -17
- package/src/data-providers/rest-data-provider.d.ts +4 -3
- package/src/data-providers/rest-data-provider.js +105 -39
- package/src/data-providers/sql-database.d.ts +7 -3
- package/src/data-providers/sql-database.js +89 -16
- package/src/data-providers/web-sql-data-provider.d.ts +3 -0
- package/src/data-providers/web-sql-data-provider.js +56 -11
- package/src/entity.d.ts +2 -2
- package/src/filter/filter-consumer-bridge-to-sql-request.d.ts +16 -16
- package/src/filter/filter-consumer-bridge-to-sql-request.js +59 -50
- package/src/filter/filter-interfaces.d.ts +5 -5
- package/src/filter/filter-interfaces.js +31 -26
- package/src/live-query/LiveQueryClient.d.ts +1 -0
- package/src/live-query/LiveQueryClient.js +283 -0
- package/src/live-query/SseSubscriptionClient.d.ts +5 -0
- package/src/live-query/SseSubscriptionClient.js +150 -0
- package/src/live-query/SubscriptionChannel.d.ts +41 -0
- package/src/live-query/SubscriptionChannel.js +209 -0
- package/src/live-query/SubscriptionServer.d.ts +38 -0
- package/src/live-query/SubscriptionServer.js +258 -0
- package/src/remult-proxy.d.ts +1 -1
- package/src/remult-proxy.js +153 -8
- package/src/remult3/RepositoryImplementation.d.ts +14 -5
- package/src/remult3/RepositoryImplementation.js +200 -85
- package/src/remult3/getId.d.ts +2 -0
- package/src/remult3/getId.js +11 -0
- package/src/remult3/remult3.d.ts +67 -10
- package/src/remult3/remult3.js +1 -0
- package/src/server-action.d.ts +5 -1
- package/src/server-action.js +11 -11
- package/src/shared-tests/db-tests-setup.d.ts +12 -5
- package/src/shared-tests/db-tests-setup.js +17 -8
- package/src/shared-tests/db-tests.db-tests.js +140 -8
- package/src/sort.d.ts +1 -0
- package/src/sort.js +18 -0
- package/src/sql-command.d.ts +5 -2
- package/src/tests/entityForCustomFilter.d.ts +6 -6
- package/src/tests/entityForCustomFilter.js +30 -31
- package/src/tests/frontend-database-tests-setup.spec.js +5 -6
- package/src/tests/testHelper.spec.d.ts +3 -1
- package/src/tests/testHelper.spec.js +104 -100
- package/src/valueConverters.js +15 -7
package/esm/server/core.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { queuedJobInfo, QueueStorage, GenericRequest, GenericRequestHandler, GenericResponse, GenericRouter, createRemultServerCore, RemultServerOptions, SpecificRoute, RemultServer } from './expressBridge';
|
|
2
2
|
export { JsonEntityFileStorage, JsonFileDataProvider } from './JsonEntityFileStorage';
|
|
3
|
+
export { SseSubscriptionServer } from '../SseSubscriptionServer';
|
|
4
|
+
export { DataProviderLiveQueryStorage } from '../live-query/data-provider-live-query-storage';
|
package/esm/server/core.js
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { createRemultServerCore } from './expressBridge';
|
|
2
2
|
export { JsonEntityFileStorage, JsonFileDataProvider } from './JsonEntityFileStorage';
|
|
3
|
+
export { SseSubscriptionServer } from '../SseSubscriptionServer';
|
|
4
|
+
export { DataProviderLiveQueryStorage } from '../live-query/data-provider-live-query-storage';
|
|
@@ -4,6 +4,7 @@ import { Remult, UserInfo } from '../src/context';
|
|
|
4
4
|
import { ClassType } from '../classType';
|
|
5
5
|
import { Repository } from '../src/remult3';
|
|
6
6
|
import { IdEntity } from '../src/id-entity';
|
|
7
|
+
import { LiveQueryStorage, SubscriptionServer } from '../src/live-query/SubscriptionServer';
|
|
7
8
|
export interface RemultServerOptions<RequestType extends GenericRequest> {
|
|
8
9
|
/** Sets a database connection for Remult.
|
|
9
10
|
*
|
|
@@ -11,8 +12,20 @@ export interface RemultServerOptions<RequestType extends GenericRequest> {
|
|
|
11
12
|
*/
|
|
12
13
|
dataProvider?: DataProvider | Promise<DataProvider> | (() => Promise<DataProvider | undefined>);
|
|
13
14
|
queueStorage?: QueueStorage;
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
liveQueryStorage?: LiveQueryStorage;
|
|
16
|
+
subscriptionServer?: SubscriptionServer;
|
|
17
|
+
initRequest?: (request: RequestType, options: InitRequestOptions) => Promise<void>;
|
|
18
|
+
requestSerializer?: {
|
|
19
|
+
toJson: (request: RequestType) => any;
|
|
20
|
+
fromJson: (request: any) => RequestType;
|
|
21
|
+
};
|
|
22
|
+
/** Will create tables and columns in supporting databases. default: true
|
|
23
|
+
*
|
|
24
|
+
* @description
|
|
25
|
+
* when set to true, it'll create entities that do not exist, and add columns that are missing.
|
|
26
|
+
*/
|
|
27
|
+
ensureSchema?: boolean;
|
|
28
|
+
getUser?: (request: RequestType) => Promise<UserInfo | undefined>;
|
|
16
29
|
initApi?: (remult: Remult) => void | Promise<void>;
|
|
17
30
|
logApiEndPoints?: boolean;
|
|
18
31
|
defaultGetLimit?: number;
|
|
@@ -20,6 +33,10 @@ export interface RemultServerOptions<RequestType extends GenericRequest> {
|
|
|
20
33
|
controllers?: ClassType<any>[];
|
|
21
34
|
rootPath?: string;
|
|
22
35
|
}
|
|
36
|
+
export interface InitRequestOptions {
|
|
37
|
+
liveQueryStorage: LiveQueryStorage;
|
|
38
|
+
readonly remult: Remult;
|
|
39
|
+
}
|
|
23
40
|
export declare function createRemultServerCore<RequestType extends GenericRequest = GenericRequest>(options?: RemultServerOptions<RequestType>): RemultServer;
|
|
24
41
|
export declare type GenericRequestHandler = (req: GenericRequest, res: GenericResponse, next: VoidFunction) => void;
|
|
25
42
|
export interface ServerHandleResponse {
|
|
@@ -73,11 +90,6 @@ export interface queuedJobInfo {
|
|
|
73
90
|
setResult(result: any): void;
|
|
74
91
|
setProgress(progress: number): void;
|
|
75
92
|
}
|
|
76
|
-
export interface ApiActionResponse {
|
|
77
|
-
error(error: any): void;
|
|
78
|
-
success(result: any): void;
|
|
79
|
-
progress(progress: number): void;
|
|
80
|
-
}
|
|
81
93
|
export interface QueueStorage {
|
|
82
94
|
createJob(url: string, userId: string): Promise<string>;
|
|
83
95
|
getJobInfo(queuedJobId: string): Promise<queuedJobInfo>;
|
|
@@ -5,11 +5,18 @@ import { allEntities, Remult } from '../src/context';
|
|
|
5
5
|
import { Entity, Fields, getEntityKey } from '../src/remult3';
|
|
6
6
|
import { IdEntity } from '../src/id-entity';
|
|
7
7
|
import { remult } from '../src/remult-proxy';
|
|
8
|
+
import { LiveQueryPublisher, InMemoryLiveQueryStorage } from '../src/live-query/SubscriptionServer';
|
|
9
|
+
import { liveQueryKeepAliveRoute, streamUrl } from '../src/live-query/SubscriptionChannel';
|
|
10
|
+
import { initDataProvider } from './initDataProvider';
|
|
11
|
+
import { SseSubscriptionServer } from "../SseSubscriptionServer";
|
|
8
12
|
;
|
|
9
13
|
export function createRemultServerCore(options) {
|
|
10
14
|
if (!options) {
|
|
11
15
|
options = {};
|
|
12
16
|
}
|
|
17
|
+
if (!options.subscriptionServer) {
|
|
18
|
+
options.subscriptionServer = new SseSubscriptionServer();
|
|
19
|
+
}
|
|
13
20
|
if (options.logApiEndPoints === undefined)
|
|
14
21
|
options.logApiEndPoints = true;
|
|
15
22
|
actionInfo.runningOnServer = true;
|
|
@@ -19,29 +26,59 @@ export function createRemultServerCore(options) {
|
|
|
19
26
|
if (!options.queueStorage) {
|
|
20
27
|
options.queueStorage = new InMemoryQueueStorage();
|
|
21
28
|
}
|
|
22
|
-
let dataProvider;
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
else
|
|
27
|
-
dataProvider = Promise.resolve(options.dataProvider);
|
|
28
|
-
dataProvider = dataProvider.then(async (dp) => {
|
|
29
|
-
if (dp)
|
|
30
|
-
return dp;
|
|
31
|
-
return new (await import('./JsonEntityFileStorage')).JsonFileDataProvider('./db');
|
|
32
|
-
});
|
|
29
|
+
let dataProvider = initDataProvider(options.dataProvider);
|
|
30
|
+
if (options.ensureSchema === undefined)
|
|
31
|
+
options.ensureSchema = true;
|
|
33
32
|
RemultAsyncLocalStorage.enable();
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
dataProvider = dataProvider.then(async (dp) => {
|
|
34
|
+
var remult = new Remult(dp);
|
|
35
|
+
await new Promise((res) => {
|
|
36
|
+
RemultAsyncLocalStorage.instance.run(remult, async () => {
|
|
37
|
+
if (options.ensureSchema) {
|
|
38
|
+
let started = false;
|
|
39
|
+
const startConsoleLog = () => {
|
|
40
|
+
if (started)
|
|
41
|
+
return;
|
|
42
|
+
started = true;
|
|
43
|
+
console.time("Ensure Schema");
|
|
44
|
+
};
|
|
45
|
+
if (dp.ensureSchema) {
|
|
46
|
+
startConsoleLog();
|
|
47
|
+
const entitiesMetadata = options.entities.map(e => remult.repo(e).metadata);
|
|
48
|
+
await dp.ensureSchema(entitiesMetadata);
|
|
49
|
+
}
|
|
50
|
+
for (const item of [options.liveQueryStorage, options.queueStorage]) {
|
|
51
|
+
if (item?.ensureSchema) {
|
|
52
|
+
startConsoleLog();
|
|
53
|
+
await item.ensureSchema();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (started)
|
|
57
|
+
console.timeEnd("Ensure Schema");
|
|
58
|
+
}
|
|
59
|
+
if (options.initApi)
|
|
39
60
|
await options.initApi(remult);
|
|
40
|
-
|
|
41
|
-
});
|
|
61
|
+
res({});
|
|
42
62
|
});
|
|
43
|
-
return dp;
|
|
44
63
|
});
|
|
64
|
+
return dp;
|
|
65
|
+
});
|
|
66
|
+
if (!options.requestSerializer) {
|
|
67
|
+
options.requestSerializer = {
|
|
68
|
+
toJson: x => {
|
|
69
|
+
const r = {
|
|
70
|
+
url: x.url
|
|
71
|
+
};
|
|
72
|
+
for (const key of ['session', 'auth', 'user']) {
|
|
73
|
+
if (x[key])
|
|
74
|
+
r[key] = x[key];
|
|
75
|
+
}
|
|
76
|
+
return r;
|
|
77
|
+
},
|
|
78
|
+
fromJson: x => {
|
|
79
|
+
return x;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
45
82
|
}
|
|
46
83
|
{
|
|
47
84
|
let allControllers = [];
|
|
@@ -90,25 +127,50 @@ export class RemultAsyncLocalStorage {
|
|
|
90
127
|
}
|
|
91
128
|
}
|
|
92
129
|
RemultAsyncLocalStorage.instance = new RemultAsyncLocalStorage(undefined);
|
|
93
|
-
|
|
130
|
+
/* @internal*/
|
|
131
|
+
export class RemultServerImplementation {
|
|
94
132
|
constructor(queue, options, dataProvider) {
|
|
95
133
|
this.queue = queue;
|
|
96
134
|
this.options = options;
|
|
97
135
|
this.dataProvider = dataProvider;
|
|
136
|
+
this.liveQueryStorage = new InMemoryLiveQueryStorage();
|
|
137
|
+
this.runWithRequest = async (req, entityKey, what) => {
|
|
138
|
+
for (const e of this.options.entities) {
|
|
139
|
+
let key = getEntityKey(e);
|
|
140
|
+
if (key === entityKey) {
|
|
141
|
+
await new Promise((result) => {
|
|
142
|
+
this.withRemult(this.options.requestSerializer.fromJson(req), undefined, async () => {
|
|
143
|
+
await what(remult.repo(e));
|
|
144
|
+
result({});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
throw new Error("Couldn't find entity " + entityKey);
|
|
151
|
+
};
|
|
98
152
|
this.registeredRouter = false;
|
|
99
153
|
this.hasQueue = false;
|
|
100
154
|
/* internal */
|
|
101
155
|
this.backendMethodsOpenApi = [];
|
|
156
|
+
if (options.liveQueryStorage)
|
|
157
|
+
this.liveQueryStorage = options.liveQueryStorage;
|
|
158
|
+
if (options.subscriptionServer)
|
|
159
|
+
this.subscriptionServer = options.subscriptionServer;
|
|
102
160
|
}
|
|
161
|
+
;
|
|
103
162
|
withRemult(req, res, next) {
|
|
104
163
|
this.process(async () => { next(); })(req, res);
|
|
105
164
|
}
|
|
106
|
-
|
|
165
|
+
getRouteImpl() {
|
|
107
166
|
if (!this.routeImpl) {
|
|
108
167
|
this.routeImpl = new RouteImplementation();
|
|
109
168
|
this.registerRouter(this.routeImpl);
|
|
110
169
|
}
|
|
111
|
-
return this.routeImpl
|
|
170
|
+
return this.routeImpl;
|
|
171
|
+
}
|
|
172
|
+
handle(req, gRes) {
|
|
173
|
+
return this.getRouteImpl().handle(req, gRes);
|
|
112
174
|
}
|
|
113
175
|
registerRouter(r) {
|
|
114
176
|
if (this.registeredRouter)
|
|
@@ -144,6 +206,21 @@ class RemultServerImplementation {
|
|
|
144
206
|
},
|
|
145
207
|
doWork: undefined
|
|
146
208
|
}, r);
|
|
209
|
+
if (this.options.subscriptionServer instanceof SseSubscriptionServer) {
|
|
210
|
+
const streamPath = this.options.rootPath + '/' + streamUrl;
|
|
211
|
+
r.route(streamPath).get(this.process(async (remult, req, res, origReq, origRes) => {
|
|
212
|
+
remult.subscriptionServer.openHttpServerStream(origReq, origRes);
|
|
213
|
+
}));
|
|
214
|
+
r.route(streamPath + '/subscribe').post(this.process(async (remult, req, res, origReq, origRes) => {
|
|
215
|
+
remult.subscriptionServer.subscribeToChannel(origReq.body, res, remult);
|
|
216
|
+
}));
|
|
217
|
+
r.route(streamPath + '/unsubscribe').post(this.process(async (remult, req, res, origReq, origRes) => {
|
|
218
|
+
remult.subscriptionServer.subscribeToChannel(origReq.body, res, remult, true);
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
r.route(this.options.rootPath + '/' + liveQueryKeepAliveRoute).post(this.process(async (remult, req, res, origReq, origRes) => {
|
|
222
|
+
res.success(await remult.liveQueryStorage.keepAliveAndReturnUnknownQueryIds(origReq.body));
|
|
223
|
+
}));
|
|
147
224
|
}
|
|
148
225
|
this.options.entities.forEach(e => {
|
|
149
226
|
let key = getEntityKey(e);
|
|
@@ -156,11 +233,11 @@ class RemultServerImplementation {
|
|
|
156
233
|
add(key, dataApiFactory, r) {
|
|
157
234
|
let myRoute = this.options.rootPath + '/' + key;
|
|
158
235
|
if (this.options.logApiEndPoints)
|
|
159
|
-
console.
|
|
236
|
+
console.info("[remult] " + myRoute);
|
|
160
237
|
r.route(myRoute)
|
|
161
|
-
.get(this.process((c, req, res) => dataApiFactory(c).httpGet(res, req))).put(this.process(async (c, req, res, orig) => dataApiFactory(c).put(res, '', orig.body)))
|
|
238
|
+
.get(this.process((c, req, res, orig) => dataApiFactory(c).httpGet(res, req, () => this.options.requestSerializer.toJson(orig)))).put(this.process(async (c, req, res, orig) => dataApiFactory(c).put(res, '', orig.body)))
|
|
162
239
|
.delete(this.process(async (c, req, res, orig) => dataApiFactory(c).delete(res, '')))
|
|
163
|
-
.post(this.process(async (c, req, res, orig) => dataApiFactory(c).httpPost(res, req, orig.body)));
|
|
240
|
+
.post(this.process(async (c, req, res, orig) => dataApiFactory(c).httpPost(res, req, orig.body, () => this.options.requestSerializer.toJson(orig))));
|
|
164
241
|
r.route(myRoute + '/:id')
|
|
165
242
|
//@ts-ignore
|
|
166
243
|
.get(this.process(async (c, req, res, orig) => dataApiFactory(c).get(res, orig.params.id)))
|
|
@@ -170,11 +247,14 @@ class RemultServerImplementation {
|
|
|
170
247
|
.delete(this.process(async (c, req, res, orig) => dataApiFactory(c).delete(res, orig.params.id)));
|
|
171
248
|
}
|
|
172
249
|
process(what) {
|
|
173
|
-
return async (req,
|
|
250
|
+
return async (req, origRes) => {
|
|
174
251
|
let myReq = new ExpressRequestBridgeToDataApiRequest(req);
|
|
175
|
-
let myRes = new ExpressResponseBridgeToDataApiResponse(
|
|
252
|
+
let myRes = new ExpressResponseBridgeToDataApiResponse(origRes, req);
|
|
176
253
|
let remult = new Remult();
|
|
254
|
+
remult.liveQueryPublisher = new LiveQueryPublisher(() => remult.subscriptionServer, () => remult.liveQueryStorage, this.runWithRequest);
|
|
177
255
|
remult.dataProvider = (await this.dataProvider);
|
|
256
|
+
remult.subscriptionServer = this.subscriptionServer;
|
|
257
|
+
remult.liveQueryStorage = this.liveQueryStorage;
|
|
178
258
|
await new Promise(res => {
|
|
179
259
|
RemultAsyncLocalStorage.instance.run(remult, async () => {
|
|
180
260
|
if (req) {
|
|
@@ -190,9 +270,17 @@ class RemultServerImplementation {
|
|
|
190
270
|
remult.user = (user);
|
|
191
271
|
}
|
|
192
272
|
if (this.options.initRequest) {
|
|
193
|
-
await this.options.initRequest(
|
|
273
|
+
await this.options.initRequest(req, {
|
|
274
|
+
remult,
|
|
275
|
+
get liveQueryStorage() {
|
|
276
|
+
return remult.liveQueryStorage;
|
|
277
|
+
},
|
|
278
|
+
set liveQueryStorage(value) {
|
|
279
|
+
remult.liveQueryStorage = value;
|
|
280
|
+
}
|
|
281
|
+
});
|
|
194
282
|
}
|
|
195
|
-
await what(remult, myReq, myRes, req);
|
|
283
|
+
await what(remult, myReq, myRes, req, origRes);
|
|
196
284
|
res({});
|
|
197
285
|
});
|
|
198
286
|
});
|
|
@@ -218,7 +306,7 @@ class RemultServerImplementation {
|
|
|
218
306
|
})();
|
|
219
307
|
this.backendMethodsOpenApi.push({ path: myUrl, allowed, tag });
|
|
220
308
|
if (this.options.logApiEndPoints)
|
|
221
|
-
console.
|
|
309
|
+
console.info("[remult] " + myUrl);
|
|
222
310
|
if (queue) {
|
|
223
311
|
this.hasQueue = true;
|
|
224
312
|
this.queue.mapQueuedAction(myUrl, what);
|
|
@@ -582,7 +670,11 @@ class inProcessQueueHandler {
|
|
|
582
670
|
this.actions.get(url)(body, req, {
|
|
583
671
|
error: error => job.setErrorResult(serializeError(error)),
|
|
584
672
|
success: result => job.setResult(result),
|
|
585
|
-
progress: progress => job.setProgress(progress)
|
|
673
|
+
progress: progress => job.setProgress(progress),
|
|
674
|
+
created: () => { throw Error("Created response not expected for queue"); },
|
|
675
|
+
deleted: () => { throw Error("deleted response not expected for queue"); },
|
|
676
|
+
notFound: () => { throw Error("notFound response not expected for queue"); },
|
|
677
|
+
forbidden: () => job.setErrorResult("Forbidden")
|
|
586
678
|
});
|
|
587
679
|
return id;
|
|
588
680
|
}
|
|
@@ -720,6 +812,18 @@ class RouteImplementation {
|
|
|
720
812
|
constructor() {
|
|
721
813
|
this.statusCode = 200;
|
|
722
814
|
}
|
|
815
|
+
write(data) {
|
|
816
|
+
gRes.write(data);
|
|
817
|
+
}
|
|
818
|
+
writeHead(statusCode, headers) {
|
|
819
|
+
gRes.writeHead(statusCode, headers);
|
|
820
|
+
this.statusCode = statusCode;
|
|
821
|
+
res({ statusCode });
|
|
822
|
+
}
|
|
823
|
+
flush() {
|
|
824
|
+
if (gRes.flush)
|
|
825
|
+
gRes.flush();
|
|
826
|
+
}
|
|
723
827
|
json(data) {
|
|
724
828
|
if (gRes !== undefined)
|
|
725
829
|
gRes.json(data);
|
package/esm/server/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { GenericRequest, RemultServer, RemultServerOptions } from './expressBridge';
|
|
2
|
-
export { queuedJobInfo, QueueStorage, GenericRequest, GenericRequestHandler, GenericResponse, GenericRouter, RemultServerOptions, SpecificRoute, RemultServer } from './expressBridge';
|
|
2
|
+
export { queuedJobInfo, QueueStorage, GenericRequest, GenericRequestHandler, GenericResponse, GenericRouter, RemultServerOptions, SpecificRoute, RemultServer, InitRequestOptions } from './expressBridge';
|
|
3
3
|
export { JsonEntityFileStorage, JsonFileDataProvider } from './JsonEntityFileStorage';
|
|
4
|
+
export { DataProviderLiveQueryStorage } from '../live-query/data-provider-live-query-storage';
|
|
4
5
|
export declare function createRemultServer<RequestType extends GenericRequest = GenericRequest>(options?: RemultServerOptions<RequestType>): RemultServer;
|
package/esm/server/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { createRemultServerCore } from './expressBridge';
|
|
1
|
+
import { createRemultServerCore, } from './expressBridge';
|
|
2
2
|
import { initAsyncHooks } from './initAsyncHooks';
|
|
3
3
|
export { JsonEntityFileStorage, JsonFileDataProvider } from './JsonEntityFileStorage';
|
|
4
|
+
export { DataProviderLiveQueryStorage } from '../live-query/data-provider-live-query-storage';
|
|
4
5
|
export function createRemultServer(options) {
|
|
5
6
|
initAsyncHooks();
|
|
6
7
|
return createRemultServerCore(options);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function initDataProvider(optionsDataProvider) {
|
|
2
|
+
let dataProvider;
|
|
3
|
+
if (typeof optionsDataProvider === "function") {
|
|
4
|
+
dataProvider = optionsDataProvider();
|
|
5
|
+
}
|
|
6
|
+
else
|
|
7
|
+
dataProvider = Promise.resolve(optionsDataProvider);
|
|
8
|
+
dataProvider = dataProvider.then(async (dp) => {
|
|
9
|
+
if (dp)
|
|
10
|
+
return dp;
|
|
11
|
+
return new (await import('./JsonEntityFileStorage')).JsonFileDataProvider('./db');
|
|
12
|
+
});
|
|
13
|
+
return dataProvider;
|
|
14
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { RestDataProviderHttpProvider } from "./data-interfaces";
|
|
2
|
+
import { RestDataProviderHttpProviderUsingFetch } from './data-providers/rest-data-provider';
|
|
3
|
+
export declare function buildRestDataProvider(provider: ExternalHttpProvider | typeof fetch): RestDataProviderHttpProviderUsingFetch | RestDataProviderHttpProvider;
|
|
4
|
+
export declare function isExternalHttpProvider(item: any): boolean;
|
|
5
|
+
export declare class HttpProviderBridgeToRestDataProviderHttpProvider implements RestDataProviderHttpProvider {
|
|
6
|
+
private http;
|
|
7
|
+
constructor(http: ExternalHttpProvider);
|
|
8
|
+
post(url: string, data: any): Promise<any>;
|
|
9
|
+
delete(url: string): Promise<void>;
|
|
10
|
+
put(url: string, data: any): Promise<any>;
|
|
11
|
+
get(url: string): Promise<any>;
|
|
12
|
+
}
|
|
13
|
+
export declare function retry<T>(what: () => Promise<T>): Promise<T>;
|
|
14
|
+
export declare function toPromise<T>(p: Promise<T> | {
|
|
15
|
+
toPromise(): Promise<T>;
|
|
16
|
+
}): Promise<any>;
|
|
17
|
+
export declare function processHttpException(ex: any): Promise<any>;
|
|
18
|
+
export interface ExternalHttpProvider {
|
|
19
|
+
post(url: string, data: any): Promise<any> | {
|
|
20
|
+
toPromise(): Promise<any>;
|
|
21
|
+
};
|
|
22
|
+
delete(url: string): Promise<void> | {
|
|
23
|
+
toPromise(): Promise<void>;
|
|
24
|
+
};
|
|
25
|
+
put(url: string, data: any): Promise<any> | {
|
|
26
|
+
toPromise(): Promise<any>;
|
|
27
|
+
};
|
|
28
|
+
get(url: string): Promise<any> | {
|
|
29
|
+
toPromise(): Promise<any>;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { RestDataProviderHttpProviderUsingFetch } from './data-providers/rest-data-provider';
|
|
2
|
+
export function buildRestDataProvider(provider) {
|
|
3
|
+
if (!provider)
|
|
4
|
+
return new RestDataProviderHttpProviderUsingFetch();
|
|
5
|
+
let httpDataProvider;
|
|
6
|
+
if (!httpDataProvider) {
|
|
7
|
+
if (isExternalHttpProvider(provider)) {
|
|
8
|
+
httpDataProvider = new HttpProviderBridgeToRestDataProviderHttpProvider(provider);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
if (!httpDataProvider) {
|
|
12
|
+
if (typeof provider === "function") {
|
|
13
|
+
httpDataProvider = new RestDataProviderHttpProviderUsingFetch(provider);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return httpDataProvider;
|
|
17
|
+
}
|
|
18
|
+
export function isExternalHttpProvider(item) {
|
|
19
|
+
let http = item;
|
|
20
|
+
if (http && http.get && http.put && http.post && http.delete)
|
|
21
|
+
return true;
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
export class HttpProviderBridgeToRestDataProviderHttpProvider {
|
|
25
|
+
constructor(http) {
|
|
26
|
+
this.http = http;
|
|
27
|
+
}
|
|
28
|
+
async post(url, data) {
|
|
29
|
+
return await retry(() => toPromise(this.http.post(url, data)));
|
|
30
|
+
}
|
|
31
|
+
delete(url) {
|
|
32
|
+
return toPromise(this.http.delete(url));
|
|
33
|
+
}
|
|
34
|
+
put(url, data) {
|
|
35
|
+
return toPromise(this.http.put(url, data));
|
|
36
|
+
}
|
|
37
|
+
async get(url) {
|
|
38
|
+
return await retry(() => toPromise(this.http.get(url)));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export async function retry(what) {
|
|
42
|
+
while (true) {
|
|
43
|
+
try {
|
|
44
|
+
return await what();
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
if (err.message?.startsWith("Error occurred while trying to proxy") ||
|
|
48
|
+
err.message?.startsWith("Error occured while trying to proxy") ||
|
|
49
|
+
err.message?.includes("http proxy error") ||
|
|
50
|
+
err.message?.startsWith("Gateway Timeout")) {
|
|
51
|
+
await new Promise((res, req) => {
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
res({});
|
|
54
|
+
}, 250);
|
|
55
|
+
});
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function toPromise(p) {
|
|
63
|
+
let r;
|
|
64
|
+
if (p["toPromise"] !== undefined) {
|
|
65
|
+
r = p["toPromise"]();
|
|
66
|
+
}
|
|
67
|
+
//@ts-ignore
|
|
68
|
+
else
|
|
69
|
+
r = p;
|
|
70
|
+
return r.then((x) => {
|
|
71
|
+
if (x && (x.status == 200 || x.status == 201) && x.headers && x.request && x.data) //for axios
|
|
72
|
+
return x.data;
|
|
73
|
+
return x;
|
|
74
|
+
}).catch(async (ex) => {
|
|
75
|
+
throw await processHttpException(ex);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
export async function processHttpException(ex) {
|
|
79
|
+
let z = await ex;
|
|
80
|
+
var error;
|
|
81
|
+
if (z.error)
|
|
82
|
+
error = z.error;
|
|
83
|
+
else if (z.isAxiosError) {
|
|
84
|
+
if (typeof z.response?.data === "string")
|
|
85
|
+
error = z.response.data;
|
|
86
|
+
else
|
|
87
|
+
error = z?.response?.data;
|
|
88
|
+
}
|
|
89
|
+
if (!error)
|
|
90
|
+
error = z.message;
|
|
91
|
+
if (z.status == 0 && z.error.isTrusted)
|
|
92
|
+
error = "Network Error";
|
|
93
|
+
if (typeof error === 'string') {
|
|
94
|
+
error = {
|
|
95
|
+
message: error,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (z.modelState)
|
|
99
|
+
error.modelState = z.modelState;
|
|
100
|
+
let httpStatusCode = z.status;
|
|
101
|
+
if (httpStatusCode === undefined)
|
|
102
|
+
httpStatusCode = z.response?.status;
|
|
103
|
+
if (httpStatusCode !== undefined && httpStatusCode !== null) {
|
|
104
|
+
error.httpStatusCode = httpStatusCode;
|
|
105
|
+
}
|
|
106
|
+
var result = Object.assign(error, {
|
|
107
|
+
// exception: ex disabled for now because JSON.stringify crashed with this
|
|
108
|
+
});
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
@@ -2,7 +2,10 @@ import { ClassType } from '../classType';
|
|
|
2
2
|
import { Allowed, AllowedForInstance } from './context';
|
|
3
3
|
import { EntityMetadata, FieldRef } from './remult3';
|
|
4
4
|
export interface FieldOptions<entityType = any, valueType = any> {
|
|
5
|
-
/**A human readable name for the field
|
|
5
|
+
/** A human readable name for the field. Can be used to achieve a consistent caption for a field throughout the app
|
|
6
|
+
* @example
|
|
7
|
+
* <input placeholder={taskRepo.metadata.fields.title.caption}/>
|
|
8
|
+
*/
|
|
6
9
|
caption?: string;
|
|
7
10
|
/** If it can store null in the database */
|
|
8
11
|
allowNull?: boolean;
|
|
@@ -32,14 +35,23 @@ export interface FieldOptions<entityType = any, valueType = any> {
|
|
|
32
35
|
* }
|
|
33
36
|
* })
|
|
34
37
|
*/
|
|
35
|
-
validate?: ((entity: entityType,
|
|
38
|
+
validate?: ((entity: entityType, fieldRef: FieldRef<entityType, valueType>) => (any | Promise<any>)) | ((entity: entityType, fieldRef: FieldRef<entityType, valueType>) => (any | Promise<any>))[];
|
|
36
39
|
/** Will be fired before this field is saved to the server/database */
|
|
37
|
-
saving?: ((entity: entityType,
|
|
40
|
+
saving?: ((entity: entityType, fieldRef: FieldRef<entityType, valueType>) => (any | Promise<any>));
|
|
38
41
|
/** An expression that will determine this fields value on the backend and be provided to the front end*/
|
|
39
42
|
serverExpression?: (entity: entityType) => valueType | Promise<valueType>;
|
|
40
43
|
/** The name of the column in the database that holds the data for this field. If no name is set, the key will be used instead. */
|
|
41
44
|
dbName?: string;
|
|
42
|
-
/** Used or fields that are based on an sql expressions, instead of a physical table column
|
|
45
|
+
/** Used or fields that are based on an sql expressions, instead of a physical table column
|
|
46
|
+
* @example
|
|
47
|
+
*
|
|
48
|
+
* @Fields.integer({
|
|
49
|
+
* sqlExpression:e=> 'length(title)'
|
|
50
|
+
* })
|
|
51
|
+
* titleLength = 0;
|
|
52
|
+
* @Fields.string()
|
|
53
|
+
* title='';
|
|
54
|
+
*/
|
|
43
55
|
sqlExpression?: string | ((entity: EntityMetadata<entityType>) => string | Promise<string>);
|
|
44
56
|
/** For fields that shouldn't be part of an update or insert statement */
|
|
45
57
|
dbReadOnly?: boolean;
|
|
@@ -62,18 +74,42 @@ export interface FieldOptions<entityType = any, valueType = any> {
|
|
|
62
74
|
/** The key to be used for this field */
|
|
63
75
|
key?: string;
|
|
64
76
|
}
|
|
77
|
+
/**Metadata for a `Field`, this metadata can be used in the user interface to provide a richer UI experience */
|
|
65
78
|
export interface FieldMetadata<valueType = any> {
|
|
79
|
+
/** The field's member name in an object.
|
|
80
|
+
* @example
|
|
81
|
+
* const taskRepo = remult.repo(Task);
|
|
82
|
+
* console.log(taskRepo.metadata.fields.title.key);
|
|
83
|
+
* // result: title
|
|
84
|
+
*/
|
|
66
85
|
readonly key: string;
|
|
67
|
-
|
|
68
|
-
|
|
86
|
+
/** A human readable caption for the field. Can be used to achieve a consistent caption for a field throughout the app
|
|
87
|
+
* @example
|
|
88
|
+
* <input placeholder={taskRepo.metadata.fields.title.caption}/>
|
|
89
|
+
*/
|
|
69
90
|
readonly caption: string;
|
|
91
|
+
/** The field's value type (number,string etc...) */
|
|
92
|
+
readonly valueType: any;
|
|
93
|
+
/** The options sent to this field's decorator */
|
|
94
|
+
readonly options: FieldOptions;
|
|
95
|
+
/** The `inputType` relevant for this field, determined by the options sent to it's decorator and the valueConverter in these options */
|
|
70
96
|
readonly inputType: string;
|
|
97
|
+
/** if null is allowed for this field */
|
|
71
98
|
readonly allowNull: boolean;
|
|
99
|
+
/** The class that contains this field
|
|
100
|
+
* @example
|
|
101
|
+
* const taskRepo = remult.repo(Task);
|
|
102
|
+
* Task == taskRepo.metadata.fields.title.target //will return true
|
|
103
|
+
*/
|
|
104
|
+
readonly target: ClassType<valueType>;
|
|
105
|
+
/** Returns the dbName - based on it's `dbName` option and it's `sqlExpression` option */
|
|
72
106
|
getDbName(): Promise<string>;
|
|
107
|
+
/** Indicates if this field is based on a server express */
|
|
73
108
|
readonly isServerExpression: boolean;
|
|
109
|
+
/** indicates that this field should only be included in select statement, and excluded from update or insert. useful for db generated ids etc... */
|
|
74
110
|
readonly dbReadOnly: boolean;
|
|
111
|
+
/** the Value converter for this field */
|
|
75
112
|
readonly valueConverter: ValueConverter<valueType>;
|
|
76
|
-
readonly options: FieldOptions;
|
|
77
113
|
}
|
|
78
114
|
export interface ValueConverter<valueType> {
|
|
79
115
|
fromJson?(val: any): valueType;
|
|
@@ -86,7 +122,7 @@ export interface ValueConverter<valueType> {
|
|
|
86
122
|
readonly fieldTypeInDb?: string;
|
|
87
123
|
readonly inputType?: string;
|
|
88
124
|
}
|
|
89
|
-
export declare type FieldValidator<entityType = any, valueType = any> = (entity: entityType,
|
|
125
|
+
export declare type FieldValidator<entityType = any, valueType = any> = (entity: entityType, fieldRef: FieldRef<entityType, valueType>) => void | Promise<void>;
|
|
90
126
|
export declare type ValueOrExpression<valueType> = valueType | (() => valueType);
|
|
91
127
|
export declare function valueOrExpressionToValue<valueType>(f: ValueOrExpression<valueType>): valueType;
|
|
92
128
|
export interface ValueListItem {
|
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
|
}
|