Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/client/query.ts
5691 views
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { is_array } from "@cocalc/util/misc";
7
import { validate_client_query } from "@cocalc/util/schema-validate";
8
import { CB } from "@cocalc/util/types/database";
9
import { ConatChangefeed } from "@cocalc/sync/table/changefeed-conat";
10
import { uuid } from "@cocalc/util/misc";
11
12
declare const $: any; // jQuery
13
14
export class QueryClient {
15
private client: any;
16
private changefeeds: { [id: string]: ConatChangefeed } = {};
17
18
constructor(client: any) {
19
this.client = client;
20
}
21
22
private doChangefeed = async (opts: {
23
query: object;
24
options?: object[];
25
cb: CB;
26
}): Promise<void> => {
27
let changefeed;
28
try {
29
changefeed = new ConatChangefeed({
30
account_id: this.client.account_id,
31
query: opts.query,
32
options: opts.options,
33
});
34
// id for canceling this changefeed
35
const id = uuid();
36
const initval = await changefeed.connect();
37
const query = {
38
[Object.keys(opts.query)[0]]: initval,
39
};
40
this.changefeeds[id] = changefeed;
41
opts.cb(undefined, { query, id });
42
changefeed.on("update", (change) => {
43
opts.cb(undefined, change);
44
});
45
} catch (err) {
46
opts.cb(`${err}`);
47
}
48
};
49
50
private doQuery = async (opts: {
51
query: object;
52
options?: object[];
53
timeout?: number;
54
}): Promise<any> => {
55
let timer: ReturnType<typeof setTimeout> | undefined;
56
57
try {
58
const queryPromise = this.client.conat_client.hub.db.userQuery({
59
query: opts.query,
60
options: opts.options,
61
timeout: opts.timeout,
62
});
63
64
// Add client-side timeout if explicitly requested
65
if (opts.timeout != null) {
66
let timedOut = false;
67
68
const timeoutPromise = new Promise<never>((_, reject) => {
69
timer = setTimeout(() => {
70
timedOut = true; // Set flag before rejecting
71
reject(new Error(`Query timed out after ${opts.timeout} ms`));
72
}, opts.timeout);
73
});
74
75
// Prevent unhandled rejection if timeout fires first
76
queryPromise.catch((err) => {
77
if (timedOut) {
78
// Timeout already happened, this is an orphaned rejection - just log it
79
console.warn("Query failed after client-side timeout:", err);
80
}
81
// If not timed out, error is handled by the race
82
});
83
84
return await Promise.race([queryPromise, timeoutPromise]);
85
} else {
86
return await queryPromise;
87
}
88
} finally {
89
if (timer != null) {
90
clearTimeout(timer);
91
}
92
}
93
};
94
95
// This works like a normal async function when
96
// opts.cb is NOT specified. When opts.cb is specified,
97
// it works like a cb and returns nothing. For changefeeds
98
// you MUST specify opts.cb, but can always optionally do so.
99
query = async (opts: {
100
query: object;
101
options?: object[]; // if given must be an array of objects, e.g., [{limit:5}]
102
changes?: boolean;
103
timeout?: number; // ms
104
cb?: CB; // support old cb interface
105
}): Promise<any> => {
106
// Deprecation warnings:
107
for (const field of ["standby", "no_post", "ignore_response"]) {
108
if (opts[field] != null) {
109
console.trace(`WARNING: passing '${field}' to query is deprecated`);
110
}
111
}
112
if (opts.options != null && !is_array(opts.options)) {
113
// should never happen...
114
throw Error("options must be an array");
115
}
116
if (opts.changes) {
117
const { cb } = opts;
118
if (cb == null) {
119
throw Error("for changefeed, must specify opts.cb");
120
}
121
await this.doChangefeed({
122
query: opts.query,
123
options: opts.options,
124
cb,
125
});
126
} else {
127
try {
128
const err = validate_client_query(opts.query, this.client.account_id);
129
if (err) {
130
throw Error(err);
131
}
132
133
const query = await this.doQuery({
134
query: opts.query,
135
options: opts.options,
136
timeout: opts.timeout,
137
});
138
139
if (opts.cb == null) {
140
return { query };
141
} else {
142
opts.cb(undefined, { query });
143
}
144
} catch (err) {
145
if (opts.cb == null) {
146
throw err;
147
} else {
148
opts.cb(err);
149
}
150
}
151
}
152
};
153
154
// cancel a changefeed created above. This is ONLY used
155
// right now by the CRM code.
156
cancel = async (id: string): Promise<void> => {
157
this.changefeeds[id]?.close();
158
delete this.changefeeds[id];
159
};
160
}
161
162