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.
Files changed (163) hide show
  1. package/SseSubscriptionServer.d.ts +23 -0
  2. package/SseSubscriptionServer.js +143 -0
  3. package/ably.d.ts +13 -0
  4. package/ably.js +59 -0
  5. package/esm/SseSubscriptionServer.d.ts +23 -0
  6. package/esm/SseSubscriptionServer.js +102 -0
  7. package/esm/ably.d.ts +13 -0
  8. package/esm/ably.js +28 -0
  9. package/esm/index.d.ts +7 -4
  10. package/esm/index.js +4 -2
  11. package/esm/live-query/data-provider-live-query-storage.d.ts +23 -0
  12. package/esm/live-query/data-provider-live-query-storage.js +78 -0
  13. package/esm/myLog.d.ts +1 -0
  14. package/esm/myLog.js +5 -0
  15. package/esm/postgres/postgres-data-provider.d.ts +4 -2
  16. package/esm/postgres/postgres-data-provider.js +18 -6
  17. package/esm/postgres/schema-builder.d.ts +2 -1
  18. package/esm/postgres/schema-builder.js +49 -29
  19. package/esm/remult-knex/index.d.ts +10 -32
  20. package/esm/remult-knex/index.js +75 -51
  21. package/esm/remult-mongo.d.ts +9 -2
  22. package/esm/remult-mongo.js +24 -8
  23. package/esm/remult-next.d.ts +11 -0
  24. package/esm/remult-next.js +27 -0
  25. package/esm/server/core.d.ts +2 -0
  26. package/esm/server/core.js +2 -0
  27. package/esm/server/expressBridge.d.ts +19 -7
  28. package/esm/server/expressBridge.js +135 -31
  29. package/esm/server/index.d.ts +2 -1
  30. package/esm/server/index.js +2 -1
  31. package/esm/server/initDataProvider.d.ts +2 -0
  32. package/esm/server/initDataProvider.js +14 -0
  33. package/esm/src/buildRestDataProvider.d.ts +31 -0
  34. package/esm/src/buildRestDataProvider.js +110 -0
  35. package/esm/src/column-interfaces.d.ts +44 -8
  36. package/esm/src/column.d.ts +3 -3
  37. package/esm/src/context.d.ts +39 -36
  38. package/esm/src/context.js +87 -117
  39. package/esm/src/data-api.d.ts +9 -3
  40. package/esm/src/data-api.js +130 -63
  41. package/esm/src/data-interfaces.d.ts +5 -1
  42. package/esm/src/data-providers/array-entity-data-provider.d.ts +1 -1
  43. package/esm/src/data-providers/array-entity-data-provider.js +2 -17
  44. package/esm/src/data-providers/rest-data-provider.d.ts +4 -3
  45. package/esm/src/data-providers/rest-data-provider.js +64 -23
  46. package/esm/src/data-providers/sql-database.d.ts +7 -3
  47. package/esm/src/data-providers/sql-database.js +60 -17
  48. package/esm/src/data-providers/web-sql-data-provider.d.ts +3 -0
  49. package/esm/src/data-providers/web-sql-data-provider.js +23 -9
  50. package/esm/src/entity.d.ts +2 -2
  51. package/esm/src/filter/filter-consumer-bridge-to-sql-request.d.ts +16 -16
  52. package/esm/src/filter/filter-consumer-bridge-to-sql-request.js +29 -21
  53. package/esm/src/filter/filter-interfaces.d.ts +5 -5
  54. package/esm/src/filter/filter-interfaces.js +31 -26
  55. package/esm/src/live-query/LiveQueryClient.d.ts +1 -0
  56. package/esm/src/live-query/LiveQueryClient.js +184 -0
  57. package/esm/src/live-query/SseSubscriptionClient.d.ts +5 -0
  58. package/esm/src/live-query/SseSubscriptionClient.js +86 -0
  59. package/esm/src/live-query/SubscriptionChannel.d.ts +41 -0
  60. package/esm/src/live-query/SubscriptionChannel.js +121 -0
  61. package/esm/src/live-query/SubscriptionServer.d.ts +38 -0
  62. package/esm/src/live-query/SubscriptionServer.js +108 -0
  63. package/esm/src/remult-proxy.d.ts +1 -1
  64. package/esm/src/remult-proxy.js +52 -7
  65. package/esm/src/remult3/RepositoryImplementation.d.ts +14 -5
  66. package/esm/src/remult3/RepositoryImplementation.js +124 -44
  67. package/esm/src/remult3/getId.d.ts +2 -0
  68. package/esm/src/remult3/getId.js +7 -0
  69. package/esm/src/remult3/remult3.d.ts +67 -10
  70. package/esm/src/remult3/remult3.js +1 -0
  71. package/esm/src/server-action.d.ts +5 -1
  72. package/esm/src/server-action.js +11 -12
  73. package/esm/src/shared-tests/db-tests-setup.d.ts +12 -5
  74. package/esm/src/shared-tests/db-tests-setup.js +14 -6
  75. package/esm/src/shared-tests/db-tests.db-tests.js +71 -10
  76. package/esm/src/sort.d.ts +1 -0
  77. package/esm/src/sort.js +18 -0
  78. package/esm/src/sql-command.d.ts +5 -2
  79. package/esm/src/tests/entityForCustomFilter.d.ts +6 -6
  80. package/esm/src/tests/entityForCustomFilter.js +20 -21
  81. package/esm/src/tests/frontend-database-tests-setup.spec.js +3 -4
  82. package/esm/src/tests/testHelper.spec.d.ts +3 -1
  83. package/esm/src/tests/testHelper.spec.js +63 -60
  84. package/esm/src/valueConverters.js +15 -7
  85. package/index.d.ts +7 -4
  86. package/index.js +7 -3
  87. package/live-query/data-provider-live-query-storage.d.ts +23 -0
  88. package/live-query/data-provider-live-query-storage.js +236 -0
  89. package/myLog.d.ts +1 -0
  90. package/myLog.js +20 -0
  91. package/package.json +18 -1
  92. package/postgres/postgres-data-provider.d.ts +4 -2
  93. package/postgres/postgres-data-provider.js +54 -39
  94. package/postgres/schema-builder.d.ts +2 -1
  95. package/postgres/schema-builder.js +118 -70
  96. package/remult-express.js +1 -1
  97. package/remult-knex/index.d.ts +10 -32
  98. package/remult-knex/index.js +134 -87
  99. package/remult-mongo.d.ts +9 -2
  100. package/remult-mongo.js +39 -7
  101. package/remult-next.d.ts +11 -0
  102. package/remult-next.js +73 -0
  103. package/server/JsonEntityFileStorage.js +2 -2
  104. package/server/core.d.ts +2 -0
  105. package/server/core.js +5 -1
  106. package/server/expressBridge.d.ts +19 -7
  107. package/server/expressBridge.js +283 -69
  108. package/server/index.d.ts +2 -1
  109. package/server/index.js +3 -1
  110. package/server/initDataProvider.d.ts +2 -0
  111. package/server/initDataProvider.js +26 -0
  112. package/src/buildRestDataProvider.d.ts +31 -0
  113. package/src/buildRestDataProvider.js +162 -0
  114. package/src/column-interfaces.d.ts +44 -8
  115. package/src/column.d.ts +3 -3
  116. package/src/context.d.ts +39 -36
  117. package/src/context.js +171 -166
  118. package/src/data-api.d.ts +9 -3
  119. package/src/data-api.js +233 -88
  120. package/src/data-interfaces.d.ts +5 -1
  121. package/src/data-providers/array-entity-data-provider.d.ts +1 -1
  122. package/src/data-providers/array-entity-data-provider.js +2 -17
  123. package/src/data-providers/rest-data-provider.d.ts +4 -3
  124. package/src/data-providers/rest-data-provider.js +105 -39
  125. package/src/data-providers/sql-database.d.ts +7 -3
  126. package/src/data-providers/sql-database.js +89 -16
  127. package/src/data-providers/web-sql-data-provider.d.ts +3 -0
  128. package/src/data-providers/web-sql-data-provider.js +56 -11
  129. package/src/entity.d.ts +2 -2
  130. package/src/filter/filter-consumer-bridge-to-sql-request.d.ts +16 -16
  131. package/src/filter/filter-consumer-bridge-to-sql-request.js +59 -50
  132. package/src/filter/filter-interfaces.d.ts +5 -5
  133. package/src/filter/filter-interfaces.js +31 -26
  134. package/src/live-query/LiveQueryClient.d.ts +1 -0
  135. package/src/live-query/LiveQueryClient.js +283 -0
  136. package/src/live-query/SseSubscriptionClient.d.ts +5 -0
  137. package/src/live-query/SseSubscriptionClient.js +150 -0
  138. package/src/live-query/SubscriptionChannel.d.ts +41 -0
  139. package/src/live-query/SubscriptionChannel.js +209 -0
  140. package/src/live-query/SubscriptionServer.d.ts +38 -0
  141. package/src/live-query/SubscriptionServer.js +258 -0
  142. package/src/remult-proxy.d.ts +1 -1
  143. package/src/remult-proxy.js +153 -8
  144. package/src/remult3/RepositoryImplementation.d.ts +14 -5
  145. package/src/remult3/RepositoryImplementation.js +200 -85
  146. package/src/remult3/getId.d.ts +2 -0
  147. package/src/remult3/getId.js +11 -0
  148. package/src/remult3/remult3.d.ts +67 -10
  149. package/src/remult3/remult3.js +1 -0
  150. package/src/server-action.d.ts +5 -1
  151. package/src/server-action.js +11 -11
  152. package/src/shared-tests/db-tests-setup.d.ts +12 -5
  153. package/src/shared-tests/db-tests-setup.js +17 -8
  154. package/src/shared-tests/db-tests.db-tests.js +140 -8
  155. package/src/sort.d.ts +1 -0
  156. package/src/sort.js +18 -0
  157. package/src/sql-command.d.ts +5 -2
  158. package/src/tests/entityForCustomFilter.d.ts +6 -6
  159. package/src/tests/entityForCustomFilter.js +30 -31
  160. package/src/tests/frontend-database-tests-setup.spec.js +5 -6
  161. package/src/tests/testHelper.spec.d.ts +3 -1
  162. package/src/tests/testHelper.spec.js +104 -100
  163. package/src/valueConverters.js +15 -7
