@onyx.dev/onyx-database 1.0.0 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +134 -2
- package/dist/aggregates-BGXzij4U.d.cts +1424 -0
- package/dist/aggregates-BGXzij4U.d.ts +1424 -0
- package/dist/edge.cjs +1954 -0
- package/dist/edge.cjs.map +1 -0
- package/dist/edge.d.cts +9 -0
- package/dist/edge.d.ts +9 -0
- package/dist/edge.js +1911 -0
- package/dist/edge.js.map +1 -0
- package/dist/gen/cli/generate.cjs +303 -56
- package/dist/gen/cli/generate.cjs.map +1 -1
- package/dist/index.cjs +303 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1325
- package/dist/index.d.ts +3 -1325
- package/dist/index.js +303 -55
- package/dist/index.js.map +1 -1
- package/dist/schema/cli/schema.cjs +473 -267
- package/dist/schema/cli/schema.cjs.map +1 -1
- package/package.json +22 -5
package/dist/edge.js
ADDED
|
@@ -0,0 +1,1911 @@
|
|
|
1
|
+
// src/config/defaults.ts
|
|
2
|
+
var DEFAULT_BASE_URL = "https://api.onyx.dev";
|
|
3
|
+
var sanitizeBaseUrl = (u) => u.replace(/\/+$/, "");
|
|
4
|
+
|
|
5
|
+
// src/errors/config-error.ts
|
|
6
|
+
var OnyxConfigError = class extends Error {
|
|
7
|
+
name = "OnyxConfigError";
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/config/chain-edge.ts
|
|
14
|
+
var gProcess = globalThis.process;
|
|
15
|
+
var dbg = (...args) => {
|
|
16
|
+
if (gProcess?.env?.ONYX_DEBUG == "true") {
|
|
17
|
+
const fmt = (v) => {
|
|
18
|
+
if (typeof v === "string") return v;
|
|
19
|
+
try {
|
|
20
|
+
return JSON.stringify(v);
|
|
21
|
+
} catch {
|
|
22
|
+
return String(v);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
gProcess.stderr?.write?.(`[onyx-config] ${args.map(fmt).join(" ")}
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
function dropUndefined(obj) {
|
|
30
|
+
if (!obj) return {};
|
|
31
|
+
const out = {};
|
|
32
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
33
|
+
if (v !== void 0) out[k] = v;
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
function readEnv(targetId) {
|
|
38
|
+
const env = gProcess?.env;
|
|
39
|
+
if (!env) return {};
|
|
40
|
+
const pick = (...keys) => {
|
|
41
|
+
for (const k of keys) {
|
|
42
|
+
const v = env[k];
|
|
43
|
+
if (typeof v === "string") {
|
|
44
|
+
const cleaned = v.replace(/[\r\n]+/g, "").trim();
|
|
45
|
+
if (cleaned !== "") return cleaned;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return void 0;
|
|
49
|
+
};
|
|
50
|
+
const envId = pick("ONYX_DATABASE_ID");
|
|
51
|
+
if (targetId && envId !== targetId) return {};
|
|
52
|
+
const res = dropUndefined({
|
|
53
|
+
baseUrl: pick("ONYX_DATABASE_BASE_URL"),
|
|
54
|
+
databaseId: envId,
|
|
55
|
+
apiKey: pick("ONYX_DATABASE_API_KEY"),
|
|
56
|
+
apiSecret: pick("ONYX_DATABASE_API_SECRET")
|
|
57
|
+
});
|
|
58
|
+
if (Object.keys(res).length === 0) return {};
|
|
59
|
+
dbg("env:", mask(res));
|
|
60
|
+
return res;
|
|
61
|
+
}
|
|
62
|
+
async function resolveConfig(input) {
|
|
63
|
+
const configPath = gProcess?.env?.ONYX_CONFIG_PATH;
|
|
64
|
+
if (configPath) {
|
|
65
|
+
throw new OnyxConfigError(
|
|
66
|
+
"ONYX_CONFIG_PATH is not supported in edge runtimes. Provide env vars or explicit config."
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const env = readEnv(input?.databaseId);
|
|
70
|
+
const merged = {
|
|
71
|
+
baseUrl: DEFAULT_BASE_URL,
|
|
72
|
+
...dropUndefined(env),
|
|
73
|
+
...dropUndefined(input)
|
|
74
|
+
};
|
|
75
|
+
dbg("merged (pre-validate):", mask(merged));
|
|
76
|
+
const baseUrl = sanitizeBaseUrl(merged.baseUrl ?? DEFAULT_BASE_URL);
|
|
77
|
+
const databaseId = merged.databaseId ?? "";
|
|
78
|
+
const apiKey = merged.apiKey ?? "";
|
|
79
|
+
const apiSecret = merged.apiSecret ?? "";
|
|
80
|
+
const gfetch = globalThis.fetch;
|
|
81
|
+
const fetchImpl = merged.fetch ?? (typeof gfetch === "function" ? (u, i) => gfetch(u, i) : async () => {
|
|
82
|
+
throw new OnyxConfigError("No fetch available; provide OnyxConfig.fetch");
|
|
83
|
+
});
|
|
84
|
+
const retryConfig = merged.retry ?? {};
|
|
85
|
+
const retryEnabled = retryConfig.enabled ?? true;
|
|
86
|
+
const maxRetries = retryConfig.maxRetries ?? 3;
|
|
87
|
+
const retryInitialDelayMs = retryConfig.initialDelayMs ?? 300;
|
|
88
|
+
const missing = [];
|
|
89
|
+
if (!databaseId) missing.push("databaseId");
|
|
90
|
+
if (!apiKey) missing.push("apiKey");
|
|
91
|
+
if (!apiSecret) missing.push("apiSecret");
|
|
92
|
+
if (missing.length) {
|
|
93
|
+
dbg("validation failed. merged:", mask(merged));
|
|
94
|
+
const sources = ["env", "explicit config"];
|
|
95
|
+
throw new OnyxConfigError(
|
|
96
|
+
`Missing required config: ${missing.join(", ")}. Sources: ${sources.join(", ")}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
const resolved = {
|
|
100
|
+
baseUrl,
|
|
101
|
+
databaseId,
|
|
102
|
+
apiKey,
|
|
103
|
+
apiSecret,
|
|
104
|
+
fetch: fetchImpl,
|
|
105
|
+
retryEnabled,
|
|
106
|
+
maxRetries,
|
|
107
|
+
retryInitialDelayMs
|
|
108
|
+
};
|
|
109
|
+
dbg("resolved:", mask(resolved));
|
|
110
|
+
return resolved;
|
|
111
|
+
}
|
|
112
|
+
function mask(obj) {
|
|
113
|
+
const clone = { ...obj };
|
|
114
|
+
if (typeof clone.apiKey === "string") clone.apiKey = "***";
|
|
115
|
+
if (typeof clone.apiSecret === "string") clone.apiSecret = "***";
|
|
116
|
+
return clone;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/errors/http-error.ts
|
|
120
|
+
var OnyxHttpError = class extends Error {
|
|
121
|
+
name = "OnyxHttpError";
|
|
122
|
+
status;
|
|
123
|
+
statusText;
|
|
124
|
+
body;
|
|
125
|
+
rawBody;
|
|
126
|
+
constructor(message, status, statusText, body, rawBody) {
|
|
127
|
+
super(message);
|
|
128
|
+
this.status = status;
|
|
129
|
+
this.statusText = statusText;
|
|
130
|
+
this.body = body;
|
|
131
|
+
this.rawBody = rawBody;
|
|
132
|
+
}
|
|
133
|
+
toJSON() {
|
|
134
|
+
return {
|
|
135
|
+
name: this.name,
|
|
136
|
+
message: this.message,
|
|
137
|
+
status: this.status,
|
|
138
|
+
statusText: this.statusText,
|
|
139
|
+
body: this.body,
|
|
140
|
+
rawBody: this.rawBody,
|
|
141
|
+
stack: this.stack
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
145
|
+
return this.toJSON();
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// src/core/http.ts
|
|
150
|
+
function parseJsonAllowNaN(txt) {
|
|
151
|
+
try {
|
|
152
|
+
return JSON.parse(txt);
|
|
153
|
+
} catch {
|
|
154
|
+
const fixed = txt.replace(/(:\s*)(NaN|Infinity|-Infinity)(\s*[,}])/g, "$1null$3");
|
|
155
|
+
return JSON.parse(fixed);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
var HttpClient = class _HttpClient {
|
|
159
|
+
baseUrl;
|
|
160
|
+
apiKey;
|
|
161
|
+
apiSecret;
|
|
162
|
+
fetchImpl;
|
|
163
|
+
defaults;
|
|
164
|
+
requestLoggingEnabled;
|
|
165
|
+
responseLoggingEnabled;
|
|
166
|
+
retryEnabled;
|
|
167
|
+
maxRetries;
|
|
168
|
+
retryInitialDelayMs;
|
|
169
|
+
shouldRetry;
|
|
170
|
+
static parseRetryAfter(header) {
|
|
171
|
+
if (!header) return null;
|
|
172
|
+
const trimmed = header.trim();
|
|
173
|
+
if (trimmed === "") return null;
|
|
174
|
+
const seconds = Number(trimmed);
|
|
175
|
+
if (Number.isFinite(seconds)) {
|
|
176
|
+
return Math.max(0, seconds * 1e3);
|
|
177
|
+
}
|
|
178
|
+
const dateMs = Date.parse(trimmed);
|
|
179
|
+
if (!Number.isNaN(dateMs)) {
|
|
180
|
+
const now = Date.now();
|
|
181
|
+
return Math.max(0, dateMs - now);
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
constructor(opts) {
|
|
186
|
+
if (!opts.baseUrl || opts.baseUrl.trim() === "") {
|
|
187
|
+
throw new OnyxConfigError("baseUrl is required");
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
new URL(opts.baseUrl);
|
|
191
|
+
} catch {
|
|
192
|
+
throw new OnyxConfigError("baseUrl must include protocol, e.g. https://");
|
|
193
|
+
}
|
|
194
|
+
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
195
|
+
this.apiKey = opts.apiKey;
|
|
196
|
+
this.apiSecret = opts.apiSecret;
|
|
197
|
+
const gfetch = globalThis.fetch;
|
|
198
|
+
if (opts.fetchImpl) {
|
|
199
|
+
this.fetchImpl = opts.fetchImpl;
|
|
200
|
+
} else if (typeof gfetch === "function") {
|
|
201
|
+
this.fetchImpl = (url, init) => gfetch(url, init);
|
|
202
|
+
} else {
|
|
203
|
+
throw new Error("global fetch is not available; provide OnyxConfig.fetch");
|
|
204
|
+
}
|
|
205
|
+
this.defaults = Object.assign({}, opts.defaultHeaders);
|
|
206
|
+
const envDebug = globalThis.process?.env?.ONYX_DEBUG === "true";
|
|
207
|
+
this.requestLoggingEnabled = !!opts.requestLoggingEnabled || envDebug;
|
|
208
|
+
this.responseLoggingEnabled = !!opts.responseLoggingEnabled || envDebug;
|
|
209
|
+
this.retryEnabled = opts.retryEnabled ?? true;
|
|
210
|
+
this.maxRetries = Math.max(0, opts.maxRetries ?? 2);
|
|
211
|
+
this.retryInitialDelayMs = Math.max(0, opts.retryInitialDelayMs ?? 100);
|
|
212
|
+
this.shouldRetry = (method, path) => method === "GET" || path.startsWith("/query/");
|
|
213
|
+
}
|
|
214
|
+
headers(extra) {
|
|
215
|
+
const extras = { ...extra ?? {} };
|
|
216
|
+
delete extras["x-onyx-key"];
|
|
217
|
+
delete extras["x-onyx-secret"];
|
|
218
|
+
return {
|
|
219
|
+
"x-onyx-key": this.apiKey,
|
|
220
|
+
"x-onyx-secret": this.apiSecret,
|
|
221
|
+
"Accept": "application/json",
|
|
222
|
+
"Content-Type": "application/json",
|
|
223
|
+
...this.defaults,
|
|
224
|
+
...extras
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
async request(method, path, body, extraHeaders) {
|
|
228
|
+
if (!path.startsWith("/")) {
|
|
229
|
+
throw new OnyxConfigError("path must start with /");
|
|
230
|
+
}
|
|
231
|
+
const url = `${this.baseUrl}${path}`;
|
|
232
|
+
const headers = this.headers({
|
|
233
|
+
...method === "DELETE" ? { Prefer: "return=representation" } : {},
|
|
234
|
+
...extraHeaders ?? {}
|
|
235
|
+
});
|
|
236
|
+
const hasExplicitContentType = extraHeaders && "Content-Type" in extraHeaders || Object.prototype.hasOwnProperty.call(this.defaults, "Content-Type");
|
|
237
|
+
if (body == null && !hasExplicitContentType) delete headers["Content-Type"];
|
|
238
|
+
if (this.requestLoggingEnabled) {
|
|
239
|
+
console.log(`${method} ${url}`);
|
|
240
|
+
if (body != null) {
|
|
241
|
+
const logBody = typeof body === "string" ? body : JSON.stringify(body);
|
|
242
|
+
console.log(logBody);
|
|
243
|
+
}
|
|
244
|
+
const headerLog = { ...headers, "x-onyx-secret": "[REDACTED]" };
|
|
245
|
+
console.log("Headers:", headerLog);
|
|
246
|
+
}
|
|
247
|
+
const payload = body == null ? void 0 : typeof body === "string" ? body : JSON.stringify(body);
|
|
248
|
+
const init = {
|
|
249
|
+
method,
|
|
250
|
+
headers,
|
|
251
|
+
body: payload
|
|
252
|
+
};
|
|
253
|
+
const canRetry = this.retryEnabled && this.shouldRetry(method, path);
|
|
254
|
+
const maxAttempts = canRetry ? this.maxRetries + 1 : 1;
|
|
255
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
256
|
+
try {
|
|
257
|
+
const res = await this.fetchImpl(url, init);
|
|
258
|
+
const contentType = res.headers.get("Content-Type") || "";
|
|
259
|
+
const raw = await res.text();
|
|
260
|
+
if (this.responseLoggingEnabled) {
|
|
261
|
+
const statusLine = `${res.status} ${res.statusText}`.trim();
|
|
262
|
+
console.log(statusLine);
|
|
263
|
+
if (raw.trim().length > 0) {
|
|
264
|
+
console.log(raw);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const isJson = raw.trim().length > 0 && (contentType.includes("application/json") || /^[\[{]/.test(raw.trim()));
|
|
268
|
+
const data = isJson ? parseJsonAllowNaN(raw) : raw;
|
|
269
|
+
if (!res.ok) {
|
|
270
|
+
const msg = typeof data === "object" && data !== null && "error" in data && typeof data.error?.message === "string" ? String(data.error.message) : `${res.status} ${res.statusText}`;
|
|
271
|
+
if (canRetry && res.status >= 500 && attempt + 1 < maxAttempts) {
|
|
272
|
+
const serverRetry = _HttpClient.parseRetryAfter(res.headers.get("retry-after"));
|
|
273
|
+
const backoff = this.retryInitialDelayMs * 2 ** attempt;
|
|
274
|
+
const delay = serverRetry ?? backoff;
|
|
275
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
throw new OnyxHttpError(msg, res.status, res.statusText, data, raw);
|
|
279
|
+
}
|
|
280
|
+
return data;
|
|
281
|
+
} catch (err) {
|
|
282
|
+
const retryable = canRetry && (!(err instanceof OnyxHttpError) || err.status >= 500);
|
|
283
|
+
if (attempt + 1 < maxAttempts && retryable) {
|
|
284
|
+
const delay = this.retryInitialDelayMs * 2 ** attempt;
|
|
285
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
throw err;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
throw new Error("Request failed after retries");
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
// src/core/stream.ts
|
|
296
|
+
var debug = (...args) => {
|
|
297
|
+
if (globalThis.process?.env?.ONYX_STREAM_DEBUG == "true")
|
|
298
|
+
console.log("[onyx-stream]", ...args);
|
|
299
|
+
};
|
|
300
|
+
async function openJsonLinesStream(fetchImpl, url, init = {}, handlers = {}) {
|
|
301
|
+
const decoder = new TextDecoder("utf-8");
|
|
302
|
+
let buffer = "";
|
|
303
|
+
let canceled = false;
|
|
304
|
+
let currentReader = null;
|
|
305
|
+
let retryCount = 0;
|
|
306
|
+
const maxRetries = 4;
|
|
307
|
+
const processLine = (line) => {
|
|
308
|
+
const trimmed = line.trim();
|
|
309
|
+
debug("line", trimmed);
|
|
310
|
+
if (!trimmed || trimmed.startsWith(":")) return;
|
|
311
|
+
const jsonLine = trimmed.startsWith("data:") ? trimmed.slice(5).trim() : trimmed;
|
|
312
|
+
let obj;
|
|
313
|
+
try {
|
|
314
|
+
obj = parseJsonAllowNaN(jsonLine);
|
|
315
|
+
} catch {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const rawAction = obj.action ?? obj.event ?? obj.type ?? obj.eventType ?? obj.changeType;
|
|
319
|
+
const entity = obj.entity;
|
|
320
|
+
const action = rawAction?.toUpperCase();
|
|
321
|
+
if (action === "CREATE" || action === "CREATED" || action === "ADDED" || action === "ADD" || action === "INSERT" || action === "INSERTED")
|
|
322
|
+
handlers.onItemAdded?.(entity);
|
|
323
|
+
else if (action === "UPDATE" || action === "UPDATED")
|
|
324
|
+
handlers.onItemUpdated?.(entity);
|
|
325
|
+
else if (action === "DELETE" || action === "DELETED" || action === "REMOVE" || action === "REMOVED")
|
|
326
|
+
handlers.onItemDeleted?.(entity);
|
|
327
|
+
const canonical = action === "ADDED" || action === "ADD" || action === "CREATE" || action === "CREATED" || action === "INSERT" || action === "INSERTED" ? "CREATE" : action === "UPDATED" || action === "UPDATE" ? "UPDATE" : action === "DELETED" || action === "DELETE" || action === "REMOVE" || action === "REMOVED" ? "DELETE" : action;
|
|
328
|
+
if (canonical && canonical !== "KEEP_ALIVE")
|
|
329
|
+
handlers.onItem?.(entity ?? null, canonical);
|
|
330
|
+
debug("dispatch", canonical, entity);
|
|
331
|
+
};
|
|
332
|
+
const connect = async () => {
|
|
333
|
+
if (canceled) return;
|
|
334
|
+
debug("connecting", url);
|
|
335
|
+
try {
|
|
336
|
+
const res = await fetchImpl(url, {
|
|
337
|
+
method: init.method ?? "PUT",
|
|
338
|
+
headers: init.headers ?? {},
|
|
339
|
+
body: init.body
|
|
340
|
+
});
|
|
341
|
+
debug("response", res.status, res.statusText);
|
|
342
|
+
if (!res.ok) {
|
|
343
|
+
const raw = await res.text();
|
|
344
|
+
let parsed = raw;
|
|
345
|
+
try {
|
|
346
|
+
parsed = parseJsonAllowNaN(raw);
|
|
347
|
+
} catch {
|
|
348
|
+
}
|
|
349
|
+
debug("non-ok", res.status);
|
|
350
|
+
throw new OnyxHttpError(`${res.status} ${res.statusText}`, res.status, res.statusText, parsed, raw);
|
|
351
|
+
}
|
|
352
|
+
const body = res.body;
|
|
353
|
+
if (!body || typeof body.getReader !== "function") {
|
|
354
|
+
debug("no reader");
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
currentReader = body.getReader();
|
|
358
|
+
debug("connected");
|
|
359
|
+
retryCount = 0;
|
|
360
|
+
pump();
|
|
361
|
+
} catch (err) {
|
|
362
|
+
debug("connect error", err);
|
|
363
|
+
if (canceled) return;
|
|
364
|
+
if (retryCount >= maxRetries) return;
|
|
365
|
+
const delay = Math.min(1e3 * 2 ** retryCount, 3e4);
|
|
366
|
+
retryCount++;
|
|
367
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
368
|
+
void connect();
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
const pump = () => {
|
|
372
|
+
if (canceled || !currentReader) return;
|
|
373
|
+
currentReader.read().then(({ done, value }) => {
|
|
374
|
+
if (canceled) return;
|
|
375
|
+
debug("chunk", { done, length: value?.length ?? 0 });
|
|
376
|
+
if (done) {
|
|
377
|
+
debug("done");
|
|
378
|
+
void connect();
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
buffer += decoder.decode(value, { stream: true });
|
|
382
|
+
const lines = buffer.split("\n");
|
|
383
|
+
buffer = lines.pop() ?? "";
|
|
384
|
+
for (const line of lines) processLine(line);
|
|
385
|
+
pump();
|
|
386
|
+
}).catch((err) => {
|
|
387
|
+
debug("pump error", err);
|
|
388
|
+
if (!canceled) void connect();
|
|
389
|
+
});
|
|
390
|
+
};
|
|
391
|
+
await connect();
|
|
392
|
+
return {
|
|
393
|
+
cancel() {
|
|
394
|
+
if (canceled) return;
|
|
395
|
+
canceled = true;
|
|
396
|
+
try {
|
|
397
|
+
currentReader?.cancel();
|
|
398
|
+
} catch {
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// src/builders/query-results.ts
|
|
405
|
+
var QueryResults = class extends Array {
|
|
406
|
+
/** Token for the next page of results or null. */
|
|
407
|
+
nextPage;
|
|
408
|
+
fetcher;
|
|
409
|
+
/**
|
|
410
|
+
* @param records - Records in the current page.
|
|
411
|
+
* @param nextPage - Token representing the next page.
|
|
412
|
+
* @param fetcher - Function used to fetch the next page when needed.
|
|
413
|
+
* @example
|
|
414
|
+
* ```ts
|
|
415
|
+
* const results = new QueryResults(users, token, t => fetchMore(t));
|
|
416
|
+
* ```
|
|
417
|
+
*/
|
|
418
|
+
constructor(records, nextPage, fetcher) {
|
|
419
|
+
const items = (() => {
|
|
420
|
+
if (records == null) return [];
|
|
421
|
+
if (Array.isArray(records)) return records;
|
|
422
|
+
if (typeof records[Symbol.iterator] === "function") {
|
|
423
|
+
return Array.from(records);
|
|
424
|
+
}
|
|
425
|
+
if (typeof records.length === "number") {
|
|
426
|
+
return Array.from(records);
|
|
427
|
+
}
|
|
428
|
+
return [records];
|
|
429
|
+
})();
|
|
430
|
+
super(...items);
|
|
431
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
432
|
+
this.nextPage = nextPage;
|
|
433
|
+
this.fetcher = fetcher;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Returns the first record in the result set.
|
|
437
|
+
* @throws Error if the result set is empty.
|
|
438
|
+
* @example
|
|
439
|
+
* ```ts
|
|
440
|
+
* const user = results.first();
|
|
441
|
+
* ```
|
|
442
|
+
*/
|
|
443
|
+
first() {
|
|
444
|
+
if (this.length === 0) throw new Error("QueryResults is empty");
|
|
445
|
+
return this[0];
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Returns the first record or `null` if the result set is empty.
|
|
449
|
+
* @example
|
|
450
|
+
* ```ts
|
|
451
|
+
* const user = results.firstOrNull();
|
|
452
|
+
* ```
|
|
453
|
+
*/
|
|
454
|
+
firstOrNull() {
|
|
455
|
+
return this.length > 0 ? this[0] : null;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Checks whether the current page has no records.
|
|
459
|
+
* @example
|
|
460
|
+
* ```ts
|
|
461
|
+
* if (results.isEmpty()) console.log('no data');
|
|
462
|
+
* ```
|
|
463
|
+
*/
|
|
464
|
+
isEmpty() {
|
|
465
|
+
return this.length === 0;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Number of records on the current page.
|
|
469
|
+
* @example
|
|
470
|
+
* ```ts
|
|
471
|
+
* console.log(results.size());
|
|
472
|
+
* ```
|
|
473
|
+
*/
|
|
474
|
+
size() {
|
|
475
|
+
return this.length;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Iterates over each record on the current page only.
|
|
479
|
+
* @param action - Function to invoke for each record.
|
|
480
|
+
* @param thisArg - Optional `this` binding for the callback.
|
|
481
|
+
* @example
|
|
482
|
+
* ```ts
|
|
483
|
+
* results.forEachOnPage(u => console.log(u.id));
|
|
484
|
+
* ```
|
|
485
|
+
*/
|
|
486
|
+
forEachOnPage(action, thisArg) {
|
|
487
|
+
super.forEach((value, index) => {
|
|
488
|
+
action.call(thisArg, value, index, this);
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Iterates over every record across all pages sequentially.
|
|
493
|
+
* @param action - Function executed for each record. Returning `false`
|
|
494
|
+
* stops iteration early.
|
|
495
|
+
* @param thisArg - Optional `this` binding for the callback.
|
|
496
|
+
* @example
|
|
497
|
+
* ```ts
|
|
498
|
+
* await results.forEach(u => {
|
|
499
|
+
* console.log(u.id);
|
|
500
|
+
* });
|
|
501
|
+
* ```
|
|
502
|
+
*/
|
|
503
|
+
forEach(action, thisArg) {
|
|
504
|
+
let index = 0;
|
|
505
|
+
return this.forEachAll(async (item) => {
|
|
506
|
+
const result = await action.call(thisArg, item, index, this);
|
|
507
|
+
index += 1;
|
|
508
|
+
return result;
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Iterates over every record across all pages sequentially.
|
|
513
|
+
* @param action - Function executed for each record. Returning `false`
|
|
514
|
+
* stops iteration early.
|
|
515
|
+
* @example
|
|
516
|
+
* ```ts
|
|
517
|
+
* await results.forEachAll(u => {
|
|
518
|
+
* if (u.disabled) return false;
|
|
519
|
+
* });
|
|
520
|
+
* ```
|
|
521
|
+
*/
|
|
522
|
+
async forEachAll(action) {
|
|
523
|
+
await this.forEachPage(async (records) => {
|
|
524
|
+
for (const r of records) {
|
|
525
|
+
const res = await action(r);
|
|
526
|
+
if (res === false) return false;
|
|
527
|
+
}
|
|
528
|
+
return true;
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Iterates page by page across the result set.
|
|
533
|
+
* @param action - Function invoked with each page of records. Returning
|
|
534
|
+
* `false` stops iteration.
|
|
535
|
+
* @example
|
|
536
|
+
* ```ts
|
|
537
|
+
* await results.forEachPage(page => {
|
|
538
|
+
* console.log(page.length);
|
|
539
|
+
* });
|
|
540
|
+
* ```
|
|
541
|
+
*/
|
|
542
|
+
async forEachPage(action) {
|
|
543
|
+
let page = this;
|
|
544
|
+
while (page) {
|
|
545
|
+
const cont = await action(Array.from(page));
|
|
546
|
+
if (cont === false) return;
|
|
547
|
+
if (page.nextPage && page.fetcher) {
|
|
548
|
+
page = await page.fetcher(page.nextPage);
|
|
549
|
+
} else {
|
|
550
|
+
page = null;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Collects all records from every page into a single array.
|
|
556
|
+
* @returns All records.
|
|
557
|
+
* @example
|
|
558
|
+
* ```ts
|
|
559
|
+
* const allUsers = await results.getAllRecords();
|
|
560
|
+
* ```
|
|
561
|
+
*/
|
|
562
|
+
async getAllRecords() {
|
|
563
|
+
const all = [];
|
|
564
|
+
await this.forEachPage((records) => {
|
|
565
|
+
all.push(...records);
|
|
566
|
+
});
|
|
567
|
+
return all;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Filters all records using the provided predicate.
|
|
571
|
+
* @param predicate - Function used to test each record.
|
|
572
|
+
* @example
|
|
573
|
+
* ```ts
|
|
574
|
+
* const enabled = await results.filterAll(u => u.enabled);
|
|
575
|
+
* ```
|
|
576
|
+
*/
|
|
577
|
+
async filterAll(predicate) {
|
|
578
|
+
const all = await this.getAllRecords();
|
|
579
|
+
return all.filter(predicate);
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Maps all records using the provided transform.
|
|
583
|
+
* @param transform - Mapping function.
|
|
584
|
+
* @example
|
|
585
|
+
* ```ts
|
|
586
|
+
* const names = await results.mapAll(u => u.name);
|
|
587
|
+
* ```
|
|
588
|
+
*/
|
|
589
|
+
async mapAll(transform) {
|
|
590
|
+
const all = await this.getAllRecords();
|
|
591
|
+
return all.map(transform);
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Extracts values for a field across all records.
|
|
595
|
+
* @param field - Name of the field to pluck.
|
|
596
|
+
* @example
|
|
597
|
+
* ```ts
|
|
598
|
+
* const ids = await results.values('id');
|
|
599
|
+
* ```
|
|
600
|
+
*/
|
|
601
|
+
// @ts-expect-error overriding Array#values
|
|
602
|
+
async values(field) {
|
|
603
|
+
const all = await this.getAllRecords();
|
|
604
|
+
return all.map((r) => r[field]);
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Maximum value produced by the selector across all records.
|
|
608
|
+
* @param selector - Function extracting a numeric value.
|
|
609
|
+
* @example
|
|
610
|
+
* ```ts
|
|
611
|
+
* const maxAge = await results.maxOfDouble(u => u.age);
|
|
612
|
+
* ```
|
|
613
|
+
*/
|
|
614
|
+
async maxOfDouble(selector) {
|
|
615
|
+
const all = await this.getAllRecords();
|
|
616
|
+
return all.reduce((max2, r) => Math.max(max2, selector(r)), -Infinity);
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Minimum value produced by the selector across all records.
|
|
620
|
+
* @param selector - Function extracting a numeric value.
|
|
621
|
+
* @example
|
|
622
|
+
* ```ts
|
|
623
|
+
* const minAge = await results.minOfDouble(u => u.age);
|
|
624
|
+
* ```
|
|
625
|
+
*/
|
|
626
|
+
async minOfDouble(selector) {
|
|
627
|
+
const all = await this.getAllRecords();
|
|
628
|
+
return all.reduce((min2, r) => Math.min(min2, selector(r)), Infinity);
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Sum of values produced by the selector across all records.
|
|
632
|
+
* @param selector - Function extracting a numeric value.
|
|
633
|
+
* @example
|
|
634
|
+
* ```ts
|
|
635
|
+
* const total = await results.sumOfDouble(u => u.score);
|
|
636
|
+
* ```
|
|
637
|
+
*/
|
|
638
|
+
async sumOfDouble(selector) {
|
|
639
|
+
const all = await this.getAllRecords();
|
|
640
|
+
return all.reduce((sum2, r) => sum2 + selector(r), 0);
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Maximum float value from the selector.
|
|
644
|
+
* @param selector - Function extracting a numeric value.
|
|
645
|
+
*/
|
|
646
|
+
async maxOfFloat(selector) {
|
|
647
|
+
return this.maxOfDouble(selector);
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Minimum float value from the selector.
|
|
651
|
+
* @param selector - Function extracting a numeric value.
|
|
652
|
+
*/
|
|
653
|
+
async minOfFloat(selector) {
|
|
654
|
+
return this.minOfDouble(selector);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Sum of float values from the selector.
|
|
658
|
+
* @param selector - Function extracting a numeric value.
|
|
659
|
+
*/
|
|
660
|
+
async sumOfFloat(selector) {
|
|
661
|
+
return this.sumOfDouble(selector);
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Maximum integer value from the selector.
|
|
665
|
+
* @param selector - Function extracting a numeric value.
|
|
666
|
+
*/
|
|
667
|
+
async maxOfInt(selector) {
|
|
668
|
+
return this.maxOfDouble(selector);
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Minimum integer value from the selector.
|
|
672
|
+
* @param selector - Function extracting a numeric value.
|
|
673
|
+
*/
|
|
674
|
+
async minOfInt(selector) {
|
|
675
|
+
return this.minOfDouble(selector);
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Sum of integer values from the selector.
|
|
679
|
+
* @param selector - Function extracting a numeric value.
|
|
680
|
+
*/
|
|
681
|
+
async sumOfInt(selector) {
|
|
682
|
+
return this.sumOfDouble(selector);
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Maximum long value from the selector.
|
|
686
|
+
* @param selector - Function extracting a numeric value.
|
|
687
|
+
*/
|
|
688
|
+
async maxOfLong(selector) {
|
|
689
|
+
return this.maxOfDouble(selector);
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Minimum long value from the selector.
|
|
693
|
+
* @param selector - Function extracting a numeric value.
|
|
694
|
+
*/
|
|
695
|
+
async minOfLong(selector) {
|
|
696
|
+
return this.minOfDouble(selector);
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Sum of long values from the selector.
|
|
700
|
+
* @param selector - Function extracting a numeric value.
|
|
701
|
+
*/
|
|
702
|
+
async sumOfLong(selector) {
|
|
703
|
+
return this.sumOfDouble(selector);
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Sum of bigint values from the selector.
|
|
707
|
+
* @param selector - Function extracting a bigint value.
|
|
708
|
+
* @example
|
|
709
|
+
* ```ts
|
|
710
|
+
* const total = await results.sumOfBigInt(u => u.balance);
|
|
711
|
+
* ```
|
|
712
|
+
*/
|
|
713
|
+
async sumOfBigInt(selector) {
|
|
714
|
+
const all = await this.getAllRecords();
|
|
715
|
+
return all.reduce((sum2, r) => sum2 + selector(r), 0n);
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Executes an action for each page in parallel.
|
|
719
|
+
* @param action - Function executed for each record concurrently.
|
|
720
|
+
* @example
|
|
721
|
+
* ```ts
|
|
722
|
+
* await results.forEachPageParallel(async u => sendEmail(u));
|
|
723
|
+
* ```
|
|
724
|
+
*/
|
|
725
|
+
async forEachPageParallel(action) {
|
|
726
|
+
await this.forEachPage((records) => Promise.all(records.map(action)).then(() => true));
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
// src/helpers/condition-normalizer.ts
|
|
731
|
+
function isQueryBuilderLike(value) {
|
|
732
|
+
return !!value && typeof value.toSerializableQueryObject === "function";
|
|
733
|
+
}
|
|
734
|
+
function normalizeCriteriaValue(value) {
|
|
735
|
+
if (Array.isArray(value)) {
|
|
736
|
+
let changed = false;
|
|
737
|
+
const normalized = value.map((item) => {
|
|
738
|
+
const result = normalizeCriteriaValue(item);
|
|
739
|
+
if (result.changed) changed = true;
|
|
740
|
+
return result.value;
|
|
741
|
+
});
|
|
742
|
+
if (!changed) {
|
|
743
|
+
for (let i = 0; i < normalized.length; i += 1) {
|
|
744
|
+
if (normalized[i] !== value[i]) {
|
|
745
|
+
changed = true;
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return { value: changed ? normalized : value, changed };
|
|
751
|
+
}
|
|
752
|
+
if (isQueryBuilderLike(value)) {
|
|
753
|
+
return { value: value.toSerializableQueryObject(), changed: true };
|
|
754
|
+
}
|
|
755
|
+
return { value, changed: false };
|
|
756
|
+
}
|
|
757
|
+
function normalizeConditionInternal(condition) {
|
|
758
|
+
if (condition.conditionType === "SingleCondition") {
|
|
759
|
+
const { value, changed: changed2 } = normalizeCriteriaValue(condition.criteria.value);
|
|
760
|
+
if (!changed2) return condition;
|
|
761
|
+
return {
|
|
762
|
+
...condition,
|
|
763
|
+
criteria: { ...condition.criteria, value }
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
let changed = false;
|
|
767
|
+
const normalizedConditions = condition.conditions.map((child) => {
|
|
768
|
+
const normalized = normalizeConditionInternal(child);
|
|
769
|
+
if (normalized !== child) changed = true;
|
|
770
|
+
return normalized;
|
|
771
|
+
});
|
|
772
|
+
if (!changed) return condition;
|
|
773
|
+
return { ...condition, conditions: normalizedConditions };
|
|
774
|
+
}
|
|
775
|
+
function normalizeCondition(condition) {
|
|
776
|
+
if (!condition) return condition;
|
|
777
|
+
return normalizeConditionInternal(condition);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// src/builders/cascade-relationship-builder.ts
|
|
781
|
+
var CascadeRelationshipBuilder = class {
|
|
782
|
+
graphName;
|
|
783
|
+
typeName;
|
|
784
|
+
target;
|
|
785
|
+
/**
|
|
786
|
+
* Set the graph name component.
|
|
787
|
+
*
|
|
788
|
+
* @param name Graph name or namespace.
|
|
789
|
+
* @example
|
|
790
|
+
* ```ts
|
|
791
|
+
* builder.graph('programs');
|
|
792
|
+
* ```
|
|
793
|
+
*/
|
|
794
|
+
graph(name) {
|
|
795
|
+
this.graphName = name;
|
|
796
|
+
return this;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Set the graph type component.
|
|
800
|
+
*
|
|
801
|
+
* @param type Graph type to target.
|
|
802
|
+
* @example
|
|
803
|
+
* ```ts
|
|
804
|
+
* builder.graphType('StreamingProgram');
|
|
805
|
+
* ```
|
|
806
|
+
*/
|
|
807
|
+
graphType(type) {
|
|
808
|
+
this.typeName = type;
|
|
809
|
+
return this;
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Set the target field for the relationship.
|
|
813
|
+
*
|
|
814
|
+
* @param field Target field name.
|
|
815
|
+
* @example
|
|
816
|
+
* ```ts
|
|
817
|
+
* builder.targetField('channelId');
|
|
818
|
+
* ```
|
|
819
|
+
*/
|
|
820
|
+
targetField(field) {
|
|
821
|
+
this.target = field;
|
|
822
|
+
return this;
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Produce the cascade relationship string using the provided source field.
|
|
826
|
+
*
|
|
827
|
+
* @param field Source field name.
|
|
828
|
+
* @example
|
|
829
|
+
* ```ts
|
|
830
|
+
* const rel = builder
|
|
831
|
+
* .graph('programs')
|
|
832
|
+
* .graphType('StreamingProgram')
|
|
833
|
+
* .targetField('channelId')
|
|
834
|
+
* .sourceField('id');
|
|
835
|
+
* // rel === 'programs:StreamingProgram(channelId, id)'
|
|
836
|
+
* ```
|
|
837
|
+
*/
|
|
838
|
+
sourceField(field) {
|
|
839
|
+
if (!this.graphName || !this.typeName || !this.target) {
|
|
840
|
+
throw new Error("Cascade relationship requires graph, type, target, and source fields");
|
|
841
|
+
}
|
|
842
|
+
return `${this.graphName}:${this.typeName}(${this.target}, ${field})`;
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
// src/helpers/schema-diff.ts
|
|
847
|
+
function mapByName(items) {
|
|
848
|
+
const map = /* @__PURE__ */ new Map();
|
|
849
|
+
for (const item of items ?? []) {
|
|
850
|
+
if (!item?.name) continue;
|
|
851
|
+
map.set(item.name, item);
|
|
852
|
+
}
|
|
853
|
+
return map;
|
|
854
|
+
}
|
|
855
|
+
function normalizeEntities(schema) {
|
|
856
|
+
if (Array.isArray(schema.entities)) {
|
|
857
|
+
return schema.entities ?? [];
|
|
858
|
+
}
|
|
859
|
+
const tables = schema.tables;
|
|
860
|
+
if (!Array.isArray(tables)) return [];
|
|
861
|
+
return tables.map((table) => ({
|
|
862
|
+
name: table.name,
|
|
863
|
+
attributes: table.attributes ?? []
|
|
864
|
+
}));
|
|
865
|
+
}
|
|
866
|
+
function normalizePartition(partition) {
|
|
867
|
+
if (partition == null) return "";
|
|
868
|
+
const trimmed = partition.trim();
|
|
869
|
+
return trimmed;
|
|
870
|
+
}
|
|
871
|
+
function identifiersEqual(a, b) {
|
|
872
|
+
if (!a && !b) return true;
|
|
873
|
+
if (!a || !b) return false;
|
|
874
|
+
return a.name === b.name && a.generator === b.generator && a.type === b.type;
|
|
875
|
+
}
|
|
876
|
+
function diffAttributes(apiAttrs, localAttrs) {
|
|
877
|
+
const apiMap = mapByName(apiAttrs);
|
|
878
|
+
const localMap = mapByName(localAttrs);
|
|
879
|
+
const added = [];
|
|
880
|
+
const removed = [];
|
|
881
|
+
const changed = [];
|
|
882
|
+
for (const [name, local] of localMap.entries()) {
|
|
883
|
+
if (!apiMap.has(name)) {
|
|
884
|
+
added.push(local);
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
const api = apiMap.get(name);
|
|
888
|
+
const apiNull = Boolean(api.isNullable);
|
|
889
|
+
const localNull = Boolean(local.isNullable);
|
|
890
|
+
if (api.type !== local.type || apiNull !== localNull) {
|
|
891
|
+
changed.push({
|
|
892
|
+
name,
|
|
893
|
+
from: { type: api.type, isNullable: apiNull },
|
|
894
|
+
to: { type: local.type, isNullable: localNull }
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
for (const name of apiMap.keys()) {
|
|
899
|
+
if (!localMap.has(name)) removed.push(name);
|
|
900
|
+
}
|
|
901
|
+
added.sort((a, b) => a.name.localeCompare(b.name));
|
|
902
|
+
removed.sort();
|
|
903
|
+
changed.sort((a, b) => a.name.localeCompare(b.name));
|
|
904
|
+
if (!added.length && !removed.length && !changed.length) return null;
|
|
905
|
+
return { added, removed, changed };
|
|
906
|
+
}
|
|
907
|
+
function diffIndexes(apiIndexes, localIndexes) {
|
|
908
|
+
const apiMap = mapByName(apiIndexes);
|
|
909
|
+
const localMap = mapByName(localIndexes);
|
|
910
|
+
const added = [];
|
|
911
|
+
const removed = [];
|
|
912
|
+
const changed = [];
|
|
913
|
+
for (const [name, local] of localMap.entries()) {
|
|
914
|
+
if (!apiMap.has(name)) {
|
|
915
|
+
added.push(local);
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
const api = apiMap.get(name);
|
|
919
|
+
const apiType = api.type ?? "DEFAULT";
|
|
920
|
+
const localType = local.type ?? "DEFAULT";
|
|
921
|
+
const apiScore = api.minimumScore;
|
|
922
|
+
const localScore = local.minimumScore;
|
|
923
|
+
if (apiType !== localType || apiScore !== localScore) {
|
|
924
|
+
changed.push({ name, from: api, to: local });
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
for (const name of apiMap.keys()) {
|
|
928
|
+
if (!localMap.has(name)) removed.push(name);
|
|
929
|
+
}
|
|
930
|
+
added.sort((a, b) => a.name.localeCompare(b.name));
|
|
931
|
+
removed.sort();
|
|
932
|
+
changed.sort((a, b) => a.name.localeCompare(b.name));
|
|
933
|
+
if (!added.length && !removed.length && !changed.length) return null;
|
|
934
|
+
return { added, removed, changed };
|
|
935
|
+
}
|
|
936
|
+
function diffResolvers(apiResolvers, localResolvers) {
|
|
937
|
+
const apiMap = mapByName(apiResolvers);
|
|
938
|
+
const localMap = mapByName(localResolvers);
|
|
939
|
+
const added = [];
|
|
940
|
+
const removed = [];
|
|
941
|
+
const changed = [];
|
|
942
|
+
for (const [name, local] of localMap.entries()) {
|
|
943
|
+
if (!apiMap.has(name)) {
|
|
944
|
+
added.push(local);
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
const api = apiMap.get(name);
|
|
948
|
+
if (api.resolver !== local.resolver) {
|
|
949
|
+
changed.push({ name, from: api, to: local });
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
for (const name of apiMap.keys()) {
|
|
953
|
+
if (!localMap.has(name)) removed.push(name);
|
|
954
|
+
}
|
|
955
|
+
added.sort((a, b) => a.name.localeCompare(b.name));
|
|
956
|
+
removed.sort();
|
|
957
|
+
changed.sort((a, b) => a.name.localeCompare(b.name));
|
|
958
|
+
if (!added.length && !removed.length && !changed.length) return null;
|
|
959
|
+
return { added, removed, changed };
|
|
960
|
+
}
|
|
961
|
+
function diffTriggers(apiTriggers, localTriggers) {
|
|
962
|
+
const apiMap = mapByName(apiTriggers);
|
|
963
|
+
const localMap = mapByName(localTriggers);
|
|
964
|
+
const added = [];
|
|
965
|
+
const removed = [];
|
|
966
|
+
const changed = [];
|
|
967
|
+
for (const [name, local] of localMap.entries()) {
|
|
968
|
+
if (!apiMap.has(name)) {
|
|
969
|
+
added.push(local);
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
const api = apiMap.get(name);
|
|
973
|
+
if (api.event !== local.event || api.trigger !== local.trigger) {
|
|
974
|
+
changed.push({ name, from: api, to: local });
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
for (const name of apiMap.keys()) {
|
|
978
|
+
if (!localMap.has(name)) removed.push(name);
|
|
979
|
+
}
|
|
980
|
+
added.sort((a, b) => a.name.localeCompare(b.name));
|
|
981
|
+
removed.sort();
|
|
982
|
+
changed.sort((a, b) => a.name.localeCompare(b.name));
|
|
983
|
+
if (!added.length && !removed.length && !changed.length) return null;
|
|
984
|
+
return { added, removed, changed };
|
|
985
|
+
}
|
|
986
|
+
function computeSchemaDiff(apiSchema, localSchema) {
|
|
987
|
+
const apiEntities = normalizeEntities(apiSchema);
|
|
988
|
+
const localEntities = normalizeEntities(localSchema);
|
|
989
|
+
const apiMap = mapByName(apiEntities);
|
|
990
|
+
const localMap = mapByName(localEntities);
|
|
991
|
+
const newTables = [];
|
|
992
|
+
const removedTables = [];
|
|
993
|
+
const changedTables = [];
|
|
994
|
+
for (const [name, localEntity] of localMap.entries()) {
|
|
995
|
+
if (!apiMap.has(name)) {
|
|
996
|
+
newTables.push(name);
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
const apiEntity = apiMap.get(name);
|
|
1000
|
+
const tableDiff = { name };
|
|
1001
|
+
const partitionFrom = normalizePartition(apiEntity.partition);
|
|
1002
|
+
const partitionTo = normalizePartition(localEntity.partition);
|
|
1003
|
+
if (partitionFrom !== partitionTo) {
|
|
1004
|
+
tableDiff.partition = { from: partitionFrom || null, to: partitionTo || null };
|
|
1005
|
+
}
|
|
1006
|
+
if (!identifiersEqual(apiEntity.identifier, localEntity.identifier)) {
|
|
1007
|
+
tableDiff.identifier = {
|
|
1008
|
+
from: apiEntity.identifier ?? null,
|
|
1009
|
+
to: localEntity.identifier ?? null
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
const attrs = diffAttributes(apiEntity.attributes, localEntity.attributes);
|
|
1013
|
+
if (attrs) tableDiff.attributes = attrs;
|
|
1014
|
+
const indexes = diffIndexes(apiEntity.indexes, localEntity.indexes);
|
|
1015
|
+
if (indexes) tableDiff.indexes = indexes;
|
|
1016
|
+
const resolvers = diffResolvers(apiEntity.resolvers, localEntity.resolvers);
|
|
1017
|
+
if (resolvers) tableDiff.resolvers = resolvers;
|
|
1018
|
+
const triggers = diffTriggers(apiEntity.triggers, localEntity.triggers);
|
|
1019
|
+
if (triggers) tableDiff.triggers = triggers;
|
|
1020
|
+
const hasChange = tableDiff.partition || tableDiff.identifier || tableDiff.attributes || tableDiff.indexes || tableDiff.resolvers || tableDiff.triggers;
|
|
1021
|
+
if (hasChange) {
|
|
1022
|
+
changedTables.push(tableDiff);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
for (const name of apiMap.keys()) {
|
|
1026
|
+
if (!localMap.has(name)) removedTables.push(name);
|
|
1027
|
+
}
|
|
1028
|
+
newTables.sort();
|
|
1029
|
+
removedTables.sort();
|
|
1030
|
+
changedTables.sort((a, b) => a.name.localeCompare(b.name));
|
|
1031
|
+
return { newTables, removedTables, changedTables };
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// src/impl/onyx-core.ts
|
|
1035
|
+
var DEFAULT_CACHE_TTL = 5 * 60 * 1e3;
|
|
1036
|
+
function toSingleCondition(criteria) {
|
|
1037
|
+
return { conditionType: "SingleCondition", criteria };
|
|
1038
|
+
}
|
|
1039
|
+
function flattenStrings(values) {
|
|
1040
|
+
const flat = [];
|
|
1041
|
+
for (const value of values) {
|
|
1042
|
+
if (Array.isArray(value)) {
|
|
1043
|
+
flat.push(...value);
|
|
1044
|
+
} else if (typeof value === "string") {
|
|
1045
|
+
flat.push(value);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
return flat;
|
|
1049
|
+
}
|
|
1050
|
+
function toCondition(input) {
|
|
1051
|
+
if (typeof input.toCondition === "function") {
|
|
1052
|
+
return input.toCondition();
|
|
1053
|
+
}
|
|
1054
|
+
const c2 = input;
|
|
1055
|
+
if (c2 && typeof c2.field === "string" && typeof c2.operator === "string") {
|
|
1056
|
+
return toSingleCondition(c2);
|
|
1057
|
+
}
|
|
1058
|
+
throw new Error("Invalid condition passed to builder.");
|
|
1059
|
+
}
|
|
1060
|
+
function serializeDates(value) {
|
|
1061
|
+
if (value instanceof Date) return value.toISOString();
|
|
1062
|
+
if (Array.isArray(value)) return value.map(serializeDates);
|
|
1063
|
+
if (value && typeof value === "object") {
|
|
1064
|
+
const out = {};
|
|
1065
|
+
for (const [k, v] of Object.entries(value)) {
|
|
1066
|
+
out[k] = serializeDates(v);
|
|
1067
|
+
}
|
|
1068
|
+
return out;
|
|
1069
|
+
}
|
|
1070
|
+
return value;
|
|
1071
|
+
}
|
|
1072
|
+
function stripEntityText(input) {
|
|
1073
|
+
const { entityText, ...rest } = input;
|
|
1074
|
+
return rest;
|
|
1075
|
+
}
|
|
1076
|
+
function normalizeSecretMetadata(input) {
|
|
1077
|
+
return { ...input, updatedAt: new Date(input.updatedAt) };
|
|
1078
|
+
}
|
|
1079
|
+
function normalizeSecretRecord(input) {
|
|
1080
|
+
return { ...input, updatedAt: new Date(input.updatedAt) };
|
|
1081
|
+
}
|
|
1082
|
+
function normalizeDate(value) {
|
|
1083
|
+
if (value == null) return void 0;
|
|
1084
|
+
if (value instanceof Date) return value;
|
|
1085
|
+
const ts = new Date(value);
|
|
1086
|
+
return Number.isNaN(ts.getTime()) ? void 0 : ts;
|
|
1087
|
+
}
|
|
1088
|
+
function normalizeSchemaRevision(input, fallbackDatabaseId) {
|
|
1089
|
+
const {
|
|
1090
|
+
meta,
|
|
1091
|
+
createdAt,
|
|
1092
|
+
publishedAt,
|
|
1093
|
+
revisionId,
|
|
1094
|
+
entityText,
|
|
1095
|
+
databaseId,
|
|
1096
|
+
entities,
|
|
1097
|
+
revisionDescription,
|
|
1098
|
+
...rest
|
|
1099
|
+
} = input;
|
|
1100
|
+
const dbId = typeof databaseId === "string" ? databaseId : fallbackDatabaseId;
|
|
1101
|
+
const entityList = Array.isArray(entities) ? entities : [];
|
|
1102
|
+
const revisionDesc = typeof revisionDescription === "string" ? revisionDescription : void 0;
|
|
1103
|
+
const mergedMeta = {
|
|
1104
|
+
revisionId: meta?.revisionId ?? revisionId,
|
|
1105
|
+
createdAt: normalizeDate(meta?.createdAt ?? createdAt),
|
|
1106
|
+
publishedAt: normalizeDate(meta?.publishedAt ?? publishedAt)
|
|
1107
|
+
};
|
|
1108
|
+
const cleanedMeta = mergedMeta.revisionId || mergedMeta.createdAt || mergedMeta.publishedAt ? mergedMeta : void 0;
|
|
1109
|
+
return {
|
|
1110
|
+
databaseId: dbId,
|
|
1111
|
+
revisionDescription: revisionDesc,
|
|
1112
|
+
entities: entityList,
|
|
1113
|
+
meta: cleanedMeta,
|
|
1114
|
+
...rest
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
var OnyxDatabaseImpl = class {
|
|
1118
|
+
cfgPromise;
|
|
1119
|
+
resolved = null;
|
|
1120
|
+
http = null;
|
|
1121
|
+
streams = /* @__PURE__ */ new Set();
|
|
1122
|
+
requestLoggingEnabled;
|
|
1123
|
+
responseLoggingEnabled;
|
|
1124
|
+
defaultPartition;
|
|
1125
|
+
constructor(config, resolveConfigWithCache) {
|
|
1126
|
+
this.requestLoggingEnabled = !!config?.requestLoggingEnabled;
|
|
1127
|
+
this.responseLoggingEnabled = !!config?.responseLoggingEnabled;
|
|
1128
|
+
this.defaultPartition = config?.partition;
|
|
1129
|
+
this.cfgPromise = resolveConfigWithCache(config);
|
|
1130
|
+
}
|
|
1131
|
+
async ensureClient() {
|
|
1132
|
+
if (!this.resolved) {
|
|
1133
|
+
this.resolved = await this.cfgPromise;
|
|
1134
|
+
}
|
|
1135
|
+
if (!this.http) {
|
|
1136
|
+
this.http = new HttpClient({
|
|
1137
|
+
baseUrl: this.resolved.baseUrl,
|
|
1138
|
+
apiKey: this.resolved.apiKey,
|
|
1139
|
+
apiSecret: this.resolved.apiSecret,
|
|
1140
|
+
fetchImpl: this.resolved.fetch,
|
|
1141
|
+
requestLoggingEnabled: this.requestLoggingEnabled,
|
|
1142
|
+
responseLoggingEnabled: this.responseLoggingEnabled,
|
|
1143
|
+
retryEnabled: this.resolved.retryEnabled,
|
|
1144
|
+
maxRetries: this.resolved.maxRetries,
|
|
1145
|
+
retryInitialDelayMs: this.resolved.retryInitialDelayMs
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
return {
|
|
1149
|
+
http: this.http,
|
|
1150
|
+
fetchImpl: this.resolved.fetch,
|
|
1151
|
+
baseUrl: this.resolved.baseUrl,
|
|
1152
|
+
databaseId: this.resolved.databaseId
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
registerStream(handle) {
|
|
1156
|
+
this.streams.add(handle);
|
|
1157
|
+
return {
|
|
1158
|
+
cancel: () => {
|
|
1159
|
+
try {
|
|
1160
|
+
handle.cancel();
|
|
1161
|
+
} finally {
|
|
1162
|
+
this.streams.delete(handle);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
/** -------- IOnyxDatabase -------- */
|
|
1168
|
+
from(table) {
|
|
1169
|
+
return new QueryBuilderImpl(this, String(table), this.defaultPartition);
|
|
1170
|
+
}
|
|
1171
|
+
select(...fields) {
|
|
1172
|
+
const qb = new QueryBuilderImpl(
|
|
1173
|
+
this,
|
|
1174
|
+
null,
|
|
1175
|
+
this.defaultPartition
|
|
1176
|
+
);
|
|
1177
|
+
qb.select(...fields);
|
|
1178
|
+
return qb;
|
|
1179
|
+
}
|
|
1180
|
+
cascade(...relationships) {
|
|
1181
|
+
const cb = new CascadeBuilderImpl(this);
|
|
1182
|
+
return cb.cascade(...relationships);
|
|
1183
|
+
}
|
|
1184
|
+
cascadeBuilder() {
|
|
1185
|
+
return new CascadeRelationshipBuilder();
|
|
1186
|
+
}
|
|
1187
|
+
// Impl
|
|
1188
|
+
save(table, entityOrEntities, options) {
|
|
1189
|
+
if (arguments.length === 1) {
|
|
1190
|
+
return new SaveBuilderImpl(this, table);
|
|
1191
|
+
}
|
|
1192
|
+
return this._saveInternal(table, entityOrEntities, options);
|
|
1193
|
+
}
|
|
1194
|
+
async batchSave(table, entities, batchSize = 1e3, options) {
|
|
1195
|
+
for (let i = 0; i < entities.length; i += batchSize) {
|
|
1196
|
+
const chunk = entities.slice(i, i + batchSize);
|
|
1197
|
+
if (chunk.length) {
|
|
1198
|
+
await this._saveInternal(String(table), chunk, options);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
async findById(table, primaryKey, options) {
|
|
1203
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1204
|
+
const params = new URLSearchParams();
|
|
1205
|
+
const partition = options?.partition ?? this.defaultPartition;
|
|
1206
|
+
if (partition) params.append("partition", partition);
|
|
1207
|
+
if (options?.resolvers?.length) params.append("resolvers", options.resolvers.join(","));
|
|
1208
|
+
const path = `/data/${encodeURIComponent(databaseId)}/${encodeURIComponent(
|
|
1209
|
+
String(table)
|
|
1210
|
+
)}/${encodeURIComponent(primaryKey)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1211
|
+
try {
|
|
1212
|
+
return await http.request("GET", path);
|
|
1213
|
+
} catch (err) {
|
|
1214
|
+
if (err instanceof OnyxHttpError && err.status === 404) return null;
|
|
1215
|
+
throw err;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
async delete(table, primaryKey, options) {
|
|
1219
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1220
|
+
const params = new URLSearchParams();
|
|
1221
|
+
const partition = options?.partition ?? this.defaultPartition;
|
|
1222
|
+
if (partition) params.append("partition", partition);
|
|
1223
|
+
if (options?.relationships?.length) {
|
|
1224
|
+
params.append("relationships", options.relationships.map(encodeURIComponent).join(","));
|
|
1225
|
+
}
|
|
1226
|
+
const path = `/data/${encodeURIComponent(databaseId)}/${encodeURIComponent(
|
|
1227
|
+
table
|
|
1228
|
+
)}/${encodeURIComponent(primaryKey)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1229
|
+
await http.request("DELETE", path);
|
|
1230
|
+
return true;
|
|
1231
|
+
}
|
|
1232
|
+
async saveDocument(doc) {
|
|
1233
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1234
|
+
const path = `/data/${encodeURIComponent(databaseId)}/document`;
|
|
1235
|
+
return http.request("PUT", path, serializeDates(doc));
|
|
1236
|
+
}
|
|
1237
|
+
async getDocument(documentId, options) {
|
|
1238
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1239
|
+
const params = new URLSearchParams();
|
|
1240
|
+
if (options?.width != null) params.append("width", String(options.width));
|
|
1241
|
+
if (options?.height != null) params.append("height", String(options.height));
|
|
1242
|
+
const path = `/data/${encodeURIComponent(databaseId)}/document/${encodeURIComponent(
|
|
1243
|
+
documentId
|
|
1244
|
+
)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1245
|
+
return http.request("GET", path);
|
|
1246
|
+
}
|
|
1247
|
+
async deleteDocument(documentId) {
|
|
1248
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1249
|
+
const path = `/data/${encodeURIComponent(databaseId)}/document/${encodeURIComponent(
|
|
1250
|
+
documentId
|
|
1251
|
+
)}`;
|
|
1252
|
+
return http.request("DELETE", path);
|
|
1253
|
+
}
|
|
1254
|
+
async getSchema(options) {
|
|
1255
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1256
|
+
const params = new URLSearchParams();
|
|
1257
|
+
const tables = options?.tables;
|
|
1258
|
+
const tableList = Array.isArray(tables) ? tables : typeof tables === "string" ? tables.split(",") : [];
|
|
1259
|
+
const normalizedTables = tableList.map((t) => t.trim()).filter(Boolean);
|
|
1260
|
+
if (normalizedTables.length) {
|
|
1261
|
+
params.append("tables", normalizedTables.map(encodeURIComponent).join(","));
|
|
1262
|
+
}
|
|
1263
|
+
const path = `/schemas/${encodeURIComponent(databaseId)}${params.size ? `?${params.toString()}` : ""}`;
|
|
1264
|
+
const res = await http.request("GET", path);
|
|
1265
|
+
return normalizeSchemaRevision(res, databaseId);
|
|
1266
|
+
}
|
|
1267
|
+
async getSchemaHistory() {
|
|
1268
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1269
|
+
const path = `/schemas/history/${encodeURIComponent(databaseId)}`;
|
|
1270
|
+
const res = await http.request("GET", path);
|
|
1271
|
+
return Array.isArray(res) ? res.map((entry) => normalizeSchemaRevision(entry, databaseId)) : [];
|
|
1272
|
+
}
|
|
1273
|
+
async diffSchema(localSchema) {
|
|
1274
|
+
const apiSchema = await this.getSchema();
|
|
1275
|
+
return computeSchemaDiff(apiSchema, localSchema);
|
|
1276
|
+
}
|
|
1277
|
+
async updateSchema(schema, options) {
|
|
1278
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1279
|
+
const params = new URLSearchParams();
|
|
1280
|
+
if (options?.publish) params.append("publish", "true");
|
|
1281
|
+
const path = `/schemas/${encodeURIComponent(databaseId)}${params.size ? `?${params.toString()}` : ""}`;
|
|
1282
|
+
const body = stripEntityText({ ...schema, databaseId: schema.databaseId ?? databaseId });
|
|
1283
|
+
const res = await http.request(
|
|
1284
|
+
"PUT",
|
|
1285
|
+
path,
|
|
1286
|
+
serializeDates(body)
|
|
1287
|
+
);
|
|
1288
|
+
return normalizeSchemaRevision(res, databaseId);
|
|
1289
|
+
}
|
|
1290
|
+
async validateSchema(schema) {
|
|
1291
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1292
|
+
const path = `/schemas/${encodeURIComponent(databaseId)}/validate`;
|
|
1293
|
+
const body = stripEntityText({ ...schema, databaseId: schema.databaseId ?? databaseId });
|
|
1294
|
+
const res = await http.request(
|
|
1295
|
+
"POST",
|
|
1296
|
+
path,
|
|
1297
|
+
serializeDates(body)
|
|
1298
|
+
);
|
|
1299
|
+
const normalizedSchema = res.schema ? normalizeSchemaRevision(res.schema, databaseId) : void 0;
|
|
1300
|
+
return {
|
|
1301
|
+
...res,
|
|
1302
|
+
valid: res.valid ?? true,
|
|
1303
|
+
schema: normalizedSchema
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
async listSecrets() {
|
|
1307
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1308
|
+
const path = `/database/${encodeURIComponent(databaseId)}/secret`;
|
|
1309
|
+
const response = await http.request(
|
|
1310
|
+
"GET",
|
|
1311
|
+
path,
|
|
1312
|
+
void 0,
|
|
1313
|
+
{ "Content-Type": "application/json" }
|
|
1314
|
+
);
|
|
1315
|
+
const records = (response.records ?? []).map(normalizeSecretMetadata);
|
|
1316
|
+
return {
|
|
1317
|
+
...response,
|
|
1318
|
+
records,
|
|
1319
|
+
meta: response.meta ?? { totalRecords: records.length }
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
async getSecret(key) {
|
|
1323
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1324
|
+
const path = `/database/${encodeURIComponent(databaseId)}/secret/${encodeURIComponent(key)}`;
|
|
1325
|
+
const record = await http.request("GET", path, void 0, {
|
|
1326
|
+
"Content-Type": "application/json"
|
|
1327
|
+
});
|
|
1328
|
+
return normalizeSecretRecord(record);
|
|
1329
|
+
}
|
|
1330
|
+
async putSecret(key, input) {
|
|
1331
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1332
|
+
const path = `/database/${encodeURIComponent(databaseId)}/secret/${encodeURIComponent(key)}`;
|
|
1333
|
+
const response = await http.request("PUT", path, serializeDates(input));
|
|
1334
|
+
return normalizeSecretMetadata(response);
|
|
1335
|
+
}
|
|
1336
|
+
async deleteSecret(key) {
|
|
1337
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1338
|
+
const path = `/database/${encodeURIComponent(databaseId)}/secret/${encodeURIComponent(key)}`;
|
|
1339
|
+
const response = await http.request(
|
|
1340
|
+
"DELETE",
|
|
1341
|
+
path
|
|
1342
|
+
);
|
|
1343
|
+
const deletedKey = response && typeof response === "object" && "key" in response ? response.key : void 0;
|
|
1344
|
+
return { key: deletedKey ?? key };
|
|
1345
|
+
}
|
|
1346
|
+
close() {
|
|
1347
|
+
for (const h of Array.from(this.streams)) {
|
|
1348
|
+
try {
|
|
1349
|
+
h.cancel();
|
|
1350
|
+
} catch {
|
|
1351
|
+
} finally {
|
|
1352
|
+
this.streams.delete(h);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
/** -------- internal helpers used by builders -------- */
|
|
1357
|
+
async _count(table, select, partition) {
|
|
1358
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1359
|
+
const params = new URLSearchParams();
|
|
1360
|
+
const p = partition ?? this.defaultPartition;
|
|
1361
|
+
if (p) params.append("partition", p);
|
|
1362
|
+
const path = `/data/${encodeURIComponent(databaseId)}/query/count/${encodeURIComponent(
|
|
1363
|
+
table
|
|
1364
|
+
)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1365
|
+
return http.request("PUT", path, serializeDates(select));
|
|
1366
|
+
}
|
|
1367
|
+
async _queryPage(table, select, opts = {}) {
|
|
1368
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1369
|
+
const params = new URLSearchParams();
|
|
1370
|
+
if (opts.pageSize != null) params.append("pageSize", String(opts.pageSize));
|
|
1371
|
+
if (opts.nextPage) params.append("nextPage", opts.nextPage);
|
|
1372
|
+
const p = opts.partition ?? this.defaultPartition;
|
|
1373
|
+
if (p) params.append("partition", p);
|
|
1374
|
+
const path = `/data/${encodeURIComponent(databaseId)}/query/${encodeURIComponent(
|
|
1375
|
+
table
|
|
1376
|
+
)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1377
|
+
return http.request("PUT", path, serializeDates(select));
|
|
1378
|
+
}
|
|
1379
|
+
async _update(table, update, partition) {
|
|
1380
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1381
|
+
const params = new URLSearchParams();
|
|
1382
|
+
const p = partition ?? this.defaultPartition;
|
|
1383
|
+
if (p) params.append("partition", p);
|
|
1384
|
+
const path = `/data/${encodeURIComponent(databaseId)}/query/update/${encodeURIComponent(
|
|
1385
|
+
table
|
|
1386
|
+
)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1387
|
+
return http.request("PUT", path, serializeDates(update));
|
|
1388
|
+
}
|
|
1389
|
+
async _deleteByQuery(table, select, partition) {
|
|
1390
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1391
|
+
const params = new URLSearchParams();
|
|
1392
|
+
const p = partition ?? this.defaultPartition;
|
|
1393
|
+
if (p) params.append("partition", p);
|
|
1394
|
+
const path = `/data/${encodeURIComponent(databaseId)}/query/delete/${encodeURIComponent(
|
|
1395
|
+
table
|
|
1396
|
+
)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1397
|
+
return http.request("PUT", path, serializeDates(select));
|
|
1398
|
+
}
|
|
1399
|
+
async _stream(table, select, includeQueryResults, keepAlive, handlers) {
|
|
1400
|
+
const { http, baseUrl, databaseId, fetchImpl } = await this.ensureClient();
|
|
1401
|
+
const params = new URLSearchParams();
|
|
1402
|
+
if (includeQueryResults) params.append("includeQueryResults", "true");
|
|
1403
|
+
if (keepAlive) params.append("keepAlive", "true");
|
|
1404
|
+
const url = `${baseUrl}/data/${encodeURIComponent(databaseId)}/query/stream/${encodeURIComponent(
|
|
1405
|
+
table
|
|
1406
|
+
)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1407
|
+
const handle = await openJsonLinesStream(
|
|
1408
|
+
fetchImpl,
|
|
1409
|
+
url,
|
|
1410
|
+
{
|
|
1411
|
+
method: "PUT",
|
|
1412
|
+
headers: http.headers({
|
|
1413
|
+
Accept: "application/x-ndjson",
|
|
1414
|
+
"Content-Type": "application/json"
|
|
1415
|
+
}),
|
|
1416
|
+
body: JSON.stringify(serializeDates(select))
|
|
1417
|
+
},
|
|
1418
|
+
handlers
|
|
1419
|
+
);
|
|
1420
|
+
return this.registerStream(handle);
|
|
1421
|
+
}
|
|
1422
|
+
async _saveInternal(table, entityOrEntities, options) {
|
|
1423
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1424
|
+
const params = new URLSearchParams();
|
|
1425
|
+
if (options?.relationships?.length) {
|
|
1426
|
+
params.append("relationships", options.relationships.map(encodeURIComponent).join(","));
|
|
1427
|
+
}
|
|
1428
|
+
const path = `/data/${encodeURIComponent(databaseId)}/${encodeURIComponent(table)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1429
|
+
return http.request("PUT", path, serializeDates(entityOrEntities));
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1432
|
+
var QueryBuilderImpl = class {
|
|
1433
|
+
db;
|
|
1434
|
+
table;
|
|
1435
|
+
fields = null;
|
|
1436
|
+
resolvers = null;
|
|
1437
|
+
conditions = null;
|
|
1438
|
+
sort = null;
|
|
1439
|
+
limitValue = null;
|
|
1440
|
+
distinctValue = false;
|
|
1441
|
+
groupByValues = null;
|
|
1442
|
+
partitionValue;
|
|
1443
|
+
pageSizeValue = null;
|
|
1444
|
+
nextPageValue = null;
|
|
1445
|
+
mode = "select";
|
|
1446
|
+
updates = null;
|
|
1447
|
+
onItemAddedListener = null;
|
|
1448
|
+
onItemUpdatedListener = null;
|
|
1449
|
+
onItemDeletedListener = null;
|
|
1450
|
+
onItemListener = null;
|
|
1451
|
+
constructor(db, table, partition) {
|
|
1452
|
+
this.db = db;
|
|
1453
|
+
this.table = table;
|
|
1454
|
+
this.partitionValue = partition;
|
|
1455
|
+
}
|
|
1456
|
+
ensureTable() {
|
|
1457
|
+
if (!this.table) throw new Error("Table is not defined. Call from(<table>) first.");
|
|
1458
|
+
return this.table;
|
|
1459
|
+
}
|
|
1460
|
+
serializableConditions() {
|
|
1461
|
+
return normalizeCondition(this.conditions);
|
|
1462
|
+
}
|
|
1463
|
+
toSelectQuery() {
|
|
1464
|
+
return {
|
|
1465
|
+
type: "SelectQuery",
|
|
1466
|
+
fields: this.fields,
|
|
1467
|
+
conditions: this.serializableConditions(),
|
|
1468
|
+
sort: this.sort,
|
|
1469
|
+
limit: this.limitValue,
|
|
1470
|
+
distinct: this.distinctValue,
|
|
1471
|
+
groupBy: this.groupByValues,
|
|
1472
|
+
partition: this.partitionValue ?? null,
|
|
1473
|
+
resolvers: this.resolvers
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
toUpdateQuery() {
|
|
1477
|
+
return {
|
|
1478
|
+
type: "UpdateQuery",
|
|
1479
|
+
conditions: this.serializableConditions(),
|
|
1480
|
+
updates: this.updates ?? {},
|
|
1481
|
+
sort: this.sort,
|
|
1482
|
+
limit: this.limitValue,
|
|
1483
|
+
partition: this.partitionValue ?? null
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
toSerializableQueryObject() {
|
|
1487
|
+
const table = this.ensureTable();
|
|
1488
|
+
const payload = this.mode === "update" ? this.toUpdateQuery() : this.toSelectQuery();
|
|
1489
|
+
return { ...payload, table };
|
|
1490
|
+
}
|
|
1491
|
+
from(table) {
|
|
1492
|
+
this.table = table;
|
|
1493
|
+
return this;
|
|
1494
|
+
}
|
|
1495
|
+
select(...fields) {
|
|
1496
|
+
const flat = flattenStrings(fields);
|
|
1497
|
+
this.fields = flat.length > 0 ? flat : null;
|
|
1498
|
+
return this;
|
|
1499
|
+
}
|
|
1500
|
+
resolve(...values) {
|
|
1501
|
+
const flat = flattenStrings(values);
|
|
1502
|
+
this.resolvers = flat.length > 0 ? flat : null;
|
|
1503
|
+
return this;
|
|
1504
|
+
}
|
|
1505
|
+
where(condition) {
|
|
1506
|
+
const c2 = toCondition(condition);
|
|
1507
|
+
if (!this.conditions) {
|
|
1508
|
+
this.conditions = c2;
|
|
1509
|
+
} else {
|
|
1510
|
+
this.conditions = {
|
|
1511
|
+
conditionType: "CompoundCondition",
|
|
1512
|
+
operator: "AND",
|
|
1513
|
+
conditions: [this.conditions, c2]
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
return this;
|
|
1517
|
+
}
|
|
1518
|
+
and(condition) {
|
|
1519
|
+
const c2 = toCondition(condition);
|
|
1520
|
+
if (!this.conditions) {
|
|
1521
|
+
this.conditions = c2;
|
|
1522
|
+
} else if (this.conditions.conditionType === "CompoundCondition" && this.conditions.operator === "AND") {
|
|
1523
|
+
this.conditions.conditions.push(c2);
|
|
1524
|
+
} else {
|
|
1525
|
+
this.conditions = {
|
|
1526
|
+
conditionType: "CompoundCondition",
|
|
1527
|
+
operator: "AND",
|
|
1528
|
+
conditions: [this.conditions, c2]
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
return this;
|
|
1532
|
+
}
|
|
1533
|
+
or(condition) {
|
|
1534
|
+
const c2 = toCondition(condition);
|
|
1535
|
+
if (!this.conditions) {
|
|
1536
|
+
this.conditions = c2;
|
|
1537
|
+
} else if (this.conditions.conditionType === "CompoundCondition" && this.conditions.operator === "OR") {
|
|
1538
|
+
this.conditions.conditions.push(c2);
|
|
1539
|
+
} else {
|
|
1540
|
+
this.conditions = {
|
|
1541
|
+
conditionType: "CompoundCondition",
|
|
1542
|
+
operator: "OR",
|
|
1543
|
+
conditions: [this.conditions, c2]
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
return this;
|
|
1547
|
+
}
|
|
1548
|
+
orderBy(...sorts) {
|
|
1549
|
+
this.sort = sorts;
|
|
1550
|
+
return this;
|
|
1551
|
+
}
|
|
1552
|
+
groupBy(...fields) {
|
|
1553
|
+
this.groupByValues = fields.length ? fields : null;
|
|
1554
|
+
return this;
|
|
1555
|
+
}
|
|
1556
|
+
distinct() {
|
|
1557
|
+
this.distinctValue = true;
|
|
1558
|
+
return this;
|
|
1559
|
+
}
|
|
1560
|
+
limit(n) {
|
|
1561
|
+
this.limitValue = n;
|
|
1562
|
+
return this;
|
|
1563
|
+
}
|
|
1564
|
+
inPartition(partition) {
|
|
1565
|
+
this.partitionValue = partition;
|
|
1566
|
+
return this;
|
|
1567
|
+
}
|
|
1568
|
+
pageSize(n) {
|
|
1569
|
+
this.pageSizeValue = n;
|
|
1570
|
+
return this;
|
|
1571
|
+
}
|
|
1572
|
+
nextPage(token) {
|
|
1573
|
+
this.nextPageValue = token;
|
|
1574
|
+
return this;
|
|
1575
|
+
}
|
|
1576
|
+
setUpdates(updates) {
|
|
1577
|
+
this.mode = "update";
|
|
1578
|
+
this.updates = updates;
|
|
1579
|
+
return this;
|
|
1580
|
+
}
|
|
1581
|
+
async count() {
|
|
1582
|
+
if (this.mode !== "select") throw new Error("Cannot call count() in update mode.");
|
|
1583
|
+
const table = this.ensureTable();
|
|
1584
|
+
return this.db._count(table, this.toSelectQuery(), this.partitionValue);
|
|
1585
|
+
}
|
|
1586
|
+
async page(options = {}) {
|
|
1587
|
+
if (this.mode !== "select") throw new Error("Cannot call page() in update mode.");
|
|
1588
|
+
const table = this.ensureTable();
|
|
1589
|
+
const final = {
|
|
1590
|
+
pageSize: this.pageSizeValue ?? options.pageSize,
|
|
1591
|
+
nextPage: this.nextPageValue ?? options.nextPage,
|
|
1592
|
+
partition: this.partitionValue
|
|
1593
|
+
};
|
|
1594
|
+
return this.db._queryPage(table, this.toSelectQuery(), final);
|
|
1595
|
+
}
|
|
1596
|
+
list(options = {}) {
|
|
1597
|
+
const size = this.pageSizeValue ?? options.pageSize;
|
|
1598
|
+
const pgPromise = this.page(options).then((pg) => {
|
|
1599
|
+
const fetcher = (token) => this.nextPage(token).list({ pageSize: size });
|
|
1600
|
+
return new QueryResults(Array.isArray(pg.records) ? pg.records : [], pg.nextPage ?? null, fetcher);
|
|
1601
|
+
});
|
|
1602
|
+
for (const m of Object.getOwnPropertyNames(QueryResults.prototype)) {
|
|
1603
|
+
if (m === "constructor") continue;
|
|
1604
|
+
pgPromise[m] = (...args) => pgPromise.then((res) => res[m](...args));
|
|
1605
|
+
}
|
|
1606
|
+
return pgPromise;
|
|
1607
|
+
}
|
|
1608
|
+
async firstOrNull() {
|
|
1609
|
+
if (this.mode !== "select") throw new Error("Cannot call firstOrNull() in update mode.");
|
|
1610
|
+
this.limitValue = 1;
|
|
1611
|
+
const pg = await this.page();
|
|
1612
|
+
return Array.isArray(pg.records) && pg.records.length > 0 ? pg.records[0] : null;
|
|
1613
|
+
}
|
|
1614
|
+
async one() {
|
|
1615
|
+
return this.firstOrNull();
|
|
1616
|
+
}
|
|
1617
|
+
async delete() {
|
|
1618
|
+
if (this.mode !== "select") throw new Error("delete() is only applicable in select mode.");
|
|
1619
|
+
const table = this.ensureTable();
|
|
1620
|
+
return this.db._deleteByQuery(table, this.toSelectQuery(), this.partitionValue);
|
|
1621
|
+
}
|
|
1622
|
+
async update() {
|
|
1623
|
+
if (this.mode !== "update") throw new Error("Call setUpdates(...) before update().");
|
|
1624
|
+
const table = this.ensureTable();
|
|
1625
|
+
const update = this.toUpdateQuery();
|
|
1626
|
+
return this.db._update(table, update, this.partitionValue);
|
|
1627
|
+
}
|
|
1628
|
+
onItemAdded(listener) {
|
|
1629
|
+
this.onItemAddedListener = listener;
|
|
1630
|
+
return this;
|
|
1631
|
+
}
|
|
1632
|
+
onItemUpdated(listener) {
|
|
1633
|
+
this.onItemUpdatedListener = listener;
|
|
1634
|
+
return this;
|
|
1635
|
+
}
|
|
1636
|
+
onItemDeleted(listener) {
|
|
1637
|
+
this.onItemDeletedListener = listener;
|
|
1638
|
+
return this;
|
|
1639
|
+
}
|
|
1640
|
+
onItem(listener) {
|
|
1641
|
+
this.onItemListener = listener;
|
|
1642
|
+
return this;
|
|
1643
|
+
}
|
|
1644
|
+
async streamEventsOnly(keepAlive = true) {
|
|
1645
|
+
return this.stream(false, keepAlive);
|
|
1646
|
+
}
|
|
1647
|
+
async streamWithQueryResults(keepAlive = false) {
|
|
1648
|
+
return this.stream(true, keepAlive);
|
|
1649
|
+
}
|
|
1650
|
+
async stream(includeQueryResults = true, keepAlive = false) {
|
|
1651
|
+
if (this.mode !== "select") throw new Error("Streaming is only applicable in select mode.");
|
|
1652
|
+
const table = this.ensureTable();
|
|
1653
|
+
return this.db._stream(table, this.toSelectQuery(), includeQueryResults, keepAlive, {
|
|
1654
|
+
onItemAdded: this.onItemAddedListener ?? void 0,
|
|
1655
|
+
onItemUpdated: this.onItemUpdatedListener ?? void 0,
|
|
1656
|
+
onItemDeleted: this.onItemDeletedListener ?? void 0,
|
|
1657
|
+
onItem: this.onItemListener ?? void 0
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
};
|
|
1661
|
+
var SaveBuilderImpl = class {
|
|
1662
|
+
db;
|
|
1663
|
+
table;
|
|
1664
|
+
relationships = null;
|
|
1665
|
+
constructor(db, table) {
|
|
1666
|
+
this.db = db;
|
|
1667
|
+
this.table = table;
|
|
1668
|
+
}
|
|
1669
|
+
cascade(...relationships) {
|
|
1670
|
+
this.relationships = relationships.flat();
|
|
1671
|
+
return this;
|
|
1672
|
+
}
|
|
1673
|
+
one(entity) {
|
|
1674
|
+
const opts = this.relationships ? { relationships: this.relationships } : void 0;
|
|
1675
|
+
return this.db._saveInternal(this.table, entity, opts);
|
|
1676
|
+
}
|
|
1677
|
+
many(entities) {
|
|
1678
|
+
const opts = this.relationships ? { relationships: this.relationships } : void 0;
|
|
1679
|
+
return this.db._saveInternal(this.table, entities, opts);
|
|
1680
|
+
}
|
|
1681
|
+
};
|
|
1682
|
+
var CascadeBuilderImpl = class {
|
|
1683
|
+
db;
|
|
1684
|
+
rels = null;
|
|
1685
|
+
constructor(db) {
|
|
1686
|
+
this.db = db;
|
|
1687
|
+
}
|
|
1688
|
+
cascade(...relationships) {
|
|
1689
|
+
this.rels = relationships.flat();
|
|
1690
|
+
return this;
|
|
1691
|
+
}
|
|
1692
|
+
save(table, entityOrEntities) {
|
|
1693
|
+
const opts = this.rels ? { relationships: this.rels } : void 0;
|
|
1694
|
+
return this.db._saveInternal(String(table), entityOrEntities, opts);
|
|
1695
|
+
}
|
|
1696
|
+
delete(table, primaryKey) {
|
|
1697
|
+
const opts = this.rels ? { relationships: this.rels } : void 0;
|
|
1698
|
+
return this.db.delete(table, primaryKey, opts);
|
|
1699
|
+
}
|
|
1700
|
+
};
|
|
1701
|
+
function createOnyxFacade(resolveConfig2) {
|
|
1702
|
+
let cachedCfg = null;
|
|
1703
|
+
function resolveConfigWithCache(config) {
|
|
1704
|
+
const ttl = config?.ttl ?? DEFAULT_CACHE_TTL;
|
|
1705
|
+
const now = Date.now();
|
|
1706
|
+
if (cachedCfg && cachedCfg.expires > now) {
|
|
1707
|
+
return cachedCfg.promise;
|
|
1708
|
+
}
|
|
1709
|
+
const { ttl: _ttl, requestLoggingEnabled: _reqLog, responseLoggingEnabled: _resLog, ...rest } = config ?? {};
|
|
1710
|
+
const promise = resolveConfig2(rest);
|
|
1711
|
+
cachedCfg = { promise, expires: now + ttl };
|
|
1712
|
+
return promise;
|
|
1713
|
+
}
|
|
1714
|
+
function clearCacheConfig() {
|
|
1715
|
+
cachedCfg = null;
|
|
1716
|
+
}
|
|
1717
|
+
return {
|
|
1718
|
+
init(config) {
|
|
1719
|
+
return new OnyxDatabaseImpl(config, resolveConfigWithCache);
|
|
1720
|
+
},
|
|
1721
|
+
clearCacheConfig
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
// src/impl/onyx-edge.ts
|
|
1726
|
+
var onyx = createOnyxFacade((config) => resolveConfig(config));
|
|
1727
|
+
|
|
1728
|
+
// src/helpers/sort.ts
|
|
1729
|
+
var asc = (field) => ({ field, order: "ASC" });
|
|
1730
|
+
var desc = (field) => ({ field, order: "DESC" });
|
|
1731
|
+
|
|
1732
|
+
// src/builders/condition-builder.ts
|
|
1733
|
+
var ConditionBuilderImpl = class {
|
|
1734
|
+
condition;
|
|
1735
|
+
/**
|
|
1736
|
+
* Initialize with an optional starting criteria.
|
|
1737
|
+
*
|
|
1738
|
+
* @param criteria Initial query criteria to seed the builder.
|
|
1739
|
+
* @example
|
|
1740
|
+
* ```ts
|
|
1741
|
+
* const builder = new ConditionBuilderImpl({ field: 'id', operator: 'eq', value: '1' });
|
|
1742
|
+
* ```
|
|
1743
|
+
*/
|
|
1744
|
+
constructor(criteria = null) {
|
|
1745
|
+
this.condition = criteria ? this.single(criteria) : null;
|
|
1746
|
+
}
|
|
1747
|
+
/**
|
|
1748
|
+
* Add a criteria combined with AND.
|
|
1749
|
+
*
|
|
1750
|
+
* @param condition Another builder or raw criteria to AND.
|
|
1751
|
+
* @example
|
|
1752
|
+
* ```ts
|
|
1753
|
+
* builder.and({ field: 'name', operator: 'eq', value: 'Ada' });
|
|
1754
|
+
* ```
|
|
1755
|
+
*/
|
|
1756
|
+
and(condition) {
|
|
1757
|
+
this.addCompound("AND", this.prepare(condition));
|
|
1758
|
+
return this;
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Add a criteria combined with OR.
|
|
1762
|
+
*
|
|
1763
|
+
* @param condition Another builder or raw criteria to OR.
|
|
1764
|
+
* @example
|
|
1765
|
+
* ```ts
|
|
1766
|
+
* builder.or({ field: 'status', operator: 'eq', value: 'active' });
|
|
1767
|
+
* ```
|
|
1768
|
+
*/
|
|
1769
|
+
or(condition) {
|
|
1770
|
+
this.addCompound("OR", this.prepare(condition));
|
|
1771
|
+
return this;
|
|
1772
|
+
}
|
|
1773
|
+
/**
|
|
1774
|
+
* Produce the composed QueryCondition.
|
|
1775
|
+
*
|
|
1776
|
+
* @example
|
|
1777
|
+
* ```ts
|
|
1778
|
+
* const condition = builder.toCondition();
|
|
1779
|
+
* ```
|
|
1780
|
+
*/
|
|
1781
|
+
toCondition() {
|
|
1782
|
+
if (!this.condition) {
|
|
1783
|
+
throw new Error("ConditionBuilder has no criteria.");
|
|
1784
|
+
}
|
|
1785
|
+
return this.condition;
|
|
1786
|
+
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Wrap raw criteria into a single condition object.
|
|
1789
|
+
*
|
|
1790
|
+
* @param criteria Criteria to wrap.
|
|
1791
|
+
* @example
|
|
1792
|
+
* ```ts
|
|
1793
|
+
* builder['single']({ field: 'id', operator: 'eq', value: '1' });
|
|
1794
|
+
* ```
|
|
1795
|
+
*/
|
|
1796
|
+
single(criteria) {
|
|
1797
|
+
return { conditionType: "SingleCondition", criteria };
|
|
1798
|
+
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Create a compound condition using the provided operator.
|
|
1801
|
+
*
|
|
1802
|
+
* @param operator Logical operator to apply.
|
|
1803
|
+
* @param conditions Child conditions to combine.
|
|
1804
|
+
* @example
|
|
1805
|
+
* ```ts
|
|
1806
|
+
* builder['compound']('AND', [condA, condB]);
|
|
1807
|
+
* ```
|
|
1808
|
+
*/
|
|
1809
|
+
compound(operator, conditions) {
|
|
1810
|
+
return { conditionType: "CompoundCondition", operator, conditions };
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Merge the next condition into the existing tree using the operator.
|
|
1814
|
+
*
|
|
1815
|
+
* @param operator Logical operator for the merge.
|
|
1816
|
+
* @param next Condition to merge into the tree.
|
|
1817
|
+
* @example
|
|
1818
|
+
* ```ts
|
|
1819
|
+
* builder['addCompound']('AND', someCondition);
|
|
1820
|
+
* ```
|
|
1821
|
+
*/
|
|
1822
|
+
addCompound(operator, next) {
|
|
1823
|
+
if (!this.condition) {
|
|
1824
|
+
this.condition = next;
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
if (this.condition.conditionType === "CompoundCondition" && this.condition.operator === operator) {
|
|
1828
|
+
this.condition.conditions.push(next);
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
this.condition = this.compound(operator, [this.condition, next]);
|
|
1832
|
+
}
|
|
1833
|
+
/**
|
|
1834
|
+
* Normalize input into a QueryCondition instance.
|
|
1835
|
+
*
|
|
1836
|
+
* @param condition Builder or raw criteria to normalize.
|
|
1837
|
+
* @example
|
|
1838
|
+
* ```ts
|
|
1839
|
+
* const qc = builder['prepare']({ field: 'id', operator: 'eq', value: '1' });
|
|
1840
|
+
* ```
|
|
1841
|
+
*/
|
|
1842
|
+
prepare(condition) {
|
|
1843
|
+
if (typeof condition.toCondition === "function") {
|
|
1844
|
+
return condition.toCondition();
|
|
1845
|
+
}
|
|
1846
|
+
const c2 = condition;
|
|
1847
|
+
if (c2 && typeof c2.field === "string" && typeof c2.operator === "string") {
|
|
1848
|
+
return this.single(c2);
|
|
1849
|
+
}
|
|
1850
|
+
throw new Error("Invalid condition");
|
|
1851
|
+
}
|
|
1852
|
+
};
|
|
1853
|
+
|
|
1854
|
+
// src/helpers/conditions.ts
|
|
1855
|
+
var c = (field, operator, value) => new ConditionBuilderImpl({ field, operator, value });
|
|
1856
|
+
var eq = (field, value) => c(field, "EQUAL", value);
|
|
1857
|
+
var neq = (field, value) => c(field, "NOT_EQUAL", value);
|
|
1858
|
+
function inOp(field, values) {
|
|
1859
|
+
const parsed = typeof values === "string" ? values.split(",").map((v) => v.trim()).filter((v) => v.length) : values;
|
|
1860
|
+
return c(field, "IN", parsed);
|
|
1861
|
+
}
|
|
1862
|
+
function within(field, values) {
|
|
1863
|
+
return inOp(field, values);
|
|
1864
|
+
}
|
|
1865
|
+
function notIn(field, values) {
|
|
1866
|
+
const parsed = typeof values === "string" ? values.split(",").map((v) => v.trim()).filter((v) => v.length) : values;
|
|
1867
|
+
return c(field, "NOT_IN", parsed);
|
|
1868
|
+
}
|
|
1869
|
+
function notWithin(field, values) {
|
|
1870
|
+
return notIn(field, values);
|
|
1871
|
+
}
|
|
1872
|
+
var between = (field, lower2, upper2) => c(field, "BETWEEN", [lower2, upper2]);
|
|
1873
|
+
var gt = (field, value) => c(field, "GREATER_THAN", value);
|
|
1874
|
+
var gte = (field, value) => c(field, "GREATER_THAN_EQUAL", value);
|
|
1875
|
+
var lt = (field, value) => c(field, "LESS_THAN", value);
|
|
1876
|
+
var lte = (field, value) => c(field, "LESS_THAN_EQUAL", value);
|
|
1877
|
+
var matches = (field, regex) => c(field, "MATCHES", regex);
|
|
1878
|
+
var notMatches = (field, regex) => c(field, "NOT_MATCHES", regex);
|
|
1879
|
+
var like = (field, pattern) => c(field, "LIKE", pattern);
|
|
1880
|
+
var notLike = (field, pattern) => c(field, "NOT_LIKE", pattern);
|
|
1881
|
+
var contains = (field, value) => c(field, "CONTAINS", value);
|
|
1882
|
+
var containsIgnoreCase = (field, value) => c(field, "CONTAINS_IGNORE_CASE", value);
|
|
1883
|
+
var notContains = (field, value) => c(field, "NOT_CONTAINS", value);
|
|
1884
|
+
var notContainsIgnoreCase = (field, value) => c(field, "NOT_CONTAINS_IGNORE_CASE", value);
|
|
1885
|
+
var startsWith = (field, prefix) => c(field, "STARTS_WITH", prefix);
|
|
1886
|
+
var notStartsWith = (field, prefix) => c(field, "NOT_STARTS_WITH", prefix);
|
|
1887
|
+
var isNull = (field) => c(field, "IS_NULL");
|
|
1888
|
+
var notNull = (field) => c(field, "NOT_NULL");
|
|
1889
|
+
|
|
1890
|
+
// src/helpers/aggregates.ts
|
|
1891
|
+
var avg = (attribute) => `avg(${attribute})`;
|
|
1892
|
+
var sum = (attribute) => `sum(${attribute})`;
|
|
1893
|
+
var count = (attribute) => `count(${attribute})`;
|
|
1894
|
+
var min = (attribute) => `min(${attribute})`;
|
|
1895
|
+
var max = (attribute) => `max(${attribute})`;
|
|
1896
|
+
var std = (attribute) => `std(${attribute})`;
|
|
1897
|
+
var variance = (attribute) => `variance(${attribute})`;
|
|
1898
|
+
var median = (attribute) => `median(${attribute})`;
|
|
1899
|
+
var upper = (attribute) => `upper(${attribute})`;
|
|
1900
|
+
var lower = (attribute) => `lower(${attribute})`;
|
|
1901
|
+
var substring = (attribute, from, length) => `substring(${attribute},${from},${length})`;
|
|
1902
|
+
var replace = (attribute, pattern, repl) => `replace(${attribute}, '${pattern.replace(/'/g, "\\'")}', '${repl.replace(/'/g, "\\'")}')`;
|
|
1903
|
+
var percentile = (attribute, p) => `percentile(${attribute}, ${p})`;
|
|
1904
|
+
|
|
1905
|
+
// src/edge.ts
|
|
1906
|
+
var sdkName = "@onyx.dev/onyx-database";
|
|
1907
|
+
var sdkVersion = "0.1.0";
|
|
1908
|
+
|
|
1909
|
+
export { QueryResults, asc, avg, between, contains, containsIgnoreCase, count, desc, eq, gt, gte, inOp, isNull, like, lower, lt, lte, matches, max, median, min, neq, notContains, notContainsIgnoreCase, notIn, notLike, notMatches, notNull, notStartsWith, notWithin, onyx, percentile, replace, sdkName, sdkVersion, startsWith, std, substring, sum, upper, variance, within };
|
|
1910
|
+
//# sourceMappingURL=edge.js.map
|
|
1911
|
+
//# sourceMappingURL=edge.js.map
|