@onyx.dev/onyx-database 0.2.0 → 0.3.0
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/gen/cli/generate.cjs +1376 -163
- package/dist/gen/cli/generate.cjs.map +1 -1
- package/dist/index.cjs +109 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +138 -1
- package/dist/index.d.ts +138 -1
- package/dist/index.js +109 -1
- package/dist/index.js.map +1 -1
- package/dist/schema/cli/schema.cjs +1698 -0
- package/dist/schema/cli/schema.cjs.map +1 -0
- package/package.json +6 -4
|
@@ -255,6 +255,97 @@ function mask(obj) {
|
|
|
255
255
|
return clone;
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
+
// gen/emit.ts
|
|
259
|
+
var DEFAULTS = {
|
|
260
|
+
schemaTypeName: "OnyxSchema",
|
|
261
|
+
timestampMode: "date",
|
|
262
|
+
modelNamePrefix: "",
|
|
263
|
+
optionalStrategy: "non-null"
|
|
264
|
+
};
|
|
265
|
+
function isValidIdentifier(name) {
|
|
266
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
|
|
267
|
+
}
|
|
268
|
+
function toPascalCase(raw) {
|
|
269
|
+
const cleaned = String(raw).replace(/[^A-Za-z0-9]+/g, " ");
|
|
270
|
+
const pc = cleaned.trim().split(/\s+/).filter(Boolean).map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
|
|
271
|
+
return pc.length === 0 ? "Table" : /^[0-9]/.test(pc) ? `T${pc}` : pc;
|
|
272
|
+
}
|
|
273
|
+
function tsTypeFor(attrType, timestampMode) {
|
|
274
|
+
switch (attrType) {
|
|
275
|
+
case "String":
|
|
276
|
+
return "string";
|
|
277
|
+
case "Int":
|
|
278
|
+
return "number";
|
|
279
|
+
case "Boolean":
|
|
280
|
+
return "boolean";
|
|
281
|
+
case "Timestamp":
|
|
282
|
+
return timestampMode === "date" ? "Date" : timestampMode === "number" ? "number" : "string";
|
|
283
|
+
case "EmbeddedList":
|
|
284
|
+
return "any[]";
|
|
285
|
+
case "EmbeddedObject":
|
|
286
|
+
return "any";
|
|
287
|
+
default:
|
|
288
|
+
return "any";
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function propertyLine(attr, timestampMode, optionalStrategy) {
|
|
292
|
+
const key = isValidIdentifier(attr.name) ? attr.name : JSON.stringify(attr.name);
|
|
293
|
+
const makeOptional = optionalStrategy === "non-null" ? !attr.isNullable : optionalStrategy === "nullable" ? attr.isNullable : false;
|
|
294
|
+
const t = tsTypeFor(attr.type, timestampMode);
|
|
295
|
+
const nullableUnion = attr.isNullable ? " | null" : "";
|
|
296
|
+
return ` ${key}${makeOptional ? "?" : ""}: ${t}${nullableUnion};`;
|
|
297
|
+
}
|
|
298
|
+
function emitTypes(schema, options) {
|
|
299
|
+
const opts = { ...DEFAULTS, ...options ?? {} };
|
|
300
|
+
const usedEnumKeys = /* @__PURE__ */ new Set();
|
|
301
|
+
const makeEnumKey = (raw) => {
|
|
302
|
+
const base = toPascalCase(raw);
|
|
303
|
+
let key = base;
|
|
304
|
+
let i = 2;
|
|
305
|
+
while (usedEnumKeys.has(key)) {
|
|
306
|
+
key = `${base}_${i++}`;
|
|
307
|
+
}
|
|
308
|
+
usedEnumKeys.add(key);
|
|
309
|
+
return key;
|
|
310
|
+
};
|
|
311
|
+
const lines = [];
|
|
312
|
+
lines.push(`// AUTO-GENERATED BY onyx-gen. DO NOT EDIT.`);
|
|
313
|
+
lines.push(`// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
314
|
+
lines.push("");
|
|
315
|
+
for (const t of schema.tables) {
|
|
316
|
+
const typeName = `${opts.modelNamePrefix}${toPascalCase(t.name)}`;
|
|
317
|
+
lines.push(`export interface ${typeName} {`);
|
|
318
|
+
for (const a of t.attributes) {
|
|
319
|
+
lines.push(propertyLine(a, opts.timestampMode, opts.optionalStrategy));
|
|
320
|
+
}
|
|
321
|
+
lines.push(" [key: string]: any;");
|
|
322
|
+
lines.push("}");
|
|
323
|
+
lines.push("");
|
|
324
|
+
}
|
|
325
|
+
lines.push(`export type ${opts.schemaTypeName} = {`);
|
|
326
|
+
for (const t of schema.tables) {
|
|
327
|
+
const key = isValidIdentifier(t.name) ? t.name : JSON.stringify(t.name);
|
|
328
|
+
const modelName = `${opts.modelNamePrefix}${toPascalCase(t.name)}`;
|
|
329
|
+
lines.push(` ${key}: ${modelName};`);
|
|
330
|
+
}
|
|
331
|
+
lines.push("};");
|
|
332
|
+
lines.push("");
|
|
333
|
+
if (opts.schemaTypeName !== "Schema") {
|
|
334
|
+
lines.push(`export type Schema = ${opts.schemaTypeName};`);
|
|
335
|
+
}
|
|
336
|
+
lines.push(`export const Schema = {} as ${opts.schemaTypeName};`);
|
|
337
|
+
lines.push("");
|
|
338
|
+
lines.push("export enum tables {");
|
|
339
|
+
for (const t of schema.tables) {
|
|
340
|
+
const enumKey = makeEnumKey(t.name);
|
|
341
|
+
const enumVal = JSON.stringify(t.name);
|
|
342
|
+
lines.push(` ${enumKey} = ${enumVal},`);
|
|
343
|
+
}
|
|
344
|
+
lines.push("}");
|
|
345
|
+
lines.push("");
|
|
346
|
+
return lines.join("\n");
|
|
347
|
+
}
|
|
348
|
+
|
|
258
349
|
// src/errors/http-error.ts
|
|
259
350
|
var OnyxHttpError = class extends Error {
|
|
260
351
|
name = "OnyxHttpError";
|
|
@@ -349,7 +440,8 @@ var HttpClient = class {
|
|
|
349
440
|
...method === "DELETE" ? { Prefer: "return=representation" } : {},
|
|
350
441
|
...extraHeaders ?? {}
|
|
351
442
|
});
|
|
352
|
-
|
|
443
|
+
const hasExplicitContentType = extraHeaders && "Content-Type" in extraHeaders || Object.prototype.hasOwnProperty.call(this.defaults, "Content-Type");
|
|
444
|
+
if (body == null && !hasExplicitContentType) delete headers["Content-Type"];
|
|
353
445
|
if (this.requestLoggingEnabled) {
|
|
354
446
|
console.log(`${method} ${url}`);
|
|
355
447
|
if (body != null) {
|
|
@@ -404,100 +496,1165 @@ var HttpClient = class {
|
|
|
404
496
|
}
|
|
405
497
|
};
|
|
406
498
|
|
|
407
|
-
//
|
|
408
|
-
var
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
modelNamePrefix: "",
|
|
412
|
-
optionalStrategy: "non-null"
|
|
499
|
+
// src/core/stream.ts
|
|
500
|
+
var debug = (...args) => {
|
|
501
|
+
if (globalThis.process?.env?.ONYX_STREAM_DEBUG == "true")
|
|
502
|
+
console.log("[onyx-stream]", ...args);
|
|
413
503
|
};
|
|
414
|
-
function
|
|
415
|
-
|
|
504
|
+
async function openJsonLinesStream(fetchImpl, url, init = {}, handlers = {}) {
|
|
505
|
+
const decoder = new TextDecoder("utf-8");
|
|
506
|
+
let buffer = "";
|
|
507
|
+
let canceled = false;
|
|
508
|
+
let currentReader = null;
|
|
509
|
+
let retryCount = 0;
|
|
510
|
+
const maxRetries = 4;
|
|
511
|
+
const processLine = (line) => {
|
|
512
|
+
const trimmed = line.trim();
|
|
513
|
+
debug("line", trimmed);
|
|
514
|
+
if (!trimmed || trimmed.startsWith(":")) return;
|
|
515
|
+
const jsonLine = trimmed.startsWith("data:") ? trimmed.slice(5).trim() : trimmed;
|
|
516
|
+
let obj;
|
|
517
|
+
try {
|
|
518
|
+
obj = parseJsonAllowNaN(jsonLine);
|
|
519
|
+
} catch {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const rawAction = obj.action ?? obj.event ?? obj.type ?? obj.eventType ?? obj.changeType;
|
|
523
|
+
const entity = obj.entity;
|
|
524
|
+
const action = rawAction?.toUpperCase();
|
|
525
|
+
if (action === "CREATE" || action === "CREATED" || action === "ADDED" || action === "ADD" || action === "INSERT" || action === "INSERTED")
|
|
526
|
+
handlers.onItemAdded?.(entity);
|
|
527
|
+
else if (action === "UPDATE" || action === "UPDATED")
|
|
528
|
+
handlers.onItemUpdated?.(entity);
|
|
529
|
+
else if (action === "DELETE" || action === "DELETED" || action === "REMOVE" || action === "REMOVED")
|
|
530
|
+
handlers.onItemDeleted?.(entity);
|
|
531
|
+
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;
|
|
532
|
+
if (canonical && canonical !== "KEEP_ALIVE")
|
|
533
|
+
handlers.onItem?.(entity ?? null, canonical);
|
|
534
|
+
debug("dispatch", canonical, entity);
|
|
535
|
+
};
|
|
536
|
+
const connect = async () => {
|
|
537
|
+
if (canceled) return;
|
|
538
|
+
debug("connecting", url);
|
|
539
|
+
try {
|
|
540
|
+
const res = await fetchImpl(url, {
|
|
541
|
+
method: init.method ?? "PUT",
|
|
542
|
+
headers: init.headers ?? {},
|
|
543
|
+
body: init.body
|
|
544
|
+
});
|
|
545
|
+
debug("response", res.status, res.statusText);
|
|
546
|
+
if (!res.ok) {
|
|
547
|
+
const raw = await res.text();
|
|
548
|
+
let parsed = raw;
|
|
549
|
+
try {
|
|
550
|
+
parsed = parseJsonAllowNaN(raw);
|
|
551
|
+
} catch {
|
|
552
|
+
}
|
|
553
|
+
debug("non-ok", res.status);
|
|
554
|
+
throw new OnyxHttpError(`${res.status} ${res.statusText}`, res.status, res.statusText, parsed, raw);
|
|
555
|
+
}
|
|
556
|
+
const body = res.body;
|
|
557
|
+
if (!body || typeof body.getReader !== "function") {
|
|
558
|
+
debug("no reader");
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
currentReader = body.getReader();
|
|
562
|
+
debug("connected");
|
|
563
|
+
retryCount = 0;
|
|
564
|
+
pump();
|
|
565
|
+
} catch (err) {
|
|
566
|
+
debug("connect error", err);
|
|
567
|
+
if (canceled) return;
|
|
568
|
+
if (retryCount >= maxRetries) return;
|
|
569
|
+
const delay = Math.min(1e3 * 2 ** retryCount, 3e4);
|
|
570
|
+
retryCount++;
|
|
571
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
572
|
+
void connect();
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
const pump = () => {
|
|
576
|
+
if (canceled || !currentReader) return;
|
|
577
|
+
currentReader.read().then(({ done, value }) => {
|
|
578
|
+
if (canceled) return;
|
|
579
|
+
debug("chunk", { done, length: value?.length ?? 0 });
|
|
580
|
+
if (done) {
|
|
581
|
+
debug("done");
|
|
582
|
+
void connect();
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
buffer += decoder.decode(value, { stream: true });
|
|
586
|
+
const lines = buffer.split("\n");
|
|
587
|
+
buffer = lines.pop() ?? "";
|
|
588
|
+
for (const line of lines) processLine(line);
|
|
589
|
+
pump();
|
|
590
|
+
}).catch((err) => {
|
|
591
|
+
debug("pump error", err);
|
|
592
|
+
if (!canceled) void connect();
|
|
593
|
+
});
|
|
594
|
+
};
|
|
595
|
+
await connect();
|
|
596
|
+
return {
|
|
597
|
+
cancel() {
|
|
598
|
+
if (canceled) return;
|
|
599
|
+
canceled = true;
|
|
600
|
+
try {
|
|
601
|
+
currentReader?.cancel();
|
|
602
|
+
} catch {
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
};
|
|
416
606
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
607
|
+
|
|
608
|
+
// src/builders/query-results.ts
|
|
609
|
+
var QueryResults = class extends Array {
|
|
610
|
+
/** Token for the next page of results or null. */
|
|
611
|
+
nextPage;
|
|
612
|
+
fetcher;
|
|
613
|
+
/**
|
|
614
|
+
* @param records - Records in the current page.
|
|
615
|
+
* @param nextPage - Token representing the next page.
|
|
616
|
+
* @param fetcher - Function used to fetch the next page when needed.
|
|
617
|
+
* @example
|
|
618
|
+
* ```ts
|
|
619
|
+
* const results = new QueryResults(users, token, t => fetchMore(t));
|
|
620
|
+
* ```
|
|
621
|
+
*/
|
|
622
|
+
constructor(records, nextPage, fetcher) {
|
|
623
|
+
const items = (() => {
|
|
624
|
+
if (records == null) return [];
|
|
625
|
+
if (Array.isArray(records)) return records;
|
|
626
|
+
if (typeof records[Symbol.iterator] === "function") {
|
|
627
|
+
return Array.from(records);
|
|
628
|
+
}
|
|
629
|
+
if (typeof records.length === "number") {
|
|
630
|
+
return Array.from(records);
|
|
631
|
+
}
|
|
632
|
+
return [records];
|
|
633
|
+
})();
|
|
634
|
+
super(...items);
|
|
635
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
636
|
+
this.nextPage = nextPage;
|
|
637
|
+
this.fetcher = fetcher;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Returns the first record in the result set.
|
|
641
|
+
* @throws Error if the result set is empty.
|
|
642
|
+
* @example
|
|
643
|
+
* ```ts
|
|
644
|
+
* const user = results.first();
|
|
645
|
+
* ```
|
|
646
|
+
*/
|
|
647
|
+
first() {
|
|
648
|
+
if (this.length === 0) throw new Error("QueryResults is empty");
|
|
649
|
+
return this[0];
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Returns the first record or `null` if the result set is empty.
|
|
653
|
+
* @example
|
|
654
|
+
* ```ts
|
|
655
|
+
* const user = results.firstOrNull();
|
|
656
|
+
* ```
|
|
657
|
+
*/
|
|
658
|
+
firstOrNull() {
|
|
659
|
+
return this.length > 0 ? this[0] : null;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Checks whether the current page has no records.
|
|
663
|
+
* @example
|
|
664
|
+
* ```ts
|
|
665
|
+
* if (results.isEmpty()) console.log('no data');
|
|
666
|
+
* ```
|
|
667
|
+
*/
|
|
668
|
+
isEmpty() {
|
|
669
|
+
return this.length === 0;
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Number of records on the current page.
|
|
673
|
+
* @example
|
|
674
|
+
* ```ts
|
|
675
|
+
* console.log(results.size());
|
|
676
|
+
* ```
|
|
677
|
+
*/
|
|
678
|
+
size() {
|
|
679
|
+
return this.length;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Iterates over each record on the current page only.
|
|
683
|
+
* @param action - Function to invoke for each record.
|
|
684
|
+
* @param thisArg - Optional `this` binding for the callback.
|
|
685
|
+
* @example
|
|
686
|
+
* ```ts
|
|
687
|
+
* results.forEachOnPage(u => console.log(u.id));
|
|
688
|
+
* ```
|
|
689
|
+
*/
|
|
690
|
+
forEachOnPage(action, thisArg) {
|
|
691
|
+
super.forEach((value, index) => {
|
|
692
|
+
action.call(thisArg, value, index, this);
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Iterates over every record across all pages sequentially.
|
|
697
|
+
* @param action - Function executed for each record. Returning `false`
|
|
698
|
+
* stops iteration early.
|
|
699
|
+
* @param thisArg - Optional `this` binding for the callback.
|
|
700
|
+
* @example
|
|
701
|
+
* ```ts
|
|
702
|
+
* await results.forEach(u => {
|
|
703
|
+
* console.log(u.id);
|
|
704
|
+
* });
|
|
705
|
+
* ```
|
|
706
|
+
*/
|
|
707
|
+
forEach(action, thisArg) {
|
|
708
|
+
let index = 0;
|
|
709
|
+
return this.forEachAll(async (item) => {
|
|
710
|
+
const result = await action.call(thisArg, item, index, this);
|
|
711
|
+
index += 1;
|
|
712
|
+
return result;
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Iterates over every record across all pages sequentially.
|
|
717
|
+
* @param action - Function executed for each record. Returning `false`
|
|
718
|
+
* stops iteration early.
|
|
719
|
+
* @example
|
|
720
|
+
* ```ts
|
|
721
|
+
* await results.forEachAll(u => {
|
|
722
|
+
* if (u.disabled) return false;
|
|
723
|
+
* });
|
|
724
|
+
* ```
|
|
725
|
+
*/
|
|
726
|
+
async forEachAll(action) {
|
|
727
|
+
await this.forEachPage(async (records) => {
|
|
728
|
+
for (const r of records) {
|
|
729
|
+
const res = await action(r);
|
|
730
|
+
if (res === false) return false;
|
|
731
|
+
}
|
|
732
|
+
return true;
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Iterates page by page across the result set.
|
|
737
|
+
* @param action - Function invoked with each page of records. Returning
|
|
738
|
+
* `false` stops iteration.
|
|
739
|
+
* @example
|
|
740
|
+
* ```ts
|
|
741
|
+
* await results.forEachPage(page => {
|
|
742
|
+
* console.log(page.length);
|
|
743
|
+
* });
|
|
744
|
+
* ```
|
|
745
|
+
*/
|
|
746
|
+
async forEachPage(action) {
|
|
747
|
+
let page = this;
|
|
748
|
+
while (page) {
|
|
749
|
+
const cont = await action(Array.from(page));
|
|
750
|
+
if (cont === false) return;
|
|
751
|
+
if (page.nextPage && page.fetcher) {
|
|
752
|
+
page = await page.fetcher(page.nextPage);
|
|
753
|
+
} else {
|
|
754
|
+
page = null;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Collects all records from every page into a single array.
|
|
760
|
+
* @returns All records.
|
|
761
|
+
* @example
|
|
762
|
+
* ```ts
|
|
763
|
+
* const allUsers = await results.getAllRecords();
|
|
764
|
+
* ```
|
|
765
|
+
*/
|
|
766
|
+
async getAllRecords() {
|
|
767
|
+
const all = [];
|
|
768
|
+
await this.forEachPage((records) => {
|
|
769
|
+
all.push(...records);
|
|
770
|
+
});
|
|
771
|
+
return all;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Filters all records using the provided predicate.
|
|
775
|
+
* @param predicate - Function used to test each record.
|
|
776
|
+
* @example
|
|
777
|
+
* ```ts
|
|
778
|
+
* const enabled = await results.filterAll(u => u.enabled);
|
|
779
|
+
* ```
|
|
780
|
+
*/
|
|
781
|
+
async filterAll(predicate) {
|
|
782
|
+
const all = await this.getAllRecords();
|
|
783
|
+
return all.filter(predicate);
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Maps all records using the provided transform.
|
|
787
|
+
* @param transform - Mapping function.
|
|
788
|
+
* @example
|
|
789
|
+
* ```ts
|
|
790
|
+
* const names = await results.mapAll(u => u.name);
|
|
791
|
+
* ```
|
|
792
|
+
*/
|
|
793
|
+
async mapAll(transform) {
|
|
794
|
+
const all = await this.getAllRecords();
|
|
795
|
+
return all.map(transform);
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Extracts values for a field across all records.
|
|
799
|
+
* @param field - Name of the field to pluck.
|
|
800
|
+
* @example
|
|
801
|
+
* ```ts
|
|
802
|
+
* const ids = await results.values('id');
|
|
803
|
+
* ```
|
|
804
|
+
*/
|
|
805
|
+
// @ts-expect-error overriding Array#values
|
|
806
|
+
async values(field) {
|
|
807
|
+
const all = await this.getAllRecords();
|
|
808
|
+
return all.map((r) => r[field]);
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Maximum value produced by the selector across all records.
|
|
812
|
+
* @param selector - Function extracting a numeric value.
|
|
813
|
+
* @example
|
|
814
|
+
* ```ts
|
|
815
|
+
* const maxAge = await results.maxOfDouble(u => u.age);
|
|
816
|
+
* ```
|
|
817
|
+
*/
|
|
818
|
+
async maxOfDouble(selector) {
|
|
819
|
+
const all = await this.getAllRecords();
|
|
820
|
+
return all.reduce((max, r) => Math.max(max, selector(r)), -Infinity);
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Minimum value produced by the selector across all records.
|
|
824
|
+
* @param selector - Function extracting a numeric value.
|
|
825
|
+
* @example
|
|
826
|
+
* ```ts
|
|
827
|
+
* const minAge = await results.minOfDouble(u => u.age);
|
|
828
|
+
* ```
|
|
829
|
+
*/
|
|
830
|
+
async minOfDouble(selector) {
|
|
831
|
+
const all = await this.getAllRecords();
|
|
832
|
+
return all.reduce((min, r) => Math.min(min, selector(r)), Infinity);
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Sum of values produced by the selector across all records.
|
|
836
|
+
* @param selector - Function extracting a numeric value.
|
|
837
|
+
* @example
|
|
838
|
+
* ```ts
|
|
839
|
+
* const total = await results.sumOfDouble(u => u.score);
|
|
840
|
+
* ```
|
|
841
|
+
*/
|
|
842
|
+
async sumOfDouble(selector) {
|
|
843
|
+
const all = await this.getAllRecords();
|
|
844
|
+
return all.reduce((sum, r) => sum + selector(r), 0);
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Maximum float value from the selector.
|
|
848
|
+
* @param selector - Function extracting a numeric value.
|
|
849
|
+
*/
|
|
850
|
+
async maxOfFloat(selector) {
|
|
851
|
+
return this.maxOfDouble(selector);
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Minimum float value from the selector.
|
|
855
|
+
* @param selector - Function extracting a numeric value.
|
|
856
|
+
*/
|
|
857
|
+
async minOfFloat(selector) {
|
|
858
|
+
return this.minOfDouble(selector);
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Sum of float values from the selector.
|
|
862
|
+
* @param selector - Function extracting a numeric value.
|
|
863
|
+
*/
|
|
864
|
+
async sumOfFloat(selector) {
|
|
865
|
+
return this.sumOfDouble(selector);
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Maximum integer value from the selector.
|
|
869
|
+
* @param selector - Function extracting a numeric value.
|
|
870
|
+
*/
|
|
871
|
+
async maxOfInt(selector) {
|
|
872
|
+
return this.maxOfDouble(selector);
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Minimum integer value from the selector.
|
|
876
|
+
* @param selector - Function extracting a numeric value.
|
|
877
|
+
*/
|
|
878
|
+
async minOfInt(selector) {
|
|
879
|
+
return this.minOfDouble(selector);
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Sum of integer values from the selector.
|
|
883
|
+
* @param selector - Function extracting a numeric value.
|
|
884
|
+
*/
|
|
885
|
+
async sumOfInt(selector) {
|
|
886
|
+
return this.sumOfDouble(selector);
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Maximum long value from the selector.
|
|
890
|
+
* @param selector - Function extracting a numeric value.
|
|
891
|
+
*/
|
|
892
|
+
async maxOfLong(selector) {
|
|
893
|
+
return this.maxOfDouble(selector);
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Minimum long value from the selector.
|
|
897
|
+
* @param selector - Function extracting a numeric value.
|
|
898
|
+
*/
|
|
899
|
+
async minOfLong(selector) {
|
|
900
|
+
return this.minOfDouble(selector);
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Sum of long values from the selector.
|
|
904
|
+
* @param selector - Function extracting a numeric value.
|
|
905
|
+
*/
|
|
906
|
+
async sumOfLong(selector) {
|
|
907
|
+
return this.sumOfDouble(selector);
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Sum of bigint values from the selector.
|
|
911
|
+
* @param selector - Function extracting a bigint value.
|
|
912
|
+
* @example
|
|
913
|
+
* ```ts
|
|
914
|
+
* const total = await results.sumOfBigInt(u => u.balance);
|
|
915
|
+
* ```
|
|
916
|
+
*/
|
|
917
|
+
async sumOfBigInt(selector) {
|
|
918
|
+
const all = await this.getAllRecords();
|
|
919
|
+
return all.reduce((sum, r) => sum + selector(r), 0n);
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Executes an action for each page in parallel.
|
|
923
|
+
* @param action - Function executed for each record concurrently.
|
|
924
|
+
* @example
|
|
925
|
+
* ```ts
|
|
926
|
+
* await results.forEachPageParallel(async u => sendEmail(u));
|
|
927
|
+
* ```
|
|
928
|
+
*/
|
|
929
|
+
async forEachPageParallel(action) {
|
|
930
|
+
await this.forEachPage((records) => Promise.all(records.map(action)).then(() => true));
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
// src/builders/cascade-relationship-builder.ts
|
|
935
|
+
var CascadeRelationshipBuilder = class {
|
|
936
|
+
graphName;
|
|
937
|
+
typeName;
|
|
938
|
+
target;
|
|
939
|
+
/**
|
|
940
|
+
* Set the graph name component.
|
|
941
|
+
*
|
|
942
|
+
* @param name Graph name or namespace.
|
|
943
|
+
* @example
|
|
944
|
+
* ```ts
|
|
945
|
+
* builder.graph('programs');
|
|
946
|
+
* ```
|
|
947
|
+
*/
|
|
948
|
+
graph(name) {
|
|
949
|
+
this.graphName = name;
|
|
950
|
+
return this;
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Set the graph type component.
|
|
954
|
+
*
|
|
955
|
+
* @param type Graph type to target.
|
|
956
|
+
* @example
|
|
957
|
+
* ```ts
|
|
958
|
+
* builder.graphType('StreamingProgram');
|
|
959
|
+
* ```
|
|
960
|
+
*/
|
|
961
|
+
graphType(type) {
|
|
962
|
+
this.typeName = type;
|
|
963
|
+
return this;
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Set the target field for the relationship.
|
|
967
|
+
*
|
|
968
|
+
* @param field Target field name.
|
|
969
|
+
* @example
|
|
970
|
+
* ```ts
|
|
971
|
+
* builder.targetField('channelId');
|
|
972
|
+
* ```
|
|
973
|
+
*/
|
|
974
|
+
targetField(field) {
|
|
975
|
+
this.target = field;
|
|
976
|
+
return this;
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Produce the cascade relationship string using the provided source field.
|
|
980
|
+
*
|
|
981
|
+
* @param field Source field name.
|
|
982
|
+
* @example
|
|
983
|
+
* ```ts
|
|
984
|
+
* const rel = builder
|
|
985
|
+
* .graph('programs')
|
|
986
|
+
* .graphType('StreamingProgram')
|
|
987
|
+
* .targetField('channelId')
|
|
988
|
+
* .sourceField('id');
|
|
989
|
+
* // rel === 'programs:StreamingProgram(channelId, id)'
|
|
990
|
+
* ```
|
|
991
|
+
*/
|
|
992
|
+
sourceField(field) {
|
|
993
|
+
if (!this.graphName || !this.typeName || !this.target) {
|
|
994
|
+
throw new Error("Cascade relationship requires graph, type, target, and source fields");
|
|
995
|
+
}
|
|
996
|
+
return `${this.graphName}:${this.typeName}(${this.target}, ${field})`;
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
// src/errors/onyx-error.ts
|
|
1001
|
+
var OnyxError = class extends Error {
|
|
1002
|
+
name = "OnyxError";
|
|
1003
|
+
constructor(message) {
|
|
1004
|
+
super(message);
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
|
|
1008
|
+
// src/impl/onyx.ts
|
|
1009
|
+
var DEFAULT_CACHE_TTL = 5 * 60 * 1e3;
|
|
1010
|
+
var cachedCfg = null;
|
|
1011
|
+
function resolveConfigWithCache(config) {
|
|
1012
|
+
const ttl = config?.ttl ?? DEFAULT_CACHE_TTL;
|
|
1013
|
+
const now = Date.now();
|
|
1014
|
+
if (cachedCfg && cachedCfg.expires > now) {
|
|
1015
|
+
return cachedCfg.promise;
|
|
1016
|
+
}
|
|
1017
|
+
const { ttl: _ttl, requestLoggingEnabled: _reqLog, responseLoggingEnabled: _resLog, ...rest } = config ?? {};
|
|
1018
|
+
const promise = resolveConfig(rest);
|
|
1019
|
+
cachedCfg = { promise, expires: now + ttl };
|
|
1020
|
+
return promise;
|
|
421
1021
|
}
|
|
422
|
-
function
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
default:
|
|
437
|
-
return "any";
|
|
1022
|
+
function clearCacheConfig() {
|
|
1023
|
+
cachedCfg = null;
|
|
1024
|
+
}
|
|
1025
|
+
function toSingleCondition(criteria) {
|
|
1026
|
+
return { conditionType: "SingleCondition", criteria };
|
|
1027
|
+
}
|
|
1028
|
+
function flattenStrings(values) {
|
|
1029
|
+
const flat = [];
|
|
1030
|
+
for (const value of values) {
|
|
1031
|
+
if (Array.isArray(value)) {
|
|
1032
|
+
flat.push(...value);
|
|
1033
|
+
} else if (typeof value === "string") {
|
|
1034
|
+
flat.push(value);
|
|
1035
|
+
}
|
|
438
1036
|
}
|
|
1037
|
+
return flat;
|
|
439
1038
|
}
|
|
440
|
-
function
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const
|
|
445
|
-
|
|
1039
|
+
function toCondition(input) {
|
|
1040
|
+
if (typeof input.toCondition === "function") {
|
|
1041
|
+
return input.toCondition();
|
|
1042
|
+
}
|
|
1043
|
+
const c = input;
|
|
1044
|
+
if (c && typeof c.field === "string" && typeof c.operator === "string") {
|
|
1045
|
+
return toSingleCondition(c);
|
|
1046
|
+
}
|
|
1047
|
+
throw new Error("Invalid condition passed to builder.");
|
|
446
1048
|
}
|
|
447
|
-
function
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
while (usedEnumKeys.has(key)) {
|
|
455
|
-
key = `${base}_${i++}`;
|
|
1049
|
+
function serializeDates(value) {
|
|
1050
|
+
if (value instanceof Date) return value.toISOString();
|
|
1051
|
+
if (Array.isArray(value)) return value.map(serializeDates);
|
|
1052
|
+
if (value && typeof value === "object") {
|
|
1053
|
+
const out = {};
|
|
1054
|
+
for (const [k, v] of Object.entries(value)) {
|
|
1055
|
+
out[k] = serializeDates(v);
|
|
456
1056
|
}
|
|
457
|
-
|
|
458
|
-
|
|
1057
|
+
return out;
|
|
1058
|
+
}
|
|
1059
|
+
return value;
|
|
1060
|
+
}
|
|
1061
|
+
function normalizeSecretMetadata(input) {
|
|
1062
|
+
return { ...input, updatedAt: new Date(input.updatedAt) };
|
|
1063
|
+
}
|
|
1064
|
+
function normalizeSecretRecord(input) {
|
|
1065
|
+
return { ...input, updatedAt: new Date(input.updatedAt) };
|
|
1066
|
+
}
|
|
1067
|
+
function normalizeDate(value) {
|
|
1068
|
+
if (value == null) return void 0;
|
|
1069
|
+
if (value instanceof Date) return value;
|
|
1070
|
+
const ts = new Date(value);
|
|
1071
|
+
return Number.isNaN(ts.getTime()) ? void 0 : ts;
|
|
1072
|
+
}
|
|
1073
|
+
function normalizeSchemaRevision(input, fallbackDatabaseId) {
|
|
1074
|
+
const { meta, createdAt, publishedAt, revisionId, ...rest } = input;
|
|
1075
|
+
const mergedMeta = {
|
|
1076
|
+
revisionId: meta?.revisionId ?? revisionId,
|
|
1077
|
+
createdAt: normalizeDate(meta?.createdAt ?? createdAt),
|
|
1078
|
+
publishedAt: normalizeDate(meta?.publishedAt ?? publishedAt)
|
|
459
1079
|
};
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
1080
|
+
const cleanedMeta = mergedMeta.revisionId || mergedMeta.createdAt || mergedMeta.publishedAt ? mergedMeta : void 0;
|
|
1081
|
+
return {
|
|
1082
|
+
...rest,
|
|
1083
|
+
databaseId: input.databaseId ?? fallbackDatabaseId,
|
|
1084
|
+
meta: cleanedMeta,
|
|
1085
|
+
entities: input.entities ?? []
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
var OnyxDatabaseImpl = class {
|
|
1089
|
+
cfgPromise;
|
|
1090
|
+
resolved = null;
|
|
1091
|
+
http = null;
|
|
1092
|
+
streams = /* @__PURE__ */ new Set();
|
|
1093
|
+
requestLoggingEnabled;
|
|
1094
|
+
responseLoggingEnabled;
|
|
1095
|
+
defaultPartition;
|
|
1096
|
+
constructor(config) {
|
|
1097
|
+
this.requestLoggingEnabled = !!config?.requestLoggingEnabled;
|
|
1098
|
+
this.responseLoggingEnabled = !!config?.responseLoggingEnabled;
|
|
1099
|
+
this.defaultPartition = config?.partition;
|
|
1100
|
+
this.cfgPromise = resolveConfigWithCache(config);
|
|
1101
|
+
}
|
|
1102
|
+
async ensureClient() {
|
|
1103
|
+
if (!this.resolved) {
|
|
1104
|
+
this.resolved = await this.cfgPromise;
|
|
469
1105
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
1106
|
+
if (!this.http) {
|
|
1107
|
+
this.http = new HttpClient({
|
|
1108
|
+
baseUrl: this.resolved.baseUrl,
|
|
1109
|
+
apiKey: this.resolved.apiKey,
|
|
1110
|
+
apiSecret: this.resolved.apiSecret,
|
|
1111
|
+
fetchImpl: this.resolved.fetch,
|
|
1112
|
+
requestLoggingEnabled: this.requestLoggingEnabled,
|
|
1113
|
+
responseLoggingEnabled: this.responseLoggingEnabled
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
return {
|
|
1117
|
+
http: this.http,
|
|
1118
|
+
fetchImpl: this.resolved.fetch,
|
|
1119
|
+
baseUrl: this.resolved.baseUrl,
|
|
1120
|
+
databaseId: this.resolved.databaseId
|
|
1121
|
+
};
|
|
473
1122
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
1123
|
+
registerStream(handle) {
|
|
1124
|
+
this.streams.add(handle);
|
|
1125
|
+
return {
|
|
1126
|
+
cancel: () => {
|
|
1127
|
+
try {
|
|
1128
|
+
handle.cancel();
|
|
1129
|
+
} finally {
|
|
1130
|
+
this.streams.delete(handle);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
479
1134
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
lines.push(`export type Schema = ${opts.schemaTypeName};`);
|
|
1135
|
+
/** -------- IOnyxDatabase -------- */
|
|
1136
|
+
from(table) {
|
|
1137
|
+
return new QueryBuilderImpl(this, String(table), this.defaultPartition);
|
|
484
1138
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
1139
|
+
select(...fields) {
|
|
1140
|
+
const qb = new QueryBuilderImpl(
|
|
1141
|
+
this,
|
|
1142
|
+
null,
|
|
1143
|
+
this.defaultPartition
|
|
1144
|
+
);
|
|
1145
|
+
qb.select(...fields);
|
|
1146
|
+
return qb;
|
|
492
1147
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
}
|
|
1148
|
+
cascade(...relationships) {
|
|
1149
|
+
const cb = new CascadeBuilderImpl(this);
|
|
1150
|
+
return cb.cascade(...relationships);
|
|
1151
|
+
}
|
|
1152
|
+
cascadeBuilder() {
|
|
1153
|
+
return new CascadeRelationshipBuilder();
|
|
1154
|
+
}
|
|
1155
|
+
// Impl
|
|
1156
|
+
save(table, entityOrEntities, options) {
|
|
1157
|
+
if (arguments.length === 1) {
|
|
1158
|
+
return new SaveBuilderImpl(this, table);
|
|
1159
|
+
}
|
|
1160
|
+
return this._saveInternal(table, entityOrEntities, options);
|
|
1161
|
+
}
|
|
1162
|
+
async batchSave(table, entities, batchSize = 1e3, options) {
|
|
1163
|
+
for (let i = 0; i < entities.length; i += batchSize) {
|
|
1164
|
+
const chunk = entities.slice(i, i + batchSize);
|
|
1165
|
+
if (chunk.length) {
|
|
1166
|
+
await this._saveInternal(String(table), chunk, options);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
async findById(table, primaryKey, options) {
|
|
1171
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1172
|
+
const params = new URLSearchParams();
|
|
1173
|
+
const partition = options?.partition ?? this.defaultPartition;
|
|
1174
|
+
if (partition) params.append("partition", partition);
|
|
1175
|
+
if (options?.resolvers?.length) params.append("resolvers", options.resolvers.join(","));
|
|
1176
|
+
const path = `/data/${encodeURIComponent(databaseId)}/${encodeURIComponent(
|
|
1177
|
+
String(table)
|
|
1178
|
+
)}/${encodeURIComponent(primaryKey)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1179
|
+
try {
|
|
1180
|
+
return await http.request("GET", path);
|
|
1181
|
+
} catch (err) {
|
|
1182
|
+
if (err instanceof OnyxHttpError && err.status === 404) return null;
|
|
1183
|
+
throw err;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
async delete(table, primaryKey, options) {
|
|
1187
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1188
|
+
const params = new URLSearchParams();
|
|
1189
|
+
const partition = options?.partition ?? this.defaultPartition;
|
|
1190
|
+
if (partition) params.append("partition", partition);
|
|
1191
|
+
if (options?.relationships?.length) {
|
|
1192
|
+
params.append("relationships", options.relationships.map(encodeURIComponent).join(","));
|
|
1193
|
+
}
|
|
1194
|
+
const path = `/data/${encodeURIComponent(databaseId)}/${encodeURIComponent(
|
|
1195
|
+
table
|
|
1196
|
+
)}/${encodeURIComponent(primaryKey)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1197
|
+
return http.request("DELETE", path);
|
|
1198
|
+
}
|
|
1199
|
+
async saveDocument(doc) {
|
|
1200
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1201
|
+
const path = `/data/${encodeURIComponent(databaseId)}/document`;
|
|
1202
|
+
return http.request("PUT", path, serializeDates(doc));
|
|
1203
|
+
}
|
|
1204
|
+
async getDocument(documentId, options) {
|
|
1205
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1206
|
+
const params = new URLSearchParams();
|
|
1207
|
+
if (options?.width != null) params.append("width", String(options.width));
|
|
1208
|
+
if (options?.height != null) params.append("height", String(options.height));
|
|
1209
|
+
const path = `/data/${encodeURIComponent(databaseId)}/document/${encodeURIComponent(
|
|
1210
|
+
documentId
|
|
1211
|
+
)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1212
|
+
return http.request("GET", path);
|
|
1213
|
+
}
|
|
1214
|
+
async deleteDocument(documentId) {
|
|
1215
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1216
|
+
const path = `/data/${encodeURIComponent(databaseId)}/document/${encodeURIComponent(
|
|
1217
|
+
documentId
|
|
1218
|
+
)}`;
|
|
1219
|
+
return http.request("DELETE", path);
|
|
1220
|
+
}
|
|
1221
|
+
async getSchema(options) {
|
|
1222
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1223
|
+
const params = new URLSearchParams();
|
|
1224
|
+
const tables = options?.tables;
|
|
1225
|
+
const tableList = Array.isArray(tables) ? tables : typeof tables === "string" ? tables.split(",") : [];
|
|
1226
|
+
const normalizedTables = tableList.map((t) => t.trim()).filter(Boolean);
|
|
1227
|
+
if (normalizedTables.length) {
|
|
1228
|
+
params.append("tables", normalizedTables.map(encodeURIComponent).join(","));
|
|
1229
|
+
}
|
|
1230
|
+
const path = `/schemas/${encodeURIComponent(databaseId)}${params.size ? `?${params.toString()}` : ""}`;
|
|
1231
|
+
const res = await http.request("GET", path);
|
|
1232
|
+
return normalizeSchemaRevision(res, databaseId);
|
|
1233
|
+
}
|
|
1234
|
+
async getSchemaHistory() {
|
|
1235
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1236
|
+
const path = `/schemas/history/${encodeURIComponent(databaseId)}`;
|
|
1237
|
+
const res = await http.request("GET", path);
|
|
1238
|
+
return Array.isArray(res) ? res.map((entry) => normalizeSchemaRevision(entry, databaseId)) : [];
|
|
1239
|
+
}
|
|
1240
|
+
async updateSchema(schema, options) {
|
|
1241
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1242
|
+
const params = new URLSearchParams();
|
|
1243
|
+
if (options?.publish) params.append("publish", "true");
|
|
1244
|
+
const path = `/schemas/${encodeURIComponent(databaseId)}${params.size ? `?${params.toString()}` : ""}`;
|
|
1245
|
+
const body = { ...schema, databaseId: schema.databaseId ?? databaseId };
|
|
1246
|
+
const res = await http.request("PUT", path, serializeDates(body));
|
|
1247
|
+
return normalizeSchemaRevision(res, databaseId);
|
|
1248
|
+
}
|
|
1249
|
+
async validateSchema(schema) {
|
|
1250
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1251
|
+
const path = `/schemas/${encodeURIComponent(databaseId)}/validate`;
|
|
1252
|
+
const body = { ...schema, databaseId: schema.databaseId ?? databaseId };
|
|
1253
|
+
const res = await http.request("POST", path, serializeDates(body));
|
|
1254
|
+
const normalizedSchema = res.schema ? normalizeSchemaRevision(res.schema, databaseId) : void 0;
|
|
1255
|
+
return {
|
|
1256
|
+
...res,
|
|
1257
|
+
valid: res.valid ?? true,
|
|
1258
|
+
schema: normalizedSchema
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
async listSecrets() {
|
|
1262
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1263
|
+
const path = `/database/${encodeURIComponent(databaseId)}/secret`;
|
|
1264
|
+
const response = await http.request(
|
|
1265
|
+
"GET",
|
|
1266
|
+
path,
|
|
1267
|
+
void 0,
|
|
1268
|
+
{ "Content-Type": "application/json" }
|
|
1269
|
+
);
|
|
1270
|
+
const records = (response.records ?? []).map(normalizeSecretMetadata);
|
|
1271
|
+
return {
|
|
1272
|
+
...response,
|
|
1273
|
+
records,
|
|
1274
|
+
meta: response.meta ?? { totalRecords: records.length }
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
async getSecret(key) {
|
|
1278
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1279
|
+
const path = `/database/${encodeURIComponent(databaseId)}/secret/${encodeURIComponent(key)}`;
|
|
1280
|
+
const record = await http.request("GET", path, void 0, {
|
|
1281
|
+
"Content-Type": "application/json"
|
|
1282
|
+
});
|
|
1283
|
+
return normalizeSecretRecord(record);
|
|
1284
|
+
}
|
|
1285
|
+
async putSecret(key, input) {
|
|
1286
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1287
|
+
const path = `/database/${encodeURIComponent(databaseId)}/secret/${encodeURIComponent(key)}`;
|
|
1288
|
+
const response = await http.request("PUT", path, serializeDates(input));
|
|
1289
|
+
return normalizeSecretMetadata(response);
|
|
1290
|
+
}
|
|
1291
|
+
async deleteSecret(key) {
|
|
1292
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1293
|
+
const path = `/database/${encodeURIComponent(databaseId)}/secret/${encodeURIComponent(key)}`;
|
|
1294
|
+
const response = await http.request(
|
|
1295
|
+
"DELETE",
|
|
1296
|
+
path
|
|
1297
|
+
);
|
|
1298
|
+
const deletedKey = response && typeof response === "object" && "key" in response ? response.key : void 0;
|
|
1299
|
+
return { key: deletedKey ?? key };
|
|
1300
|
+
}
|
|
1301
|
+
close() {
|
|
1302
|
+
for (const h of Array.from(this.streams)) {
|
|
1303
|
+
try {
|
|
1304
|
+
h.cancel();
|
|
1305
|
+
} catch {
|
|
1306
|
+
} finally {
|
|
1307
|
+
this.streams.delete(h);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
/** -------- internal helpers used by builders -------- */
|
|
1312
|
+
async _count(table, select, partition) {
|
|
1313
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1314
|
+
const params = new URLSearchParams();
|
|
1315
|
+
const p = partition ?? this.defaultPartition;
|
|
1316
|
+
if (p) params.append("partition", p);
|
|
1317
|
+
const path = `/data/${encodeURIComponent(databaseId)}/query/count/${encodeURIComponent(
|
|
1318
|
+
table
|
|
1319
|
+
)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1320
|
+
return http.request("PUT", path, serializeDates(select));
|
|
1321
|
+
}
|
|
1322
|
+
async _queryPage(table, select, opts = {}) {
|
|
1323
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1324
|
+
const params = new URLSearchParams();
|
|
1325
|
+
if (opts.pageSize != null) params.append("pageSize", String(opts.pageSize));
|
|
1326
|
+
if (opts.nextPage) params.append("nextPage", opts.nextPage);
|
|
1327
|
+
const p = opts.partition ?? this.defaultPartition;
|
|
1328
|
+
if (p) params.append("partition", p);
|
|
1329
|
+
const path = `/data/${encodeURIComponent(databaseId)}/query/${encodeURIComponent(
|
|
1330
|
+
table
|
|
1331
|
+
)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1332
|
+
return http.request("PUT", path, serializeDates(select));
|
|
1333
|
+
}
|
|
1334
|
+
async _update(table, update, partition) {
|
|
1335
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1336
|
+
const params = new URLSearchParams();
|
|
1337
|
+
const p = partition ?? this.defaultPartition;
|
|
1338
|
+
if (p) params.append("partition", p);
|
|
1339
|
+
const path = `/data/${encodeURIComponent(databaseId)}/query/update/${encodeURIComponent(
|
|
1340
|
+
table
|
|
1341
|
+
)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1342
|
+
return http.request("PUT", path, serializeDates(update));
|
|
1343
|
+
}
|
|
1344
|
+
async _deleteByQuery(table, select, partition) {
|
|
1345
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1346
|
+
const params = new URLSearchParams();
|
|
1347
|
+
const p = partition ?? this.defaultPartition;
|
|
1348
|
+
if (p) params.append("partition", p);
|
|
1349
|
+
const path = `/data/${encodeURIComponent(databaseId)}/query/delete/${encodeURIComponent(
|
|
1350
|
+
table
|
|
1351
|
+
)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1352
|
+
return http.request("PUT", path, serializeDates(select));
|
|
1353
|
+
}
|
|
1354
|
+
async _stream(table, select, includeQueryResults, keepAlive, handlers) {
|
|
1355
|
+
const { http, baseUrl, databaseId, fetchImpl } = await this.ensureClient();
|
|
1356
|
+
const params = new URLSearchParams();
|
|
1357
|
+
if (includeQueryResults) params.append("includeQueryResults", "true");
|
|
1358
|
+
if (keepAlive) params.append("keepAlive", "true");
|
|
1359
|
+
const url = `${baseUrl}/data/${encodeURIComponent(databaseId)}/query/stream/${encodeURIComponent(
|
|
1360
|
+
table
|
|
1361
|
+
)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1362
|
+
const handle = await openJsonLinesStream(
|
|
1363
|
+
fetchImpl,
|
|
1364
|
+
url,
|
|
1365
|
+
{
|
|
1366
|
+
method: "PUT",
|
|
1367
|
+
headers: http.headers({
|
|
1368
|
+
Accept: "application/x-ndjson",
|
|
1369
|
+
"Content-Type": "application/json"
|
|
1370
|
+
}),
|
|
1371
|
+
body: JSON.stringify(serializeDates(select))
|
|
1372
|
+
},
|
|
1373
|
+
handlers
|
|
1374
|
+
);
|
|
1375
|
+
return this.registerStream(handle);
|
|
1376
|
+
}
|
|
1377
|
+
async _saveInternal(table, entityOrEntities, options) {
|
|
1378
|
+
const { http, databaseId } = await this.ensureClient();
|
|
1379
|
+
const params = new URLSearchParams();
|
|
1380
|
+
if (options?.relationships?.length) {
|
|
1381
|
+
params.append("relationships", options.relationships.map(encodeURIComponent).join(","));
|
|
1382
|
+
}
|
|
1383
|
+
const path = `/data/${encodeURIComponent(databaseId)}/${encodeURIComponent(table)}${params.toString() ? `?${params.toString()}` : ""}`;
|
|
1384
|
+
return http.request("PUT", path, serializeDates(entityOrEntities));
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
var QueryBuilderImpl = class {
|
|
1388
|
+
db;
|
|
1389
|
+
table;
|
|
1390
|
+
fields = null;
|
|
1391
|
+
resolvers = null;
|
|
1392
|
+
conditions = null;
|
|
1393
|
+
sort = null;
|
|
1394
|
+
limitValue = null;
|
|
1395
|
+
distinctValue = false;
|
|
1396
|
+
groupByValues = null;
|
|
1397
|
+
partitionValue;
|
|
1398
|
+
pageSizeValue = null;
|
|
1399
|
+
nextPageValue = null;
|
|
1400
|
+
mode = "select";
|
|
1401
|
+
updates = null;
|
|
1402
|
+
onItemAddedListener = null;
|
|
1403
|
+
onItemUpdatedListener = null;
|
|
1404
|
+
onItemDeletedListener = null;
|
|
1405
|
+
onItemListener = null;
|
|
1406
|
+
constructor(db, table, partition) {
|
|
1407
|
+
this.db = db;
|
|
1408
|
+
this.table = table;
|
|
1409
|
+
this.partitionValue = partition;
|
|
1410
|
+
}
|
|
1411
|
+
ensureTable() {
|
|
1412
|
+
if (!this.table) throw new Error("Table is not defined. Call from(<table>) first.");
|
|
1413
|
+
return this.table;
|
|
1414
|
+
}
|
|
1415
|
+
toSelectQuery() {
|
|
1416
|
+
return {
|
|
1417
|
+
type: "SelectQuery",
|
|
1418
|
+
fields: this.fields,
|
|
1419
|
+
conditions: this.conditions,
|
|
1420
|
+
sort: this.sort,
|
|
1421
|
+
limit: this.limitValue,
|
|
1422
|
+
distinct: this.distinctValue,
|
|
1423
|
+
groupBy: this.groupByValues,
|
|
1424
|
+
partition: this.partitionValue ?? null,
|
|
1425
|
+
resolvers: this.resolvers
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
from(table) {
|
|
1429
|
+
this.table = table;
|
|
1430
|
+
return this;
|
|
1431
|
+
}
|
|
1432
|
+
select(...fields) {
|
|
1433
|
+
const flat = flattenStrings(fields);
|
|
1434
|
+
this.fields = flat.length > 0 ? flat : null;
|
|
1435
|
+
return this;
|
|
1436
|
+
}
|
|
1437
|
+
resolve(...values) {
|
|
1438
|
+
const flat = flattenStrings(values);
|
|
1439
|
+
this.resolvers = flat.length > 0 ? flat : null;
|
|
1440
|
+
return this;
|
|
1441
|
+
}
|
|
1442
|
+
where(condition) {
|
|
1443
|
+
const c = toCondition(condition);
|
|
1444
|
+
if (!this.conditions) {
|
|
1445
|
+
this.conditions = c;
|
|
1446
|
+
} else {
|
|
1447
|
+
this.conditions = {
|
|
1448
|
+
conditionType: "CompoundCondition",
|
|
1449
|
+
operator: "AND",
|
|
1450
|
+
conditions: [this.conditions, c]
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
return this;
|
|
1454
|
+
}
|
|
1455
|
+
and(condition) {
|
|
1456
|
+
const c = toCondition(condition);
|
|
1457
|
+
if (!this.conditions) {
|
|
1458
|
+
this.conditions = c;
|
|
1459
|
+
} else if (this.conditions.conditionType === "CompoundCondition" && this.conditions.operator === "AND") {
|
|
1460
|
+
this.conditions.conditions.push(c);
|
|
1461
|
+
} else {
|
|
1462
|
+
this.conditions = {
|
|
1463
|
+
conditionType: "CompoundCondition",
|
|
1464
|
+
operator: "AND",
|
|
1465
|
+
conditions: [this.conditions, c]
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
return this;
|
|
1469
|
+
}
|
|
1470
|
+
or(condition) {
|
|
1471
|
+
const c = toCondition(condition);
|
|
1472
|
+
if (!this.conditions) {
|
|
1473
|
+
this.conditions = c;
|
|
1474
|
+
} else if (this.conditions.conditionType === "CompoundCondition" && this.conditions.operator === "OR") {
|
|
1475
|
+
this.conditions.conditions.push(c);
|
|
1476
|
+
} else {
|
|
1477
|
+
this.conditions = {
|
|
1478
|
+
conditionType: "CompoundCondition",
|
|
1479
|
+
operator: "OR",
|
|
1480
|
+
conditions: [this.conditions, c]
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
return this;
|
|
1484
|
+
}
|
|
1485
|
+
orderBy(...sorts) {
|
|
1486
|
+
this.sort = sorts;
|
|
1487
|
+
return this;
|
|
1488
|
+
}
|
|
1489
|
+
groupBy(...fields) {
|
|
1490
|
+
this.groupByValues = fields.length ? fields : null;
|
|
1491
|
+
return this;
|
|
1492
|
+
}
|
|
1493
|
+
distinct() {
|
|
1494
|
+
this.distinctValue = true;
|
|
1495
|
+
return this;
|
|
1496
|
+
}
|
|
1497
|
+
limit(n) {
|
|
1498
|
+
this.limitValue = n;
|
|
1499
|
+
return this;
|
|
1500
|
+
}
|
|
1501
|
+
inPartition(partition) {
|
|
1502
|
+
this.partitionValue = partition;
|
|
1503
|
+
return this;
|
|
1504
|
+
}
|
|
1505
|
+
pageSize(n) {
|
|
1506
|
+
this.pageSizeValue = n;
|
|
1507
|
+
return this;
|
|
1508
|
+
}
|
|
1509
|
+
nextPage(token) {
|
|
1510
|
+
this.nextPageValue = token;
|
|
1511
|
+
return this;
|
|
1512
|
+
}
|
|
1513
|
+
setUpdates(updates) {
|
|
1514
|
+
this.mode = "update";
|
|
1515
|
+
this.updates = updates;
|
|
1516
|
+
return this;
|
|
1517
|
+
}
|
|
1518
|
+
async count() {
|
|
1519
|
+
if (this.mode !== "select") throw new Error("Cannot call count() in update mode.");
|
|
1520
|
+
const table = this.ensureTable();
|
|
1521
|
+
return this.db._count(table, this.toSelectQuery(), this.partitionValue);
|
|
1522
|
+
}
|
|
1523
|
+
async page(options = {}) {
|
|
1524
|
+
if (this.mode !== "select") throw new Error("Cannot call page() in update mode.");
|
|
1525
|
+
const table = this.ensureTable();
|
|
1526
|
+
const final = {
|
|
1527
|
+
pageSize: this.pageSizeValue ?? options.pageSize,
|
|
1528
|
+
nextPage: this.nextPageValue ?? options.nextPage,
|
|
1529
|
+
partition: this.partitionValue
|
|
1530
|
+
};
|
|
1531
|
+
return this.db._queryPage(table, this.toSelectQuery(), final);
|
|
1532
|
+
}
|
|
1533
|
+
list(options = {}) {
|
|
1534
|
+
const size = this.pageSizeValue ?? options.pageSize;
|
|
1535
|
+
const pgPromise = this.page(options).then((pg) => {
|
|
1536
|
+
const fetcher = (token) => this.nextPage(token).list({ pageSize: size });
|
|
1537
|
+
return new QueryResults(Array.isArray(pg.records) ? pg.records : [], pg.nextPage ?? null, fetcher);
|
|
1538
|
+
});
|
|
1539
|
+
for (const m of Object.getOwnPropertyNames(QueryResults.prototype)) {
|
|
1540
|
+
if (m === "constructor") continue;
|
|
1541
|
+
pgPromise[m] = (...args) => pgPromise.then((res) => res[m](...args));
|
|
1542
|
+
}
|
|
1543
|
+
return pgPromise;
|
|
1544
|
+
}
|
|
1545
|
+
async firstOrNull() {
|
|
1546
|
+
if (this.mode !== "select") throw new Error("Cannot call firstOrNull() in update mode.");
|
|
1547
|
+
if (!this.conditions) throw new OnyxError("firstOrNull() requires a where() clause.");
|
|
1548
|
+
this.limitValue = 1;
|
|
1549
|
+
const pg = await this.page();
|
|
1550
|
+
return Array.isArray(pg.records) && pg.records.length > 0 ? pg.records[0] : null;
|
|
1551
|
+
}
|
|
1552
|
+
async one() {
|
|
1553
|
+
return this.firstOrNull();
|
|
1554
|
+
}
|
|
1555
|
+
async delete() {
|
|
1556
|
+
if (this.mode !== "select") throw new Error("delete() is only applicable in select mode.");
|
|
1557
|
+
const table = this.ensureTable();
|
|
1558
|
+
return this.db._deleteByQuery(table, this.toSelectQuery(), this.partitionValue);
|
|
1559
|
+
}
|
|
1560
|
+
async update() {
|
|
1561
|
+
if (this.mode !== "update") throw new Error("Call setUpdates(...) before update().");
|
|
1562
|
+
const table = this.ensureTable();
|
|
1563
|
+
const update = {
|
|
1564
|
+
type: "UpdateQuery",
|
|
1565
|
+
conditions: this.conditions,
|
|
1566
|
+
updates: this.updates ?? {},
|
|
1567
|
+
sort: this.sort,
|
|
1568
|
+
limit: this.limitValue,
|
|
1569
|
+
partition: this.partitionValue ?? null
|
|
1570
|
+
};
|
|
1571
|
+
return this.db._update(table, update, this.partitionValue);
|
|
1572
|
+
}
|
|
1573
|
+
onItemAdded(listener) {
|
|
1574
|
+
this.onItemAddedListener = listener;
|
|
1575
|
+
return this;
|
|
1576
|
+
}
|
|
1577
|
+
onItemUpdated(listener) {
|
|
1578
|
+
this.onItemUpdatedListener = listener;
|
|
1579
|
+
return this;
|
|
1580
|
+
}
|
|
1581
|
+
onItemDeleted(listener) {
|
|
1582
|
+
this.onItemDeletedListener = listener;
|
|
1583
|
+
return this;
|
|
1584
|
+
}
|
|
1585
|
+
onItem(listener) {
|
|
1586
|
+
this.onItemListener = listener;
|
|
1587
|
+
return this;
|
|
1588
|
+
}
|
|
1589
|
+
async streamEventsOnly(keepAlive = true) {
|
|
1590
|
+
return this.stream(false, keepAlive);
|
|
1591
|
+
}
|
|
1592
|
+
async streamWithQueryResults(keepAlive = false) {
|
|
1593
|
+
return this.stream(true, keepAlive);
|
|
1594
|
+
}
|
|
1595
|
+
async stream(includeQueryResults = true, keepAlive = false) {
|
|
1596
|
+
if (this.mode !== "select") throw new Error("Streaming is only applicable in select mode.");
|
|
1597
|
+
const table = this.ensureTable();
|
|
1598
|
+
return this.db._stream(table, this.toSelectQuery(), includeQueryResults, keepAlive, {
|
|
1599
|
+
onItemAdded: this.onItemAddedListener ?? void 0,
|
|
1600
|
+
onItemUpdated: this.onItemUpdatedListener ?? void 0,
|
|
1601
|
+
onItemDeleted: this.onItemDeletedListener ?? void 0,
|
|
1602
|
+
onItem: this.onItemListener ?? void 0
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
};
|
|
1606
|
+
var SaveBuilderImpl = class {
|
|
1607
|
+
db;
|
|
1608
|
+
table;
|
|
1609
|
+
relationships = null;
|
|
1610
|
+
constructor(db, table) {
|
|
1611
|
+
this.db = db;
|
|
1612
|
+
this.table = table;
|
|
1613
|
+
}
|
|
1614
|
+
cascade(...relationships) {
|
|
1615
|
+
this.relationships = relationships.flat();
|
|
1616
|
+
return this;
|
|
1617
|
+
}
|
|
1618
|
+
one(entity) {
|
|
1619
|
+
const opts = this.relationships ? { relationships: this.relationships } : void 0;
|
|
1620
|
+
return this.db._saveInternal(this.table, entity, opts);
|
|
1621
|
+
}
|
|
1622
|
+
many(entities) {
|
|
1623
|
+
const opts = this.relationships ? { relationships: this.relationships } : void 0;
|
|
1624
|
+
return this.db._saveInternal(this.table, entities, opts);
|
|
1625
|
+
}
|
|
1626
|
+
};
|
|
1627
|
+
var CascadeBuilderImpl = class {
|
|
1628
|
+
db;
|
|
1629
|
+
rels = null;
|
|
1630
|
+
constructor(db) {
|
|
1631
|
+
this.db = db;
|
|
1632
|
+
}
|
|
1633
|
+
cascade(...relationships) {
|
|
1634
|
+
this.rels = relationships.flat();
|
|
1635
|
+
return this;
|
|
1636
|
+
}
|
|
1637
|
+
save(table, entityOrEntities) {
|
|
1638
|
+
const opts = this.rels ? { relationships: this.rels } : void 0;
|
|
1639
|
+
return this.db._saveInternal(String(table), entityOrEntities, opts);
|
|
1640
|
+
}
|
|
1641
|
+
delete(table, primaryKey) {
|
|
1642
|
+
const opts = this.rels ? { relationships: this.rels } : void 0;
|
|
1643
|
+
return this.db.delete(table, primaryKey, opts);
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
var onyx = {
|
|
1647
|
+
init(config) {
|
|
1648
|
+
return new OnyxDatabaseImpl(config);
|
|
1649
|
+
},
|
|
1650
|
+
clearCacheConfig
|
|
1651
|
+
};
|
|
497
1652
|
|
|
498
1653
|
// gen/generate.ts
|
|
1654
|
+
var DEFAULT_SCHEMA_PATH = "./onyx.schema.json";
|
|
1655
|
+
var DEFAULT_TYPES_OUT = "./onyx/types.ts";
|
|
499
1656
|
var DEFAULTS2 = {
|
|
500
|
-
source: "
|
|
1657
|
+
source: "file",
|
|
501
1658
|
outBaseName: "onyx.schema",
|
|
502
1659
|
emitJson: false,
|
|
503
1660
|
overwrite: true,
|
|
@@ -506,6 +1663,14 @@ var DEFAULTS2 = {
|
|
|
506
1663
|
optional: "non-null",
|
|
507
1664
|
quiet: false
|
|
508
1665
|
};
|
|
1666
|
+
function toArray(val) {
|
|
1667
|
+
if (!val) return [];
|
|
1668
|
+
return Array.isArray(val) ? val : [val];
|
|
1669
|
+
}
|
|
1670
|
+
function isTypesFilePath(p) {
|
|
1671
|
+
if (!p) return false;
|
|
1672
|
+
return p.endsWith(".ts") || p.endsWith(".mts") || p.endsWith(".cts") || p.endsWith(".d.ts") || p.endsWith(".d.mts") || p.endsWith(".d.cts");
|
|
1673
|
+
}
|
|
509
1674
|
async function readFileJson(path) {
|
|
510
1675
|
const fs = await import('fs/promises');
|
|
511
1676
|
const txt = await fs.readFile(path, "utf8");
|
|
@@ -529,108 +1694,162 @@ async function writeFile(path, data, overwrite) {
|
|
|
529
1694
|
function isIntrospection(x) {
|
|
530
1695
|
return !!x && typeof x === "object" && Array.isArray(x.tables);
|
|
531
1696
|
}
|
|
532
|
-
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
1697
|
+
function normalizeAttributeType(raw) {
|
|
1698
|
+
const t = typeof raw === "string" ? raw : "";
|
|
1699
|
+
switch (t) {
|
|
1700
|
+
case "String":
|
|
1701
|
+
return "String";
|
|
1702
|
+
case "Boolean":
|
|
1703
|
+
return "Boolean";
|
|
1704
|
+
case "Timestamp":
|
|
1705
|
+
return "Timestamp";
|
|
1706
|
+
case "EmbeddedList":
|
|
1707
|
+
return "EmbeddedList";
|
|
1708
|
+
case "EmbeddedObject":
|
|
1709
|
+
return "EmbeddedObject";
|
|
1710
|
+
case "Int":
|
|
1711
|
+
case "Byte":
|
|
1712
|
+
case "Short":
|
|
1713
|
+
case "Float":
|
|
1714
|
+
case "Double":
|
|
1715
|
+
case "Long":
|
|
1716
|
+
return "Int";
|
|
1717
|
+
default:
|
|
1718
|
+
return "EmbeddedObject";
|
|
549
1719
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
1720
|
+
}
|
|
1721
|
+
function toOnyxIntrospectionFromEntities(entities) {
|
|
1722
|
+
return {
|
|
1723
|
+
tables: entities.map((entity) => ({
|
|
1724
|
+
name: entity.name,
|
|
1725
|
+
attributes: (entity.attributes ?? []).map((attr) => ({
|
|
1726
|
+
name: attr.name,
|
|
1727
|
+
type: normalizeAttributeType(attr.type),
|
|
1728
|
+
isNullable: Boolean(attr.isNullable)
|
|
1729
|
+
}))
|
|
1730
|
+
}))
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
function normalizeIntrospection(raw) {
|
|
1734
|
+
if (isIntrospection(raw)) return raw;
|
|
1735
|
+
const entities = raw.entities;
|
|
1736
|
+
if (Array.isArray(entities)) {
|
|
1737
|
+
return toOnyxIntrospectionFromEntities(entities);
|
|
1738
|
+
}
|
|
1739
|
+
throw new Error('Invalid schema: missing "tables" array.');
|
|
1740
|
+
}
|
|
1741
|
+
async function fetchSchemaFromApi(config) {
|
|
1742
|
+
const db = onyx.init({
|
|
1743
|
+
baseUrl: config.baseUrl,
|
|
1744
|
+
databaseId: config.databaseId,
|
|
1745
|
+
apiKey: config.apiKey,
|
|
1746
|
+
apiSecret: config.apiSecret,
|
|
1747
|
+
fetch: config.fetch
|
|
1748
|
+
});
|
|
1749
|
+
const schema = await db.getSchema();
|
|
1750
|
+
return normalizeIntrospection(schema);
|
|
554
1751
|
}
|
|
555
1752
|
async function generateTypes(options) {
|
|
556
1753
|
const path = await import('path');
|
|
557
1754
|
const opts = { ...DEFAULTS2, ...options ?? {} };
|
|
558
|
-
const
|
|
559
|
-
let
|
|
560
|
-
if (opts.source === "file" || opts.source === "auto" &&
|
|
561
|
-
if (!
|
|
1755
|
+
const resolvedSchemaPath = opts.schemaPath ?? (opts.source === "file" ? DEFAULT_SCHEMA_PATH : void 0);
|
|
1756
|
+
let schemaInput = null;
|
|
1757
|
+
if (opts.source === "file" || opts.source === "auto" && resolvedSchemaPath) {
|
|
1758
|
+
if (!resolvedSchemaPath) throw new Error('schemaPath is required when source="file"');
|
|
562
1759
|
if (!opts.quiet)
|
|
563
|
-
process__default.default.stderr.write(`[onyx-gen] reading schema from file: ${
|
|
1760
|
+
process__default.default.stderr.write(`[onyx-gen] reading schema from file: ${resolvedSchemaPath}
|
|
564
1761
|
`);
|
|
565
|
-
|
|
1762
|
+
schemaInput = await readFileJson(resolvedSchemaPath);
|
|
566
1763
|
}
|
|
567
|
-
if (!
|
|
1764
|
+
if (!schemaInput) {
|
|
568
1765
|
if (opts.source === "file") throw new Error("Failed to read schema from file");
|
|
569
1766
|
const cfg = await resolveConfig({});
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
apiSecret: cfg.apiSecret,
|
|
574
|
-
fetchImpl: cfg.fetch
|
|
575
|
-
});
|
|
1767
|
+
if (!cfg.databaseId) {
|
|
1768
|
+
throw new Error("Missing databaseId. Set ONYX_DATABASE_ID or pass to onyx.init().");
|
|
1769
|
+
}
|
|
576
1770
|
if (!opts.quiet)
|
|
577
1771
|
process__default.default.stderr.write(`[onyx-gen] fetching schema from API for db ${cfg.databaseId}
|
|
578
1772
|
`);
|
|
579
|
-
|
|
580
|
-
}
|
|
581
|
-
if (!isIntrospection(schema)) {
|
|
582
|
-
throw new Error('Invalid schema: missing "tables" array.');
|
|
583
|
-
}
|
|
584
|
-
const outIsFile = typeof opts.typesOutFile === "string" && (opts.typesOutFile.endsWith(".ts") || opts.typesOutFile.endsWith(".mts") || opts.typesOutFile.endsWith(".cts") || opts.typesOutFile.endsWith(".d.ts") || opts.typesOutFile.endsWith(".d.mts") || opts.typesOutFile.endsWith(".d.cts"));
|
|
585
|
-
let typesPath;
|
|
586
|
-
let jsonBaseName;
|
|
587
|
-
let typesDirAbs;
|
|
588
|
-
if (outIsFile) {
|
|
589
|
-
const typesOutFile = opts.typesOutFile;
|
|
590
|
-
typesPath = path.resolve(process__default.default.cwd(), typesOutFile);
|
|
591
|
-
typesDirAbs = path.dirname(typesPath);
|
|
592
|
-
await ensureDir(typesDirAbs);
|
|
593
|
-
jsonBaseName = path.basename(typesPath, path.extname(typesPath));
|
|
594
|
-
} else {
|
|
595
|
-
typesDirAbs = path.resolve(process__default.default.cwd(), typesDir);
|
|
596
|
-
await ensureDir(typesDirAbs);
|
|
597
|
-
typesPath = path.join(typesDirAbs, `${opts.outBaseName}.ts`);
|
|
598
|
-
jsonBaseName = opts.outBaseName;
|
|
1773
|
+
schemaInput = await fetchSchemaFromApi(cfg);
|
|
599
1774
|
}
|
|
1775
|
+
const schema = normalizeIntrospection(schemaInput);
|
|
1776
|
+
const outTargets = [
|
|
1777
|
+
...toArray(opts.typesOutFile),
|
|
1778
|
+
...toArray(opts.typesOutDir),
|
|
1779
|
+
...toArray(opts.outDir)
|
|
1780
|
+
].map((p) => p.trim()).filter(Boolean);
|
|
1781
|
+
if (outTargets.length === 0) {
|
|
1782
|
+
outTargets.push(DEFAULT_TYPES_OUT);
|
|
1783
|
+
}
|
|
1784
|
+
const outputs = outTargets.map((target) => {
|
|
1785
|
+
const outIsFile = isTypesFilePath(target);
|
|
1786
|
+
const typesPath = outIsFile ? path.resolve(process__default.default.cwd(), target) : path.join(path.resolve(process__default.default.cwd(), target), `${opts.outBaseName}.ts`);
|
|
1787
|
+
const typesDirAbs = outIsFile ? path.dirname(typesPath) : path.resolve(process__default.default.cwd(), target);
|
|
1788
|
+
const jsonBaseName = outIsFile ? path.basename(typesPath, path.extname(typesPath)) : opts.outBaseName;
|
|
1789
|
+
return { typesPath, typesDirAbs, jsonBaseName };
|
|
1790
|
+
});
|
|
600
1791
|
const types = emitTypes(schema, {
|
|
601
1792
|
schemaTypeName: opts.schemaTypeName,
|
|
602
1793
|
timestampMode: opts.timestampMode,
|
|
603
1794
|
modelNamePrefix: opts.prefix ?? "",
|
|
604
1795
|
optionalStrategy: opts.optional
|
|
605
1796
|
});
|
|
606
|
-
|
|
1797
|
+
const typesPaths = [];
|
|
1798
|
+
for (const out of outputs) {
|
|
1799
|
+
await ensureDir(out.typesDirAbs);
|
|
1800
|
+
await writeFile(out.typesPath, `${types}
|
|
607
1801
|
`, opts.overwrite);
|
|
608
|
-
|
|
1802
|
+
typesPaths.push(out.typesPath);
|
|
1803
|
+
}
|
|
1804
|
+
let jsonPaths;
|
|
609
1805
|
if (opts.emitJson) {
|
|
610
|
-
const jsonOutDirAbs = path.resolve(
|
|
611
|
-
process__default.default.cwd(),
|
|
612
|
-
opts.jsonOutDir ?? (outIsFile ? typesDirAbs : typesDirAbs)
|
|
613
|
-
);
|
|
614
|
-
await ensureDir(jsonOutDirAbs);
|
|
615
|
-
jsonPath = path.join(jsonOutDirAbs, `${jsonBaseName}.json`);
|
|
616
1806
|
const jsonPretty = JSON.stringify(schema, null, 2);
|
|
617
|
-
|
|
1807
|
+
jsonPaths = [];
|
|
1808
|
+
for (const out of outputs) {
|
|
1809
|
+
const jsonOutDirAbs = path.resolve(process__default.default.cwd(), opts.jsonOutDir ?? out.typesDirAbs);
|
|
1810
|
+
await ensureDir(jsonOutDirAbs);
|
|
1811
|
+
const jsonPath = path.join(jsonOutDirAbs, `${out.jsonBaseName}.json`);
|
|
1812
|
+
await writeFile(jsonPath, `${jsonPretty}
|
|
618
1813
|
`, opts.overwrite);
|
|
1814
|
+
jsonPaths.push(jsonPath);
|
|
1815
|
+
}
|
|
619
1816
|
}
|
|
620
1817
|
if (!opts.quiet) {
|
|
621
|
-
|
|
1818
|
+
for (const p of typesPaths) {
|
|
1819
|
+
process__default.default.stderr.write(`[onyx-gen] wrote ${p}
|
|
622
1820
|
`);
|
|
623
|
-
|
|
1821
|
+
}
|
|
1822
|
+
if (jsonPaths) {
|
|
1823
|
+
for (const jp of jsonPaths) {
|
|
1824
|
+
process__default.default.stderr.write(`[onyx-gen] wrote ${jp}
|
|
624
1825
|
`);
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
625
1828
|
}
|
|
626
|
-
return { typesPath, jsonPath };
|
|
1829
|
+
return { typesPath: typesPaths[0], jsonPath: jsonPaths?.[0], typesPaths, jsonPaths };
|
|
627
1830
|
}
|
|
628
1831
|
|
|
629
1832
|
// gen/cli/generate.ts
|
|
630
|
-
function
|
|
1833
|
+
function isTypesFilePath2(p) {
|
|
631
1834
|
if (!p) return false;
|
|
632
1835
|
return p.endsWith(".ts") || p.endsWith(".mts") || p.endsWith(".cts") || p.endsWith(".d.ts") || p.endsWith(".d.mts") || p.endsWith(".d.cts");
|
|
633
1836
|
}
|
|
1837
|
+
function appendVal(current, val) {
|
|
1838
|
+
if (!current) return val;
|
|
1839
|
+
return Array.isArray(current) ? [...current, val] : [current, val];
|
|
1840
|
+
}
|
|
1841
|
+
function addOut(opts, raw, forceFile = false) {
|
|
1842
|
+
if (!raw) throw new Error("Missing value for output flag");
|
|
1843
|
+
const parts = raw.split(",").map((p) => p.trim()).filter(Boolean);
|
|
1844
|
+
for (const val of parts) {
|
|
1845
|
+
if (forceFile || isTypesFilePath2(val)) {
|
|
1846
|
+
opts.typesOutFile = appendVal(opts.typesOutFile, val);
|
|
1847
|
+
} else {
|
|
1848
|
+
opts.typesOutDir = appendVal(opts.typesOutDir, val);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
return opts;
|
|
1852
|
+
}
|
|
634
1853
|
function parseArgs(argv) {
|
|
635
1854
|
const opts = {};
|
|
636
1855
|
const next = (i) => argv[i + 1];
|
|
@@ -639,27 +1858,19 @@ function parseArgs(argv) {
|
|
|
639
1858
|
switch (a) {
|
|
640
1859
|
case "--out":
|
|
641
1860
|
case "--outDir": {
|
|
642
|
-
|
|
643
|
-
if (!val) throw new Error(`Missing value for ${a}`);
|
|
644
|
-
if (isTypesFilePath(val)) opts.typesOutFile = val;
|
|
645
|
-
else opts.typesOutDir = val;
|
|
1861
|
+
addOut(opts, next(i));
|
|
646
1862
|
i++;
|
|
647
1863
|
break;
|
|
648
1864
|
}
|
|
649
1865
|
case "--types-out":
|
|
650
1866
|
case "--typesOut": {
|
|
651
|
-
|
|
652
|
-
if (!val) throw new Error(`Missing value for ${a}`);
|
|
653
|
-
if (isTypesFilePath(val)) opts.typesOutFile = val;
|
|
654
|
-
else opts.typesOutDir = val;
|
|
1867
|
+
addOut(opts, next(i));
|
|
655
1868
|
i++;
|
|
656
1869
|
break;
|
|
657
1870
|
}
|
|
658
1871
|
case "--types-file":
|
|
659
1872
|
case "--typesFile": {
|
|
660
|
-
|
|
661
|
-
if (!val) throw new Error(`Missing value for ${a}`);
|
|
662
|
-
opts.typesOutFile = val;
|
|
1873
|
+
addOut(opts, next(i), true);
|
|
663
1874
|
i++;
|
|
664
1875
|
break;
|
|
665
1876
|
}
|
|
@@ -757,16 +1968,17 @@ Usage:
|
|
|
757
1968
|
onyx-gen [options]
|
|
758
1969
|
|
|
759
1970
|
Output selection:
|
|
760
|
-
--out <path>
|
|
761
|
-
Otherwise treats
|
|
762
|
-
|
|
763
|
-
--types-
|
|
1971
|
+
--out <path[,path2,...]> If a value ends with ".ts", writes exactly to that file.
|
|
1972
|
+
Otherwise treats it as a directory. Comma-separate or repeat to
|
|
1973
|
+
write multiple outputs. Default: ./onyx/types.ts
|
|
1974
|
+
--types-out <dir|file>[,more] Same as --out (file-or-dir).
|
|
1975
|
+
--types-file <file.ts>[,more] Explicit file output(s).
|
|
764
1976
|
--base, --baseName <name> Base filename (without ext) when writing to a directory (default: onyx.schema)
|
|
765
1977
|
--json-out <dir> JSON output directory (used only with --emit-json)
|
|
766
1978
|
|
|
767
1979
|
Source selection:
|
|
768
|
-
--source <auto|api|file> Where to get schema (default:
|
|
769
|
-
--schema <path> Path to schema JSON when --source=file (
|
|
1980
|
+
--source <auto|api|file> Where to get schema (default: file)
|
|
1981
|
+
--schema <path> Path to schema JSON when --source=file (default: ./onyx.schema.json)
|
|
770
1982
|
|
|
771
1983
|
Type emission:
|
|
772
1984
|
--timestamps <string|date|number> Timestamp representation in types (default: date)
|
|
@@ -783,6 +1995,7 @@ Other:
|
|
|
783
1995
|
-h, --help Show this help
|
|
784
1996
|
|
|
785
1997
|
Notes:
|
|
1998
|
+
\u2022 Running with no flags defaults to: --source file --schema ./onyx.schema.json --out ./onyx/types.ts
|
|
786
1999
|
\u2022 Env/config for --source=api uses the same resolver as onyx.init()
|
|
787
2000
|
(env vars, ./onyx-database.json, ~/.onyx/onyx-database-<id>.json, etc.).
|
|
788
2001
|
`);
|