remult 0.25.6-exp.1 → 0.25.6-exp.3
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/esm/server/expressBridge.js +2 -0
- package/esm/src/filter/filter-interfaces.js +70 -19
- package/esm/src/live-query/SubscriptionChannel.js +30 -0
- package/esm/src/remult3/remult3.js +45 -85
- package/esm/src/sort.js +58 -0
- package/package.json +1 -1
- package/server/expressBridge.d.ts +1 -1
- package/server/expressBridge.js +8 -2
- package/src/column-interfaces.d.ts +85 -0
- package/src/context.d.ts +61 -4
- package/src/filter/filter-interfaces.d.ts +115 -6
- package/src/filter/filter-interfaces.js +75 -24
- package/src/live-query/SubscriptionChannel.d.ts +43 -0
- package/src/live-query/SubscriptionChannel.js +30 -0
- package/src/remult3/Fields.d.ts +2 -2
- package/src/remult3/remult3.d.ts +65 -28
- package/src/remult3/remult3.js +45 -85
- package/src/sort.d.ts +58 -0
- package/src/sort.js +53 -0
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
import { getEntityRef, getEntitySettings } from '../remult3/getEntityRef.js';
|
|
2
2
|
import { getRelationFieldInfo } from '../remult3/relationInfoMember.js';
|
|
3
|
+
/**
|
|
4
|
+
* The `Filter` class is a helper class that focuses on filter-related concerns. It provides methods
|
|
5
|
+
* for creating and applying filters in queries.
|
|
6
|
+
*/
|
|
3
7
|
export class Filter {
|
|
4
8
|
apply;
|
|
5
|
-
constructor(apply) {
|
|
6
|
-
this.apply = apply;
|
|
7
|
-
}
|
|
8
|
-
__applyToConsumer(add) {
|
|
9
|
-
this.apply(add);
|
|
10
|
-
}
|
|
11
|
-
static async resolve(filter) {
|
|
12
|
-
if (typeof filter === 'function')
|
|
13
|
-
return await filter();
|
|
14
|
-
return filter;
|
|
15
|
-
}
|
|
16
9
|
static createCustom(rawFilterTranslator, key = '') {
|
|
17
10
|
let rawFilterInfo = { key: key, rawFilterTranslator };
|
|
18
11
|
return Object.assign((x) => {
|
|
@@ -26,6 +19,58 @@ export class Filter {
|
|
|
26
19
|
};
|
|
27
20
|
}, { rawFilterInfo });
|
|
28
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Translates an `EntityFilter` to a plain JSON object that can be stored or transported.
|
|
24
|
+
*
|
|
25
|
+
* @template T The entity type for the filter.
|
|
26
|
+
* @param {EntityMetadata<T>} entityDefs The metadata of the entity associated with the filter.
|
|
27
|
+
* @param {EntityFilter<T>} where The `EntityFilter` to be translated.
|
|
28
|
+
* @returns {any} A plain JSON object representing the `EntityFilter`.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // Assuming `Task` is an entity class
|
|
32
|
+
* const jsonFilter = Filter.entityFilterToJson(Task, { completed: true });
|
|
33
|
+
* // `jsonFilter` can now be stored or transported as JSON
|
|
34
|
+
*/
|
|
35
|
+
static entityFilterToJson(entityDefs, where) {
|
|
36
|
+
return Filter.fromEntityFilter(entityDefs, where).toJson();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Translates a plain JSON object back into an `EntityFilter`.
|
|
40
|
+
*
|
|
41
|
+
* @template T The entity type for the filter.
|
|
42
|
+
* @param {EntityMetadata<T>} entityDefs The metadata of the entity associated with the filter.
|
|
43
|
+
* @param {any} packed The plain JSON object representing the `EntityFilter`.
|
|
44
|
+
* @returns {EntityFilter<T>} The reconstructed `EntityFilter`.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* // Assuming `Task` is an entity class and `jsonFilter` is a JSON object representing an EntityFilter
|
|
48
|
+
* const taskFilter = Filter.entityFilterFromJson(Task, jsonFilter);
|
|
49
|
+
* // Using the reconstructed `EntityFilter` in a query
|
|
50
|
+
* const tasks = await remult.repo(Task).find({ where: taskFilter });
|
|
51
|
+
* for (const task of tasks) {
|
|
52
|
+
* // Do something for each task based on the filter
|
|
53
|
+
* }
|
|
54
|
+
*/
|
|
55
|
+
static entityFilterFromJson(entityDefs, packed) {
|
|
56
|
+
return buildFilterFromRequestParameters(entityDefs, {
|
|
57
|
+
get: (key) => packed[key],
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Converts an `EntityFilter` to a `Filter` that can be used by the `DataProvider`. This method is
|
|
62
|
+
* mainly used internally.
|
|
63
|
+
*
|
|
64
|
+
* @template T The entity type for the filter.
|
|
65
|
+
* @param {EntityMetadata<T>} entity The metadata of the entity associated with the filter.
|
|
66
|
+
* @param {EntityFilter<T>} whereItem The `EntityFilter` to be converted.
|
|
67
|
+
* @returns {Filter} A `Filter` instance that can be used by the `DataProvider`.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* // Assuming `Task` is an entity class and `taskFilter` is an EntityFilter
|
|
71
|
+
* const filter = Filter.fromEntityFilter(Task, taskFilter);
|
|
72
|
+
* // `filter` can now be used with the DataProvider
|
|
73
|
+
*/
|
|
29
74
|
static fromEntityFilter(entity, whereItem) {
|
|
30
75
|
let result = [];
|
|
31
76
|
for (const key in whereItem) {
|
|
@@ -122,19 +167,25 @@ export class Filter {
|
|
|
122
167
|
}
|
|
123
168
|
return new AndFilter(...result);
|
|
124
169
|
}
|
|
170
|
+
constructor(apply) {
|
|
171
|
+
this.apply = apply;
|
|
172
|
+
}
|
|
173
|
+
__applyToConsumer(add) {
|
|
174
|
+
this.apply(add);
|
|
175
|
+
}
|
|
176
|
+
//@internal
|
|
177
|
+
static async resolve(filter) {
|
|
178
|
+
if (typeof filter === 'function')
|
|
179
|
+
return await filter();
|
|
180
|
+
return filter;
|
|
181
|
+
}
|
|
182
|
+
//@internal
|
|
125
183
|
toJson() {
|
|
126
184
|
let r = new FilterSerializer();
|
|
127
185
|
this.__applyToConsumer(r);
|
|
128
186
|
return r.result;
|
|
129
187
|
}
|
|
130
|
-
|
|
131
|
-
return Filter.fromEntityFilter(entityDefs, where).toJson();
|
|
132
|
-
}
|
|
133
|
-
static entityFilterFromJson(entityDefs, packed) {
|
|
134
|
-
return buildFilterFromRequestParameters(entityDefs, {
|
|
135
|
-
get: (key) => packed[key],
|
|
136
|
-
});
|
|
137
|
-
}
|
|
188
|
+
//@internal
|
|
138
189
|
static async translateCustomWhere(r, entity, remult) {
|
|
139
190
|
let f = new customTranslator(async (filterKey, custom) => {
|
|
140
191
|
let r = [];
|
|
@@ -101,11 +101,41 @@ export class LiveQuerySubscriber {
|
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
export const liveQueryKeepAliveRoute = '_liveQueryKeepAlive';
|
|
104
|
+
/**
|
|
105
|
+
* The `SubscriptionChannel` class is used to send messages from the backend to the frontend,
|
|
106
|
+
* using the same mechanism used by live queries.
|
|
107
|
+
*
|
|
108
|
+
* @template messageType The type of the message that the channel will handle.
|
|
109
|
+
* @example
|
|
110
|
+
* // Defined in code that is shared between the frontend and the backend
|
|
111
|
+
* const statusChange = new SubscriptionChannel<{ oldStatus: number, newStatus: number }>("statusChange");
|
|
112
|
+
*
|
|
113
|
+
* // Backend: Publishing a message
|
|
114
|
+
* statusChange.publish({ oldStatus: 1, newStatus: 2 });
|
|
115
|
+
*
|
|
116
|
+
* // Frontend: Subscribing to messages
|
|
117
|
+
* statusChange.subscribe((message) => {
|
|
118
|
+
* console.log(`Status changed from ${message.oldStatus} to ${message.newStatus}`);
|
|
119
|
+
* });
|
|
120
|
+
*
|
|
121
|
+
* // Note: If you want to publish from the frontend, use a BackendMethod for that.
|
|
122
|
+
*/
|
|
104
123
|
export class SubscriptionChannel {
|
|
105
124
|
channelKey;
|
|
125
|
+
/**
|
|
126
|
+
* Constructs a new `SubscriptionChannel` instance.
|
|
127
|
+
*
|
|
128
|
+
* @param {string} channelKey The key that identifies the channel.
|
|
129
|
+
*/
|
|
106
130
|
constructor(channelKey) {
|
|
107
131
|
this.channelKey = channelKey;
|
|
108
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Publishes a message to the channel. This method should only be used on the backend.
|
|
135
|
+
*
|
|
136
|
+
* @param {messageType} message The message to be published.
|
|
137
|
+
* @param {Remult} [remult] An optional instance of Remult to use for publishing the message.
|
|
138
|
+
*/
|
|
109
139
|
publish(message, remult) {
|
|
110
140
|
remult = remult || defaultRemult;
|
|
111
141
|
remult.subscriptionServer.publishMessage(this.channelKey, message);
|
|
@@ -9,116 +9,76 @@ export {};
|
|
|
9
9
|
* review react summit
|
|
10
10
|
#
|
|
11
11
|
|
|
12
|
-
//y1 -
|
|
13
|
-
//y1 -
|
|
12
|
+
//y1 - getFields didn't work for kobi in the home component
|
|
13
|
+
//y1 - talk about export api is not possible in +server - how did we face that in others, and how do we doc it - ended up using _api
|
|
14
14
|
//y1 - number default storage in knex is decimal and it by default has 8,2 - meaning only 999,999 - I think it should be way bigger than that. in firefly we do at least 18 - https://discord.com/channels/975754286384418847/1195162533447876658
|
|
15
|
-
//y1 -
|
|
15
|
+
//y1 - admin url!
|
|
16
|
+
//y1 - consider sql expression gets a dbnames of it's own (that already has the "tableName" defined correctly) maybe also the filter translator
|
|
17
|
+
//y1 - talk about modules in init express with entities/controllers,initRequest,initApi
|
|
16
18
|
//y1 - tried to upgrade vitest, nuxt tests are failing with loading uuid - sounds familiar?
|
|
17
|
-
//y1 - I think that the tests you've setup cover next app router - I added to the setup, but not sure where else
|
|
19
|
+
//y1 - I think that the tests you've setup don't cover next app router - I added to the setup, but not sure where else
|
|
20
|
+
//y1 - support get with backend method, with url search params as the first parameter, & url as second parameter
|
|
21
|
+
//y1 - talk about the parameter issue with backend methods
|
|
18
22
|
//y1 - select data provider per entity https://discord.com/channels/975754286384418847/976006081748807690/1201415305885397003
|
|
19
|
-
//
|
|
23
|
+
//y2 - live query refresh of view on table update
|
|
24
|
+
//y2 - livequery for findfirst (@JY)
|
|
20
25
|
|
|
26
|
+
/*y2 -
|
|
27
|
+
//y2 - allow api update only for new rows
|
|
28
|
+
@Fields.string<Category>({
|
|
29
|
+
allowApiUpdate: (c) => getEntityRef(c).isNew(),
|
|
30
|
+
})
|
|
31
|
+
Description = ""*/
|
|
32
|
+
//y2 - get backend methods to work when specifying types for date, and entities as poco's
|
|
33
|
+
//y2 - conside law-q db based on schema issue - I think that while running the dataProvider function, we should have a valid remult - maybe even have a valid remult, that will be valid until api is run
|
|
34
|
+
//y2 - #239 - (@JY) add a way to get from fieldMetadata back to entity repo (like we have in fieldRef)
|
|
35
|
+
//y2 - constraints (@JY)
|
|
36
|
+
// remult admin
|
|
21
37
|
//p1 - Small thing, I get SvelteKitError: Not found: /vite.svg
|
|
22
38
|
//p1 - understand the to many relation for the admin, based on the to one
|
|
23
39
|
//p1 - relation from order details to order gave a compound id info - and it is not true - same for the relation to product
|
|
24
|
-
//p1 - live query with include
|
|
25
40
|
//p1 - new row when there are relations, looks funny (see product)
|
|
26
41
|
//p1 - the + row in the bottom should extend to the full width
|
|
42
|
+
//p1 - need a way to extract the fields from the relation - for generating relation based sql
|
|
43
|
+
//p1 - allow conditional admin - like allowed
|
|
44
|
+
//p1 - remult-admin doesn't handle primary key that has compound column
|
|
45
|
+
//p1 - remult-admin didn't show a update for a table with a uniqua that is numeric
|
|
46
|
+
//remult
|
|
47
|
+
//p1 - better support union types (status, etc... so it'll work also in filter etc...)
|
|
48
|
+
//p1 - add tablename parameter for dbNamesOf
|
|
49
|
+
//p1 -check if count doesn't respect allow read (I think)
|
|
50
|
+
//p1 - when a tasks table exists in a different schema - we get many errors
|
|
51
|
+
//p1 - live query with include
|
|
27
52
|
//p1 - adjust angular tutorial starter kit for latest angular (as is in tutorial)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
53
|
+
//p2 - I think there should be a way to throw a forbidden exception
|
|
54
|
+
//p2 - processError in remult express
|
|
55
|
+
//p2 - allow find options preprocessor for api calls, to use for authorization
|
|
56
|
+
//p2 - when subscribe is forbidden - the query still runs after the renew process
|
|
57
|
+
//p2 - 'update tasks set where id = $1
|
|
58
|
+
//p1 - ValueListFieldType - the decorator gives an error in react vite project - see langulage yedidya
|
|
59
|
+
//p2 - type metadata.key - to keyof entity - based on cwis input
|
|
60
|
+
//y2 - remove __dirname from tutorials
|
|
61
|
+
//p2 - when value changes for field with error, clear error - so the user will feel comfortable
|
|
62
|
+
//p2 - allowApiUpdate should be false for include in api false
|
|
63
|
+
//docs
|
|
64
|
+
//p2 - make sure that internal members do not appear in the documentation - try running it on the d.ts instead of the code itself.
|
|
65
|
+
//------
|
|
34
66
|
//y2 - from the crm-demo(https://crm-demo.up.railway.app/deals), after editing a deal: - _updateEntityBasedOnApi
|
|
35
67
|
//y2 - Backend methods are transactions, it's not intuitive and maybe should be optional / opt in
|
|
36
68
|
//y2 - how to run a transaction as a user
|
|
37
69
|
//p2 - enum column
|
|
38
|
-
//p2 - I think there should be a way to throw a forbidden exception
|
|
39
|
-
//y2 - get backend methods to work when specifying types for date, and entities as poco's
|
|
40
|
-
//y2 - required
|
|
41
|
-
//y2 - reconsider validators
|
|
42
70
|
//y2 - message for invalid value
|
|
43
71
|
//y2 - message for relation that is missing
|
|
44
|
-
//p2 - more column types
|
|
45
|
-
//p2 - processError in remult express
|
|
46
|
-
//p2 - allow find options preprocessor for api calls, to use for authorization
|
|
47
|
-
//p2 - write doc about controlling updates of rows...
|
|
48
|
-
//p1 - need a way to extract the fields from the relation - for generating relation based sql
|
|
49
|
-
//p1 - when doing this, work on graphql relation with fields not just field
|
|
50
|
-
//p2 - add docs for apply changes
|
|
51
|
-
//p2 - when subscribe is forbidden - the query still runs after the renew process
|
|
52
|
-
//p2 - 'update tasks set where id = $1
|
|
53
72
|
//y2 - consider multi tenancies
|
|
54
73
|
//y2 - allow api read to also support instance and filter. - problem with promise
|
|
55
|
-
//p2 -
|
|
56
|
-
/**
|
|
57
|
-
* //p2 - doc this
|
|
58
|
-
remult.apiClient.httpClient = (
|
|
59
|
-
input: RequestInfo | URL,
|
|
60
|
-
init?: RequestInit
|
|
61
|
-
) => {
|
|
62
|
-
return fetch(input, {
|
|
63
|
-
...init,
|
|
64
|
-
headers: {
|
|
65
|
-
authorization: 'Bearer ' + sessionStorage.sessionId,
|
|
66
|
-
},
|
|
67
|
-
cache: 'no-store',
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
&&
|
|
72
|
-
remult.apiClient.url='localhost:3007/api
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
*/
|
|
76
|
-
//p1 - allow conditional admin - like allowed
|
|
77
|
-
//p1 - remult-admin doesn't handle primary key that has compound column
|
|
78
|
-
//p1 - remult-admin didn't show a update for a table with a uniqua that is numeric
|
|
79
|
-
//p1 -check if checouknt doesn't respect allow read (I think)
|
|
80
|
-
//p1 - when a tasks table exists in a different schema - we get many errors
|
|
81
|
-
//p1 - allow api update only for new rows
|
|
82
|
-
//p1 - getFields didn't work for kobi in the home component
|
|
83
|
-
//p1 - add to docs https://codesandbox.io/p/devbox/demo-sql-expression-wqvj4g
|
|
84
|
-
//p1 - ValueListFieldType - the decorator gives an error in react vite project - see langulage yedidya
|
|
85
|
-
//y1 - talk about export api is not possible in +server - how did we face that in others, and how do we doc it
|
|
74
|
+
//p2 - more column types
|
|
86
75
|
//p2 - document validators
|
|
87
76
|
//p2 - and validators to reference
|
|
88
77
|
//y2 - discuss a default date formatter
|
|
89
78
|
//y2 - add some api testing framework for user unit tests (will help with codesandbox based discussions)
|
|
90
|
-
//y2 - live query refresh of view on table update
|
|
91
|
-
//y2 - livequery for findfirst (@JY)
|
|
92
|
-
//y2 - #239 - (@JY) add a way to get from fieldMetadata back to entity repo (like we have in fieldRef)
|
|
93
|
-
//p2 - constraints (@JY)
|
|
94
|
-
//p2 - type metadata.key - to keyof entity - based on cwis input
|
|
95
|
-
//y2 - remove __dirname from tutorials
|
|
96
|
-
//p2 - when value changes for field with error, clear error - so the user will feel comfortable
|
|
97
|
-
//p2 - conside law-q db based on schema issue - I think that while running the dataProvider function, we should have a valid remult - maybe even have a valid remult, that will be valid until api is run
|
|
98
|
-
//y2 - we should really reconsider allow null by default to be true - I think that what we're doing is confusing for most . In my case I added a volunteer relation, and didn't set any value to it - and I get an error that null value is not allowed for it
|
|
99
|
-
//p1 - add a follow up doc to the tutorial on how to user a real user table for the users
|
|
100
|
-
/**
|
|
101
|
-
* our default is allow null false.
|
|
102
|
-
* for sql databases we add a db default of 0/''/false
|
|
103
|
-
* when we set a relation to null - we update the related field to null
|
|
104
|
-
* we do not enforce allow null false (mongo, json it'll get stored)
|
|
105
|
-
|
|
106
|
-
*/
|
|
107
|
-
//p2 - allowApiUpdate should be false for include in api false
|
|
108
79
|
//[ ] V2 - what to do about for relations count?
|
|
109
80
|
//[ ] V2 - condition? not to fetch if null etc....
|
|
110
81
|
//[ ] V3 - all these fields will also appear in the where etc... in the typescript api - but we will not enforce them
|
|
111
82
|
//y2 - consider if field types should include validation in them by default (string,number that it's not NaN etc...) and if so, what message?
|
|
112
83
|
//y2 - should enforce integer - currently we probably round / truncate it
|
|
113
84
|
//p1 - adjust react tutorial to esm
|
|
114
|
-
//p1 - write docs on with remult - and usage in init api
|
|
115
|
-
//p1 - document value converter - including field type in the db
|
|
116
|
-
/*y1 - discuss sql stuff:
|
|
117
|
-
https://discord.com/channels/975754286384418847/975754286384418852/1198997156678357043
|
|
118
|
-
https://codesandbox.io/p/devbox/demo-sql-expression-forked-f94p6m?file=%2Fsrc%2Findex.ts%3A21%2C3-21%2C12&workspaceId=c0f7eccf-c151-423b-ac8e-0f0f3e1ad350
|
|
119
|
-
https://codesandbox.io/p/devbox/demo-sql-expression-forked-c2jhyc?file=%2Fsrc%2Findex.ts%3A37%2C50&workspaceId=7e9d0505-ea79-46f1-8973-50cadde4fa96
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
*/
|
|
123
|
-
//p1 - make dbNames of have a default wrapIdentifier based on the current data provider
|
|
124
|
-
//p1 - prevent circular in sql expression
|
package/esm/src/sort.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { CompoundIdField } from './CompoundIdField.js';
|
|
2
2
|
import { getRelationFieldInfo } from './remult3/relationInfoMember.js';
|
|
3
|
+
/**
|
|
4
|
+
* The `Sort` class is used to describe sorting criteria for queries. It is mainly used internally,
|
|
5
|
+
* but it provides a few useful functions for working with sorting.
|
|
6
|
+
*/
|
|
3
7
|
export class Sort {
|
|
8
|
+
/**
|
|
9
|
+
* Translates the current `Sort` instance into an `EntityOrderBy` object.
|
|
10
|
+
*
|
|
11
|
+
* @returns {EntityOrderBy<any>} An `EntityOrderBy` object representing the sort criteria.
|
|
12
|
+
*/
|
|
4
13
|
toEntityOrderBy() {
|
|
5
14
|
let result = {};
|
|
6
15
|
for (const seg of this.Segments) {
|
|
@@ -12,10 +21,25 @@ export class Sort {
|
|
|
12
21
|
}
|
|
13
22
|
return result;
|
|
14
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Constructs a `Sort` instance with the provided sort segments.
|
|
26
|
+
*
|
|
27
|
+
* @param {...SortSegment[]} segments The sort segments to be included in the sort criteria.
|
|
28
|
+
*/
|
|
15
29
|
constructor(...segments) {
|
|
16
30
|
this.Segments = segments;
|
|
17
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* The segments of the sort criteria.
|
|
34
|
+
*
|
|
35
|
+
* @type {SortSegment[]}
|
|
36
|
+
*/
|
|
18
37
|
Segments;
|
|
38
|
+
/**
|
|
39
|
+
* Reverses the sort order of the current sort criteria.
|
|
40
|
+
*
|
|
41
|
+
* @returns {Sort} A new `Sort` instance with the reversed sort order.
|
|
42
|
+
*/
|
|
19
43
|
reverse() {
|
|
20
44
|
let r = new Sort();
|
|
21
45
|
for (const s of this.Segments) {
|
|
@@ -23,6 +47,14 @@ export class Sort {
|
|
|
23
47
|
}
|
|
24
48
|
return r;
|
|
25
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Compares two objects based on the current sort criteria.
|
|
52
|
+
*
|
|
53
|
+
* @param {any} a The first object to compare.
|
|
54
|
+
* @param {any} b The second object to compare.
|
|
55
|
+
* @param {function(FieldMetadata): string} [getFieldKey] An optional function to get the field key for comparison.
|
|
56
|
+
* @returns {number} A negative value if `a` should come before `b`, a positive value if `a` should come after `b`, or zero if they are equal.
|
|
57
|
+
*/
|
|
26
58
|
compare(a, b, getFieldKey) {
|
|
27
59
|
if (!getFieldKey)
|
|
28
60
|
getFieldKey = (x) => x.key;
|
|
@@ -43,6 +75,14 @@ export class Sort {
|
|
|
43
75
|
}
|
|
44
76
|
return r;
|
|
45
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Translates an `EntityOrderBy` to a `Sort` instance.
|
|
80
|
+
*
|
|
81
|
+
* @template T The entity type for the order by.
|
|
82
|
+
* @param {EntityMetadata<T>} entityDefs The metadata of the entity associated with the order by.
|
|
83
|
+
* @param {EntityOrderBy<T>} [orderBy] The `EntityOrderBy` to be translated.
|
|
84
|
+
* @returns {Sort} A `Sort` instance representing the translated order by.
|
|
85
|
+
*/
|
|
46
86
|
static translateOrderByToSort(entityDefs, orderBy) {
|
|
47
87
|
if (!orderBy)
|
|
48
88
|
return undefined;
|
|
@@ -86,6 +126,15 @@ export class Sort {
|
|
|
86
126
|
}
|
|
87
127
|
return sort;
|
|
88
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Creates a unique `Sort` instance based on the provided `Sort` and the entity metadata.
|
|
131
|
+
* This ensures that the sort criteria result in a unique ordering of entities.
|
|
132
|
+
*
|
|
133
|
+
* @template T The entity type for the sort.
|
|
134
|
+
* @param {EntityMetadata<T>} entityMetadata The metadata of the entity associated with the sort.
|
|
135
|
+
* @param {Sort} [orderBy] The `Sort` instance to be made unique.
|
|
136
|
+
* @returns {Sort} A `Sort` instance representing the unique sort criteria.
|
|
137
|
+
*/
|
|
89
138
|
static createUniqueSort(entityMetadata, orderBy) {
|
|
90
139
|
if ((!orderBy || Object.keys(orderBy).length === 0) &&
|
|
91
140
|
entityMetadata.options.defaultOrderBy)
|
|
@@ -104,6 +153,15 @@ export class Sort {
|
|
|
104
153
|
}
|
|
105
154
|
return orderBy;
|
|
106
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Creates a unique `EntityOrderBy` based on the provided `EntityOrderBy` and the entity metadata.
|
|
158
|
+
* This ensures that the order by criteria result in a unique ordering of entities.
|
|
159
|
+
*
|
|
160
|
+
* @template T The entity type for the order by.
|
|
161
|
+
* @param {EntityMetadata<T>} entityMetadata The metadata of the entity associated with the order by.
|
|
162
|
+
* @param {EntityOrderBy<T>} [orderBy] The `EntityOrderBy` to be made unique.
|
|
163
|
+
* @returns {EntityOrderBy<T>} An `EntityOrderBy` representing the unique order by criteria.
|
|
164
|
+
*/
|
|
107
165
|
static createUniqueEntityOrderBy(entityMetadata, orderBy) {
|
|
108
166
|
if (!orderBy || Object.keys(orderBy).length === 0)
|
|
109
167
|
orderBy = entityMetadata.options.defaultOrderBy;
|
package/package.json
CHANGED
|
@@ -74,7 +74,7 @@ export interface RemultServer<RequestType> extends RemultServerCore<RequestType>
|
|
|
74
74
|
withRemultAsync<T>(request: RequestType | undefined, what: () => Promise<T>): Promise<T>;
|
|
75
75
|
}
|
|
76
76
|
export interface RemultServerCore<RequestType> {
|
|
77
|
-
getRemult(req
|
|
77
|
+
getRemult(req?: RequestType): Promise<Remult>;
|
|
78
78
|
openApiDoc(options: {
|
|
79
79
|
title: string;
|
|
80
80
|
version?: string;
|
package/server/expressBridge.js
CHANGED
|
@@ -597,13 +597,19 @@ var RemultServerImplementation = /** @class */ (function () {
|
|
|
597
597
|
var _this = this;
|
|
598
598
|
return tslib_1.__generator(this, function (_a) {
|
|
599
599
|
switch (_a.label) {
|
|
600
|
-
case 0:
|
|
600
|
+
case 0:
|
|
601
|
+
if (!!req) return [3 /*break*/, 2];
|
|
602
|
+
return [4 /*yield*/, this.runWithRemult(function (c) { return tslib_1.__awaiter(_this, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) {
|
|
603
|
+
return [2 /*return*/, (remult = c)];
|
|
604
|
+
}); }); })];
|
|
605
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
606
|
+
case 2: return [4 /*yield*/, this.process(function (c) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
|
601
607
|
return tslib_1.__generator(this, function (_a) {
|
|
602
608
|
remult = c;
|
|
603
609
|
return [2 /*return*/];
|
|
604
610
|
});
|
|
605
611
|
}); })(req, undefined)];
|
|
606
|
-
case
|
|
612
|
+
case 3:
|
|
607
613
|
_a.sent();
|
|
608
614
|
return [2 /*return*/, remult];
|
|
609
615
|
}
|
|
@@ -165,15 +165,100 @@ export interface FieldMetadata<valueType = any, entityType = any> {
|
|
|
165
165
|
*/
|
|
166
166
|
fromInput(inputValue: string, inputType?: string): valueType;
|
|
167
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Interface for converting values between different formats, such as in-memory objects, database storage,
|
|
170
|
+
* JSON data transfer objects (DTOs), and HTML input elements.
|
|
171
|
+
*
|
|
172
|
+
* @template valueType The type of the value that the converter handles.
|
|
173
|
+
*/
|
|
168
174
|
export interface ValueConverter<valueType> {
|
|
175
|
+
/**
|
|
176
|
+
* Converts a value from a JSON DTO to the valueType. This method is typically used when receiving data
|
|
177
|
+
* from a REST API call or deserializing a JSON payload.
|
|
178
|
+
*
|
|
179
|
+
* @param val The value to convert.
|
|
180
|
+
* @returns The converted value.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* fromJson: val => new Date(val)
|
|
184
|
+
*/
|
|
169
185
|
fromJson?(val: any): valueType;
|
|
186
|
+
/**
|
|
187
|
+
* Converts a value of valueType to a JSON DTO. This method is typically used when sending data
|
|
188
|
+
* to a REST API or serializing an object to a JSON payload.
|
|
189
|
+
*
|
|
190
|
+
* @param val The value to convert.
|
|
191
|
+
* @returns The converted value.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* toJson: val => val?.toISOString()
|
|
195
|
+
*/
|
|
170
196
|
toJson?(val: valueType): any;
|
|
197
|
+
/**
|
|
198
|
+
* Converts a value from the database format to the valueType.
|
|
199
|
+
*
|
|
200
|
+
* @param val The value to convert.
|
|
201
|
+
* @returns The converted value.
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* fromDb: val => new Date(val)
|
|
205
|
+
*/
|
|
171
206
|
fromDb?(val: any): valueType;
|
|
207
|
+
/**
|
|
208
|
+
* Converts a value of valueType to the database format.
|
|
209
|
+
*
|
|
210
|
+
* @param val The value to convert.
|
|
211
|
+
* @returns The converted value.
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* toDb: val => val?.toISOString()
|
|
215
|
+
*/
|
|
172
216
|
toDb?(val: valueType): any;
|
|
217
|
+
/**
|
|
218
|
+
* Converts a value of valueType to a string suitable for an HTML input element.
|
|
219
|
+
*
|
|
220
|
+
* @param val The value to convert.
|
|
221
|
+
* @param inputType The type of the input element (optional).
|
|
222
|
+
* @returns The converted value as a string.
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* toInput: (val, inputType) => val?.toISOString().substring(0, 10)
|
|
226
|
+
*/
|
|
173
227
|
toInput?(val: valueType, inputType?: string): string;
|
|
228
|
+
/**
|
|
229
|
+
* Converts a string from an HTML input element to the valueType.
|
|
230
|
+
*
|
|
231
|
+
* @param val The value to convert.
|
|
232
|
+
* @param inputType The type of the input element (optional).
|
|
233
|
+
* @returns The converted value.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* fromInput: (val, inputType) => new Date(val)
|
|
237
|
+
*/
|
|
174
238
|
fromInput?(val: string, inputType?: string): valueType;
|
|
239
|
+
/**
|
|
240
|
+
* Returns a displayable string representation of a value of valueType.
|
|
241
|
+
*
|
|
242
|
+
* @param val The value to convert.
|
|
243
|
+
* @returns The displayable string.
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* displayValue: val => val?.toLocaleDateString()
|
|
247
|
+
*/
|
|
175
248
|
displayValue?(val: valueType): string;
|
|
249
|
+
/**
|
|
250
|
+
* Specifies the storage type used in the database for this field.
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* readonly fieldTypeInDb = 'decimal(18,2)';
|
|
254
|
+
*/
|
|
176
255
|
readonly fieldTypeInDb?: string;
|
|
256
|
+
/**
|
|
257
|
+
* Specifies the type of HTML input element suitable for values of valueType.
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* readonly inputType = 'date';
|
|
261
|
+
*/
|
|
177
262
|
readonly inputType?: string;
|
|
178
263
|
}
|
|
179
264
|
export declare type FieldValidator<entityType = any, valueType = any> = (entity: entityType, event: ValidateFieldEvent<entityType, valueType>) => boolean | string | void | undefined | Promise<boolean | string | void | undefined>;
|
package/src/context.d.ts
CHANGED
|
@@ -76,19 +76,76 @@ export declare class Remult {
|
|
|
76
76
|
export type GetArguments<T> = T extends (...args: infer FirstArgument) => any ? FirstArgument : never;
|
|
77
77
|
export interface RemultContext {
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Interface for configuring the API client used by Remult to perform HTTP calls to the backend.
|
|
81
|
+
*/
|
|
79
82
|
export interface ApiClient {
|
|
80
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* The HTTP client to use when making API calls. It can be set to a function with the `fetch` signature
|
|
85
|
+
* or an object that has `post`, `put`, `delete`, and `get` methods. This can also be used to inject
|
|
86
|
+
* logic before each HTTP call, such as adding authorization headers.
|
|
87
|
+
*
|
|
81
88
|
* @example
|
|
89
|
+
* // Using Axios
|
|
82
90
|
* remult.apiClient.httpClient = axios;
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* // Using Angular HttpClient
|
|
94
|
+
* remult.apiClient.httpClient = httpClient;
|
|
95
|
+
*
|
|
83
96
|
* @example
|
|
84
|
-
*
|
|
97
|
+
* // Using fetch (default)
|
|
98
|
+
* remult.apiClient.httpClient = fetch;
|
|
99
|
+
*
|
|
85
100
|
* @example
|
|
86
|
-
*
|
|
101
|
+
* // Adding bearer token authorization
|
|
102
|
+
* remult.apiClient.httpClient = (input: RequestInfo | URL, init?: RequestInit) => {
|
|
103
|
+
* return fetch(input, {
|
|
104
|
+
* ...init,
|
|
105
|
+
* headers: {
|
|
106
|
+
* authorization: 'Bearer ' + sessionStorage.sessionId,
|
|
107
|
+
* },
|
|
108
|
+
* cache: 'no-store',
|
|
109
|
+
* });
|
|
110
|
+
* };
|
|
87
111
|
*/
|
|
88
112
|
httpClient?: ExternalHttpProvider | typeof fetch;
|
|
89
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* The base URL for making API calls. By default, it is set to '/api'. It can be modified to be relative
|
|
115
|
+
* or to use a different domain for the server.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* // Relative URL
|
|
119
|
+
* remult.apiClient.url = './api';
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* // Different domain
|
|
123
|
+
* remult.apiClient.url = 'https://example.com/api';
|
|
124
|
+
*/
|
|
90
125
|
url?: string;
|
|
126
|
+
/**
|
|
127
|
+
* The subscription client used for real-time data updates. By default, it is set to use Server-Sent Events (SSE).
|
|
128
|
+
* It can be set to any subscription provider as illustrated in the Remult tutorial for deploying to a serverless environment.
|
|
129
|
+
*
|
|
130
|
+
* @see https://remult.dev/tutorials/react-next/deployment.html#deploying-to-a-serverless-environment
|
|
131
|
+
*/
|
|
91
132
|
subscriptionClient?: SubscriptionClient;
|
|
133
|
+
/**
|
|
134
|
+
* A function that wraps message handling for subscriptions. This is useful for executing some code before
|
|
135
|
+
* or after any message arrives from the subscription. For example, in Angular, this can be used to trigger
|
|
136
|
+
* a render by calling the `NgZone` run method.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* // Angular example
|
|
140
|
+
* import { Component, NgZone } from '@angular/core';
|
|
141
|
+
* import { remult } from "remult";
|
|
142
|
+
*
|
|
143
|
+
* export class AppComponent {
|
|
144
|
+
* constructor(zone: NgZone) {
|
|
145
|
+
* remult.apiClient.wrapMessageHandling = handler => zone.run(() => handler());
|
|
146
|
+
* }
|
|
147
|
+
* }
|
|
148
|
+
*/
|
|
92
149
|
wrapMessageHandling?: (x: VoidFunction) => void;
|
|
93
150
|
}
|
|
94
151
|
export interface ControllerOptions {
|