@@ -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';
@@ -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
- initRequest?: (remult: Remult, origReq: RequestType) => Promise<void>;
15
- getUser?: (request: RequestType) => Promise<UserInfo>;
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 (typeof options.dataProvider === "function") {
24
- dataProvider = options.dataProvider();
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
- if (options.initApi) {
35
- dataProvider = dataProvider.then(async (dp) => {
36
- var remult = new Remult(dp);
37
- await new Promise((res) => {
38
- RemultAsyncLocalStorage.instance.run(remult, async () => {
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
- res({});
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
- class RemultServerImplementation {
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
- handle(req, gRes) {
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.handle(req, gRes);
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.log("[remult] " + myRoute);
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, res) => {
250
+ return async (req, origRes) => {
174
251
  let myReq = new ExpressRequestBridgeToDataApiRequest(req);
175
- let myRes = new ExpressResponseBridgeToDataApiResponse(res, req);
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(remult, req);
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.log("[remult] " + myUrl);
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);
@@ -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;
@@ -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,2 @@
1
+ import { DataProvider } from '../src/data-interfaces';
2
+ export declare function initDataProvider(optionsDataProvider?: DataProvider | Promise<DataProvider> | (() => Promise<DataProvider | undefined>)): Promise<DataProvider>;
@@ -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, col: FieldRef<entityType, valueType>) => (any | Promise<any>)) | ((entity: entityType, col: FieldRef<entityType, valueType>) => (any | Promise<any>))[];
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, col: FieldRef<entityType, valueType>) => (any | Promise<any>));
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
- readonly target: ClassType<valueType>;
68
- readonly valueType: any;
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, col: FieldRef<entityType, valueType>) => void | Promise<void>;
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 {
@@ -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: string;
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: string);
33
+ constructor(repository: RepositoryImplementation<T>, id: idType<T>);
34
34
  get item(): T;
35
35
  waitLoad(): Promise<T>;
36
36
  }