remult 0.18.1-exp.25 → 0.18.1-exp.26

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/graphql.js CHANGED
@@ -1,17 +1,24 @@
1
1
  import { Remult } from ".";
2
2
  import { allEntities } from "./src/context";
3
- import { getEntityKey } from "./src/remult3";
4
3
  import { DataApi } from "./src/data-api";
5
4
  export function remultGraphql(api) {
6
5
  let r = new Remult();
7
- let types = '';
6
+ let types = [];
7
+ let filterTypes = '';
8
8
  let query = '';
9
9
  let root = {};
10
+ function getType(key) {
11
+ let t = types.find(t => t.key === key);
12
+ if (!t)
13
+ types.push((t = ({ fields: "", key, moreFields: "", resultProcessors: [] })));
14
+ return t;
15
+ }
10
16
  for (const e of allEntities) {
11
17
  let meta = r.repo(e).metadata;
12
- let fields = '';
13
18
  let filterFields = '';
14
- let key = getEntityKey(e);
19
+ let key = meta.key;
20
+ const t = getType(key);
21
+ let q = "\n\t" + key + "(options: options, filter:" + key + "Filter): [" + key + "]";
15
22
  if (key) {
16
23
  for (const f of meta.fields) {
17
24
  {
@@ -19,6 +26,7 @@ export function remultGraphql(api) {
19
26
  switch (f.valueType) {
20
27
  case Boolean:
21
28
  type = "Boolean";
29
+ break;
22
30
  case Number:
23
31
  {
24
32
  if (f.valueConverter?.fieldTypeInDb == 'integer')
@@ -26,8 +34,31 @@ export function remultGraphql(api) {
26
34
  else
27
35
  type = "Float";
28
36
  }
37
+ break;
38
+ }
39
+ if (allEntities.includes(f.valueType)) {
40
+ const refKey = r.repo(f.valueType).metadata.key;
41
+ t.fields += "\n\t" + f.key + ":" + refKey;
42
+ t.resultProcessors.push(r => {
43
+ const val = r[f.key];
44
+ r[f.key] = async (args, req, gqlInfo) => {
45
+ const queryResult = await root[refKey]({ ...args.filter, filter: { id: val }, options: { limit: 1 } }, req, gqlInfo);
46
+ if (queryResult.length > 0)
47
+ return queryResult[0];
48
+ return null;
49
+ };
50
+ });
51
+ let refT = getType(refKey);
52
+ refT.moreFields += q;
53
+ refT.resultProcessors.push(r => {
54
+ const val = r.id;
55
+ r[key] = async (args, req, gqlInfo) => {
56
+ return await root[key]({ filter: { ...args.filter, [f.key]: val }, options: args.options }, req, gqlInfo);
57
+ };
58
+ });
29
59
  }
30
- fields += "\n\t" + f.key + ":" + type;
60
+ else
61
+ t.fields += "\n\t" + f.key + ":" + type;
31
62
  for (const operator of ["", "_ne"]) {
32
63
  filterFields += "\n\t" + f.key + operator + ":" + type;
33
64
  }
@@ -44,41 +75,49 @@ export function remultGraphql(api) {
44
75
  filterFields += "\n\t" + f.key + "_in:[" + type + "]";
45
76
  }
46
77
  }
47
- types += "type " + key + "{" + fields + "\n}\n";
48
- types += "input " + key + "Filter{" + filterFields + "\n\tOR:[" + key + "Filter]\n}\n";
49
- query += "\n\t" + key + "(options: options, filter:" + key + "Filter): [" + key + "]";
50
- root[key] = async ({ options, filter }, req) => {
51
- let remult = await api.getRemult(req);
52
- let repo = remult.repo(e);
53
- let dApi = new DataApi(repo, remult);
54
- let result;
55
- let err;
56
- await dApi.getArray({
57
- success: x => result = x,
58
- created: undefined,
59
- deleted: undefined,
60
- error: x => err = x,
61
- forbidden: () => err = 'forbidden',
62
- notFound: () => err = 'not found',
63
- progress: undefined
64
- }, {
65
- get: key => {
66
- if (options)
67
- switch (key) {
68
- case "_limit":
69
- return options.limit;
70
- case "_page":
71
- return options.page;
72
- case "_sort":
73
- return options.sort;
74
- case "_order":
75
- return options.order;
78
+ filterTypes += "input " + key + "Filter{" + filterFields + "\n\tOR:[" + key + "Filter]\n}\n";
79
+ query += q;
80
+ root[key] = async ({ options, filter }, req, a) => {
81
+ return new Promise(async (res, error) => {
82
+ api.withRemult(req, undefined, async () => {
83
+ let remult = await api.getRemult(req);
84
+ let repo = remult.repo(e);
85
+ let dApi = new DataApi(repo, remult);
86
+ let result;
87
+ let err;
88
+ await dApi.getArray({
89
+ success: x => result = x.map(y => {
90
+ t.resultProcessors.forEach(z => z(y));
91
+ return y;
92
+ }),
93
+ created: undefined,
94
+ deleted: undefined,
95
+ error: x => err = x,
96
+ forbidden: () => err = 'forbidden',
97
+ notFound: () => err = 'not found',
98
+ progress: undefined
99
+ }, {
100
+ get: key => {
101
+ if (options)
102
+ switch (key) {
103
+ case "_limit":
104
+ return options.limit;
105
+ case "_page":
106
+ return options.page;
107
+ case "_sort":
108
+ return options.sort;
109
+ case "_order":
110
+ return options.order;
111
+ }
76
112
  }
77
- }
78
- }, filter);
79
- if (err)
80
- throw err;
81
- return result;
113
+ }, filter);
114
+ if (err) {
115
+ error(err);
116
+ return;
117
+ }
118
+ res(result);
119
+ });
120
+ });
82
121
  };
83
122
  }
84
123
  }
@@ -88,15 +127,15 @@ export function remultGraphql(api) {
88
127
  }
89
128
  return {
90
129
  rootValue: root,
91
- schema: `
130
+ schema: `${types.map(({ key, fields, moreFields }) => "type " + key + "{" + fields + moreFields + "\n}\n").join('')}
131
+ ${query}
132
+ ${filterTypes}
92
133
  input options{
93
134
  limit:Int
94
135
  page:Int
95
136
  sort:String
96
137
  order:String
97
138
  }
98
- ${types}
99
- ${query}
100
139
  `
101
140
  };
102
141
  }
@@ -7,10 +7,13 @@ export class AblySubscriptionClient {
7
7
  close: () => {
8
8
  this.ably.connection.close();
9
9
  },
10
- subscribe: (channel, handler) => {
10
+ subscribe: async (channel, handler) => {
11
11
  let myHandler = (y) => handler(y.data);
12
- this.ably.channels.get(channel).subscribe(y => myHandler(y));
13
- return () => { myHandler = () => { }; };
12
+ await this.ably.channels.get(channel).subscribe(y => myHandler(y));
13
+ return () => {
14
+ myHandler = () => { };
15
+ this.ably.channels.get(channel).unsubscribe();
16
+ };
14
17
  }
15
18
  };
16
19
  }
@@ -19,7 +19,7 @@ export class Remult {
19
19
  itemChanged: () => { }
20
20
  };
21
21
  /* @internal*/
22
- this.liveQuerySubscriber = new LiveQueryClient(() => this.apiClient);
22
+ this.liveQuerySubscriber = new LiveQueryClient(() => this.apiClient, () => this.user?.id);
23
23
  /** context information that can be used to store custom information that will be disposed as part of the `remult` object */
24
24
  this.context = {};
25
25
  /** The api client that will be used by `remult` to perform calls to the `api` */
@@ -15,7 +15,7 @@ export declare class DataApi<T = any> {
15
15
  findOptions: FindOptions<T>;
16
16
  }>;
17
17
  getArray(response: DataApiResponse, request: DataApiRequest, filterBody?: any): Promise<void>;
18
- liveQuery(response: DataApiResponse, request: DataApiRequest, filterBody: any, serializeRequest: () => any): Promise<void>;
18
+ liveQuery(response: DataApiResponse, request: DataApiRequest, filterBody: any, serializeRequest: () => any, queryChannel: string): Promise<void>;
19
19
  private buildWhere;
20
20
  private doOnId;
21
21
  put(response: DataApiResponse, id: any, body: any): Promise<void>;
@@ -36,3 +36,4 @@ export interface DataApiRequest {
36
36
  }
37
37
  export declare function determineSort(sortUrlParm: string, dirUrlParam: string): any;
38
38
  export declare function serializeError(data: ErrorInfo): ErrorInfo<any>;
39
+ export declare function getLiveQueryChannel(queryId: string, userId: string): string;
@@ -1,10 +1,9 @@
1
- import { v4 as uuid } from 'uuid';
2
1
  import { customUrlToken, buildFilterFromRequestParameters } from './filter/filter-interfaces';
3
2
  import { doTransaction } from './context';
4
3
  import { Filter } from './filter/filter-interfaces';
5
4
  import { ForbiddenError } from './server-action';
6
5
  import { getId } from './remult3/getId';
7
- import { findOptionsToJson } from './data-providers/rest-data-provider';
6
+ import { findOptionsToJson, liveQueryAction } from './data-providers/rest-data-provider';
8
7
  export class DataApi {
9
8
  constructor(repository, remult) {
10
9
  this.repository = repository;
@@ -12,9 +11,9 @@ export class DataApi {
12
11
  }
13
12
  httpGet(res, req, serializeRequest) {
14
13
  const action = req?.get("__action");
14
+ if (action?.startsWith(liveQueryAction))
15
+ return this.liveQuery(res, req, undefined, serializeRequest, getLiveQueryChannel(action.substring(liveQueryAction.length), this.remult.user?.id));
15
16
  switch (action) {
16
- case "liveQuery":
17
- return this.liveQuery(res, req, undefined, serializeRequest);
18
17
  case "get":
19
18
  case "count":
20
19
  return this.count(res, req, undefined);
@@ -23,15 +22,15 @@ export class DataApi {
23
22
  }
24
23
  httpPost(res, req, body, serializeRequest) {
25
24
  const action = req?.get("__action");
25
+ if (action?.startsWith(liveQueryAction))
26
+ return this.liveQuery(res, req, undefined, serializeRequest, getLiveQueryChannel(action.substring(liveQueryAction.length), this.remult.user?.id));
26
27
  switch (action) {
27
- case "liveQuery":
28
- return this.liveQuery(res, req, body, serializeRequest);
29
28
  case "get":
30
29
  return this.getArray(res, req, body);
31
30
  case "count":
32
31
  return this.count(res, req, body);
33
32
  case "endLiveQuery":
34
- this.remult.liveQueryStorage.remove(body.id);
33
+ this.remult.liveQueryStorage.remove(getLiveQueryChannel(body.id, this.remult.user?.id));
35
34
  res.success("ok");
36
35
  return;
37
36
  default:
@@ -122,14 +121,13 @@ export class DataApi {
122
121
  response.error(err);
123
122
  }
124
123
  }
125
- async liveQuery(response, request, filterBody, serializeRequest) {
124
+ async liveQuery(response, request, filterBody, serializeRequest, queryChannel) {
126
125
  if (!this.repository.metadata.apiReadAllowed) {
127
126
  response.forbidden();
128
127
  return;
129
128
  }
130
129
  try {
131
130
  const r = await this.getArrayImpl(response, request, filterBody);
132
- const queryChannel = `users:${this.remult.user?.id}:queries:${uuid()}`;
133
131
  const data = {
134
132
  requestJson: serializeRequest(),
135
133
  findOptionsJson: findOptionsToJson(r.findOptions, this.repository.metadata),
@@ -140,10 +138,7 @@ export class DataApi {
140
138
  id: queryChannel,
141
139
  data
142
140
  });
143
- response.success({
144
- queryChannel,
145
- result: r.r
146
- });
141
+ response.success(r.r);
147
142
  }
148
143
  catch (err) {
149
144
  if (err.isForbiddenError)
@@ -271,3 +266,6 @@ export function serializeError(data) {
271
266
  data = { message: x };
272
267
  return data;
273
268
  }
269
+ export function getLiveQueryChannel(queryId, userId) {
270
+ return `users:${userId}:queries:${queryId}`;
271
+ }
@@ -36,3 +36,4 @@ export declare class RestDataProviderHttpProviderUsingFetch implements RestDataP
36
36
  }): Promise<any>;
37
37
  }
38
38
  export declare function addFilterToUrlAndReturnTrueIfSuccessful(filter: any, url: UrlBuilder): boolean;
39
+ export declare const liveQueryAction = "liveQuery|";
@@ -117,13 +117,13 @@ export class RestEntityDataProvider {
117
117
  return {
118
118
  createKey: () => JSON.stringify({ url, filterObject }),
119
119
  run,
120
- subscribe: async () => {
121
- const result = await run("liveQuery");
120
+ subscribe: async (queryId) => {
121
+ const result = await run(liveQueryAction + queryId);
122
122
  return {
123
- ...result,
123
+ result,
124
124
  unsubscribe: async () => {
125
125
  return actionInfo.runActionWithoutBlockingUI(() => this.http().post(this.url() + "?__action=endLiveQuery", {
126
- id: result.queryChannel
126
+ id: queryId
127
127
  }));
128
128
  }
129
129
  };
@@ -239,3 +239,4 @@ export function addFilterToUrlAndReturnTrueIfSuccessful(filter, url) {
239
239
  }
240
240
  return true;
241
241
  }
242
+ export const liveQueryAction = "liveQuery|";
@@ -3,11 +3,11 @@ import { buildRestDataProvider } from "../buildRestDataProvider";
3
3
  import { LiveQuerySubscriber, liveQueryKeepAliveRoute } from './SubscriptionChannel';
4
4
  /* @internal*/
5
5
  export class LiveQueryClient {
6
- constructor(apiProvider) {
6
+ constructor(apiProvider, getUserId) {
7
7
  this.apiProvider = apiProvider;
8
+ this.getUserId = getUserId;
8
9
  this.queries = new Map();
9
10
  this.channels = new Map();
10
- this.timeoutToCloseWhenNotClosed = 1000;
11
11
  }
12
12
  wrapMessageHandling(handleMessage) {
13
13
  var x = this.apiProvider().wrapMessageHandling;
@@ -25,23 +25,22 @@ export class LiveQueryClient {
25
25
  this.channels.clear();
26
26
  this.closeIfNoListeners();
27
27
  }
28
- subscribeChannel(key, onResult) {
28
+ async subscribeChannel(key, onResult) {
29
29
  let onUnsubscribe = () => { };
30
- this.openIfNoOpened().then(() => {
30
+ const client = await this.openIfNoOpened();
31
+ try {
31
32
  let q = this.channels.get(key);
32
33
  if (!q) {
33
34
  this.channels.set(key, q = new MessageChannel());
34
- this.client.then(c => {
35
- try {
36
- q.unsubscribe = c.subscribe(key, value => this.wrapMessageHandling(() => q.handle(value)), err => {
37
- onResult.error(err);
38
- });
39
- }
40
- catch (err) {
35
+ try {
36
+ q.unsubscribe = await client.subscribe(key, value => this.wrapMessageHandling(() => q.handle(value)), err => {
41
37
  onResult.error(err);
42
- throw err;
43
- }
44
- });
38
+ });
39
+ }
40
+ catch (err) {
41
+ onResult.error(err);
42
+ throw err;
43
+ }
45
44
  }
46
45
  q.listeners.push(onResult);
47
46
  onUnsubscribe = () => {
@@ -52,67 +51,73 @@ export class LiveQueryClient {
52
51
  }
53
52
  this.closeIfNoListeners();
54
53
  };
55
- });
54
+ }
55
+ catch (err) {
56
+ onResult.error(err);
57
+ throw err;
58
+ }
56
59
  return () => {
57
60
  onUnsubscribe();
58
61
  onUnsubscribe = () => { };
59
62
  };
60
63
  }
61
64
  closeIfNoListeners() {
62
- this.runPromise(new Promise((res) => {
63
- setTimeout(() => {
64
- if (this.client)
65
- if (this.queries.size === 0 && this.channels.size === 0) {
66
- this.runPromise(this.client.then(x => x.close()));
67
- this.client = undefined;
68
- clearInterval(this.interval);
69
- this.interval = undefined;
70
- }
71
- res({});
72
- }, this.timeoutToCloseWhenNotClosed);
73
- }));
65
+ if (this.client)
66
+ if (this.queries.size === 0 && this.channels.size === 0) {
67
+ this.runPromise(this.client.then(x => x.close()));
68
+ this.client = undefined;
69
+ clearInterval(this.interval);
70
+ this.interval = undefined;
71
+ }
74
72
  }
75
- //TODO 1 - consider the time that may pass from the get request to the subscribe to the channel, in some cases this could mean, a call to server to get token and a call to the external provider - it may be some time
76
73
  subscribe(repo, options, listener) {
77
- let onUnsubscribe = () => { };
74
+ let alive = true;
75
+ let onUnsubscribe = () => { alive = false; };
78
76
  this.runPromise(repo.buildEntityDataProviderFindOptions(options)
79
- .then(opts => this.openIfNoOpened()
80
- .then(() => {
77
+ .then(opts => {
78
+ if (!alive)
79
+ return;
81
80
  const { createKey, subscribe } = new RestDataProvider(this.apiProvider).getEntityDataProvider(repo.metadata).buildFindRequest(opts);
82
81
  const eventTypeKey = createKey();
83
82
  let q = this.queries.get(eventTypeKey);
84
83
  if (!q) {
85
- this.queries.set(eventTypeKey, q = new LiveQuerySubscriber(repo, { entityKey: repo.metadata.key, orderBy: options.orderBy }));
84
+ this.queries.set(eventTypeKey, q = new LiveQuerySubscriber(repo, { entityKey: repo.metadata.key, orderBy: options.orderBy }, this.getUserId()));
86
85
  q.subscribeCode = () => {
87
86
  if (q.unsubscribe) {
88
87
  q.unsubscribe();
89
- //TODO 1- consider race scenario where unsubscribe is called before subscribe
90
88
  q.unsubscribe = () => { };
91
89
  }
92
- this.runPromise(subscribe()
93
- .then(r => {
94
- if (q.listeners.length === 0) {
95
- r.unsubscribe();
90
+ this.runPromise(this.subscribeChannel(q.queryChannel, {
91
+ next: (value) => this.runPromise(q.handle(value)),
92
+ complete: () => { },
93
+ error: er => {
94
+ q.listeners.forEach(l => l.error(er));
95
+ }
96
+ }).then(unsubscribeToChannel => {
97
+ if (q.listeners.length == 0) {
98
+ unsubscribeToChannel();
96
99
  return;
97
100
  }
98
- let unsubscribeToChannel = this.subscribeChannel(r.queryChannel, {
99
- next: (value) => this.runPromise(q.handle(value)),
100
- complete: () => { },
101
- error: er => {
102
- q.listeners.forEach(l => l.error(er));
101
+ this.runPromise(subscribe(q.id)
102
+ .then(r => {
103
+ if (q.listeners.length === 0) {
104
+ r.unsubscribe();
105
+ unsubscribeToChannel();
106
+ return;
103
107
  }
104
- });
105
- q.unsubscribe = () => {
106
- q.unsubscribe = () => { };
107
- unsubscribeToChannel();
108
- this.runPromise(r.unsubscribe());
109
- };
110
- this.runPromise(q.setAllItems(r.result));
111
- q.queryChannel = r.queryChannel;
112
- })
113
- .catch(err => {
114
- listener.error(err);
115
- }));
108
+ this.runPromise(q.setAllItems(r.result));
109
+ q.unsubscribe = () => {
110
+ q.unsubscribe = () => { };
111
+ unsubscribeToChannel();
112
+ this.runPromise(r.unsubscribe());
113
+ };
114
+ })
115
+ .catch(err => {
116
+ listener.error(err);
117
+ }));
118
+ })).catch(err => {
119
+ q.listeners.forEach(l => l.error(err));
120
+ });
116
121
  };
117
122
  q.subscribeCode();
118
123
  }
@@ -129,7 +134,7 @@ export class LiveQueryClient {
129
134
  }
130
135
  this.closeIfNoListeners();
131
136
  };
132
- }))
137
+ })
133
138
  .catch(err => {
134
139
  listener.error(err);
135
140
  }));
@@ -13,11 +13,11 @@ export class SseSubscriptionClient {
13
13
  close() {
14
14
  source.close();
15
15
  },
16
- subscribe(channel, handler) {
16
+ async subscribe(channel, handler) {
17
17
  let listeners = channels.get(channel);
18
18
  if (!listeners) {
19
19
  channels.set(channel, listeners = []);
20
- subscribeToChannel(channel);
20
+ await subscribeToChannel(channel);
21
21
  }
22
22
  listeners.push(handler);
23
23
  return () => {
@@ -69,8 +69,8 @@ export class SseSubscriptionClient {
69
69
  });
70
70
  }
71
71
  });
72
- function subscribeToChannel(channel) {
73
- actionInfo.runActionWithoutBlockingUI(() => provider.post(remult.apiClient.url + '/' + streamUrl + '/subscribe', {
72
+ async function subscribeToChannel(channel) {
73
+ return actionInfo.runActionWithoutBlockingUI(() => provider.post(remult.apiClient.url + '/' + streamUrl + '/subscribe', {
74
74
  channel: channel,
75
75
  clientId: connectionId
76
76
  }));
@@ -7,7 +7,7 @@ export interface SubscriptionListener<type> {
7
7
  }
8
8
  export declare type Unsubscribe = VoidFunction;
9
9
  export interface SubscriptionClientConnection {
10
- subscribe(channel: string, onMessage: (message: any) => void, onError: (err: any) => void): Unsubscribe;
10
+ subscribe(channel: string, onMessage: (message: any) => void, onError: (err: any) => void): Promise<Unsubscribe>;
11
11
  close(): void;
12
12
  }
13
13
  export interface SubscriptionClient {
@@ -1,14 +1,18 @@
1
1
  import { remult as defaultRemult, Sort } from '../../index';
2
2
  import { getId } from '../remult3/getId';
3
+ import { v4 as uuid } from 'uuid';
4
+ import { getLiveQueryChannel } from '../data-api';
3
5
  export const streamUrl = 'stream';
4
6
  //@internal
5
7
  export class LiveQuerySubscriber {
6
- constructor(repo, query) {
8
+ constructor(repo, query, userId) {
7
9
  this.repo = repo;
8
10
  this.query = query;
9
11
  this.unsubscribe = () => { };
10
12
  this.defaultQueryState = [];
11
13
  this.listeners = [];
14
+ this.id = uuid();
15
+ this.queryChannel = getLiveQueryChannel(this.id, userId);
12
16
  }
13
17
  sendDefaultState(onResult) {
14
18
  onResult(this.createReducerType(() => [...this.defaultQueryState], this.allItemsMessage(this.defaultQueryState)));
@@ -30,7 +34,15 @@ export class LiveQuerySubscriber {
30
34
  ];
31
35
  }
32
36
  forListeners(what, changes) {
33
- what(reducer => this.defaultQueryState = reducer(this.defaultQueryState));
37
+ what(reducer => {
38
+ this.defaultQueryState = reducer(this.defaultQueryState);
39
+ if (changes.find(c => c.type === "add" || c.type === "replace")) {
40
+ if (this.query.orderBy) {
41
+ const o = Sort.translateOrderByToSort(this.repo.metadata, this.query.orderBy);
42
+ this.defaultQueryState.sort((a, b) => o.compare(a, b));
43
+ }
44
+ }
45
+ });
34
46
  for (const l of this.listeners) {
35
47
  what(reducer => {
36
48
  l.next(this.createReducerType(reducer, changes));
@@ -59,7 +71,6 @@ export class LiveQuerySubscriber {
59
71
  listener(items => {
60
72
  if (!items)
61
73
  items = [];
62
- let needSort = false;
63
74
  for (const message of messages) {
64
75
  switch (message.type) {
65
76
  case "all":
@@ -67,13 +78,11 @@ export class LiveQuerySubscriber {
67
78
  break;
68
79
  case "replace": {
69
80
  items = items.map(x => getId(this.repo.metadata, x) === message.data.oldId ? message.data.item : x);
70
- needSort = true;
71
81
  break;
72
82
  }
73
83
  case "add":
74
84
  items = items.filter(x => getId(this.repo.metadata, x) !== getId(this.repo.metadata, message.data.item));
75
85
  items.push(message.data.item);
76
- needSort = true;
77
86
  break;
78
87
  case "remove":
79
88
  items = items.filter(x => getId(this.repo.metadata, x) !== message.data.id);
@@ -81,12 +90,6 @@ export class LiveQuerySubscriber {
81
90
  }
82
91
  ;
83
92
  }
84
- if (needSort) {
85
- if (this.query.orderBy) {
86
- const o = Sort.translateOrderByToSort(this.repo.metadata, this.query.orderBy);
87
- items.sort((a, b) => o.compare(a, b));
88
- }
89
- }
90
93
  return items;
91
94
  });
92
95
  }, messages);
@@ -42,7 +42,7 @@ export interface BackendMethodOptions<type> {
42
42
  export declare const actionInfo: {
43
43
  allActions: any[];
44
44
  runningOnServer: boolean;
45
- runActionWithoutBlockingUI: (what: () => Promise<any>) => Promise<any>;
45
+ runActionWithoutBlockingUI: <T>(what: () => Promise<T>) => Promise<T>;
46
46
  startBusyWithProgress: () => {
47
47
  progress: (percent: number) => void;
48
48
  close: () => void;