@centia-io/sdk 0.0.43 → 0.0.44
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/LICENSE +21 -21
- package/README.md +570 -570
- package/dist/centia-io-sdk.cjs +253 -115
- package/dist/centia-io-sdk.d.cts +119 -69
- package/dist/centia-io-sdk.d.cts.map +1 -1
- package/dist/centia-io-sdk.d.ts +119 -69
- package/dist/centia-io-sdk.d.ts.map +1 -1
- package/dist/centia-io-sdk.js +253 -116
- package/dist/centia-io-sdk.js.map +1 -1
- package/dist/centia-io-sdk.umd.js +1382 -1244
- package/package.json +36 -36
|
@@ -5,1007 +5,1069 @@
|
|
|
5
5
|
})(this, function(exports) {
|
|
6
6
|
|
|
7
7
|
//#region src/util/jwt-decode.ts
|
|
8
|
-
var InvalidTokenError = class extends Error {};
|
|
9
|
-
InvalidTokenError.prototype.name = "InvalidTokenError";
|
|
10
|
-
function b64DecodeUnicode(str) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
function base64UrlDecode(str) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
function jwtDecode(token, options) {
|
|
36
|
-
if (typeof token !== "string") throw new InvalidTokenError("Invalid token specified: must be a string");
|
|
37
|
-
options ||= {};
|
|
38
|
-
const pos = options.header === true ? 0 : 1;
|
|
39
|
-
const part = token.split(".")[pos];
|
|
40
|
-
if (typeof part !== "string") throw new InvalidTokenError(`Invalid token specified: missing part #${pos + 1}`);
|
|
41
|
-
let decoded;
|
|
42
|
-
try {
|
|
43
|
-
decoded = base64UrlDecode(part);
|
|
44
|
-
} catch (e) {
|
|
45
|
-
throw new InvalidTokenError(`Invalid token specified: invalid base64 for part #${pos + 1} (${e.message})`);
|
|
8
|
+
var InvalidTokenError = class extends Error {};
|
|
9
|
+
InvalidTokenError.prototype.name = "InvalidTokenError";
|
|
10
|
+
function b64DecodeUnicode(str) {
|
|
11
|
+
return decodeURIComponent(atob(str).replace(/(.)/g, (m, p) => {
|
|
12
|
+
let code = p.charCodeAt(0).toString(16).toUpperCase();
|
|
13
|
+
if (code.length < 2) code = "0" + code;
|
|
14
|
+
return "%" + code;
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
function base64UrlDecode(str) {
|
|
18
|
+
let output = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
19
|
+
switch (output.length % 4) {
|
|
20
|
+
case 0: break;
|
|
21
|
+
case 2:
|
|
22
|
+
output += "==";
|
|
23
|
+
break;
|
|
24
|
+
case 3:
|
|
25
|
+
output += "=";
|
|
26
|
+
break;
|
|
27
|
+
default: throw new Error("base64 string is not of the correct length");
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
return b64DecodeUnicode(output);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
return atob(output);
|
|
33
|
+
}
|
|
46
34
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
35
|
+
function jwtDecode(token, options) {
|
|
36
|
+
if (typeof token !== "string") throw new InvalidTokenError("Invalid token specified: must be a string");
|
|
37
|
+
options ||= {};
|
|
38
|
+
const pos = options.header === true ? 0 : 1;
|
|
39
|
+
const part = token.split(".")[pos];
|
|
40
|
+
if (typeof part !== "string") throw new InvalidTokenError(`Invalid token specified: missing part #${pos + 1}`);
|
|
41
|
+
let decoded;
|
|
42
|
+
try {
|
|
43
|
+
decoded = base64UrlDecode(part);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
throw new InvalidTokenError(`Invalid token specified: invalid base64 for part #${pos + 1} (${e.message})`);
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(decoded);
|
|
49
|
+
} catch (e) {
|
|
50
|
+
throw new InvalidTokenError(`Invalid token specified: invalid json for part #${pos + 1} (${e.message})`);
|
|
51
|
+
}
|
|
51
52
|
}
|
|
52
|
-
}
|
|
53
53
|
|
|
54
54
|
//#endregion
|
|
55
55
|
//#region src/util/storage.ts
|
|
56
|
-
var MemoryStorage = class {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
var MemoryStorage = class {
|
|
57
|
+
constructor() {
|
|
58
|
+
this.store = /* @__PURE__ */ new Map();
|
|
59
|
+
}
|
|
60
|
+
getItem(key) {
|
|
61
|
+
return this.store.has(key) ? this.store.get(key) : null;
|
|
62
|
+
}
|
|
63
|
+
setItem(key, value) {
|
|
64
|
+
this.store.set(key, String(value));
|
|
65
|
+
}
|
|
66
|
+
removeItem(key) {
|
|
67
|
+
this.store.delete(key);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
let cached = null;
|
|
71
|
+
function getStorage() {
|
|
72
|
+
if (cached) return cached;
|
|
73
|
+
try {
|
|
74
|
+
const g$1 = typeof globalThis !== "undefined" ? globalThis : window;
|
|
75
|
+
if (g$1 && g$1.localStorage && typeof g$1.localStorage.getItem === "function") {
|
|
76
|
+
cached = g$1.localStorage;
|
|
77
|
+
return cached;
|
|
78
|
+
}
|
|
79
|
+
} catch (e) {}
|
|
80
|
+
const g = typeof globalThis !== "undefined" ? globalThis : {};
|
|
81
|
+
if (!g.__gc2_memory_storage) g.__gc2_memory_storage = new MemoryStorage();
|
|
82
|
+
cached = g.__gc2_memory_storage;
|
|
83
|
+
return cached;
|
|
68
84
|
}
|
|
69
|
-
};
|
|
70
|
-
let cached = null;
|
|
71
|
-
function getStorage() {
|
|
72
|
-
if (cached) return cached;
|
|
73
|
-
try {
|
|
74
|
-
const g$1 = typeof globalThis !== "undefined" ? globalThis : window;
|
|
75
|
-
if (g$1 && g$1.localStorage && typeof g$1.localStorage.getItem === "function") {
|
|
76
|
-
cached = g$1.localStorage;
|
|
77
|
-
return cached;
|
|
78
|
-
}
|
|
79
|
-
} catch (e) {}
|
|
80
|
-
const g = typeof globalThis !== "undefined" ? globalThis : {};
|
|
81
|
-
if (!g.__gc2_memory_storage) g.__gc2_memory_storage = new MemoryStorage();
|
|
82
|
-
cached = g.__gc2_memory_storage;
|
|
83
|
-
return cached;
|
|
84
|
-
}
|
|
85
85
|
|
|
86
86
|
//#endregion
|
|
87
87
|
//#region src/util/utils.ts
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
/**
|
|
89
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
90
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
91
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
92
|
+
*
|
|
93
|
+
*/
|
|
94
|
+
const generatePkceChallenge = async () => {
|
|
95
|
+
const generateRandomString = () => {
|
|
96
|
+
const array = new Uint32Array(28);
|
|
97
|
+
crypto.getRandomValues(array);
|
|
98
|
+
return Array.from(array, (dec) => ("0" + dec.toString(16)).substr(-2)).join("");
|
|
99
|
+
};
|
|
100
|
+
const sha256 = (plain) => {
|
|
101
|
+
const data = new TextEncoder().encode(plain);
|
|
102
|
+
return crypto.subtle.digest("SHA-256", data);
|
|
103
|
+
};
|
|
104
|
+
const base64urlEncode = (str) => {
|
|
105
|
+
return btoa(String.fromCharCode.apply(null, [...new Uint8Array(str)])).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
106
|
+
};
|
|
107
|
+
async function pkceChallengeFromVerifier(v) {
|
|
108
|
+
return base64urlEncode(await sha256(v));
|
|
109
|
+
}
|
|
110
|
+
const { state, codeVerifier } = {
|
|
111
|
+
state: generateRandomString(),
|
|
112
|
+
codeVerifier: generateRandomString()
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
state,
|
|
116
|
+
codeVerifier,
|
|
117
|
+
codeChallenge: await pkceChallengeFromVerifier(codeVerifier)
|
|
118
|
+
};
|
|
93
119
|
};
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
120
|
+
const isTokenExpired = (token) => {
|
|
121
|
+
let isJwtExpired = false;
|
|
122
|
+
const { exp } = jwtDecode(token);
|
|
123
|
+
const currentTime = (/* @__PURE__ */ new Date()).getTime() / 1e3;
|
|
124
|
+
if (exp) {
|
|
125
|
+
if (currentTime > exp) isJwtExpired = true;
|
|
126
|
+
}
|
|
127
|
+
return isJwtExpired;
|
|
97
128
|
};
|
|
98
|
-
const
|
|
99
|
-
return
|
|
129
|
+
const claims = (token) => {
|
|
130
|
+
return jwtDecode(token);
|
|
100
131
|
};
|
|
101
|
-
async
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
132
|
+
const isLogin = async (gc2) => {
|
|
133
|
+
const { accessToken, refreshToken } = getTokens();
|
|
134
|
+
if (!accessToken && !refreshToken) return false;
|
|
135
|
+
if (!accessToken || accessToken && isTokenExpired(accessToken)) {
|
|
136
|
+
if (refreshToken && isTokenExpired(refreshToken)) {
|
|
137
|
+
clearTokens();
|
|
138
|
+
clearOptions();
|
|
139
|
+
throw new Error("Refresh token has expired. Please login again.");
|
|
140
|
+
}
|
|
141
|
+
if (refreshToken) try {
|
|
142
|
+
const data = await gc2.getRefreshToken(refreshToken);
|
|
143
|
+
setTokens({
|
|
144
|
+
accessToken: data.access_token,
|
|
145
|
+
refreshToken,
|
|
146
|
+
idToken: data?.id_token
|
|
147
|
+
});
|
|
148
|
+
console.log("Access token refreshed");
|
|
149
|
+
} catch (e) {
|
|
150
|
+
throw new Error("Could not get refresh token.");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
107
154
|
};
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
155
|
+
const setTokens = (tokens) => {
|
|
156
|
+
getStorage().setItem("gc2_tokens", JSON.stringify({
|
|
157
|
+
"accessToken": tokens.accessToken,
|
|
158
|
+
"refreshToken": tokens.refreshToken,
|
|
159
|
+
"idToken": tokens?.idToken || ""
|
|
160
|
+
}));
|
|
112
161
|
};
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
accessToken: data.access_token,
|
|
139
|
-
refreshToken,
|
|
140
|
-
idToken: data?.id_token
|
|
141
|
-
});
|
|
142
|
-
console.log("Access token refreshed");
|
|
143
|
-
} catch (e) {
|
|
144
|
-
throw new Error("Could not get refresh token.");
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return true;
|
|
148
|
-
};
|
|
149
|
-
const setTokens = (tokens) => {
|
|
150
|
-
getStorage().setItem("gc2_tokens", JSON.stringify({
|
|
151
|
-
"accessToken": tokens.accessToken,
|
|
152
|
-
"refreshToken": tokens.refreshToken,
|
|
153
|
-
"idToken": tokens?.idToken || ""
|
|
154
|
-
}));
|
|
155
|
-
};
|
|
156
|
-
const getTokens = () => {
|
|
157
|
-
const str = getStorage().getItem("gc2_tokens");
|
|
158
|
-
const tokens = str ? JSON.parse(str) : {};
|
|
159
|
-
return {
|
|
160
|
-
accessToken: tokens?.accessToken || "",
|
|
161
|
-
refreshToken: tokens?.refreshToken || "",
|
|
162
|
-
idToken: tokens?.idToken || ""
|
|
162
|
+
const getTokens = () => {
|
|
163
|
+
const str = getStorage().getItem("gc2_tokens");
|
|
164
|
+
const tokens = str ? JSON.parse(str) : {};
|
|
165
|
+
return {
|
|
166
|
+
accessToken: tokens?.accessToken || "",
|
|
167
|
+
refreshToken: tokens?.refreshToken || "",
|
|
168
|
+
idToken: tokens?.idToken || ""
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
const setOptions = (options) => {
|
|
172
|
+
getStorage().setItem("gc2_options", JSON.stringify({
|
|
173
|
+
"clientId": options.clientId,
|
|
174
|
+
"host": options.host,
|
|
175
|
+
"redirectUri": options.redirectUri,
|
|
176
|
+
"clientSecret": options.clientSecret || null
|
|
177
|
+
}));
|
|
178
|
+
};
|
|
179
|
+
const getOptions = () => {
|
|
180
|
+
const str = getStorage().getItem("gc2_options");
|
|
181
|
+
const options = str ? JSON.parse(str) : {};
|
|
182
|
+
return {
|
|
183
|
+
clientId: options?.clientId || "",
|
|
184
|
+
host: options?.host || "",
|
|
185
|
+
redirectUri: options?.redirectUri || ""
|
|
186
|
+
};
|
|
163
187
|
};
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
"
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
clientId: options?.clientId || "",
|
|
177
|
-
host: options?.host || "",
|
|
178
|
-
redirectUri: options?.redirectUri || ""
|
|
188
|
+
const clearTokens = () => {
|
|
189
|
+
getStorage().removeItem("gc2_tokens");
|
|
190
|
+
};
|
|
191
|
+
const clearOptions = () => {
|
|
192
|
+
getStorage().removeItem("gc2_options");
|
|
193
|
+
};
|
|
194
|
+
const getNonce = () => {
|
|
195
|
+
return getStorage().getItem("gc2_nonce");
|
|
196
|
+
};
|
|
197
|
+
const clearNonce = () => {
|
|
198
|
+
getStorage().removeItem("gc2_nonce");
|
|
179
199
|
};
|
|
180
|
-
};
|
|
181
|
-
const clearTokens = () => {
|
|
182
|
-
getStorage().removeItem("gc2_tokens");
|
|
183
|
-
};
|
|
184
|
-
const clearOptions = () => {
|
|
185
|
-
getStorage().removeItem("gc2_options");
|
|
186
|
-
};
|
|
187
|
-
const getNonce = () => {
|
|
188
|
-
return getStorage().getItem("gc2_nonce");
|
|
189
|
-
};
|
|
190
|
-
const clearNonce = () => {
|
|
191
|
-
getStorage().removeItem("gc2_nonce");
|
|
192
|
-
};
|
|
193
200
|
|
|
194
201
|
//#endregion
|
|
195
202
|
//#region src/services/gc2.services.ts
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
headers
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
async pollToken(deviceCode, interval) {
|
|
235
|
-
const path = this.options.tokenUri ?? `${this.host}/api/v4/oauth`;
|
|
236
|
-
const getToken = async () => {
|
|
237
|
-
try {
|
|
238
|
-
return await this.request(this.buildUrl(path), "POST", {
|
|
239
|
-
client_id: this.options.clientId,
|
|
240
|
-
device_code: deviceCode,
|
|
241
|
-
grant_type: "device_code"
|
|
242
|
-
});
|
|
243
|
-
} catch (e) {
|
|
244
|
-
const err = JSON.parse(e.message.split(": ")[1]);
|
|
245
|
-
if (err.error === "authorization_pending") return null;
|
|
246
|
-
return err.error_description;
|
|
203
|
+
/**
|
|
204
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
205
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
206
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
207
|
+
*
|
|
208
|
+
*/
|
|
209
|
+
var Gc2Service = class {
|
|
210
|
+
constructor(options) {
|
|
211
|
+
this.options = options;
|
|
212
|
+
this.host = options.host;
|
|
213
|
+
}
|
|
214
|
+
isCodeFlowOptions(options) {
|
|
215
|
+
return "redirectUri" in options;
|
|
216
|
+
}
|
|
217
|
+
isPasswordFlowOptions(options) {
|
|
218
|
+
return "username" in options;
|
|
219
|
+
}
|
|
220
|
+
isSignUpOptions(options) {
|
|
221
|
+
return "parentDb" in options;
|
|
222
|
+
}
|
|
223
|
+
buildUrl(path) {
|
|
224
|
+
if (path.startsWith("http://") || path.startsWith("https://")) return path;
|
|
225
|
+
return `${this.host}${path}`;
|
|
226
|
+
}
|
|
227
|
+
async request(url, method, body, contentType = "application/json") {
|
|
228
|
+
const headers = { "Content-Type": contentType };
|
|
229
|
+
let payload;
|
|
230
|
+
if (contentType === "application/json") payload = JSON.stringify(body);
|
|
231
|
+
else payload = new URLSearchParams(body).toString();
|
|
232
|
+
const response = await fetch(url, {
|
|
233
|
+
method,
|
|
234
|
+
headers,
|
|
235
|
+
body: payload
|
|
236
|
+
});
|
|
237
|
+
if (!response.ok) {
|
|
238
|
+
const errText = await response.text();
|
|
239
|
+
throw new Error(`HTTP error ${response.status}: ${errText}`);
|
|
247
240
|
}
|
|
248
|
-
|
|
249
|
-
let response = await getToken();
|
|
250
|
-
while (response === null) {
|
|
251
|
-
await new Promise((resolve) => setTimeout(resolve, interval * 1100));
|
|
252
|
-
response = await getToken();
|
|
241
|
+
return response.json();
|
|
253
242
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
client_id
|
|
290
|
-
redirect_uri
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
243
|
+
async getDeviceCode() {
|
|
244
|
+
const path = this.options.deviceUri ?? `${this.host}/api/v4/oauth/device`;
|
|
245
|
+
return this.request(this.buildUrl(path), "POST", { client_id: this.options.clientId });
|
|
246
|
+
}
|
|
247
|
+
async pollToken(deviceCode, interval) {
|
|
248
|
+
const path = this.options.tokenUri ?? `${this.host}/api/v4/oauth`;
|
|
249
|
+
const getToken = async () => {
|
|
250
|
+
try {
|
|
251
|
+
return await this.request(this.buildUrl(path), "POST", {
|
|
252
|
+
client_id: this.options.clientId,
|
|
253
|
+
device_code: deviceCode,
|
|
254
|
+
grant_type: "device_code"
|
|
255
|
+
});
|
|
256
|
+
} catch (e) {
|
|
257
|
+
const err = JSON.parse(e.message.split(": ")[1]);
|
|
258
|
+
if (err.error === "authorization_pending") return null;
|
|
259
|
+
return err.error_description;
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
let response = await getToken();
|
|
263
|
+
while (response === null) {
|
|
264
|
+
await new Promise((resolve) => setTimeout(resolve, interval * 1100));
|
|
265
|
+
response = await getToken();
|
|
266
|
+
}
|
|
267
|
+
if (typeof response === "string") throw new Error(response);
|
|
268
|
+
return response;
|
|
269
|
+
}
|
|
270
|
+
getAuthorizationCodeURL(codeChallenge, state) {
|
|
271
|
+
let redirectUri;
|
|
272
|
+
if (this.isCodeFlowOptions(this.options)) redirectUri = this.options.redirectUri;
|
|
273
|
+
else throw new Error("CodeFlow options required for this operation");
|
|
274
|
+
const base = this.options.authUri ?? `${this.host}/auth/`;
|
|
275
|
+
const params = new URLSearchParams();
|
|
276
|
+
const nonce = getNonce();
|
|
277
|
+
params.set("response_type", "code");
|
|
278
|
+
params.set("client_id", this.options.clientId);
|
|
279
|
+
params.set("redirect_uri", redirectUri);
|
|
280
|
+
params.set("state", state);
|
|
281
|
+
params.set("code_challenge", codeChallenge);
|
|
282
|
+
params.set("code_challenge_method", "S256");
|
|
283
|
+
if (nonce) params.set("nonce", nonce);
|
|
284
|
+
if (this.options.scope) params.set("scope", this.options.scope);
|
|
285
|
+
return `${base}?${params.toString()}`;
|
|
286
|
+
}
|
|
287
|
+
getSignUpURL() {
|
|
288
|
+
if (!this.isSignUpOptions(this.options)) throw new Error("CodeFlow options required for this operation");
|
|
289
|
+
const base = this.options.authUri ?? `${this.host}/signup/`;
|
|
290
|
+
const params = new URLSearchParams();
|
|
291
|
+
params.set("client_id", this.options.clientId);
|
|
292
|
+
params.set("parentdb", this.options.parentDb);
|
|
293
|
+
params.set("redirect_uri", this.options.redirectUri);
|
|
294
|
+
return `${base}?${params.toString()}`;
|
|
295
|
+
}
|
|
296
|
+
async getAuthorizationCodeToken(code, codeVerifier) {
|
|
297
|
+
let redirectUri;
|
|
298
|
+
if (this.isCodeFlowOptions(this.options)) redirectUri = this.options.redirectUri;
|
|
299
|
+
else throw new Error("CodeFlow options required for this operation");
|
|
300
|
+
const path = this.options.tokenUri ?? `${this.host}/api/v4/oauth`;
|
|
301
|
+
return this.request(this.buildUrl(path), "POST", {
|
|
302
|
+
client_id: this.options.clientId,
|
|
303
|
+
redirect_uri: redirectUri,
|
|
304
|
+
grant_type: "authorization_code",
|
|
305
|
+
code,
|
|
306
|
+
code_verifier: codeVerifier
|
|
307
|
+
}, "application/x-www-form-urlencoded");
|
|
308
|
+
}
|
|
309
|
+
async getPasswordToken() {
|
|
310
|
+
let username, password, database;
|
|
311
|
+
if (this.isPasswordFlowOptions(this.options)) {
|
|
312
|
+
username = this.options.username;
|
|
313
|
+
password = this.options.password;
|
|
314
|
+
database = this.options.database;
|
|
315
|
+
} else throw new Error("PasswordFlow options required for this operation");
|
|
316
|
+
const path = `${this.host}/api/v4/oauth`;
|
|
317
|
+
return this.request(this.buildUrl(path), "POST", {
|
|
318
|
+
client_id: this.options.clientId,
|
|
319
|
+
client_secret: this.options.clientSecret,
|
|
320
|
+
grant_type: "password",
|
|
321
|
+
username,
|
|
322
|
+
password,
|
|
323
|
+
database
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
async getRefreshToken(token) {
|
|
327
|
+
const path = this.options.tokenUri ?? `${this.host}/api/v4/oauth`;
|
|
328
|
+
return this.request(this.buildUrl(path), "POST", {
|
|
329
|
+
client_id: this.options.clientId,
|
|
330
|
+
grant_type: "refresh_token",
|
|
331
|
+
refresh_token: token
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
getSignOutURL() {
|
|
335
|
+
let redirectUri;
|
|
336
|
+
if (this.isCodeFlowOptions(this.options)) redirectUri = this.options.redirectUri;
|
|
337
|
+
else throw new Error("CodeFlow options required for this operation");
|
|
338
|
+
const params = new URLSearchParams({ redirect_uri: redirectUri });
|
|
339
|
+
return this.options.logoutUri ?? `${this.host}/signout?${params.toString()}`;
|
|
340
|
+
}
|
|
341
|
+
};
|
|
328
342
|
|
|
329
343
|
//#endregion
|
|
330
344
|
//#region src/CodeFlow.ts
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
345
|
+
/**
|
|
346
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
347
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
348
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
349
|
+
*
|
|
350
|
+
*/
|
|
351
|
+
var CodeFlow = class {
|
|
352
|
+
constructor(options) {
|
|
353
|
+
this.options = options;
|
|
354
|
+
this.service = new Gc2Service(options);
|
|
355
|
+
}
|
|
356
|
+
async redirectHandle() {
|
|
357
|
+
const url = window.location.search;
|
|
358
|
+
const queryParams = new URLSearchParams(url);
|
|
359
|
+
if (queryParams.get("error")) throw new Error(`Failed to redirect: ${url}`);
|
|
360
|
+
const code = queryParams.get("code");
|
|
361
|
+
if (code) {
|
|
362
|
+
if (queryParams.get("state") !== getStorage().getItem("state")) throw new Error("Possible CSRF attack. Aborting login!");
|
|
363
|
+
try {
|
|
364
|
+
const { access_token, refresh_token, id_token } = await this.service.getAuthorizationCodeToken(code, getStorage().getItem("codeVerifier"));
|
|
365
|
+
setTokens({
|
|
366
|
+
accessToken: access_token,
|
|
367
|
+
refreshToken: refresh_token,
|
|
368
|
+
idToken: id_token
|
|
369
|
+
});
|
|
370
|
+
setOptions({
|
|
371
|
+
clientId: this.options.clientId,
|
|
372
|
+
host: this.options.host,
|
|
373
|
+
redirectUri: this.options.redirectUri
|
|
374
|
+
});
|
|
375
|
+
getStorage().removeItem("state");
|
|
376
|
+
getStorage().removeItem("codeVerifier");
|
|
377
|
+
const params = new URLSearchParams(window.location.search);
|
|
378
|
+
params.delete("code");
|
|
379
|
+
params.delete("state");
|
|
380
|
+
const loc = window.location;
|
|
381
|
+
const newUrl = loc.origin + loc.pathname + (params.size > 0 ? "?" + params.toString() : "");
|
|
382
|
+
history.pushState(null, "", newUrl);
|
|
383
|
+
return Promise.resolve(true);
|
|
384
|
+
} catch (e) {
|
|
385
|
+
throw new Error(e.message);
|
|
386
|
+
}
|
|
366
387
|
}
|
|
388
|
+
return await isLogin(this.service);
|
|
367
389
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
};
|
|
390
|
+
async signIn() {
|
|
391
|
+
const { state, codeVerifier, codeChallenge } = await generatePkceChallenge();
|
|
392
|
+
getStorage().setItem("state", state);
|
|
393
|
+
getStorage().setItem("codeVerifier", codeVerifier);
|
|
394
|
+
window.location.assign(this.service.getAuthorizationCodeURL(codeChallenge, state));
|
|
395
|
+
}
|
|
396
|
+
signOut() {
|
|
397
|
+
this.clear();
|
|
398
|
+
window.location.assign(this.service.getSignOutURL());
|
|
399
|
+
}
|
|
400
|
+
clear() {
|
|
401
|
+
clearTokens();
|
|
402
|
+
clearOptions();
|
|
403
|
+
clearNonce();
|
|
404
|
+
}
|
|
405
|
+
};
|
|
386
406
|
|
|
387
407
|
//#endregion
|
|
388
408
|
//#region src/PasswordFlow.ts
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
409
|
+
/**
|
|
410
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
411
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
412
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
413
|
+
*
|
|
414
|
+
*/
|
|
415
|
+
var PasswordFlow = class {
|
|
416
|
+
constructor(options) {
|
|
417
|
+
this.options = options;
|
|
418
|
+
this.service = new Gc2Service(options);
|
|
419
|
+
}
|
|
420
|
+
async signIn() {
|
|
421
|
+
const { access_token, refresh_token } = await this.service.getPasswordToken();
|
|
422
|
+
setTokens({
|
|
423
|
+
accessToken: access_token,
|
|
424
|
+
refreshToken: refresh_token
|
|
425
|
+
});
|
|
426
|
+
setOptions({
|
|
427
|
+
clientId: this.options.clientId,
|
|
428
|
+
host: this.options.host,
|
|
429
|
+
redirectUri: "",
|
|
430
|
+
clientSecret: this.options.clientSecret
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
signOut() {
|
|
434
|
+
this.clear();
|
|
435
|
+
}
|
|
436
|
+
clear() {
|
|
437
|
+
clearTokens();
|
|
438
|
+
clearOptions();
|
|
439
|
+
clearNonce();
|
|
440
|
+
}
|
|
441
|
+
};
|
|
415
442
|
|
|
416
443
|
//#endregion
|
|
417
444
|
//#region src/util/request-headers.ts
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
445
|
+
/**
|
|
446
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
447
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
448
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
449
|
+
*
|
|
450
|
+
*/
|
|
451
|
+
const getHeaders = async (contentType = "application/json") => {
|
|
452
|
+
if (!await isLogin(new Gc2Service(getOptions()))) return Promise.reject("Is not logged in");
|
|
453
|
+
const { accessToken } = getTokens();
|
|
454
|
+
const headers = {
|
|
455
|
+
Accept: "application/json",
|
|
456
|
+
Cookie: "XDEBUG_SESSION=XDEBUG_ECLIPSE",
|
|
457
|
+
Authorization: accessToken ? "Bearer " + accessToken : null
|
|
458
|
+
};
|
|
459
|
+
if (contentType) headers["Content-Type"] = contentType;
|
|
460
|
+
return headers;
|
|
425
461
|
};
|
|
426
|
-
|
|
427
|
-
return headers;
|
|
428
|
-
};
|
|
429
|
-
var request_headers_default = getHeaders;
|
|
462
|
+
var request_headers_default = getHeaders;
|
|
430
463
|
|
|
431
464
|
//#endregion
|
|
432
465
|
//#region src/util/make-request.ts
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
466
|
+
/**
|
|
467
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
468
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
469
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
470
|
+
*
|
|
471
|
+
*/
|
|
472
|
+
const make = async (version, resource, method, payload, contentType = "application/json") => {
|
|
473
|
+
const options = getOptions();
|
|
474
|
+
let request = {
|
|
475
|
+
method,
|
|
476
|
+
headers: await request_headers_default(contentType),
|
|
477
|
+
redirect: "manual"
|
|
478
|
+
};
|
|
479
|
+
if (payload) request.body = contentType === "application/json" ? JSON.stringify(payload) : payload;
|
|
480
|
+
if (version !== null) return await fetch(options.host + `/api/v${version}/${resource}`, request);
|
|
481
|
+
else return await fetch(options.host + `/api/${resource}`, request);
|
|
439
482
|
};
|
|
440
|
-
|
|
441
|
-
return await fetch(options.host + `/api/v${version}/${resource}`, request);
|
|
442
|
-
};
|
|
443
|
-
var make_request_default = make;
|
|
483
|
+
var make_request_default = make;
|
|
444
484
|
|
|
445
485
|
//#endregion
|
|
446
486
|
//#region src/util/get-response.ts
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
res =
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
}
|
|
462
|
-
|
|
487
|
+
/**
|
|
488
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
489
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
490
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
491
|
+
*
|
|
492
|
+
*/
|
|
493
|
+
const get = async (response, expectedCode) => {
|
|
494
|
+
let res = null;
|
|
495
|
+
let bodyText = "";
|
|
496
|
+
try {
|
|
497
|
+
bodyText = await response.text();
|
|
498
|
+
} catch (e) {}
|
|
499
|
+
if (bodyText) try {
|
|
500
|
+
res = JSON.parse(bodyText);
|
|
501
|
+
} catch (e) {}
|
|
502
|
+
if (response.status !== expectedCode) {
|
|
503
|
+
const msg = res && (res.message || res.error) || bodyText || `Unexpected status ${response.status}`;
|
|
504
|
+
throw new Error(msg);
|
|
505
|
+
}
|
|
506
|
+
return res;
|
|
507
|
+
};
|
|
508
|
+
var get_response_default = get;
|
|
463
509
|
|
|
464
510
|
//#endregion
|
|
465
511
|
//#region src/Sql.ts
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
512
|
+
/**
|
|
513
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
514
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
515
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
516
|
+
*
|
|
517
|
+
*/
|
|
518
|
+
var Sql = class {
|
|
519
|
+
async exec(request) {
|
|
520
|
+
return await get_response_default(await make_request_default("4", `sql`, "POST", request), 200);
|
|
521
|
+
}
|
|
522
|
+
};
|
|
471
523
|
|
|
472
524
|
//#endregion
|
|
473
525
|
//#region src/Rpc.ts
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
526
|
+
/**
|
|
527
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
528
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
529
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
530
|
+
*
|
|
531
|
+
*/
|
|
532
|
+
var Rpc = class {
|
|
533
|
+
async call(request) {
|
|
534
|
+
return await get_response_default(await make_request_default("4", `call`, "POST", request), 200);
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
//#endregion
|
|
539
|
+
//#region src/Gql.ts
|
|
540
|
+
/**
|
|
541
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
542
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
543
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
544
|
+
*
|
|
545
|
+
*/
|
|
546
|
+
var Gql = class {
|
|
547
|
+
constructor(schema) {
|
|
548
|
+
this.schema = schema;
|
|
549
|
+
}
|
|
550
|
+
async request(request) {
|
|
551
|
+
return await get_response_default(await make_request_default(null, `graphql/schema/${this.schema}`, "POST", request), 200);
|
|
552
|
+
}
|
|
553
|
+
};
|
|
479
554
|
|
|
480
555
|
//#endregion
|
|
481
556
|
//#region src/Meta.ts
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
557
|
+
/**
|
|
558
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
559
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
560
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
561
|
+
*
|
|
562
|
+
*/
|
|
563
|
+
var Meta = class {
|
|
564
|
+
async query(rel) {
|
|
565
|
+
return await get_response_default(await make_request_default("3", `meta/${rel}`, "GET", null), 200);
|
|
566
|
+
}
|
|
567
|
+
};
|
|
487
568
|
|
|
488
569
|
//#endregion
|
|
489
570
|
//#region src/Status.ts
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
571
|
+
/**
|
|
572
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
573
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
574
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
575
|
+
*
|
|
576
|
+
*/
|
|
577
|
+
var Status = class {
|
|
578
|
+
isAuth() {
|
|
579
|
+
const tokens = getTokens();
|
|
580
|
+
return !(!tokens.accessToken && !tokens.refreshToken);
|
|
581
|
+
}
|
|
582
|
+
getTokens() {
|
|
583
|
+
return getTokens();
|
|
584
|
+
}
|
|
585
|
+
};
|
|
499
586
|
|
|
500
587
|
//#endregion
|
|
501
588
|
//#region src/Claims.ts
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
589
|
+
/**
|
|
590
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
591
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
592
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
593
|
+
*
|
|
594
|
+
*/
|
|
595
|
+
var Claims = class {
|
|
596
|
+
get() {
|
|
597
|
+
const tokens = getTokens().accessToken;
|
|
598
|
+
return claims(tokens);
|
|
599
|
+
}
|
|
600
|
+
};
|
|
508
601
|
|
|
509
602
|
//#endregion
|
|
510
603
|
//#region src/Users.ts
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
604
|
+
/**
|
|
605
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
606
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
607
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
608
|
+
*
|
|
609
|
+
*/
|
|
610
|
+
var Users = class {
|
|
611
|
+
async get(user) {
|
|
612
|
+
return await get_response_default(await make_request_default("4", `users/${user}`, "GET", null), 200);
|
|
613
|
+
}
|
|
614
|
+
};
|
|
516
615
|
|
|
517
616
|
//#endregion
|
|
518
617
|
//#region src/Ws.ts
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
618
|
+
/**
|
|
619
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
620
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
621
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
622
|
+
*
|
|
623
|
+
*/
|
|
624
|
+
var Ws = class {
|
|
625
|
+
constructor(options) {
|
|
626
|
+
this.options = options;
|
|
627
|
+
this.options.wsClient = this.options?.wsClient ?? WebSocket;
|
|
628
|
+
}
|
|
629
|
+
connect() {
|
|
630
|
+
const me = this;
|
|
631
|
+
const { accessToken } = getTokens();
|
|
632
|
+
const connect = () => {
|
|
633
|
+
let queryString = `?token=` + accessToken;
|
|
634
|
+
if (this.options?.rel) queryString = queryString + `&rel=` + this.options.rel;
|
|
635
|
+
const WSClass = this.options.wsClient;
|
|
636
|
+
const ws = new WSClass(this.options.host + `/` + queryString);
|
|
637
|
+
ws.onopen = function() {
|
|
638
|
+
console.log("WebSocket connected!");
|
|
639
|
+
};
|
|
640
|
+
ws.onmessage = function(event) {
|
|
641
|
+
me.options.callBack(event.data);
|
|
642
|
+
};
|
|
643
|
+
ws.onclose = function(event) {
|
|
644
|
+
if (accessToken !== "") {
|
|
645
|
+
console.log("WebSocket closed, reconnecting in 3 seconds...", event.reason);
|
|
646
|
+
setTimeout(connect, 3e3);
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
ws.onerror = function(err) {
|
|
650
|
+
console.error("WebSocket error observed:", err);
|
|
651
|
+
ws.close();
|
|
652
|
+
};
|
|
547
653
|
};
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
}
|
|
551
|
-
};
|
|
654
|
+
if (accessToken !== "") connect();
|
|
655
|
+
}
|
|
656
|
+
};
|
|
552
657
|
|
|
553
658
|
//#endregion
|
|
554
659
|
//#region src/Stats.ts
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
660
|
+
/**
|
|
661
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
662
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
663
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
664
|
+
*
|
|
665
|
+
*/
|
|
666
|
+
var Stats = class {
|
|
667
|
+
async get() {
|
|
668
|
+
return await get_response_default(await make_request_default("4", `stats`, "GET", null), 200);
|
|
669
|
+
}
|
|
670
|
+
};
|
|
560
671
|
|
|
561
672
|
//#endregion
|
|
562
673
|
//#region src/Tables.ts
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
674
|
+
/**
|
|
675
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
676
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
677
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
678
|
+
*
|
|
679
|
+
*/
|
|
680
|
+
var Tables = class {
|
|
681
|
+
async get(schema, table) {
|
|
682
|
+
return await get_response_default(await make_request_default("4", `schemas/${encodeURIComponent(schema)}/tables/${encodeURIComponent(table)}`, "GET", null), 200);
|
|
683
|
+
}
|
|
684
|
+
async create(schema, table, payload) {
|
|
685
|
+
return await get_response_default(await make_request_default("4", `schemas/${encodeURIComponent(schema)}/tables/${encodeURIComponent(table)}`, "POST", payload), 200);
|
|
686
|
+
}
|
|
687
|
+
async patch(schema, table, payload) {
|
|
688
|
+
return await get_response_default(await make_request_default("4", `schemas/${encodeURIComponent(schema)}/tables/${encodeURIComponent(table)}`, "PATCH", payload), 200);
|
|
689
|
+
}
|
|
690
|
+
async delete(schema, table) {
|
|
691
|
+
return await get_response_default(await make_request_default("4", `schemas/${encodeURIComponent(schema)}/tables/${encodeURIComponent(table)}`, "DELETE", null), 204);
|
|
692
|
+
}
|
|
693
|
+
};
|
|
577
694
|
|
|
578
695
|
//#endregion
|
|
579
696
|
//#region src/Api.ts
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
697
|
+
/**
|
|
698
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
699
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
700
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
701
|
+
*
|
|
702
|
+
*/
|
|
703
|
+
function isPlainObject$1(v) {
|
|
704
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
705
|
+
}
|
|
706
|
+
function validateParamsForMethod(method, params) {
|
|
707
|
+
if (Array.isArray(params)) {
|
|
708
|
+
const badIndex = params.findIndex((p) => !isPlainObject$1(p));
|
|
709
|
+
if (badIndex !== -1) throw new TypeError(`createApi: Invalid argument at index ${badIndex} for RPC method "${method}". Expected a plain object.`);
|
|
710
|
+
return params;
|
|
711
|
+
}
|
|
712
|
+
if (params === void 0) return {};
|
|
713
|
+
if (!isPlainObject$1(params)) throw new TypeError(`createApi: Invalid argument for RPC method "${method}". Expected a plain object.`);
|
|
587
714
|
return params;
|
|
588
715
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
jsonrpc: "2.0",
|
|
623
|
-
method: name,
|
|
624
|
-
id: 1,
|
|
625
|
-
params
|
|
626
|
-
};
|
|
627
|
-
const res = await rpc.call(request);
|
|
628
|
-
return extractDataFromResponse(String(name), res);
|
|
629
|
-
}
|
|
630
|
-
function createApi() {
|
|
631
|
-
return new Proxy({}, { get(_target, prop) {
|
|
632
|
-
if (typeof prop !== "string") return void 0;
|
|
633
|
-
return (...args) => {
|
|
634
|
-
return dispatch(prop, args.length === 0 ? {} : args.length === 1 ? args[0] : args);
|
|
716
|
+
function extractDataFromResponse(method, res) {
|
|
717
|
+
if (!res || typeof res !== "object") throw new TypeError(`createApi: Invalid RPC response for method "${method}". Expected an object.`);
|
|
718
|
+
if (res.jsonrpc !== "2.0") throw new TypeError(`createApi: Invalid RPC response for method "${method}". Missing or invalid jsonrpc version.`);
|
|
719
|
+
const err = res.error;
|
|
720
|
+
if (err !== void 0) {
|
|
721
|
+
if (!err || typeof err !== "object") throw new TypeError(`createApi: Invalid RPC error for method "${method}". Expected 'error' to be an object.`);
|
|
722
|
+
const code = err.code;
|
|
723
|
+
const message = err.message;
|
|
724
|
+
const data$1 = err.data;
|
|
725
|
+
const codeIsNum = typeof code === "number" && Number.isFinite(code);
|
|
726
|
+
const details = typeof message === "string" && message.length > 0 ? message : "Unknown error";
|
|
727
|
+
const e = /* @__PURE__ */ new Error(`createApi: RPC error for method "${method}"${codeIsNum ? ` (${code})` : ""}: ${details}`);
|
|
728
|
+
e.code = code;
|
|
729
|
+
if (data$1 !== void 0) e.data = data$1;
|
|
730
|
+
e.method = method;
|
|
731
|
+
e.name = "JsonRpcError";
|
|
732
|
+
throw e;
|
|
733
|
+
}
|
|
734
|
+
const result = res.result;
|
|
735
|
+
if (!result || typeof result !== "object") throw new TypeError(`createApi: Invalid RPC response for method "${method}". Missing result object.`);
|
|
736
|
+
const data = result.data;
|
|
737
|
+
if (!Array.isArray(data)) throw new TypeError(`createApi: Invalid RPC response for method "${method}". Expected result.data to be an array.`);
|
|
738
|
+
return data;
|
|
739
|
+
}
|
|
740
|
+
async function dispatch(name, paramsLike) {
|
|
741
|
+
if (typeof name !== "string" || name.length === 0) throw new TypeError("createApi: RPC method name must be a non-empty string.");
|
|
742
|
+
const params = validateParamsForMethod(String(name), paramsLike);
|
|
743
|
+
const rpc = new Rpc();
|
|
744
|
+
const request = {
|
|
745
|
+
jsonrpc: "2.0",
|
|
746
|
+
method: name,
|
|
747
|
+
id: 1,
|
|
748
|
+
params
|
|
635
749
|
};
|
|
636
|
-
|
|
637
|
-
|
|
750
|
+
const res = await rpc.call(request);
|
|
751
|
+
return extractDataFromResponse(String(name), res);
|
|
752
|
+
}
|
|
753
|
+
function createApi() {
|
|
754
|
+
return new Proxy({}, { get(_target, prop) {
|
|
755
|
+
if (typeof prop !== "string") return void 0;
|
|
756
|
+
return (...args) => {
|
|
757
|
+
return dispatch(prop, args.length === 0 ? {} : args.length === 1 ? args[0] : args);
|
|
758
|
+
};
|
|
759
|
+
} });
|
|
760
|
+
}
|
|
638
761
|
|
|
639
762
|
//#endregion
|
|
640
763
|
//#region src/SignUp.ts
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
764
|
+
/**
|
|
765
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
766
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
767
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
768
|
+
*
|
|
769
|
+
*/
|
|
770
|
+
var SignUp = class {
|
|
771
|
+
constructor(options) {
|
|
772
|
+
this.options = options;
|
|
773
|
+
this.service = new Gc2Service(options);
|
|
774
|
+
}
|
|
775
|
+
async signUp() {
|
|
776
|
+
window.location.assign(this.service.getSignUpURL());
|
|
777
|
+
}
|
|
778
|
+
};
|
|
650
779
|
|
|
651
780
|
//#endregion
|
|
652
781
|
//#region src/SqlBuilder.ts
|
|
653
|
-
function findTable(schema, name) {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
}
|
|
658
|
-
function findColumn(table, name) {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
}
|
|
663
|
-
function getPrimaryKeyColumns(table) {
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}
|
|
667
|
-
function typeNameToHint(typname, isArray) {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
}
|
|
672
|
-
function addTypeHintForParam(typeHints, paramName, col, value) {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
function expectedScalarKind(typname) {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
}
|
|
691
|
-
function isArrayShapedGeomTypename(typname) {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
}
|
|
695
|
-
function isArrayShapedGeomScalarValue(col, value) {
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
}
|
|
700
|
-
function expectedRangeInnerKind(typname) {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
}
|
|
706
|
-
function isPlainObject(v) {
|
|
707
|
-
|
|
708
|
-
}
|
|
709
|
-
function isFiniteNumber(n) {
|
|
710
|
-
|
|
711
|
-
}
|
|
712
|
-
function isPointLike(v) {
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
}
|
|
718
|
-
function validateGeometryForColumn(col, value, context) {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
}
|
|
757
|
-
function validateRangeForColumn(col, value, context) {
|
|
758
|
-
if (!isPlainObject(value)) throw new Error(`Invalid value for ${context}. Expected a range object for type ${col._typname}`);
|
|
759
|
-
const r = value;
|
|
760
|
-
const hasLower = Object.prototype.hasOwnProperty.call(r, "lower");
|
|
761
|
-
const hasUpper = Object.prototype.hasOwnProperty.call(r, "upper");
|
|
762
|
-
const hasLi = Object.prototype.hasOwnProperty.call(r, "lowerInclusive");
|
|
763
|
-
const hasUi = Object.prototype.hasOwnProperty.call(r, "upperInclusive");
|
|
764
|
-
if (!hasLower || !hasUpper || !hasLi || !hasUi) throw new Error(`Invalid range for ${context}. Required properties: lower, upper, lowerInclusive, upperInclusive`);
|
|
765
|
-
if (typeof r.lowerInclusive !== "boolean" || typeof r.upperInclusive !== "boolean") throw new Error(`Invalid range for ${context}. lowerInclusive and upperInclusive must be boolean`);
|
|
766
|
-
const inner = expectedRangeInnerKind(col._typname);
|
|
767
|
-
if (inner === "number") {
|
|
768
|
-
if (typeof r.lower !== "number" || !Number.isFinite(r.lower)) throw new Error(`Invalid range.lower for ${context}. Expected number for type ${col._typname}`);
|
|
769
|
-
if (typeof r.upper !== "number" || !Number.isFinite(r.upper)) throw new Error(`Invalid range.upper for ${context}. Expected number for type ${col._typname}`);
|
|
770
|
-
} else if (inner === "string") {
|
|
771
|
-
if (typeof r.lower !== "string") throw new Error(`Invalid range.lower for ${context}. Expected string for type ${col._typname}`);
|
|
772
|
-
if (typeof r.upper !== "string") throw new Error(`Invalid range.upper for ${context}. Expected string for type ${col._typname}`);
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
function validateIntervalForColumn(col, value, context) {
|
|
776
|
-
if (!isPlainObject(value)) throw new Error(`Invalid value for ${context}. Expected an interval object for type ${col._typname}`);
|
|
777
|
-
const v = value;
|
|
778
|
-
for (const k of [
|
|
779
|
-
"y",
|
|
780
|
-
"m",
|
|
781
|
-
"d",
|
|
782
|
-
"h",
|
|
783
|
-
"i",
|
|
784
|
-
"s"
|
|
785
|
-
]) {
|
|
786
|
-
if (!Object.prototype.hasOwnProperty.call(v, k)) throw new Error(`Invalid interval for ${context}. Missing property '${k}'`);
|
|
787
|
-
const num = v[k];
|
|
788
|
-
if (typeof num !== "number" || !Number.isFinite(num)) throw new Error(`Invalid interval.${String(k)} for ${context}. Expected finite number`);
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
function isValidJsonLike(value) {
|
|
792
|
-
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "object" && value !== null;
|
|
793
|
-
}
|
|
794
|
-
function validateScalarForColumn(col, value, context) {
|
|
795
|
-
const kind = expectedScalarKind(col._typname);
|
|
796
|
-
if (kind === "unknown") return;
|
|
797
|
-
if (kind === "number") {
|
|
798
|
-
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`Invalid value for ${context}. Expected number for type ${col._typname}, got ${typeof value}`);
|
|
799
|
-
return;
|
|
800
|
-
}
|
|
801
|
-
if (kind === "string") {
|
|
802
|
-
if (typeof value !== "string") throw new Error(`Invalid value for ${context}. Expected string for type ${col._typname}, got ${typeof value}`);
|
|
803
|
-
return;
|
|
804
|
-
}
|
|
805
|
-
if (kind === "boolean") {
|
|
806
|
-
if (typeof value !== "boolean") throw new Error(`Invalid value for ${context}. Expected boolean for type ${col._typname}, got ${typeof value}`);
|
|
807
|
-
return;
|
|
808
|
-
}
|
|
809
|
-
if (kind === "json") {
|
|
810
|
-
if (!isValidJsonLike(value)) throw new Error(`Invalid value for ${context}. Expected JSON-compatible value for type ${col._typname}`);
|
|
811
|
-
return;
|
|
782
|
+
function findTable(schema, name) {
|
|
783
|
+
const tbl = schema.tables.find((t) => t.name === name);
|
|
784
|
+
if (!tbl) throw new Error(`Table not found in schema: ${name}`);
|
|
785
|
+
return tbl;
|
|
786
|
+
}
|
|
787
|
+
function findColumn(table, name) {
|
|
788
|
+
const col = table.columns.find((c) => c.name === name);
|
|
789
|
+
if (!col) throw new Error(`Column not found in table ${table.name}: ${name}`);
|
|
790
|
+
return col;
|
|
791
|
+
}
|
|
792
|
+
function getPrimaryKeyColumns(table) {
|
|
793
|
+
const pk = (table.constraints || []).find((c) => c.constraint === "primary" && Array.isArray(c.columns) && c.columns.length > 0);
|
|
794
|
+
return pk ? pk.columns.map(String) : [];
|
|
795
|
+
}
|
|
796
|
+
function typeNameToHint(typname, isArray) {
|
|
797
|
+
if (!typname) return void 0;
|
|
798
|
+
const base = typname;
|
|
799
|
+
return isArray ? base + "[]" : base;
|
|
800
|
+
}
|
|
801
|
+
function addTypeHintForParam(typeHints, paramName, col, value) {
|
|
802
|
+
let isArr = col._is_array;
|
|
803
|
+
if (Array.isArray(value)) if (isArrayShapedGeomTypename(col._typname)) isArr = value.every(Array.isArray);
|
|
804
|
+
else isArr = true;
|
|
805
|
+
const hint = typeNameToHint(col._typname, isArr);
|
|
806
|
+
if (hint) typeHints[paramName] = hint;
|
|
807
|
+
}
|
|
808
|
+
function expectedScalarKind(typname) {
|
|
809
|
+
const t = typname.toLowerCase();
|
|
810
|
+
if (t === "int2" || t === "int4" || t === "int8" || t === "float4" || t === "float8") return "number";
|
|
811
|
+
if (t === "numeric" || t === "decimal") return "string";
|
|
812
|
+
if (t === "varchar" || t === "text" || t === "bpchar" || t === "char" || t === "date" || t === "time" || t === "timetz" || t === "timestamp" || t === "timestamptz") return "string";
|
|
813
|
+
if (t === "bool") return "boolean";
|
|
814
|
+
if (t === "json" || t === "jsonb") return "json";
|
|
815
|
+
if (t === "int4range" || t === "int8range" || t === "numrange" || t === "tsrange" || t === "tstzrange" || t === "daterange") return "range";
|
|
816
|
+
if (t === "interval") return "interval";
|
|
817
|
+
if (t === "point" || t === "line" || t === "lseg" || t === "box" || t === "path" || t === "polygon" || t === "circle") return "geom";
|
|
818
|
+
return "unknown";
|
|
819
|
+
}
|
|
820
|
+
function isArrayShapedGeomTypename(typname) {
|
|
821
|
+
const t = typname.toLowerCase();
|
|
822
|
+
return t === "path" || t === "polygon";
|
|
823
|
+
}
|
|
824
|
+
function isArrayShapedGeomScalarValue(col, value) {
|
|
825
|
+
if (!Array.isArray(value)) return false;
|
|
826
|
+
if (!isArrayShapedGeomTypename(col._typname)) return false;
|
|
827
|
+
return !value.every(Array.isArray);
|
|
828
|
+
}
|
|
829
|
+
function expectedRangeInnerKind(typname) {
|
|
830
|
+
const t = typname.toLowerCase();
|
|
831
|
+
if (t === "int4range" || t === "int8range") return "number";
|
|
832
|
+
if (t === "numrange" || t === "tsrange" || t === "tstzrange" || t === "daterange") return "string";
|
|
833
|
+
return "unknown";
|
|
834
|
+
}
|
|
835
|
+
function isPlainObject(v) {
|
|
836
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
837
|
+
}
|
|
838
|
+
function isFiniteNumber(n) {
|
|
839
|
+
return typeof n === "number" && Number.isFinite(n);
|
|
840
|
+
}
|
|
841
|
+
function isPointLike(v) {
|
|
842
|
+
if (!isPlainObject(v)) return false;
|
|
843
|
+
const x = v.x;
|
|
844
|
+
const y = v.y;
|
|
845
|
+
return isFiniteNumber(x) && isFiniteNumber(y);
|
|
846
|
+
}
|
|
847
|
+
function validateGeometryForColumn(col, value, context) {
|
|
848
|
+
const t = col._typname.toLowerCase();
|
|
849
|
+
if (t === "point") {
|
|
850
|
+
if (!isPointLike(value)) throw new Error(`Invalid value for ${context}. Expected Point { x: number, y: number }`);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
if (t === "line") {
|
|
854
|
+
if (!isPlainObject(value)) throw new Error(`Invalid value for ${context}. Expected Line { A: number, B: number, C: number }`);
|
|
855
|
+
const A = value.A, B = value.B, C = value.C;
|
|
856
|
+
if (!isFiniteNumber(A) || !isFiniteNumber(B) || !isFiniteNumber(C)) throw new Error(`Invalid Line for ${context}. A, B, C must be finite numbers`);
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
if (t === "lseg" || t === "box") {
|
|
860
|
+
if (!isPlainObject(value)) throw new Error(`Invalid value for ${context}. Expected ${t.toUpperCase()} { start: Point, end: Point }`);
|
|
861
|
+
const start = value.start;
|
|
862
|
+
const end = value.end;
|
|
863
|
+
if (!isPointLike(start) || !isPointLike(end)) throw new Error(`Invalid ${t} for ${context}. 'start' and 'end' must be Point { x, y }`);
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
if (t === "path") {
|
|
867
|
+
if (!Array.isArray(value) || value.length < 1) throw new Error(`Invalid value for ${context}. Expected Path [isClosed: boolean, ...points: Point[]]`);
|
|
868
|
+
const [isClosed, ...points] = value;
|
|
869
|
+
if (typeof isClosed !== "boolean") throw new Error(`Invalid Path for ${context}. First element must be boolean (isClosed)`);
|
|
870
|
+
for (let i = 0; i < points.length; i++) if (!isPointLike(points[i])) throw new Error(`Invalid Path for ${context}. Element at index ${i + 1} must be Point { x, y }`);
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
if (t === "polygon") {
|
|
874
|
+
if (!Array.isArray(value)) throw new Error(`Invalid value for ${context}. Expected Polygon as Point[]`);
|
|
875
|
+
for (let i = 0; i < value.length; i++) if (!isPointLike(value[i])) throw new Error(`Invalid Polygon for ${context}. Element at index ${i} must be Point { x, y }`);
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
if (t === "circle") {
|
|
879
|
+
if (!isPlainObject(value)) throw new Error(`Invalid value for ${context}. Expected Circle { center: Point, radius: number }`);
|
|
880
|
+
const center = value.center;
|
|
881
|
+
const radius = value.radius;
|
|
882
|
+
if (!isPointLike(center) || !isFiniteNumber(radius)) throw new Error(`Invalid Circle for ${context}. 'center' must be Point and 'radius' must be finite number`);
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
812
885
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
886
|
+
function validateRangeForColumn(col, value, context) {
|
|
887
|
+
if (!isPlainObject(value)) throw new Error(`Invalid value for ${context}. Expected a range object for type ${col._typname}`);
|
|
888
|
+
const r = value;
|
|
889
|
+
const hasLower = Object.prototype.hasOwnProperty.call(r, "lower");
|
|
890
|
+
const hasUpper = Object.prototype.hasOwnProperty.call(r, "upper");
|
|
891
|
+
const hasLi = Object.prototype.hasOwnProperty.call(r, "lowerInclusive");
|
|
892
|
+
const hasUi = Object.prototype.hasOwnProperty.call(r, "upperInclusive");
|
|
893
|
+
if (!hasLower || !hasUpper || !hasLi || !hasUi) throw new Error(`Invalid range for ${context}. Required properties: lower, upper, lowerInclusive, upperInclusive`);
|
|
894
|
+
if (typeof r.lowerInclusive !== "boolean" || typeof r.upperInclusive !== "boolean") throw new Error(`Invalid range for ${context}. lowerInclusive and upperInclusive must be boolean`);
|
|
895
|
+
const inner = expectedRangeInnerKind(col._typname);
|
|
896
|
+
if (inner === "number") {
|
|
897
|
+
if (typeof r.lower !== "number" || !Number.isFinite(r.lower)) throw new Error(`Invalid range.lower for ${context}. Expected number for type ${col._typname}`);
|
|
898
|
+
if (typeof r.upper !== "number" || !Number.isFinite(r.upper)) throw new Error(`Invalid range.upper for ${context}. Expected number for type ${col._typname}`);
|
|
899
|
+
} else if (inner === "string") {
|
|
900
|
+
if (typeof r.lower !== "string") throw new Error(`Invalid range.lower for ${context}. Expected string for type ${col._typname}`);
|
|
901
|
+
if (typeof r.upper !== "string") throw new Error(`Invalid range.upper for ${context}. Expected string for type ${col._typname}`);
|
|
902
|
+
}
|
|
816
903
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
904
|
+
function validateIntervalForColumn(col, value, context) {
|
|
905
|
+
if (!isPlainObject(value)) throw new Error(`Invalid value for ${context}. Expected an interval object for type ${col._typname}`);
|
|
906
|
+
const v = value;
|
|
907
|
+
for (const k of [
|
|
908
|
+
"y",
|
|
909
|
+
"m",
|
|
910
|
+
"d",
|
|
911
|
+
"h",
|
|
912
|
+
"i",
|
|
913
|
+
"s"
|
|
914
|
+
]) {
|
|
915
|
+
if (!Object.prototype.hasOwnProperty.call(v, k)) throw new Error(`Invalid interval for ${context}. Missing property '${k}'`);
|
|
916
|
+
const num = v[k];
|
|
917
|
+
if (typeof num !== "number" || !Number.isFinite(num)) throw new Error(`Invalid interval.${String(k)} for ${context}. Expected finite number`);
|
|
918
|
+
}
|
|
820
919
|
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
return;
|
|
920
|
+
function isValidJsonLike(value) {
|
|
921
|
+
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "object" && value !== null;
|
|
824
922
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
if (
|
|
833
|
-
|
|
923
|
+
function validateScalarForColumn(col, value, context) {
|
|
924
|
+
const kind = expectedScalarKind(col._typname);
|
|
925
|
+
if (kind === "unknown") return;
|
|
926
|
+
if (kind === "number") {
|
|
927
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`Invalid value for ${context}. Expected number for type ${col._typname}, got ${typeof value}`);
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
if (kind === "string") {
|
|
931
|
+
if (typeof value !== "string") throw new Error(`Invalid value for ${context}. Expected string for type ${col._typname}, got ${typeof value}`);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
if (kind === "boolean") {
|
|
935
|
+
if (typeof value !== "boolean") throw new Error(`Invalid value for ${context}. Expected boolean for type ${col._typname}, got ${typeof value}`);
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
if (kind === "json") {
|
|
939
|
+
if (!isValidJsonLike(value)) throw new Error(`Invalid value for ${context}. Expected JSON-compatible value for type ${col._typname}`);
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
if (kind === "range") {
|
|
943
|
+
validateRangeForColumn(col, value, context);
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
if (kind === "interval") {
|
|
947
|
+
validateIntervalForColumn(col, value, context);
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
if (kind === "geom") {
|
|
951
|
+
validateGeometryForColumn(col, value, context);
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
834
954
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
955
|
+
function validateComparisonValue(col, key, value, op) {
|
|
956
|
+
if (value === void 0 || value === null) throw new Error(`Operator ${op} on column ${key} requires a non-null value. Use isnull/notnull for null checks.`);
|
|
957
|
+
if (col._is_array) {
|
|
958
|
+
if (!Array.isArray(value)) throw new Error(`Operator ${op} on array column ${key} requires an array value`);
|
|
959
|
+
for (const [i, v] of value.entries()) validateScalarForColumn(col, v, `column ${key}[${i}]`);
|
|
960
|
+
} else {
|
|
961
|
+
if (Array.isArray(value) && !isArrayShapedGeomScalarValue(col, value)) throw new Error(`Operator ${op} on scalar column ${key} cannot accept an array value`);
|
|
962
|
+
validateScalarForColumn(col, value, `column ${key}`);
|
|
842
963
|
}
|
|
843
964
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
for (const c of tc) if (c.constraint === "foreign" && c.referenced_table === base.name && c.columns?.length && c.referenced_columns?.length && c.columns.length === c.referenced_columns.length) return c.referenced_columns.map((rcol, i) => ({
|
|
853
|
-
left: String(rcol),
|
|
854
|
-
right: String(c.columns[i])
|
|
855
|
-
}));
|
|
856
|
-
return null;
|
|
857
|
-
}
|
|
858
|
-
var TableQueryImpl = class {
|
|
859
|
-
constructor(schema, tableName) {
|
|
860
|
-
this.schema = schema;
|
|
861
|
-
this.table = findTable(schema, tableName);
|
|
965
|
+
function validateInArrayValues(col, key, values, op) {
|
|
966
|
+
if (!col._is_array) {
|
|
967
|
+
let idx = 0;
|
|
968
|
+
for (const v of values) {
|
|
969
|
+
validateScalarForColumn(col, v, `column ${key} (element ${idx})`);
|
|
970
|
+
idx++;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
862
973
|
}
|
|
863
|
-
|
|
864
|
-
const
|
|
865
|
-
const
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
const
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
if (value === null) {
|
|
913
|
-
if (!col.is_nullable) throw new Error(`Column ${table.name}.${key} is not nullable; cannot compare to null`);
|
|
914
|
-
andParts.push(`"${table.name}"."${key}" is null`);
|
|
915
|
-
} else if (Array.isArray(value) && !isArrayShapedGeomScalarValue(col, value)) {
|
|
916
|
-
validateInArrayValues(col, key, value, "in");
|
|
917
|
-
andParts.push(`"${table.name}"."${key}" = ANY(:${paramName})`);
|
|
918
|
-
params[paramName] = value;
|
|
919
|
-
addTypeHintForParam(type_hints, paramName, col, value);
|
|
920
|
-
} else {
|
|
921
|
-
validateScalarForColumn(col, value, `column ${key}`);
|
|
922
|
-
andParts.push(`"${table.name}"."${key}" = :${paramName}`);
|
|
923
|
-
params[paramName] = value;
|
|
924
|
-
addTypeHintForParam(type_hints, paramName, col, value);
|
|
974
|
+
function findJoinOn(base, target) {
|
|
975
|
+
const bc = base.constraints || [];
|
|
976
|
+
const tc = target.constraints || [];
|
|
977
|
+
for (const c of bc) if (c.constraint === "foreign" && c.referenced_table === target.name && c.columns?.length && c.referenced_columns?.length && c.columns.length === c.referenced_columns.length) return c.columns.map((col, i) => ({
|
|
978
|
+
left: String(col),
|
|
979
|
+
right: String(c.referenced_columns[i])
|
|
980
|
+
}));
|
|
981
|
+
for (const c of tc) if (c.constraint === "foreign" && c.referenced_table === base.name && c.columns?.length && c.referenced_columns?.length && c.columns.length === c.referenced_columns.length) return c.referenced_columns.map((rcol, i) => ({
|
|
982
|
+
left: String(rcol),
|
|
983
|
+
right: String(c.columns[i])
|
|
984
|
+
}));
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
var TableQueryImpl = class {
|
|
988
|
+
constructor(schema, tableName) {
|
|
989
|
+
this.schema = schema;
|
|
990
|
+
this.table = findTable(schema, tableName);
|
|
991
|
+
}
|
|
992
|
+
select(cols) {
|
|
993
|
+
const table = this.table;
|
|
994
|
+
const schema = this.schema;
|
|
995
|
+
const selected = cols && cols.length ? cols : ["*"];
|
|
996
|
+
const state = {
|
|
997
|
+
table,
|
|
998
|
+
selected,
|
|
999
|
+
where: {},
|
|
1000
|
+
orWhereGroups: [],
|
|
1001
|
+
andOps: [],
|
|
1002
|
+
orOpGroups: [],
|
|
1003
|
+
order: [],
|
|
1004
|
+
limit: void 0,
|
|
1005
|
+
offset: void 0,
|
|
1006
|
+
joins: [],
|
|
1007
|
+
joinSelections: []
|
|
1008
|
+
};
|
|
1009
|
+
return new class {
|
|
1010
|
+
constructor() {
|
|
1011
|
+
this.s = state;
|
|
1012
|
+
this.toSql = () => {
|
|
1013
|
+
const params = {};
|
|
1014
|
+
const type_hints = {};
|
|
1015
|
+
let p = 0;
|
|
1016
|
+
const parts = [];
|
|
1017
|
+
const selectParts = [];
|
|
1018
|
+
if (selected.length === 1 && selected[0] === "*") selectParts.push(`"${table.name}".*`);
|
|
1019
|
+
else {
|
|
1020
|
+
const sel = selected;
|
|
1021
|
+
for (const c of sel) findColumn(table, String(c));
|
|
1022
|
+
selectParts.push(sel.map((c) => `"${table.name}"."${c}"`).join(", "));
|
|
925
1023
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
const qualified = `"${table.name}"."${key}"`;
|
|
932
|
-
if (op === "isnull" || op === "notnull") {
|
|
933
|
-
if (pred.value !== void 0) throw new Error(`Operator ${op} does not take a value for column ${key}`);
|
|
934
|
-
andParts.push(`${qualified} is ${op === "isnull" ? "null" : "not null"}`);
|
|
935
|
-
} else if (op === "in" || op === "notin") {
|
|
936
|
-
const val = pred.value;
|
|
937
|
-
if (!Array.isArray(val)) throw new Error(`Operator ${op} requires an array value for column ${key}`);
|
|
938
|
-
validateInArrayValues(col, key, val, op);
|
|
939
|
-
p += 1;
|
|
940
|
-
const paramName = `${table.name}_${key}_${p}`;
|
|
941
|
-
andParts.push(`${qualified} ${op === "in" ? "= ANY" : "!= ALL"}(:${paramName})`);
|
|
942
|
-
params[paramName] = val;
|
|
943
|
-
addTypeHintForParam(type_hints, paramName, col, val);
|
|
944
|
-
} else if (op === "like" || op === "ilike" || op === "notlike" || op === "notilike") {
|
|
945
|
-
const val = pred.value;
|
|
946
|
-
if (typeof val !== "string") throw new Error(`Operator ${op} requires a string value for column ${key}`);
|
|
947
|
-
p += 1;
|
|
948
|
-
const paramName = `${table.name}_${key}_${p}`;
|
|
949
|
-
const sqlOp = op === "like" ? "like" : op === "ilike" ? "ilike" : op === "notlike" ? "not like" : "not ilike";
|
|
950
|
-
andParts.push(`${qualified} ${sqlOp} :${paramName}`);
|
|
951
|
-
params[paramName] = val;
|
|
952
|
-
addTypeHintForParam(type_hints, paramName, col, val);
|
|
953
|
-
} else {
|
|
954
|
-
const val = pred.value;
|
|
955
|
-
if (val === void 0) throw new Error(`Operator ${op} requires a value for column ${key}`);
|
|
956
|
-
validateComparisonValue(col, key, val, op);
|
|
957
|
-
p += 1;
|
|
958
|
-
const paramName = `${table.name}_${key}_${p}`;
|
|
959
|
-
if (op === "=") andParts.push(`${qualified} = :${paramName}`);
|
|
960
|
-
else if (op === "!=") andParts.push(`${qualified} <> :${paramName}`);
|
|
961
|
-
else if (op === "<" || op === "<=" || op === ">" || op === ">=") andParts.push(`${qualified} ${op} :${paramName}`);
|
|
962
|
-
else throw new Error(`Unsupported operator: ${op}`);
|
|
963
|
-
params[paramName] = val;
|
|
964
|
-
addTypeHintForParam(type_hints, paramName, col, val);
|
|
1024
|
+
for (const js of state.joinSelections) if (js.selected.length === 1 && js.selected[0] === "*") selectParts.push(`"${js.target.name}".*`);
|
|
1025
|
+
else {
|
|
1026
|
+
const cols$1 = js.selected;
|
|
1027
|
+
for (const c of cols$1) findColumn(js.target, String(c));
|
|
1028
|
+
selectParts.push(cols$1.map((c) => `"${js.target.name}"."${c}"`).join(", "));
|
|
965
1029
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1030
|
+
parts.push(`select ${selectParts.join(", ")} from "${schema.name}"."${table.name}"`);
|
|
1031
|
+
for (const j of state.joins) {
|
|
1032
|
+
const onExpr = j.on.map((p$1) => `"${j.source.name}"."${p$1.left}" = "${j.target.name}"."${p$1.right}"`).join(" and ");
|
|
1033
|
+
parts.push(`${j.type} join "${schema.name}"."${j.target.name}" on ${onExpr}`);
|
|
1034
|
+
}
|
|
1035
|
+
const andParts = [];
|
|
1036
|
+
for (const key in state.where) {
|
|
1037
|
+
const value = state.where[key];
|
|
972
1038
|
const col = findColumn(table, key);
|
|
973
1039
|
p += 1;
|
|
974
1040
|
const paramName = `${table.name}_${key}_${p}`;
|
|
975
1041
|
if (value === null) {
|
|
976
1042
|
if (!col.is_nullable) throw new Error(`Column ${table.name}.${key} is not nullable; cannot compare to null`);
|
|
977
|
-
|
|
1043
|
+
andParts.push(`"${table.name}"."${key}" is null`);
|
|
978
1044
|
} else if (Array.isArray(value) && !isArrayShapedGeomScalarValue(col, value)) {
|
|
979
1045
|
validateInArrayValues(col, key, value, "in");
|
|
980
|
-
|
|
1046
|
+
andParts.push(`"${table.name}"."${key}" = ANY(:${paramName})`);
|
|
981
1047
|
params[paramName] = value;
|
|
982
1048
|
addTypeHintForParam(type_hints, paramName, col, value);
|
|
983
1049
|
} else {
|
|
984
1050
|
validateScalarForColumn(col, value, `column ${key}`);
|
|
985
|
-
|
|
1051
|
+
andParts.push(`"${table.name}"."${key}" = :${paramName}`);
|
|
986
1052
|
params[paramName] = value;
|
|
987
1053
|
addTypeHintForParam(type_hints, paramName, col, value);
|
|
988
1054
|
}
|
|
989
1055
|
}
|
|
990
|
-
|
|
991
|
-
}
|
|
992
|
-
for (const group of state.orOpGroups) {
|
|
993
|
-
const orParts = [];
|
|
994
|
-
for (const pred of group) {
|
|
1056
|
+
for (const pred of state.andOps) {
|
|
995
1057
|
const key = String(pred.col);
|
|
996
1058
|
const op = String(pred.op);
|
|
997
1059
|
const col = findColumn(table, key);
|
|
998
1060
|
const qualified = `"${table.name}"."${key}"`;
|
|
999
1061
|
if (op === "isnull" || op === "notnull") {
|
|
1000
1062
|
if (pred.value !== void 0) throw new Error(`Operator ${op} does not take a value for column ${key}`);
|
|
1001
|
-
|
|
1063
|
+
andParts.push(`${qualified} is ${op === "isnull" ? "null" : "not null"}`);
|
|
1002
1064
|
} else if (op === "in" || op === "notin") {
|
|
1003
1065
|
const val = pred.value;
|
|
1004
1066
|
if (!Array.isArray(val)) throw new Error(`Operator ${op} requires an array value for column ${key}`);
|
|
1005
1067
|
validateInArrayValues(col, key, val, op);
|
|
1006
1068
|
p += 1;
|
|
1007
1069
|
const paramName = `${table.name}_${key}_${p}`;
|
|
1008
|
-
|
|
1070
|
+
andParts.push(`${qualified} ${op === "in" ? "= ANY" : "!= ALL"}(:${paramName})`);
|
|
1009
1071
|
params[paramName] = val;
|
|
1010
1072
|
addTypeHintForParam(type_hints, paramName, col, val);
|
|
1011
1073
|
} else if (op === "like" || op === "ilike" || op === "notlike" || op === "notilike") {
|
|
@@ -1014,7 +1076,7 @@ var TableQueryImpl = class {
|
|
|
1014
1076
|
p += 1;
|
|
1015
1077
|
const paramName = `${table.name}_${key}_${p}`;
|
|
1016
1078
|
const sqlOp = op === "like" ? "like" : op === "ilike" ? "ilike" : op === "notlike" ? "not like" : "not ilike";
|
|
1017
|
-
|
|
1079
|
+
andParts.push(`${qualified} ${sqlOp} :${paramName}`);
|
|
1018
1080
|
params[paramName] = val;
|
|
1019
1081
|
addTypeHintForParam(type_hints, paramName, col, val);
|
|
1020
1082
|
} else {
|
|
@@ -1023,252 +1085,137 @@ var TableQueryImpl = class {
|
|
|
1023
1085
|
validateComparisonValue(col, key, val, op);
|
|
1024
1086
|
p += 1;
|
|
1025
1087
|
const paramName = `${table.name}_${key}_${p}`;
|
|
1026
|
-
if (op === "=")
|
|
1027
|
-
else if (op === "!=")
|
|
1028
|
-
else if (op === "<" || op === "<=" || op === ">" || op === ">=")
|
|
1088
|
+
if (op === "=") andParts.push(`${qualified} = :${paramName}`);
|
|
1089
|
+
else if (op === "!=") andParts.push(`${qualified} <> :${paramName}`);
|
|
1090
|
+
else if (op === "<" || op === "<=" || op === ">" || op === ">=") andParts.push(`${qualified} ${op} :${paramName}`);
|
|
1029
1091
|
else throw new Error(`Unsupported operator: ${op}`);
|
|
1030
1092
|
params[paramName] = val;
|
|
1031
1093
|
addTypeHintForParam(type_hints, paramName, col, val);
|
|
1032
1094
|
}
|
|
1033
1095
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1096
|
+
const orGroupSql = [];
|
|
1097
|
+
for (const group of state.orWhereGroups) {
|
|
1098
|
+
const orParts = [];
|
|
1099
|
+
for (const key in group) {
|
|
1100
|
+
const value = group[key];
|
|
1101
|
+
const col = findColumn(table, key);
|
|
1102
|
+
p += 1;
|
|
1103
|
+
const paramName = `${table.name}_${key}_${p}`;
|
|
1104
|
+
if (value === null) {
|
|
1105
|
+
if (!col.is_nullable) throw new Error(`Column ${table.name}.${key} is not nullable; cannot compare to null`);
|
|
1106
|
+
orParts.push(`"${table.name}"."${key}" is null`);
|
|
1107
|
+
} else if (Array.isArray(value) && !isArrayShapedGeomScalarValue(col, value)) {
|
|
1108
|
+
validateInArrayValues(col, key, value, "in");
|
|
1109
|
+
orParts.push(`"${table.name}"."${key}" = ANY(:${paramName})`);
|
|
1110
|
+
params[paramName] = value;
|
|
1111
|
+
addTypeHintForParam(type_hints, paramName, col, value);
|
|
1112
|
+
} else {
|
|
1113
|
+
validateScalarForColumn(col, value, `column ${key}`);
|
|
1114
|
+
orParts.push(`"${table.name}"."${key}" = :${paramName}`);
|
|
1115
|
+
params[paramName] = value;
|
|
1116
|
+
addTypeHintForParam(type_hints, paramName, col, value);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
if (orParts.length) orGroupSql.push("(" + orParts.join(" or ") + ")");
|
|
1120
|
+
}
|
|
1121
|
+
for (const group of state.orOpGroups) {
|
|
1122
|
+
const orParts = [];
|
|
1123
|
+
for (const pred of group) {
|
|
1124
|
+
const key = String(pred.col);
|
|
1125
|
+
const op = String(pred.op);
|
|
1126
|
+
const col = findColumn(table, key);
|
|
1127
|
+
const qualified = `"${table.name}"."${key}"`;
|
|
1128
|
+
if (op === "isnull" || op === "notnull") {
|
|
1129
|
+
if (pred.value !== void 0) throw new Error(`Operator ${op} does not take a value for column ${key}`);
|
|
1130
|
+
orParts.push(`${qualified} is ${op === "isnull" ? "null" : "not null"}`);
|
|
1131
|
+
} else if (op === "in" || op === "notin") {
|
|
1132
|
+
const val = pred.value;
|
|
1133
|
+
if (!Array.isArray(val)) throw new Error(`Operator ${op} requires an array value for column ${key}`);
|
|
1134
|
+
validateInArrayValues(col, key, val, op);
|
|
1135
|
+
p += 1;
|
|
1136
|
+
const paramName = `${table.name}_${key}_${p}`;
|
|
1137
|
+
orParts.push(`${qualified} ${op === "in" ? "= ANY" : "!= ALL"}(:${paramName})`);
|
|
1138
|
+
params[paramName] = val;
|
|
1139
|
+
addTypeHintForParam(type_hints, paramName, col, val);
|
|
1140
|
+
} else if (op === "like" || op === "ilike" || op === "notlike" || op === "notilike") {
|
|
1141
|
+
const val = pred.value;
|
|
1142
|
+
if (typeof val !== "string") throw new Error(`Operator ${op} requires a string value for column ${key}`);
|
|
1143
|
+
p += 1;
|
|
1144
|
+
const paramName = `${table.name}_${key}_${p}`;
|
|
1145
|
+
const sqlOp = op === "like" ? "like" : op === "ilike" ? "ilike" : op === "notlike" ? "not like" : "not ilike";
|
|
1146
|
+
orParts.push(`${qualified} ${sqlOp} :${paramName}`);
|
|
1147
|
+
params[paramName] = val;
|
|
1148
|
+
addTypeHintForParam(type_hints, paramName, col, val);
|
|
1149
|
+
} else {
|
|
1150
|
+
const val = pred.value;
|
|
1151
|
+
if (val === void 0) throw new Error(`Operator ${op} requires a value for column ${key}`);
|
|
1152
|
+
validateComparisonValue(col, key, val, op);
|
|
1153
|
+
p += 1;
|
|
1154
|
+
const paramName = `${table.name}_${key}_${p}`;
|
|
1155
|
+
if (op === "=") orParts.push(`${qualified} = :${paramName}`);
|
|
1156
|
+
else if (op === "!=") orParts.push(`${qualified} <> :${paramName}`);
|
|
1157
|
+
else if (op === "<" || op === "<=" || op === ">" || op === ">=") orParts.push(`${qualified} ${op} :${paramName}`);
|
|
1158
|
+
else throw new Error(`Unsupported operator: ${op}`);
|
|
1159
|
+
params[paramName] = val;
|
|
1160
|
+
addTypeHintForParam(type_hints, paramName, col, val);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
if (orParts.length) orGroupSql.push("(" + orParts.join(" or ") + ")");
|
|
1164
|
+
}
|
|
1165
|
+
if (orGroupSql.length) {
|
|
1166
|
+
const andSql = andParts.length ? `(${andParts.join(" and ")})` : "";
|
|
1167
|
+
const orSql = orGroupSql.join(" or ");
|
|
1168
|
+
const full = andSql ? `${andSql} or ${orSql}` : orSql;
|
|
1169
|
+
if (full.trim().length) parts.push("where " + full);
|
|
1170
|
+
} else if (andParts.length) parts.push("where " + andParts.join(" and "));
|
|
1171
|
+
if (state.order.length) {
|
|
1172
|
+
const orders = [];
|
|
1173
|
+
for (const o of state.order) orders.push(`"${table.name}"."${o.col}" ${o.dir}`);
|
|
1174
|
+
parts.push("order by " + orders.join(", "));
|
|
1175
|
+
}
|
|
1176
|
+
if (state.limit !== void 0) parts.push("limit " + state.limit);
|
|
1177
|
+
if (state.offset !== void 0) parts.push("offset " + state.offset);
|
|
1178
|
+
return {
|
|
1179
|
+
q: parts.join(" "),
|
|
1180
|
+
params: Object.keys(params).length > 0 ? params : void 0,
|
|
1181
|
+
type_hints: Object.keys(type_hints).length ? type_hints : void 0
|
|
1182
|
+
};
|
|
1053
1183
|
};
|
|
1054
|
-
};
|
|
1055
|
-
}
|
|
1056
|
-
selectFrom(tableName, cols$1) {
|
|
1057
|
-
const jtName = String(tableName);
|
|
1058
|
-
const join = this.s.joins.find((j) => j.target.name === jtName);
|
|
1059
|
-
if (!join) throw new Error(`selectFrom('${jtName}') requires a prior join('${jtName}') call`);
|
|
1060
|
-
const sel = !cols$1 || cols$1.length === 0 ? ["*"] : cols$1.map(String);
|
|
1061
|
-
if (!(sel.length === 1 && sel[0] === "*")) for (const c of sel) findColumn(join.target, String(c));
|
|
1062
|
-
const existing = this.s.joinSelections.find((js) => js.target.name === jtName);
|
|
1063
|
-
if (existing) {
|
|
1064
|
-
if (existing.selected.includes("*")) return this;
|
|
1065
|
-
if (sel.length === 1 && sel[0] === "*") existing.selected = ["*"];
|
|
1066
|
-
else {
|
|
1067
|
-
const set = new Set(existing.selected);
|
|
1068
|
-
for (const c of sel) set.add(String(c));
|
|
1069
|
-
existing.selected = Array.from(set);
|
|
1070
|
-
}
|
|
1071
|
-
} else this.s.joinSelections.push({
|
|
1072
|
-
target: join.target,
|
|
1073
|
-
selected: sel
|
|
1074
|
-
});
|
|
1075
|
-
return this;
|
|
1076
|
-
}
|
|
1077
|
-
andWhere(where) {
|
|
1078
|
-
for (const k in where) this.s.where[k] = where[k];
|
|
1079
|
-
return this;
|
|
1080
|
-
}
|
|
1081
|
-
/** @deprecated Use andWhere() instead */
|
|
1082
|
-
where(where) {
|
|
1083
|
-
return this.andWhere(where);
|
|
1084
|
-
}
|
|
1085
|
-
orWhere(where) {
|
|
1086
|
-
this.s.orWhereGroups.push(where);
|
|
1087
|
-
return this;
|
|
1088
|
-
}
|
|
1089
|
-
wherePk(pk) {
|
|
1090
|
-
const pkCols = getPrimaryKeyColumns(table);
|
|
1091
|
-
if (!pkCols || pkCols.length === 0) throw new Error(`Table ${table.name} does not have a primary key`);
|
|
1092
|
-
if (pkCols.length === 1) {
|
|
1093
|
-
const colName = pkCols[0];
|
|
1094
|
-
const colDef = findColumn(table, colName);
|
|
1095
|
-
if (pk === null || pk === void 0) throw new Error(`wherePk on ${table.name} requires a non-null value for primary key column ${colName}`);
|
|
1096
|
-
if (Array.isArray(pk)) throw new Error(`wherePk on ${table.name} expects a scalar for primary key column ${colName}, not an array`);
|
|
1097
|
-
if (isPlainObject(pk)) throw new Error(`wherePk on ${table.name} expects a scalar for primary key column ${colName}`);
|
|
1098
|
-
validateScalarForColumn(colDef, pk, `primary key ${table.name}.${colName}`);
|
|
1099
|
-
this.s.where[colName] = pk;
|
|
1100
|
-
return this;
|
|
1101
|
-
} else {
|
|
1102
|
-
if (!isPlainObject(pk) || pk === null) throw new Error(`wherePk on ${table.name} requires an object with keys: ${pkCols.join(", ")}`);
|
|
1103
|
-
const obj = pk;
|
|
1104
|
-
for (const k of Object.keys(obj)) if (!pkCols.includes(k)) throw new Error(`wherePk received unknown key '${k}'. Expected keys: ${pkCols.join(", ")}`);
|
|
1105
|
-
for (const colName of pkCols) {
|
|
1106
|
-
if (!(colName in obj)) throw new Error(`wherePk missing key '${colName}'. Required keys: ${pkCols.join(", ")}`);
|
|
1107
|
-
const v = obj[colName];
|
|
1108
|
-
if (v === null || v === void 0) throw new Error(`wherePk on ${table.name} requires non-null values for all primary key columns (${pkCols.join(", ")})`);
|
|
1109
|
-
if (Array.isArray(v)) throw new Error(`wherePk on ${table.name} expects scalar values for primary key column ${colName}, not an array`);
|
|
1110
|
-
validateScalarForColumn(findColumn(table, colName), v, `primary key ${table.name}.${colName}`);
|
|
1111
|
-
this.s.where[colName] = v;
|
|
1112
|
-
}
|
|
1113
|
-
return this;
|
|
1114
1184
|
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
}
|
|
1134
|
-
andWhereOpGroup(predicates) {
|
|
1135
|
-
const group = predicates.map((p) => ({
|
|
1136
|
-
col: p[0],
|
|
1137
|
-
op: p[1],
|
|
1138
|
-
value: p[2]
|
|
1139
|
-
}));
|
|
1140
|
-
for (const g of group) this.s.andOps.push(g);
|
|
1141
|
-
return this;
|
|
1142
|
-
}
|
|
1143
|
-
orWhereOpGroup(predicates) {
|
|
1144
|
-
const group = predicates.map((p) => ({
|
|
1145
|
-
col: p[0],
|
|
1146
|
-
op: p[1],
|
|
1147
|
-
value: p[2]
|
|
1148
|
-
}));
|
|
1149
|
-
this.s.orOpGroups.push(group);
|
|
1150
|
-
return this;
|
|
1151
|
-
}
|
|
1152
|
-
orderBy(order) {
|
|
1153
|
-
this.s.order = [];
|
|
1154
|
-
const isValidDir = (d) => d === "asc" || d === "desc";
|
|
1155
|
-
if (typeof order === "string") {
|
|
1156
|
-
findColumn(table, String(order));
|
|
1157
|
-
this.s.order.push({
|
|
1158
|
-
col: order,
|
|
1159
|
-
dir: "asc"
|
|
1160
|
-
});
|
|
1161
|
-
} else for (const item of order) {
|
|
1162
|
-
const col = String(item[0]);
|
|
1163
|
-
const dir = String(item[1]);
|
|
1164
|
-
findColumn(table, col);
|
|
1165
|
-
if (!isValidDir(dir)) throw new Error(`Invalid order direction: ${dir}. Allowed: asc | desc`);
|
|
1166
|
-
this.s.order.push({
|
|
1167
|
-
col: item[0],
|
|
1168
|
-
dir
|
|
1185
|
+
selectFrom(tableName, cols$1) {
|
|
1186
|
+
const jtName = String(tableName);
|
|
1187
|
+
const join = this.s.joins.find((j) => j.target.name === jtName);
|
|
1188
|
+
if (!join) throw new Error(`selectFrom('${jtName}') requires a prior join('${jtName}') call`);
|
|
1189
|
+
const sel = !cols$1 || cols$1.length === 0 ? ["*"] : cols$1.map(String);
|
|
1190
|
+
if (!(sel.length === 1 && sel[0] === "*")) for (const c of sel) findColumn(join.target, String(c));
|
|
1191
|
+
const existing = this.s.joinSelections.find((js) => js.target.name === jtName);
|
|
1192
|
+
if (existing) {
|
|
1193
|
+
if (existing.selected.includes("*")) return this;
|
|
1194
|
+
if (sel.length === 1 && sel[0] === "*") existing.selected = ["*"];
|
|
1195
|
+
else {
|
|
1196
|
+
const set = new Set(existing.selected);
|
|
1197
|
+
for (const c of sel) set.add(String(c));
|
|
1198
|
+
existing.selected = Array.from(set);
|
|
1199
|
+
}
|
|
1200
|
+
} else this.s.joinSelections.push({
|
|
1201
|
+
target: join.target,
|
|
1202
|
+
selected: sel
|
|
1169
1203
|
});
|
|
1204
|
+
return this;
|
|
1170
1205
|
}
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
if (typeof n !== "number" || !Number.isFinite(n) || !Number.isInteger(n) || n < 0) throw new Error(`Invalid limit: ${n}. Limit must be a non-negative integer`);
|
|
1175
|
-
this.s.limit = n;
|
|
1176
|
-
return this;
|
|
1177
|
-
}
|
|
1178
|
-
offset(n) {
|
|
1179
|
-
if (typeof n !== "number" || !Number.isFinite(n) || !Number.isInteger(n) || n < 0) throw new Error(`Invalid offset: ${n}. Offset must be a non-negative integer`);
|
|
1180
|
-
this.s.offset = n;
|
|
1181
|
-
return this;
|
|
1182
|
-
}
|
|
1183
|
-
join(tableName, type = "inner") {
|
|
1184
|
-
const target = findTable(schema, String(tableName));
|
|
1185
|
-
const sources = [];
|
|
1186
|
-
for (let i = this.s.joins.length - 1; i >= 0; i--) {
|
|
1187
|
-
const src = this.s.joins[i].target;
|
|
1188
|
-
if (!sources.some((s) => s.name === src.name)) sources.push(src);
|
|
1189
|
-
}
|
|
1190
|
-
if (!sources.some((s) => s.name === table.name)) sources.push(table);
|
|
1191
|
-
let pickedSource = null;
|
|
1192
|
-
let pairs = null;
|
|
1193
|
-
for (const src of sources) {
|
|
1194
|
-
const p = findJoinOn(src, target);
|
|
1195
|
-
if (p && p.length) {
|
|
1196
|
-
pickedSource = src;
|
|
1197
|
-
pairs = p;
|
|
1198
|
-
break;
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
if (!pairs || !pickedSource) {
|
|
1202
|
-
const candidates = sources.map((s) => s.name).join(", ");
|
|
1203
|
-
throw new Error(`No foreign key relation found between any of [${candidates}] and ${target.name}`);
|
|
1206
|
+
andWhere(where) {
|
|
1207
|
+
for (const k in where) this.s.where[k] = where[k];
|
|
1208
|
+
return this;
|
|
1204
1209
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
type: jt,
|
|
1209
|
-
source: pickedSource,
|
|
1210
|
-
target,
|
|
1211
|
-
on: pairs
|
|
1212
|
-
});
|
|
1213
|
-
return this;
|
|
1214
|
-
}
|
|
1215
|
-
}();
|
|
1216
|
-
}
|
|
1217
|
-
insert(values) {
|
|
1218
|
-
const table = this.table;
|
|
1219
|
-
const schema = this.schema;
|
|
1220
|
-
const state = {
|
|
1221
|
-
values,
|
|
1222
|
-
returning: []
|
|
1223
|
-
};
|
|
1224
|
-
return new class {
|
|
1225
|
-
returning(cols) {
|
|
1226
|
-
state.returning = cols || [];
|
|
1227
|
-
return this;
|
|
1228
|
-
}
|
|
1229
|
-
toSql() {
|
|
1230
|
-
const colsArr = [];
|
|
1231
|
-
const vals = [];
|
|
1232
|
-
const params = {};
|
|
1233
|
-
const type_hints = {};
|
|
1234
|
-
let p = 0;
|
|
1235
|
-
for (const key in state.values) {
|
|
1236
|
-
const value = state.values[key];
|
|
1237
|
-
const col = findColumn(table, key);
|
|
1238
|
-
p += 1;
|
|
1239
|
-
const paramName = `${table.name}_${key}_${p}`;
|
|
1240
|
-
colsArr.push(`"${key}"`);
|
|
1241
|
-
vals.push(`:${paramName}`);
|
|
1242
|
-
params[paramName] = value;
|
|
1243
|
-
addTypeHintForParam(type_hints, paramName, col, value);
|
|
1210
|
+
/** @deprecated Use andWhere() instead */
|
|
1211
|
+
where(where) {
|
|
1212
|
+
return this.andWhere(where);
|
|
1244
1213
|
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
if (state.returning.length) parts.push("returning " + state.returning.join(", "));
|
|
1248
|
-
const req = {
|
|
1249
|
-
q: parts.join(" "),
|
|
1250
|
-
params: Object.keys(params).length > 0 ? params : void 0,
|
|
1251
|
-
type_hints: Object.keys(type_hints).length ? type_hints : void 0
|
|
1252
|
-
};
|
|
1253
|
-
return state.returning.length ? req : req;
|
|
1254
|
-
}
|
|
1255
|
-
}();
|
|
1256
|
-
}
|
|
1257
|
-
update(values) {
|
|
1258
|
-
const table = this.table;
|
|
1259
|
-
const schema = this.schema;
|
|
1260
|
-
const state = {
|
|
1261
|
-
values,
|
|
1262
|
-
where: {},
|
|
1263
|
-
returning: []
|
|
1264
|
-
};
|
|
1265
|
-
return new class {
|
|
1266
|
-
constructor() {
|
|
1267
|
-
this.where = (w) => {
|
|
1268
|
-
for (const k in w) state.where[k] = w[k];
|
|
1214
|
+
orWhere(where) {
|
|
1215
|
+
this.s.orWhereGroups.push(where);
|
|
1269
1216
|
return this;
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1217
|
+
}
|
|
1218
|
+
wherePk(pk) {
|
|
1272
1219
|
const pkCols = getPrimaryKeyColumns(table);
|
|
1273
1220
|
if (!pkCols || pkCols.length === 0) throw new Error(`Table ${table.name} does not have a primary key`);
|
|
1274
1221
|
if (pkCols.length === 1) {
|
|
@@ -1278,7 +1225,8 @@ var TableQueryImpl = class {
|
|
|
1278
1225
|
if (Array.isArray(pk)) throw new Error(`wherePk on ${table.name} expects a scalar for primary key column ${colName}, not an array`);
|
|
1279
1226
|
if (isPlainObject(pk)) throw new Error(`wherePk on ${table.name} expects a scalar for primary key column ${colName}`);
|
|
1280
1227
|
validateScalarForColumn(colDef, pk, `primary key ${table.name}.${colName}`);
|
|
1281
|
-
|
|
1228
|
+
this.s.where[colName] = pk;
|
|
1229
|
+
return this;
|
|
1282
1230
|
} else {
|
|
1283
1231
|
if (!isPlainObject(pk) || pk === null) throw new Error(`wherePk on ${table.name} requires an object with keys: ${pkCols.join(", ")}`);
|
|
1284
1232
|
const obj = pk;
|
|
@@ -1289,146 +1237,336 @@ var TableQueryImpl = class {
|
|
|
1289
1237
|
if (v === null || v === void 0) throw new Error(`wherePk on ${table.name} requires non-null values for all primary key columns (${pkCols.join(", ")})`);
|
|
1290
1238
|
if (Array.isArray(v)) throw new Error(`wherePk on ${table.name} expects scalar values for primary key column ${colName}, not an array`);
|
|
1291
1239
|
validateScalarForColumn(findColumn(table, colName), v, `primary key ${table.name}.${colName}`);
|
|
1292
|
-
|
|
1240
|
+
this.s.where[colName] = v;
|
|
1293
1241
|
}
|
|
1242
|
+
return this;
|
|
1294
1243
|
}
|
|
1244
|
+
}
|
|
1245
|
+
andWhereOp(col, op, ...args) {
|
|
1246
|
+
const value = args[0];
|
|
1247
|
+
this.s.andOps.push({
|
|
1248
|
+
col,
|
|
1249
|
+
op,
|
|
1250
|
+
value
|
|
1251
|
+
});
|
|
1295
1252
|
return this;
|
|
1296
|
-
};
|
|
1297
|
-
}
|
|
1298
|
-
returning(cols) {
|
|
1299
|
-
state.returning = cols || [];
|
|
1300
|
-
return this;
|
|
1301
|
-
}
|
|
1302
|
-
toSql() {
|
|
1303
|
-
const params = {};
|
|
1304
|
-
const type_hints = {};
|
|
1305
|
-
let p = 0;
|
|
1306
|
-
const setParts = [];
|
|
1307
|
-
for (const key in state.values) {
|
|
1308
|
-
const value = state.values[key];
|
|
1309
|
-
const col = findColumn(table, key);
|
|
1310
|
-
p += 1;
|
|
1311
|
-
const paramName = `${table.name}_${key}_${p}`;
|
|
1312
|
-
setParts.push(`"${key}" = :${paramName}`);
|
|
1313
|
-
params[paramName] = value;
|
|
1314
|
-
addTypeHintForParam(type_hints, paramName, col, value);
|
|
1315
1253
|
}
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1254
|
+
orWhereOp(col, op, ...args) {
|
|
1255
|
+
const value = args[0];
|
|
1256
|
+
this.s.orOpGroups.push([{
|
|
1257
|
+
col,
|
|
1258
|
+
op,
|
|
1259
|
+
value
|
|
1260
|
+
}]);
|
|
1261
|
+
return this;
|
|
1262
|
+
}
|
|
1263
|
+
andWhereOpGroup(predicates) {
|
|
1264
|
+
const group = predicates.map((p) => ({
|
|
1265
|
+
col: p[0],
|
|
1266
|
+
op: p[1],
|
|
1267
|
+
value: p[2]
|
|
1268
|
+
}));
|
|
1269
|
+
for (const g of group) this.s.andOps.push(g);
|
|
1270
|
+
return this;
|
|
1271
|
+
}
|
|
1272
|
+
orWhereOpGroup(predicates) {
|
|
1273
|
+
const group = predicates.map((p) => ({
|
|
1274
|
+
col: p[0],
|
|
1275
|
+
op: p[1],
|
|
1276
|
+
value: p[2]
|
|
1277
|
+
}));
|
|
1278
|
+
this.s.orOpGroups.push(group);
|
|
1279
|
+
return this;
|
|
1280
|
+
}
|
|
1281
|
+
orderBy(order) {
|
|
1282
|
+
this.s.order = [];
|
|
1283
|
+
const isValidDir = (d) => d === "asc" || d === "desc";
|
|
1284
|
+
if (typeof order === "string") {
|
|
1285
|
+
findColumn(table, String(order));
|
|
1286
|
+
this.s.order.push({
|
|
1287
|
+
col: order,
|
|
1288
|
+
dir: "asc"
|
|
1289
|
+
});
|
|
1290
|
+
} else for (const item of order) {
|
|
1291
|
+
const col = String(item[0]);
|
|
1292
|
+
const dir = String(item[1]);
|
|
1293
|
+
findColumn(table, col);
|
|
1294
|
+
if (!isValidDir(dir)) throw new Error(`Invalid order direction: ${dir}. Allowed: asc | desc`);
|
|
1295
|
+
this.s.order.push({
|
|
1296
|
+
col: item[0],
|
|
1297
|
+
dir
|
|
1298
|
+
});
|
|
1335
1299
|
}
|
|
1300
|
+
return this;
|
|
1336
1301
|
}
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
if (state.returning.length) parts.push("returning " + state.returning.join(", "));
|
|
1341
|
-
const req = {
|
|
1342
|
-
q: parts.join(" "),
|
|
1343
|
-
params: Object.keys(params).length > 0 ? params : void 0,
|
|
1344
|
-
type_hints: Object.keys(type_hints).length ? type_hints : void 0
|
|
1345
|
-
};
|
|
1346
|
-
return state.returning.length ? req : req;
|
|
1347
|
-
}
|
|
1348
|
-
}();
|
|
1349
|
-
}
|
|
1350
|
-
delete() {
|
|
1351
|
-
const table = this.table;
|
|
1352
|
-
const schema = this.schema;
|
|
1353
|
-
const state = {
|
|
1354
|
-
where: {},
|
|
1355
|
-
returning: []
|
|
1356
|
-
};
|
|
1357
|
-
return new class {
|
|
1358
|
-
constructor() {
|
|
1359
|
-
this.where = (w) => {
|
|
1360
|
-
for (const k in w) state.where[k] = w[k];
|
|
1302
|
+
limit(n) {
|
|
1303
|
+
if (typeof n !== "number" || !Number.isFinite(n) || !Number.isInteger(n) || n < 0) throw new Error(`Invalid limit: ${n}. Limit must be a non-negative integer`);
|
|
1304
|
+
this.s.limit = n;
|
|
1361
1305
|
return this;
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
const
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1306
|
+
}
|
|
1307
|
+
offset(n) {
|
|
1308
|
+
if (typeof n !== "number" || !Number.isFinite(n) || !Number.isInteger(n) || n < 0) throw new Error(`Invalid offset: ${n}. Offset must be a non-negative integer`);
|
|
1309
|
+
this.s.offset = n;
|
|
1310
|
+
return this;
|
|
1311
|
+
}
|
|
1312
|
+
join(tableName, type = "inner") {
|
|
1313
|
+
const target = findTable(schema, String(tableName));
|
|
1314
|
+
const sources = [];
|
|
1315
|
+
for (let i = this.s.joins.length - 1; i >= 0; i--) {
|
|
1316
|
+
const src = this.s.joins[i].target;
|
|
1317
|
+
if (!sources.some((s) => s.name === src.name)) sources.push(src);
|
|
1318
|
+
}
|
|
1319
|
+
if (!sources.some((s) => s.name === table.name)) sources.push(table);
|
|
1320
|
+
let pickedSource = null;
|
|
1321
|
+
let pairs = null;
|
|
1322
|
+
for (const src of sources) {
|
|
1323
|
+
const p = findJoinOn(src, target);
|
|
1324
|
+
if (p && p.length) {
|
|
1325
|
+
pickedSource = src;
|
|
1326
|
+
pairs = p;
|
|
1327
|
+
break;
|
|
1377
1328
|
}
|
|
1378
1329
|
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
returning(cols) {
|
|
1383
|
-
state.returning = cols || [];
|
|
1384
|
-
return this;
|
|
1385
|
-
}
|
|
1386
|
-
toSql() {
|
|
1387
|
-
const params = {};
|
|
1388
|
-
const type_hints = {};
|
|
1389
|
-
let p = 0;
|
|
1390
|
-
const whereClauses = [];
|
|
1391
|
-
for (const key in state.where) {
|
|
1392
|
-
const value = state.where[key];
|
|
1393
|
-
const col = findColumn(table, key);
|
|
1394
|
-
p += 1;
|
|
1395
|
-
const paramName = `${table.name}_${key}_${p}`;
|
|
1396
|
-
if (value === null) {
|
|
1397
|
-
if (!col.is_nullable) throw new Error(`Column ${table.name}.${key} is not nullable; cannot compare to null`);
|
|
1398
|
-
whereClauses.push(`"${key}" is null`);
|
|
1399
|
-
} else if (Array.isArray(value)) {
|
|
1400
|
-
validateInArrayValues(col, key, value, "in");
|
|
1401
|
-
whereClauses.push(`"${key}" = ANY(:${paramName})`);
|
|
1402
|
-
params[paramName] = value;
|
|
1403
|
-
addTypeHintForParam(type_hints, paramName, col, value);
|
|
1404
|
-
} else {
|
|
1405
|
-
validateScalarForColumn(col, value, `column ${key}`);
|
|
1406
|
-
whereClauses.push(`"${key}" = :${paramName}`);
|
|
1407
|
-
params[paramName] = value;
|
|
1408
|
-
addTypeHintForParam(type_hints, paramName, col, value);
|
|
1330
|
+
if (!pairs || !pickedSource) {
|
|
1331
|
+
const candidates = sources.map((s) => s.name).join(", ");
|
|
1332
|
+
throw new Error(`No foreign key relation found between any of [${candidates}] and ${target.name}`);
|
|
1409
1333
|
}
|
|
1334
|
+
const jt = String(type);
|
|
1335
|
+
if (jt !== "inner" && jt !== "left" && jt !== "right" && jt !== "full") throw new Error(`Invalid join type: ${jt}. Allowed: inner | left | right | full`);
|
|
1336
|
+
this.s.joins.push({
|
|
1337
|
+
type: jt,
|
|
1338
|
+
source: pickedSource,
|
|
1339
|
+
target,
|
|
1340
|
+
on: pairs
|
|
1341
|
+
});
|
|
1342
|
+
return this;
|
|
1410
1343
|
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1344
|
+
}();
|
|
1345
|
+
}
|
|
1346
|
+
insert(values) {
|
|
1347
|
+
const table = this.table;
|
|
1348
|
+
const schema = this.schema;
|
|
1349
|
+
const state = {
|
|
1350
|
+
values,
|
|
1351
|
+
returning: []
|
|
1352
|
+
};
|
|
1353
|
+
return new class {
|
|
1354
|
+
constructor() {
|
|
1355
|
+
this.returning = (cols) => {
|
|
1356
|
+
state.returning = cols || [];
|
|
1357
|
+
return this;
|
|
1358
|
+
};
|
|
1359
|
+
this.toSql = () => {
|
|
1360
|
+
const cols = [];
|
|
1361
|
+
const vals = [];
|
|
1362
|
+
const params = {};
|
|
1363
|
+
const type_hints = {};
|
|
1364
|
+
let p = 0;
|
|
1365
|
+
for (const key in state.values) {
|
|
1366
|
+
const value = state.values[key];
|
|
1367
|
+
const col = findColumn(table, key);
|
|
1368
|
+
p += 1;
|
|
1369
|
+
const paramName = `${table.name}_${key}_${p}`;
|
|
1370
|
+
cols.push(`"${key}"`);
|
|
1371
|
+
vals.push(`:${paramName}`);
|
|
1372
|
+
params[paramName] = value;
|
|
1373
|
+
addTypeHintForParam(type_hints, paramName, col, value);
|
|
1374
|
+
}
|
|
1375
|
+
const parts = [];
|
|
1376
|
+
parts.push(`insert into "${schema.name}"."${table.name}" (${cols.join(", ")}) values (${vals.join(", ")})`);
|
|
1377
|
+
if (state.returning.length) parts.push("returning " + state.returning.join(", "));
|
|
1378
|
+
return {
|
|
1379
|
+
q: parts.join(" "),
|
|
1380
|
+
params: Object.keys(params).length > 0 ? params : void 0,
|
|
1381
|
+
type_hints: Object.keys(type_hints).length ? type_hints : void 0
|
|
1382
|
+
};
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
}();
|
|
1386
|
+
}
|
|
1387
|
+
update(values) {
|
|
1388
|
+
const table = this.table;
|
|
1389
|
+
const schema = this.schema;
|
|
1390
|
+
const state = {
|
|
1391
|
+
values,
|
|
1392
|
+
where: {},
|
|
1393
|
+
returning: []
|
|
1394
|
+
};
|
|
1395
|
+
return new class {
|
|
1396
|
+
constructor() {
|
|
1397
|
+
this.where = (w) => {
|
|
1398
|
+
for (const k in w) state.where[k] = w[k];
|
|
1399
|
+
return this;
|
|
1400
|
+
};
|
|
1401
|
+
this.wherePk = (pk) => {
|
|
1402
|
+
const pkCols = getPrimaryKeyColumns(table);
|
|
1403
|
+
if (!pkCols || pkCols.length === 0) throw new Error(`Table ${table.name} does not have a primary key`);
|
|
1404
|
+
if (pkCols.length === 1) {
|
|
1405
|
+
const colName = pkCols[0];
|
|
1406
|
+
const colDef = findColumn(table, colName);
|
|
1407
|
+
if (pk === null || pk === void 0) throw new Error(`wherePk on ${table.name} requires a non-null value for primary key column ${colName}`);
|
|
1408
|
+
if (Array.isArray(pk)) throw new Error(`wherePk on ${table.name} expects a scalar for primary key column ${colName}, not an array`);
|
|
1409
|
+
if (isPlainObject(pk)) throw new Error(`wherePk on ${table.name} expects a scalar for primary key column ${colName}`);
|
|
1410
|
+
validateScalarForColumn(colDef, pk, `primary key ${table.name}.${colName}`);
|
|
1411
|
+
state.where[colName] = pk;
|
|
1412
|
+
} else {
|
|
1413
|
+
if (!isPlainObject(pk) || pk === null) throw new Error(`wherePk on ${table.name} requires an object with keys: ${pkCols.join(", ")}`);
|
|
1414
|
+
const obj = pk;
|
|
1415
|
+
for (const k of Object.keys(obj)) if (!pkCols.includes(k)) throw new Error(`wherePk received unknown key '${k}'. Expected keys: ${pkCols.join(", ")}`);
|
|
1416
|
+
for (const colName of pkCols) {
|
|
1417
|
+
if (!(colName in obj)) throw new Error(`wherePk missing key '${colName}'. Required keys: ${pkCols.join(", ")}`);
|
|
1418
|
+
const v = obj[colName];
|
|
1419
|
+
if (v === null || v === void 0) throw new Error(`wherePk on ${table.name} requires non-null values for all primary key columns (${pkCols.join(", ")})`);
|
|
1420
|
+
if (Array.isArray(v)) throw new Error(`wherePk on ${table.name} expects scalar values for primary key column ${colName}, not an array`);
|
|
1421
|
+
validateScalarForColumn(findColumn(table, colName), v, `primary key ${table.name}.${colName}`);
|
|
1422
|
+
state.where[colName] = v;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
return this;
|
|
1426
|
+
};
|
|
1427
|
+
this.returning = (cols) => {
|
|
1428
|
+
state.returning = cols || [];
|
|
1429
|
+
return this;
|
|
1430
|
+
};
|
|
1431
|
+
this.toSql = () => {
|
|
1432
|
+
const params = {};
|
|
1433
|
+
const type_hints = {};
|
|
1434
|
+
let p = 0;
|
|
1435
|
+
const setParts = [];
|
|
1436
|
+
for (const key in state.values) {
|
|
1437
|
+
const value = state.values[key];
|
|
1438
|
+
const col = findColumn(table, key);
|
|
1439
|
+
p += 1;
|
|
1440
|
+
const paramName = `${table.name}_${key}_${p}`;
|
|
1441
|
+
setParts.push(`"${key}" = :${paramName}`);
|
|
1442
|
+
params[paramName] = value;
|
|
1443
|
+
addTypeHintForParam(type_hints, paramName, col, value);
|
|
1444
|
+
}
|
|
1445
|
+
const whereClauses = [];
|
|
1446
|
+
for (const key in state.where) {
|
|
1447
|
+
const value = state.where[key];
|
|
1448
|
+
const col = findColumn(table, key);
|
|
1449
|
+
p += 1;
|
|
1450
|
+
const paramName = `${table.name}_${key}_${p}`;
|
|
1451
|
+
if (value === null) {
|
|
1452
|
+
if (!col.is_nullable) throw new Error(`Column ${table.name}.${key} is not nullable; cannot compare to null`);
|
|
1453
|
+
whereClauses.push(`"${key}" is null`);
|
|
1454
|
+
} else if (Array.isArray(value) && !isArrayShapedGeomScalarValue(col, value)) {
|
|
1455
|
+
validateInArrayValues(col, key, value, "in");
|
|
1456
|
+
whereClauses.push(`"${key}" = ANY(:${paramName})`);
|
|
1457
|
+
params[paramName] = value;
|
|
1458
|
+
addTypeHintForParam(type_hints, paramName, col, value);
|
|
1459
|
+
} else {
|
|
1460
|
+
validateScalarForColumn(col, value, `column ${key}`);
|
|
1461
|
+
whereClauses.push(`"${key}" = :${paramName}`);
|
|
1462
|
+
params[paramName] = value;
|
|
1463
|
+
addTypeHintForParam(type_hints, paramName, col, value);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
const parts = [];
|
|
1467
|
+
parts.push(`update "${schema.name}"."${table.name}" set ${setParts.join(", ")}`);
|
|
1468
|
+
if (whereClauses.length) parts.push("where " + whereClauses.join(" and "));
|
|
1469
|
+
if (state.returning.length) parts.push("returning " + state.returning.join(", "));
|
|
1470
|
+
return {
|
|
1471
|
+
q: parts.join(" "),
|
|
1472
|
+
params: Object.keys(params).length > 0 ? params : void 0,
|
|
1473
|
+
type_hints: Object.keys(type_hints).length ? type_hints : void 0
|
|
1474
|
+
};
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
}();
|
|
1478
|
+
}
|
|
1479
|
+
delete() {
|
|
1480
|
+
const table = this.table;
|
|
1481
|
+
const schema = this.schema;
|
|
1482
|
+
const state = {
|
|
1483
|
+
where: {},
|
|
1484
|
+
returning: []
|
|
1485
|
+
};
|
|
1486
|
+
return new class {
|
|
1487
|
+
constructor() {
|
|
1488
|
+
this.where = (w) => {
|
|
1489
|
+
for (const k in w) state.where[k] = w[k];
|
|
1490
|
+
return this;
|
|
1491
|
+
};
|
|
1492
|
+
this.wherePk = (pk) => {
|
|
1493
|
+
const pkCols = getPrimaryKeyColumns(table);
|
|
1494
|
+
if (!pkCols || pkCols.length === 0) throw new Error(`Table ${table.name} does not have a primary key`);
|
|
1495
|
+
if (pkCols.length === 1) {
|
|
1496
|
+
const col = pkCols[0];
|
|
1497
|
+
if (isPlainObject(pk)) throw new Error(`wherePk on ${table.name} expects a scalar for primary key column ${col}`);
|
|
1498
|
+
state.where[col] = pk;
|
|
1499
|
+
} else {
|
|
1500
|
+
if (!isPlainObject(pk)) throw new Error(`wherePk on ${table.name} requires an object with keys: ${pkCols.join(", ")}`);
|
|
1501
|
+
const obj = pk;
|
|
1502
|
+
for (const k of Object.keys(obj)) if (!pkCols.includes(k)) throw new Error(`wherePk received unknown key '${k}'. Expected keys: ${pkCols.join(", ")}`);
|
|
1503
|
+
for (const col of pkCols) {
|
|
1504
|
+
if (!(col in obj)) throw new Error(`wherePk missing key '${col}'. Required keys: ${pkCols.join(", ")}`);
|
|
1505
|
+
state.where[col] = obj[col];
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
return this;
|
|
1509
|
+
};
|
|
1510
|
+
this.returning = (cols) => {
|
|
1511
|
+
state.returning = cols || [];
|
|
1512
|
+
return this;
|
|
1513
|
+
};
|
|
1514
|
+
this.toSql = () => {
|
|
1515
|
+
const params = {};
|
|
1516
|
+
const type_hints = {};
|
|
1517
|
+
let p = 0;
|
|
1518
|
+
const whereClauses = [];
|
|
1519
|
+
for (const key in state.where) {
|
|
1520
|
+
const value = state.where[key];
|
|
1521
|
+
const col = findColumn(table, key);
|
|
1522
|
+
p += 1;
|
|
1523
|
+
const paramName = `${table.name}_${key}_${p}`;
|
|
1524
|
+
if (value === null) {
|
|
1525
|
+
if (!col.is_nullable) throw new Error(`Column ${table.name}.${key} is not nullable; cannot compare to null`);
|
|
1526
|
+
whereClauses.push(`"${key}" is null`);
|
|
1527
|
+
} else if (Array.isArray(value)) {
|
|
1528
|
+
validateInArrayValues(col, key, value, "in");
|
|
1529
|
+
whereClauses.push(`"${key}" = ANY(:${paramName})`);
|
|
1530
|
+
params[paramName] = value;
|
|
1531
|
+
addTypeHintForParam(type_hints, paramName, col, value);
|
|
1532
|
+
} else {
|
|
1533
|
+
validateScalarForColumn(col, value, `column ${key}`);
|
|
1534
|
+
whereClauses.push(`"${key}" = :${paramName}`);
|
|
1535
|
+
params[paramName] = value;
|
|
1536
|
+
addTypeHintForParam(type_hints, paramName, col, value);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
const parts = [];
|
|
1540
|
+
parts.push(`delete from "${schema.name}"."${table.name}"`);
|
|
1541
|
+
if (whereClauses.length) parts.push("where " + whereClauses.join(" and "));
|
|
1542
|
+
if (state.returning.length) parts.push("returning " + state.returning.join(", "));
|
|
1543
|
+
return {
|
|
1544
|
+
q: parts.join(" "),
|
|
1545
|
+
params: Object.keys(params).length > 0 ? params : void 0,
|
|
1546
|
+
type_hints: Object.keys(type_hints).length ? type_hints : void 0
|
|
1547
|
+
};
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
}();
|
|
1551
|
+
}
|
|
1552
|
+
};
|
|
1553
|
+
function createSqlBuilder(schema) {
|
|
1554
|
+
return { table: (name) => new TableQueryImpl(schema, String(name)) };
|
|
1423
1555
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1556
|
+
|
|
1557
|
+
//#endregion
|
|
1558
|
+
//#region src/index.ts
|
|
1559
|
+
/**
|
|
1560
|
+
* @author Martin Høgh <mh@mapcentia.com>
|
|
1561
|
+
* @copyright 2013-2026 MapCentia ApS
|
|
1562
|
+
* @license https://opensource.org/license/mit The MIT License
|
|
1563
|
+
*
|
|
1564
|
+
*/
|
|
1428
1565
|
|
|
1429
1566
|
//#endregion
|
|
1430
1567
|
exports.Claims = Claims;
|
|
1431
1568
|
exports.CodeFlow = CodeFlow;
|
|
1569
|
+
exports.Gql = Gql;
|
|
1432
1570
|
exports.Meta = Meta;
|
|
1433
1571
|
exports.PasswordFlow = PasswordFlow;
|
|
1434
1572
|
exports.Rpc = Rpc;
|