remult 0.16.3 → 0.16.4
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/remult-knex/index.js +9 -2
- package/esm/src/backend-tests/backend-database-test-setup.backend-spec.d.ts +1 -0
- package/esm/src/backend-tests/backend-database-test-setup.backend-spec.js +10 -0
- package/esm/src/live-query/EventSourceLiveQuery.d.ts +5 -0
- package/esm/src/live-query/EventSourceLiveQuery.js +48 -0
- package/esm/src/live-query/EventSourceLiveQueryProvider.d.ts +6 -0
- package/esm/src/live-query/EventSourceLiveQueryProvider.js +24 -0
- package/esm/src/live-query/LiveQuery.d.ts +67 -0
- package/esm/src/live-query/LiveQuery.js +222 -0
- package/esm/src/live-query/LiveQueryManager.d.ts +33 -0
- package/esm/src/live-query/LiveQueryManager.js +97 -0
- package/esm/src/shared-tests/db-tests-setup.d.ts +1 -1
- package/esm/src/shared-tests/db-tests-setup.js +1 -0
- package/esm/src/valueConverters.js +2 -0
- package/package.json +1 -1
- package/remult-knex/index.js +16 -8
- package/src/backend-tests/backend-database-test-setup.backend-spec.d.ts +1 -0
- package/src/backend-tests/backend-database-test-setup.backend-spec.js +11 -1
- package/src/live-query/EventSourceLiveQuery.d.ts +5 -0
- package/src/live-query/EventSourceLiveQuery.js +62 -0
- package/src/live-query/EventSourceLiveQueryProvider.d.ts +6 -0
- package/src/live-query/EventSourceLiveQueryProvider.js +32 -0
- package/src/live-query/LiveQuery.d.ts +67 -0
- package/src/live-query/LiveQuery.js +305 -0
- package/src/live-query/LiveQueryManager.d.ts +33 -0
- package/src/live-query/LiveQueryManager.js +196 -0
- package/src/shared-tests/db-tests-setup.d.ts +1 -1
- package/src/shared-tests/db-tests-setup.js +1 -0
- package/src/valueConverters.js +2 -0
- package/esm/postgres/postgresColumnSyntax.d.ts +0 -2
- package/esm/postgres/postgresColumnSyntax.js +0 -30
- package/esm/remult-middleware.d.ts +0 -2
- package/esm/remult-middleware.js +0 -65
- package/esm/server/register-actions-on-server.d.ts +0 -3
- package/esm/server/register-actions-on-server.js +0 -24
- package/esm/server/register-entities-on-server.d.ts +0 -3
- package/esm/server/register-entities-on-server.js +0 -15
- package/esm/valueConverters.d.ts +0 -12
- package/esm/valueConverters.js +0 -169
- package/postgres/postgresColumnSyntax.d.ts +0 -2
- package/postgres/postgresColumnSyntax.js +0 -34
- package/remult-middleware.d.ts +0 -2
- package/remult-middleware.js +0 -70
- package/server/register-actions-on-server.d.ts +0 -3
- package/server/register-actions-on-server.js +0 -50
- package/server/register-entities-on-server.d.ts +0 -3
- package/server/register-entities-on-server.js +0 -19
- package/valueConverters.d.ts +0 -12
- package/valueConverters.js +0 -168
package/esm/remult-knex/index.js
CHANGED
|
@@ -153,8 +153,15 @@ class KnexEntityDataProvider {
|
|
|
153
153
|
}
|
|
154
154
|
let insert = this.knex(e.entityName).insert(insertObject);
|
|
155
155
|
if (isAutoIncrement(this.entity.idMetadata.field)) {
|
|
156
|
-
let
|
|
157
|
-
|
|
156
|
+
let newId;
|
|
157
|
+
if (this.knex.client.config.client === 'mysql2') {
|
|
158
|
+
let result = await this.knex(e.entityName).insert(insertObject, this.entity.idMetadata.field.key);
|
|
159
|
+
newId = result[0];
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
let result = await insert.returning(this.entity.idMetadata.field.key);
|
|
163
|
+
newId = result[0].id;
|
|
164
|
+
}
|
|
158
165
|
resultFilter = new Filter(x => x.isEqualTo(this.entity.idMetadata.field, newId));
|
|
159
166
|
}
|
|
160
167
|
else
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { dbTestWhatSignature } from "../shared-tests/db-tests-setup";
|
|
2
2
|
export declare const testKnexPGSqlImpl: (key: string, what: dbTestWhatSignature, focus?: boolean) => void;
|
|
3
|
+
export declare const mySqlTest: import("../shared-tests/db-tests-setup").dbTestMethodSignature;
|
|
3
4
|
export declare function testPostgresImplementation(key: string, what: dbTestWhatSignature, focus?: boolean): void;
|
|
4
5
|
export declare function testMongo(key: string, what: dbTestWhatSignature, focus?: boolean): void;
|
|
5
6
|
import '../shared-tests';
|
|
@@ -53,6 +53,16 @@ if (true)
|
|
|
53
53
|
filename: ":memory:"
|
|
54
54
|
},
|
|
55
55
|
}), "sqlite3"));
|
|
56
|
+
export const mySqlTest = addDatabaseToTest(testKnexSqlImpl(Knex.default({
|
|
57
|
+
client: 'mysql2',
|
|
58
|
+
connection: {
|
|
59
|
+
user: 'root',
|
|
60
|
+
password: 'MASTERKEY',
|
|
61
|
+
host: '127.0.0.1',
|
|
62
|
+
database: 'test'
|
|
63
|
+
}
|
|
64
|
+
//debug: true
|
|
65
|
+
}), "mysql2"));
|
|
56
66
|
let pg = createPostgresConnection({
|
|
57
67
|
autoCreateTables: false
|
|
58
68
|
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { LiveQueryClient, streamUrl } from './LiveQuery';
|
|
2
|
+
import { fetchEventSource } from '@microsoft/fetch-event-source';
|
|
3
|
+
import { remult } from '../remult-proxy';
|
|
4
|
+
export class EventSourceLiveQuery extends LiveQueryClient {
|
|
5
|
+
constructor(wrapMessage) {
|
|
6
|
+
super(new EventSourceLiveQueryProvider(wrapMessage));
|
|
7
|
+
this.wrapMessage = wrapMessage;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
class EventSourceLiveQueryProvider {
|
|
11
|
+
constructor(wrapMessage, url = undefined, jwtToken) {
|
|
12
|
+
this.wrapMessage = wrapMessage;
|
|
13
|
+
this.url = url;
|
|
14
|
+
this.jwtToken = jwtToken;
|
|
15
|
+
this.lastId = 0;
|
|
16
|
+
if (!this.wrapMessage)
|
|
17
|
+
this.wrapMessage = x => x();
|
|
18
|
+
if (!this.url) {
|
|
19
|
+
this.url = remult.apiClient.url + '/' + streamUrl;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
openStreamAndReturnCloseFunction(clientId, onMessage) {
|
|
23
|
+
const ctrl = new AbortController();
|
|
24
|
+
const headers = {
|
|
25
|
+
"client-id": clientId
|
|
26
|
+
};
|
|
27
|
+
if (this.jwtToken) {
|
|
28
|
+
headers["Authorization"] = "Bearer " + this.jwtToken;
|
|
29
|
+
}
|
|
30
|
+
fetchEventSource(this.url, {
|
|
31
|
+
headers,
|
|
32
|
+
onmessage: message => {
|
|
33
|
+
const mid = +message.id;
|
|
34
|
+
if (mid <= this.lastId && this.lastId - mid < 10)
|
|
35
|
+
return;
|
|
36
|
+
this.lastId = mid;
|
|
37
|
+
console.log(message.data);
|
|
38
|
+
if (message.event !== 'keep-alive') {
|
|
39
|
+
this.wrapMessage(() => onMessage(message));
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
onopen: async () => {
|
|
43
|
+
},
|
|
44
|
+
signal: ctrl.signal,
|
|
45
|
+
});
|
|
46
|
+
return () => ctrl.abort();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { LiveQueryProvider, MessageHandler } from "./LiveQuery";
|
|
2
|
+
export declare class EventSourceLiveQueryProvider implements LiveQueryProvider {
|
|
3
|
+
private wrapMessage?;
|
|
4
|
+
constructor(wrapMessage?: (what: () => void) => void);
|
|
5
|
+
openStreamAndReturnCloseFunction(clientId: string, onMessage: MessageHandler): Promise<VoidFunction>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { remult } from "../remult-proxy";
|
|
2
|
+
import { streamUrl } from "./LiveQuery";
|
|
3
|
+
export class EventSourceLiveQueryProvider {
|
|
4
|
+
constructor(wrapMessage) {
|
|
5
|
+
this.wrapMessage = wrapMessage;
|
|
6
|
+
if (!this.wrapMessage)
|
|
7
|
+
this.wrapMessage = x => x();
|
|
8
|
+
}
|
|
9
|
+
openStreamAndReturnCloseFunction(clientId, onMessage) {
|
|
10
|
+
const source = new EventSource(remult.apiClient.url + '/' + streamUrl + "?id=" + clientId, {
|
|
11
|
+
withCredentials: true
|
|
12
|
+
});
|
|
13
|
+
source.onmessage = e => {
|
|
14
|
+
this.wrapMessage(() => onMessage(JSON.parse(e.data)));
|
|
15
|
+
};
|
|
16
|
+
return new Promise((res) => {
|
|
17
|
+
source.onopen = e => {
|
|
18
|
+
res(() => {
|
|
19
|
+
source.close();
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { EntityOrderBy, FindOptions, Remult, Repository, RestDataProviderHttpProvider } from '../../index';
|
|
2
|
+
import { Allowed } from '../context';
|
|
3
|
+
import { ServerEventDispatcher } from './LiveQueryManager';
|
|
4
|
+
export declare const streamUrl = "stream1";
|
|
5
|
+
export interface LiveQueryProvider {
|
|
6
|
+
openStreamAndReturnCloseFunction(clientId: string, onMessage: MessageHandler): Promise<VoidFunction>;
|
|
7
|
+
}
|
|
8
|
+
export declare type MessageHandler = (message: {
|
|
9
|
+
data: any;
|
|
10
|
+
event: string;
|
|
11
|
+
}) => void;
|
|
12
|
+
export declare class LiveQueryClient {
|
|
13
|
+
lqp: LiveQueryProvider;
|
|
14
|
+
private provider?;
|
|
15
|
+
clientId: any;
|
|
16
|
+
private queries;
|
|
17
|
+
private channels;
|
|
18
|
+
constructor(lqp: LiveQueryProvider, provider?: RestDataProviderHttpProvider);
|
|
19
|
+
runPromise(p: Promise<any>): Promise<any>;
|
|
20
|
+
close(): void;
|
|
21
|
+
subscribeChannel<T>(key: string, onResult: (item: T) => void): () => void;
|
|
22
|
+
private closeIfNoListeners;
|
|
23
|
+
subscribe<entityType>(repo: Repository<entityType>, options: FindOptions<entityType>, onResult: (reducer: (prevState: entityType[]) => entityType[]) => void): () => void;
|
|
24
|
+
closeListener: Promise<VoidFunction>;
|
|
25
|
+
private openIfNoOpened;
|
|
26
|
+
}
|
|
27
|
+
export declare type listener = (message: any) => void;
|
|
28
|
+
export interface SubscribeToQueryArgs<entityType = any> {
|
|
29
|
+
entityKey: string;
|
|
30
|
+
orderBy?: EntityOrderBy<entityType>;
|
|
31
|
+
}
|
|
32
|
+
export declare type liveQueryMessage = {
|
|
33
|
+
type: "all";
|
|
34
|
+
data: any[];
|
|
35
|
+
} | {
|
|
36
|
+
type: "add";
|
|
37
|
+
data: any;
|
|
38
|
+
} | {
|
|
39
|
+
type: 'replace';
|
|
40
|
+
data: {
|
|
41
|
+
oldId: any;
|
|
42
|
+
item: any;
|
|
43
|
+
};
|
|
44
|
+
} | {
|
|
45
|
+
type: "remove";
|
|
46
|
+
data: {
|
|
47
|
+
id: any;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
export interface SubscribeResult {
|
|
51
|
+
result: [];
|
|
52
|
+
id: string;
|
|
53
|
+
}
|
|
54
|
+
export interface ChannelSubscribe {
|
|
55
|
+
clientId: string;
|
|
56
|
+
channel: string;
|
|
57
|
+
remove: boolean;
|
|
58
|
+
}
|
|
59
|
+
export declare class AMessageChannel<messageType> {
|
|
60
|
+
private subscribedAllowed;
|
|
61
|
+
userCanSubscribe(channel: string, remult: Remult): boolean;
|
|
62
|
+
private key;
|
|
63
|
+
constructor(key: (string | ((remult: Remult) => string)), subscribedAllowed: Allowed);
|
|
64
|
+
send(what: messageType, remult?: Remult): void;
|
|
65
|
+
subscribe(client: LiveQueryClient, onValue: (value: messageType) => void, remult?: Remult): void;
|
|
66
|
+
dispatcher: ServerEventDispatcher;
|
|
67
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { getEntityRef, remult as defaultRemult, Sort } from '../../index';
|
|
2
|
+
import { v4 as uuid } from 'uuid';
|
|
3
|
+
import { RestEntityDataProvider } from '../data-providers/rest-data-provider';
|
|
4
|
+
import { buildRestDataProvider } from '../context';
|
|
5
|
+
export const streamUrl = 'stream1';
|
|
6
|
+
class LiveQueryOnFrontEnd {
|
|
7
|
+
constructor(repo, query) {
|
|
8
|
+
this.repo = repo;
|
|
9
|
+
this.query = query;
|
|
10
|
+
this.items = [];
|
|
11
|
+
this.listeners = [];
|
|
12
|
+
}
|
|
13
|
+
async setAllItems(result) {
|
|
14
|
+
const items = await Promise.all(result.map(item => this.repo.fromJson(item)));
|
|
15
|
+
this.forListeners(listener => {
|
|
16
|
+
listener(x => {
|
|
17
|
+
return items;
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
forListeners(what) {
|
|
22
|
+
for (const l of this.listeners) {
|
|
23
|
+
what(l);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async handle(message) {
|
|
27
|
+
const sort = (items) => {
|
|
28
|
+
if (this.query.orderBy) {
|
|
29
|
+
const o = Sort.translateOrderByToSort(this.repo.metadata, this.query.orderBy);
|
|
30
|
+
items.sort((a, b) => o.compare(a, b));
|
|
31
|
+
}
|
|
32
|
+
return items;
|
|
33
|
+
};
|
|
34
|
+
switch (message.type) {
|
|
35
|
+
case "all":
|
|
36
|
+
this.setAllItems(message.data);
|
|
37
|
+
break;
|
|
38
|
+
case "replace": {
|
|
39
|
+
const item = await this.repo.fromJson(message.data.item);
|
|
40
|
+
this.forListeners(listener => {
|
|
41
|
+
listener(items => {
|
|
42
|
+
if (!items)
|
|
43
|
+
items = [];
|
|
44
|
+
return sort(items.map(x => this.repo.getEntityRef(x).getId() === message.data.oldId ? item : x));
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
case "add":
|
|
50
|
+
{
|
|
51
|
+
const item = await this.repo.fromJson(message.data.item);
|
|
52
|
+
this.forListeners(listener => listener(items => {
|
|
53
|
+
if (!items)
|
|
54
|
+
items = [];
|
|
55
|
+
items.push(item);
|
|
56
|
+
return sort(items);
|
|
57
|
+
}));
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case "remove":
|
|
61
|
+
this.forListeners(listener => listener(items => {
|
|
62
|
+
if (!items)
|
|
63
|
+
items = [];
|
|
64
|
+
return this.items.filter(x => getEntityRef(x).getId() !== message.data.id);
|
|
65
|
+
}));
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
class MessageChannel {
|
|
72
|
+
constructor() {
|
|
73
|
+
this.listeners = [];
|
|
74
|
+
}
|
|
75
|
+
async handle(message) {
|
|
76
|
+
for (const l of this.listeners) {
|
|
77
|
+
l(message);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export class LiveQueryClient {
|
|
82
|
+
constructor(lqp, provider) {
|
|
83
|
+
this.lqp = lqp;
|
|
84
|
+
this.provider = provider;
|
|
85
|
+
this.clientId = uuid();
|
|
86
|
+
this.queries = new Map();
|
|
87
|
+
this.channels = new Map();
|
|
88
|
+
}
|
|
89
|
+
runPromise(p) {
|
|
90
|
+
return p;
|
|
91
|
+
}
|
|
92
|
+
close() {
|
|
93
|
+
this.queries.clear();
|
|
94
|
+
this.channels.clear();
|
|
95
|
+
this.closeIfNoListeners();
|
|
96
|
+
}
|
|
97
|
+
subscribeChannel(key, onResult) {
|
|
98
|
+
let onUnsubscribe = () => { };
|
|
99
|
+
this.openIfNoOpened().then(() => {
|
|
100
|
+
let q = this.channels.get(key);
|
|
101
|
+
if (!q) {
|
|
102
|
+
this.channels.set(key, q = new MessageChannel());
|
|
103
|
+
this.provider.post(defaultRemult.apiClient.url + '/' + streamUrl, {
|
|
104
|
+
channel: key,
|
|
105
|
+
clientId: this.clientId,
|
|
106
|
+
remove: false
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
}
|
|
111
|
+
q.listeners.push(onResult);
|
|
112
|
+
onUnsubscribe = () => {
|
|
113
|
+
q.listeners.splice(q.listeners.indexOf(onResult), 1);
|
|
114
|
+
if (q.listeners.length == 0) {
|
|
115
|
+
this.channels.delete(key);
|
|
116
|
+
}
|
|
117
|
+
this.closeIfNoListeners();
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
return () => {
|
|
121
|
+
onUnsubscribe();
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
closeIfNoListeners() {
|
|
125
|
+
if (this.queries.size === 0 && this.channels.size === 0) {
|
|
126
|
+
this.runPromise(this.closeListener.then(x => x()));
|
|
127
|
+
this.closeListener = undefined;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
subscribe(repo, options, onResult) {
|
|
131
|
+
let alive = true;
|
|
132
|
+
let onUnsubscribe = () => { };
|
|
133
|
+
this.runPromise(this.openIfNoOpened().then(() => repo.buildEntityDataProviderFindOptions(options)
|
|
134
|
+
.then(opts => {
|
|
135
|
+
if (!alive)
|
|
136
|
+
return;
|
|
137
|
+
const { url, filterObject } = new RestEntityDataProvider(() => defaultRemult.apiClient.url + '/' + repo.metadata.key, () => this.provider, repo.metadata)
|
|
138
|
+
.buildFindRequest(opts);
|
|
139
|
+
const eventTypeKey = JSON.stringify({ url, filterObject });
|
|
140
|
+
let q = this.queries.get(eventTypeKey);
|
|
141
|
+
if (!q) {
|
|
142
|
+
this.queries.set(eventTypeKey, q = new LiveQueryOnFrontEnd(repo, { entityKey: repo.metadata.key, orderBy: options.orderBy }));
|
|
143
|
+
url.add("__action", 'subscribe|' + this.clientId);
|
|
144
|
+
const thenResult = (r) => {
|
|
145
|
+
this.runPromise(q.setAllItems(r.result));
|
|
146
|
+
q.id = r.id;
|
|
147
|
+
};
|
|
148
|
+
if (filterObject) {
|
|
149
|
+
this.runPromise(this.provider.post(url.url, filterObject).then(thenResult));
|
|
150
|
+
}
|
|
151
|
+
else
|
|
152
|
+
this.runPromise(this.provider.get(url.url).then(thenResult));
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
onResult(x => q.items);
|
|
156
|
+
}
|
|
157
|
+
q.listeners.push(onResult);
|
|
158
|
+
onUnsubscribe = () => {
|
|
159
|
+
q.listeners.splice(q.listeners.indexOf(onResult), 1);
|
|
160
|
+
if (q.listeners.length == 0) {
|
|
161
|
+
this.queries.delete(eventTypeKey);
|
|
162
|
+
}
|
|
163
|
+
this.closeIfNoListeners();
|
|
164
|
+
};
|
|
165
|
+
})));
|
|
166
|
+
return () => {
|
|
167
|
+
alive = false;
|
|
168
|
+
onUnsubscribe();
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
openIfNoOpened() {
|
|
172
|
+
if (!this.provider) {
|
|
173
|
+
this.provider = buildRestDataProvider(defaultRemult.apiClient.httpClient);
|
|
174
|
+
}
|
|
175
|
+
if (!this.closeListener)
|
|
176
|
+
return this.runPromise(this.closeListener = this.lqp.openStreamAndReturnCloseFunction(this.clientId, message => {
|
|
177
|
+
for (const q of this.queries.values()) {
|
|
178
|
+
if (q.id === message.event) {
|
|
179
|
+
this.runPromise(q.handle(message.data));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const channel = this.channels.get(message.event);
|
|
183
|
+
if (channel) {
|
|
184
|
+
channel.handle(message.data);
|
|
185
|
+
}
|
|
186
|
+
}));
|
|
187
|
+
return this.closeListener;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
export class AMessageChannel {
|
|
191
|
+
constructor(key, subscribedAllowed) {
|
|
192
|
+
this.subscribedAllowed = subscribedAllowed;
|
|
193
|
+
if (typeof key === "string")
|
|
194
|
+
this.key = () => key;
|
|
195
|
+
else
|
|
196
|
+
this.key = key;
|
|
197
|
+
}
|
|
198
|
+
userCanSubscribe(channel, remult) {
|
|
199
|
+
if (!remult)
|
|
200
|
+
remult = defaultRemult;
|
|
201
|
+
if (channel == this.key(remult) && remult.isAllowed(this.subscribedAllowed))
|
|
202
|
+
return true;
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
send(what, remult) {
|
|
206
|
+
if (!this.dispatcher)
|
|
207
|
+
throw new Error("Message couldn't be send since no dispatcher was set");
|
|
208
|
+
this.dispatcher.sendChannelMessage(this.key(remult || defaultRemult), what);
|
|
209
|
+
}
|
|
210
|
+
subscribe(client, onValue, remult) {
|
|
211
|
+
client.subscribeChannel(this.key(remult || defaultRemult), onValue);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/*
|
|
215
|
+
[V] use entity normal http route for this - with __action.
|
|
216
|
+
[] move id from header to url in stream registration.
|
|
217
|
+
[] transaction accumulates messages.
|
|
218
|
+
[] on unsubscribe, also unsubscribe on server
|
|
219
|
+
[] consolidate channel & query
|
|
220
|
+
[] fix stream api url on server
|
|
221
|
+
[] remove client id from header
|
|
222
|
+
*/
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Remult } from '../..';
|
|
2
|
+
import { Repository, EntityRef, FindOptions } from '../remult3';
|
|
3
|
+
import { LiveQueryProvider } from '../data-api';
|
|
4
|
+
import { liveQueryMessage } from './LiveQuery';
|
|
5
|
+
export declare class LiveQueryManager implements LiveQueryProvider {
|
|
6
|
+
private dispatcher;
|
|
7
|
+
constructor(dispatcher: ServerEventDispatcher);
|
|
8
|
+
subscribe(repo: Repository<any>, clientId: string, findOptions: FindOptions<any>, remult: Remult, ids: any[]): string;
|
|
9
|
+
clients: clientInfo[];
|
|
10
|
+
sendMessage(key: string, message: liveQueryMessage): void;
|
|
11
|
+
hasListeners(ref: EntityRef<any>): boolean;
|
|
12
|
+
runPromise(p: Promise<any>): void;
|
|
13
|
+
saved(ref: EntityRef<any>): void;
|
|
14
|
+
deleted(ref: EntityRef<any>): void;
|
|
15
|
+
}
|
|
16
|
+
export interface clientInfo {
|
|
17
|
+
clientId: string;
|
|
18
|
+
queries: ({
|
|
19
|
+
id: string;
|
|
20
|
+
repo: Repository<any>;
|
|
21
|
+
findOptions: FindOptions<any>;
|
|
22
|
+
ids: any[];
|
|
23
|
+
})[];
|
|
24
|
+
}
|
|
25
|
+
export interface ServerEventDispatcher {
|
|
26
|
+
sendQueryMessage(message: ServerEventMessage): void;
|
|
27
|
+
sendChannelMessage<T>(channel: string, message: any): void;
|
|
28
|
+
}
|
|
29
|
+
export interface ServerEventMessage {
|
|
30
|
+
clientId: string;
|
|
31
|
+
queryId: string;
|
|
32
|
+
message: liveQueryMessage;
|
|
33
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { v4 as uuid } from 'uuid';
|
|
2
|
+
export class LiveQueryManager {
|
|
3
|
+
constructor(dispatcher) {
|
|
4
|
+
this.dispatcher = dispatcher;
|
|
5
|
+
this.clients = [];
|
|
6
|
+
}
|
|
7
|
+
subscribe(repo, clientId, findOptions, remult, ids) {
|
|
8
|
+
let client = this.clients.find(c => c.clientId === clientId);
|
|
9
|
+
if (!client) {
|
|
10
|
+
this.clients.push(client = { clientId: clientId, queries: [] });
|
|
11
|
+
}
|
|
12
|
+
const id = uuid();
|
|
13
|
+
console.log({ ids });
|
|
14
|
+
client.queries.push({
|
|
15
|
+
id,
|
|
16
|
+
findOptions: findOptions,
|
|
17
|
+
repo,
|
|
18
|
+
ids
|
|
19
|
+
});
|
|
20
|
+
return id;
|
|
21
|
+
}
|
|
22
|
+
sendMessage(key, message) {
|
|
23
|
+
for (const c of this.clients) {
|
|
24
|
+
for (const q of c.queries) {
|
|
25
|
+
if (q.repo.metadata.key === key) {
|
|
26
|
+
this.dispatcher.sendQueryMessage({ clientId: c.clientId, queryId: q.id, message });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
hasListeners(ref) {
|
|
32
|
+
for (const c of this.clients) {
|
|
33
|
+
for (const q of c.queries) {
|
|
34
|
+
if (q.repo.metadata.key === ref.metadata.key)
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
runPromise(p) {
|
|
41
|
+
}
|
|
42
|
+
//TODO - reconsider usage of zone
|
|
43
|
+
saved(ref) {
|
|
44
|
+
const isNew = ref.isNew();
|
|
45
|
+
const origId = isNew ? ref.getId() : ref.getOriginalId();
|
|
46
|
+
for (const c of this.clients) {
|
|
47
|
+
for (const q of c.queries) {
|
|
48
|
+
if (q.repo.metadata.key === ref.metadata.key) {
|
|
49
|
+
this.runPromise(q.repo.find(q.findOptions).then(currentItems => {
|
|
50
|
+
const currentIds = currentItems.map(x => q.repo.getEntityRef(x).getId());
|
|
51
|
+
const sendMessage = (message) => {
|
|
52
|
+
console.log({ message: message.type });
|
|
53
|
+
this.dispatcher.sendQueryMessage({ clientId: c.clientId, queryId: q.id, message });
|
|
54
|
+
};
|
|
55
|
+
console.log({ ids: q.ids, currentIds });
|
|
56
|
+
for (const id of q.ids.filter(y => !currentIds.includes(y))) {
|
|
57
|
+
if (id != origId || !currentIds.includes(ref.getId()))
|
|
58
|
+
sendMessage({
|
|
59
|
+
type: "remove",
|
|
60
|
+
data: {
|
|
61
|
+
id: id
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
for (const item of currentItems) {
|
|
66
|
+
const itemRef = q.repo.getEntityRef(item);
|
|
67
|
+
if (itemRef.getId() == ref.getId() && q.ids.includes(origId)) {
|
|
68
|
+
sendMessage({
|
|
69
|
+
type: "replace",
|
|
70
|
+
data: {
|
|
71
|
+
oldId: origId,
|
|
72
|
+
item: itemRef.toApiJson()
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
else if (!q.ids.includes(itemRef.getId())) {
|
|
77
|
+
sendMessage({
|
|
78
|
+
type: "add",
|
|
79
|
+
data: { item: itemRef.toApiJson() }
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
q.ids = currentIds;
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
deleted(ref) {
|
|
90
|
+
if (!this.hasListeners(ref))
|
|
91
|
+
return;
|
|
92
|
+
this.sendMessage(ref.metadata.key, {
|
|
93
|
+
type: "remove",
|
|
94
|
+
data: { id: ref.getId() }
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -6,7 +6,7 @@ import { CategoriesForTesting } from "../tests/remult-3-entities";
|
|
|
6
6
|
import { Status } from "../tests/testModel/models";
|
|
7
7
|
export declare function itWithFocus(key: string, what: () => Promise<void>, focus?: boolean): void;
|
|
8
8
|
export declare function testAll(key: string, what: dbTestWhatSignature, focus?: boolean): void;
|
|
9
|
-
export declare function addDatabaseToTest(tester: dbTestMethodSignature):
|
|
9
|
+
export declare function addDatabaseToTest(tester: dbTestMethodSignature): dbTestMethodSignature;
|
|
10
10
|
export declare function testInMemory(key: string, what: dbTestWhatSignature, focus?: boolean): void;
|
|
11
11
|
export declare type dbTestWhatSignature = (db: {
|
|
12
12
|
db: DataProvider;
|
|
@@ -100,6 +100,8 @@ ValueConverters.Boolean = {
|
|
|
100
100
|
fromJson: value => {
|
|
101
101
|
if (typeof value === "boolean")
|
|
102
102
|
return value;
|
|
103
|
+
if (value === 1)
|
|
104
|
+
return true;
|
|
103
105
|
if (value !== undefined && value !== null) {
|
|
104
106
|
return value.toString().trim().toLowerCase() == 'true';
|
|
105
107
|
}
|
package/package.json
CHANGED
package/remult-knex/index.js
CHANGED
|
@@ -257,7 +257,7 @@ var KnexEntityDataProvider = /** @class */ (function () {
|
|
|
257
257
|
};
|
|
258
258
|
KnexEntityDataProvider.prototype.insert = function (data) {
|
|
259
259
|
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
260
|
-
var e, resultFilter, insertObject, _a, _b, x, v, key, e_3_1, insert, result,
|
|
260
|
+
var e, resultFilter, insertObject, _a, _b, x, v, key, e_3_1, insert, newId_1, result, result;
|
|
261
261
|
var e_3, _c;
|
|
262
262
|
var _this = this;
|
|
263
263
|
return tslib_1.__generator(this, function (_d) {
|
|
@@ -304,18 +304,26 @@ var KnexEntityDataProvider = /** @class */ (function () {
|
|
|
304
304
|
return [7 /*endfinally*/];
|
|
305
305
|
case 10:
|
|
306
306
|
insert = this.knex(e.entityName).insert(insertObject);
|
|
307
|
-
if (!remult3_1.isAutoIncrement(this.entity.idMetadata.field)) return [3 /*break*/,
|
|
308
|
-
|
|
307
|
+
if (!remult3_1.isAutoIncrement(this.entity.idMetadata.field)) return [3 /*break*/, 15];
|
|
308
|
+
if (!(this.knex.client.config.client === 'mysql2')) return [3 /*break*/, 12];
|
|
309
|
+
return [4 /*yield*/, this.knex(e.entityName).insert(insertObject, this.entity.idMetadata.field.key)];
|
|
309
310
|
case 11:
|
|
310
311
|
result = _d.sent();
|
|
311
|
-
newId_1 = result[0]
|
|
312
|
-
resultFilter = new filter_interfaces_1.Filter(function (x) { return x.isEqualTo(_this.entity.idMetadata.field, newId_1); });
|
|
312
|
+
newId_1 = result[0];
|
|
313
313
|
return [3 /*break*/, 14];
|
|
314
|
-
case 12: return [4 /*yield*/, insert];
|
|
314
|
+
case 12: return [4 /*yield*/, insert.returning(this.entity.idMetadata.field.key)];
|
|
315
315
|
case 13:
|
|
316
|
-
_d.sent();
|
|
316
|
+
result = _d.sent();
|
|
317
|
+
newId_1 = result[0].id;
|
|
317
318
|
_d.label = 14;
|
|
318
|
-
case 14:
|
|
319
|
+
case 14:
|
|
320
|
+
resultFilter = new filter_interfaces_1.Filter(function (x) { return x.isEqualTo(_this.entity.idMetadata.field, newId_1); });
|
|
321
|
+
return [3 /*break*/, 17];
|
|
322
|
+
case 15: return [4 /*yield*/, insert];
|
|
323
|
+
case 16:
|
|
324
|
+
_d.sent();
|
|
325
|
+
_d.label = 17;
|
|
326
|
+
case 17: return [2 /*return*/, this.find({ where: resultFilter }).then(function (y) {
|
|
319
327
|
return y[0];
|
|
320
328
|
})];
|
|
321
329
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { dbTestWhatSignature } from "../shared-tests/db-tests-setup";
|
|
2
2
|
export declare const testKnexPGSqlImpl: (key: string, what: dbTestWhatSignature, focus?: boolean) => void;
|
|
3
|
+
export declare const mySqlTest: import("../shared-tests/db-tests-setup").dbTestMethodSignature;
|
|
3
4
|
export declare function testPostgresImplementation(key: string, what: dbTestWhatSignature, focus?: boolean): void;
|
|
4
5
|
export declare function testMongo(key: string, what: dbTestWhatSignature, focus?: boolean): void;
|
|
5
6
|
import '../shared-tests';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.testMongo = exports.testPostgresImplementation = exports.testKnexPGSqlImpl = void 0;
|
|
3
|
+
exports.testMongo = exports.testPostgresImplementation = exports.mySqlTest = exports.testKnexPGSqlImpl = void 0;
|
|
4
4
|
var tslib_1 = require("tslib");
|
|
5
5
|
var context_1 = require("../context");
|
|
6
6
|
var remult_knex_1 = require("../../remult-knex");
|
|
@@ -87,6 +87,16 @@ if (true)
|
|
|
87
87
|
filename: ":memory:"
|
|
88
88
|
},
|
|
89
89
|
}), "sqlite3"));
|
|
90
|
+
exports.mySqlTest = db_tests_setup_1.addDatabaseToTest(testKnexSqlImpl(Knex.default({
|
|
91
|
+
client: 'mysql2',
|
|
92
|
+
connection: {
|
|
93
|
+
user: 'root',
|
|
94
|
+
password: 'MASTERKEY',
|
|
95
|
+
host: '127.0.0.1',
|
|
96
|
+
database: 'test'
|
|
97
|
+
}
|
|
98
|
+
//debug: true
|
|
99
|
+
}), "mysql2"));
|
|
90
100
|
var pg = postgres_1.createPostgresConnection({
|
|
91
101
|
autoCreateTables: false
|
|
92
102
|
});
|