@onyx.dev/onyx-database 0.2.10 → 1.0.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.
@@ -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
- if (body == null) delete headers["Content-Type"];
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,96 +496,1232 @@ var HttpClient = class {
404
496
  }
405
497
  };
406
498
 
407
- // gen/emit.ts
408
- var DEFAULTS = {
409
- schemaTypeName: "OnyxSchema",
410
- timestampMode: "date",
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 isValidIdentifier(name) {
415
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
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
- function toPascalCase(raw) {
418
- const cleaned = String(raw).replace(/[^A-Za-z0-9]+/g, " ");
419
- const pc = cleaned.trim().split(/\s+/).filter(Boolean).map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
420
- return pc.length === 0 ? "Table" : /^[0-9]/.test(pc) ? `T${pc}` : pc;
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/helpers/condition-normalizer.ts
935
+ function isQueryBuilderLike(value) {
936
+ return !!value && typeof value.toSerializableQueryObject === "function";
421
937
  }
422
- function tsTypeFor(attrType, timestampMode) {
423
- switch (attrType) {
424
- case "String":
425
- return "string";
426
- case "Int":
427
- return "number";
428
- case "Boolean":
429
- return "boolean";
430
- case "Timestamp":
431
- return timestampMode === "date" ? "Date" : timestampMode === "number" ? "number" : "string";
432
- case "EmbeddedList":
433
- return "any[]";
434
- case "EmbeddedObject":
435
- return "any";
436
- default:
437
- return "any";
938
+ function normalizeCriteriaValue(value) {
939
+ if (Array.isArray(value)) {
940
+ let changed = false;
941
+ const normalized = value.map((item) => {
942
+ const result = normalizeCriteriaValue(item);
943
+ if (result.changed) changed = true;
944
+ return result.value;
945
+ });
946
+ if (!changed) {
947
+ for (let i = 0; i < normalized.length; i += 1) {
948
+ if (normalized[i] !== value[i]) {
949
+ changed = true;
950
+ break;
951
+ }
952
+ }
953
+ }
954
+ return { value: changed ? normalized : value, changed };
438
955
  }
956
+ if (isQueryBuilderLike(value)) {
957
+ return { value: value.toSerializableQueryObject(), changed: true };
958
+ }
959
+ return { value, changed: false };
439
960
  }
440
- function propertyLine(attr, timestampMode, optionalStrategy) {
441
- const key = isValidIdentifier(attr.name) ? attr.name : JSON.stringify(attr.name);
442
- const makeOptional = optionalStrategy === "non-null" ? !attr.isNullable : optionalStrategy === "nullable" ? attr.isNullable : false;
443
- const t = tsTypeFor(attr.type, timestampMode);
444
- const nullableUnion = attr.isNullable ? " | null" : "";
445
- return ` ${key}${makeOptional ? "?" : ""}: ${t}${nullableUnion};`;
961
+ function normalizeConditionInternal(condition) {
962
+ if (condition.conditionType === "SingleCondition") {
963
+ const { value, changed: changed2 } = normalizeCriteriaValue(condition.criteria.value);
964
+ if (!changed2) return condition;
965
+ return {
966
+ ...condition,
967
+ criteria: { ...condition.criteria, value }
968
+ };
969
+ }
970
+ let changed = false;
971
+ const normalizedConditions = condition.conditions.map((child) => {
972
+ const normalized = normalizeConditionInternal(child);
973
+ if (normalized !== child) changed = true;
974
+ return normalized;
975
+ });
976
+ if (!changed) return condition;
977
+ return { ...condition, conditions: normalizedConditions };
446
978
  }
447
- function emitTypes(schema, options) {
448
- const opts = { ...DEFAULTS, ...options ?? {} };
449
- const usedEnumKeys = /* @__PURE__ */ new Set();
450
- const makeEnumKey = (raw) => {
451
- const base = toPascalCase(raw);
452
- let key = base;
453
- let i = 2;
454
- while (usedEnumKeys.has(key)) {
455
- key = `${base}_${i++}`;
979
+ function normalizeCondition(condition) {
980
+ if (!condition) return condition;
981
+ return normalizeConditionInternal(condition);
982
+ }
983
+
984
+ // src/builders/cascade-relationship-builder.ts
985
+ var CascadeRelationshipBuilder = class {
986
+ graphName;
987
+ typeName;
988
+ target;
989
+ /**
990
+ * Set the graph name component.
991
+ *
992
+ * @param name Graph name or namespace.
993
+ * @example
994
+ * ```ts
995
+ * builder.graph('programs');
996
+ * ```
997
+ */
998
+ graph(name) {
999
+ this.graphName = name;
1000
+ return this;
1001
+ }
1002
+ /**
1003
+ * Set the graph type component.
1004
+ *
1005
+ * @param type Graph type to target.
1006
+ * @example
1007
+ * ```ts
1008
+ * builder.graphType('StreamingProgram');
1009
+ * ```
1010
+ */
1011
+ graphType(type) {
1012
+ this.typeName = type;
1013
+ return this;
1014
+ }
1015
+ /**
1016
+ * Set the target field for the relationship.
1017
+ *
1018
+ * @param field Target field name.
1019
+ * @example
1020
+ * ```ts
1021
+ * builder.targetField('channelId');
1022
+ * ```
1023
+ */
1024
+ targetField(field) {
1025
+ this.target = field;
1026
+ return this;
1027
+ }
1028
+ /**
1029
+ * Produce the cascade relationship string using the provided source field.
1030
+ *
1031
+ * @param field Source field name.
1032
+ * @example
1033
+ * ```ts
1034
+ * const rel = builder
1035
+ * .graph('programs')
1036
+ * .graphType('StreamingProgram')
1037
+ * .targetField('channelId')
1038
+ * .sourceField('id');
1039
+ * // rel === 'programs:StreamingProgram(channelId, id)'
1040
+ * ```
1041
+ */
1042
+ sourceField(field) {
1043
+ if (!this.graphName || !this.typeName || !this.target) {
1044
+ throw new Error("Cascade relationship requires graph, type, target, and source fields");
456
1045
  }
457
- usedEnumKeys.add(key);
458
- return key;
459
- };
460
- const lines = [];
461
- lines.push(`// AUTO-GENERATED BY onyx-gen. DO NOT EDIT.`);
462
- lines.push(`// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`);
463
- lines.push("");
464
- for (const t of schema.tables) {
465
- const typeName = `${opts.modelNamePrefix}${toPascalCase(t.name)}`;
466
- lines.push(`export interface ${typeName} {`);
467
- for (const a of t.attributes) {
468
- lines.push(propertyLine(a, opts.timestampMode, opts.optionalStrategy));
1046
+ return `${this.graphName}:${this.typeName}(${this.target}, ${field})`;
1047
+ }
1048
+ };
1049
+
1050
+ // src/errors/onyx-error.ts
1051
+ var OnyxError = class extends Error {
1052
+ name = "OnyxError";
1053
+ constructor(message) {
1054
+ super(message);
1055
+ }
1056
+ };
1057
+
1058
+ // src/impl/onyx.ts
1059
+ var DEFAULT_CACHE_TTL = 5 * 60 * 1e3;
1060
+ var cachedCfg = null;
1061
+ function resolveConfigWithCache(config) {
1062
+ const ttl = config?.ttl ?? DEFAULT_CACHE_TTL;
1063
+ const now = Date.now();
1064
+ if (cachedCfg && cachedCfg.expires > now) {
1065
+ return cachedCfg.promise;
1066
+ }
1067
+ const { ttl: _ttl, requestLoggingEnabled: _reqLog, responseLoggingEnabled: _resLog, ...rest } = config ?? {};
1068
+ const promise = resolveConfig(rest);
1069
+ cachedCfg = { promise, expires: now + ttl };
1070
+ return promise;
1071
+ }
1072
+ function clearCacheConfig() {
1073
+ cachedCfg = null;
1074
+ }
1075
+ function toSingleCondition(criteria) {
1076
+ return { conditionType: "SingleCondition", criteria };
1077
+ }
1078
+ function flattenStrings(values) {
1079
+ const flat = [];
1080
+ for (const value of values) {
1081
+ if (Array.isArray(value)) {
1082
+ flat.push(...value);
1083
+ } else if (typeof value === "string") {
1084
+ flat.push(value);
469
1085
  }
470
- lines.push(" [key: string]: any;");
471
- lines.push("}");
472
- lines.push("");
473
1086
  }
474
- lines.push(`export type ${opts.schemaTypeName} = {`);
475
- for (const t of schema.tables) {
476
- const key = isValidIdentifier(t.name) ? t.name : JSON.stringify(t.name);
477
- const modelName = `${opts.modelNamePrefix}${toPascalCase(t.name)}`;
478
- lines.push(` ${key}: ${modelName};`);
1087
+ return flat;
1088
+ }
1089
+ function toCondition(input) {
1090
+ if (typeof input.toCondition === "function") {
1091
+ return input.toCondition();
479
1092
  }
480
- lines.push("};");
481
- lines.push("");
482
- if (opts.schemaTypeName !== "Schema") {
483
- lines.push(`export type Schema = ${opts.schemaTypeName};`);
1093
+ const c = input;
1094
+ if (c && typeof c.field === "string" && typeof c.operator === "string") {
1095
+ return toSingleCondition(c);
484
1096
  }
485
- lines.push(`export const Schema = {} as ${opts.schemaTypeName};`);
486
- lines.push("");
487
- lines.push("export enum tables {");
488
- for (const t of schema.tables) {
489
- const enumKey = makeEnumKey(t.name);
490
- const enumVal = JSON.stringify(t.name);
491
- lines.push(` ${enumKey} = ${enumVal},`);
1097
+ throw new Error("Invalid condition passed to builder.");
1098
+ }
1099
+ function serializeDates(value) {
1100
+ if (value instanceof Date) return value.toISOString();
1101
+ if (Array.isArray(value)) return value.map(serializeDates);
1102
+ if (value && typeof value === "object") {
1103
+ const out = {};
1104
+ for (const [k, v] of Object.entries(value)) {
1105
+ out[k] = serializeDates(v);
1106
+ }
1107
+ return out;
492
1108
  }
493
- lines.push("}");
494
- lines.push("");
495
- return lines.join("\n");
1109
+ return value;
1110
+ }
1111
+ function stripEntityText(input) {
1112
+ const { entityText, ...rest } = input;
1113
+ return rest;
1114
+ }
1115
+ function normalizeSecretMetadata(input) {
1116
+ return { ...input, updatedAt: new Date(input.updatedAt) };
496
1117
  }
1118
+ function normalizeSecretRecord(input) {
1119
+ return { ...input, updatedAt: new Date(input.updatedAt) };
1120
+ }
1121
+ function normalizeDate(value) {
1122
+ if (value == null) return void 0;
1123
+ if (value instanceof Date) return value;
1124
+ const ts = new Date(value);
1125
+ return Number.isNaN(ts.getTime()) ? void 0 : ts;
1126
+ }
1127
+ function normalizeSchemaRevision(input, fallbackDatabaseId) {
1128
+ const { meta, createdAt, publishedAt, revisionId, entityText, ...rest } = input;
1129
+ const mergedMeta = {
1130
+ revisionId: meta?.revisionId ?? revisionId,
1131
+ createdAt: normalizeDate(meta?.createdAt ?? createdAt),
1132
+ publishedAt: normalizeDate(meta?.publishedAt ?? publishedAt)
1133
+ };
1134
+ const cleanedMeta = mergedMeta.revisionId || mergedMeta.createdAt || mergedMeta.publishedAt ? mergedMeta : void 0;
1135
+ return {
1136
+ ...rest,
1137
+ databaseId: input.databaseId ?? fallbackDatabaseId,
1138
+ meta: cleanedMeta,
1139
+ entities: input.entities ?? []
1140
+ };
1141
+ }
1142
+ var OnyxDatabaseImpl = class {
1143
+ cfgPromise;
1144
+ resolved = null;
1145
+ http = null;
1146
+ streams = /* @__PURE__ */ new Set();
1147
+ requestLoggingEnabled;
1148
+ responseLoggingEnabled;
1149
+ defaultPartition;
1150
+ constructor(config) {
1151
+ this.requestLoggingEnabled = !!config?.requestLoggingEnabled;
1152
+ this.responseLoggingEnabled = !!config?.responseLoggingEnabled;
1153
+ this.defaultPartition = config?.partition;
1154
+ this.cfgPromise = resolveConfigWithCache(config);
1155
+ }
1156
+ async ensureClient() {
1157
+ if (!this.resolved) {
1158
+ this.resolved = await this.cfgPromise;
1159
+ }
1160
+ if (!this.http) {
1161
+ this.http = new HttpClient({
1162
+ baseUrl: this.resolved.baseUrl,
1163
+ apiKey: this.resolved.apiKey,
1164
+ apiSecret: this.resolved.apiSecret,
1165
+ fetchImpl: this.resolved.fetch,
1166
+ requestLoggingEnabled: this.requestLoggingEnabled,
1167
+ responseLoggingEnabled: this.responseLoggingEnabled
1168
+ });
1169
+ }
1170
+ return {
1171
+ http: this.http,
1172
+ fetchImpl: this.resolved.fetch,
1173
+ baseUrl: this.resolved.baseUrl,
1174
+ databaseId: this.resolved.databaseId
1175
+ };
1176
+ }
1177
+ registerStream(handle) {
1178
+ this.streams.add(handle);
1179
+ return {
1180
+ cancel: () => {
1181
+ try {
1182
+ handle.cancel();
1183
+ } finally {
1184
+ this.streams.delete(handle);
1185
+ }
1186
+ }
1187
+ };
1188
+ }
1189
+ /** -------- IOnyxDatabase -------- */
1190
+ from(table) {
1191
+ return new QueryBuilderImpl(this, String(table), this.defaultPartition);
1192
+ }
1193
+ select(...fields) {
1194
+ const qb = new QueryBuilderImpl(
1195
+ this,
1196
+ null,
1197
+ this.defaultPartition
1198
+ );
1199
+ qb.select(...fields);
1200
+ return qb;
1201
+ }
1202
+ cascade(...relationships) {
1203
+ const cb = new CascadeBuilderImpl(this);
1204
+ return cb.cascade(...relationships);
1205
+ }
1206
+ cascadeBuilder() {
1207
+ return new CascadeRelationshipBuilder();
1208
+ }
1209
+ // Impl
1210
+ save(table, entityOrEntities, options) {
1211
+ if (arguments.length === 1) {
1212
+ return new SaveBuilderImpl(this, table);
1213
+ }
1214
+ return this._saveInternal(table, entityOrEntities, options);
1215
+ }
1216
+ async batchSave(table, entities, batchSize = 1e3, options) {
1217
+ for (let i = 0; i < entities.length; i += batchSize) {
1218
+ const chunk = entities.slice(i, i + batchSize);
1219
+ if (chunk.length) {
1220
+ await this._saveInternal(String(table), chunk, options);
1221
+ }
1222
+ }
1223
+ }
1224
+ async findById(table, primaryKey, options) {
1225
+ const { http, databaseId } = await this.ensureClient();
1226
+ const params = new URLSearchParams();
1227
+ const partition = options?.partition ?? this.defaultPartition;
1228
+ if (partition) params.append("partition", partition);
1229
+ if (options?.resolvers?.length) params.append("resolvers", options.resolvers.join(","));
1230
+ const path = `/data/${encodeURIComponent(databaseId)}/${encodeURIComponent(
1231
+ String(table)
1232
+ )}/${encodeURIComponent(primaryKey)}${params.toString() ? `?${params.toString()}` : ""}`;
1233
+ try {
1234
+ return await http.request("GET", path);
1235
+ } catch (err) {
1236
+ if (err instanceof OnyxHttpError && err.status === 404) return null;
1237
+ throw err;
1238
+ }
1239
+ }
1240
+ async delete(table, primaryKey, options) {
1241
+ const { http, databaseId } = await this.ensureClient();
1242
+ const params = new URLSearchParams();
1243
+ const partition = options?.partition ?? this.defaultPartition;
1244
+ if (partition) params.append("partition", partition);
1245
+ if (options?.relationships?.length) {
1246
+ params.append("relationships", options.relationships.map(encodeURIComponent).join(","));
1247
+ }
1248
+ const path = `/data/${encodeURIComponent(databaseId)}/${encodeURIComponent(
1249
+ table
1250
+ )}/${encodeURIComponent(primaryKey)}${params.toString() ? `?${params.toString()}` : ""}`;
1251
+ return http.request("DELETE", path);
1252
+ }
1253
+ async saveDocument(doc) {
1254
+ const { http, databaseId } = await this.ensureClient();
1255
+ const path = `/data/${encodeURIComponent(databaseId)}/document`;
1256
+ return http.request("PUT", path, serializeDates(doc));
1257
+ }
1258
+ async getDocument(documentId, options) {
1259
+ const { http, databaseId } = await this.ensureClient();
1260
+ const params = new URLSearchParams();
1261
+ if (options?.width != null) params.append("width", String(options.width));
1262
+ if (options?.height != null) params.append("height", String(options.height));
1263
+ const path = `/data/${encodeURIComponent(databaseId)}/document/${encodeURIComponent(
1264
+ documentId
1265
+ )}${params.toString() ? `?${params.toString()}` : ""}`;
1266
+ return http.request("GET", path);
1267
+ }
1268
+ async deleteDocument(documentId) {
1269
+ const { http, databaseId } = await this.ensureClient();
1270
+ const path = `/data/${encodeURIComponent(databaseId)}/document/${encodeURIComponent(
1271
+ documentId
1272
+ )}`;
1273
+ return http.request("DELETE", path);
1274
+ }
1275
+ async getSchema(options) {
1276
+ const { http, databaseId } = await this.ensureClient();
1277
+ const params = new URLSearchParams();
1278
+ const tables = options?.tables;
1279
+ const tableList = Array.isArray(tables) ? tables : typeof tables === "string" ? tables.split(",") : [];
1280
+ const normalizedTables = tableList.map((t) => t.trim()).filter(Boolean);
1281
+ if (normalizedTables.length) {
1282
+ params.append("tables", normalizedTables.map(encodeURIComponent).join(","));
1283
+ }
1284
+ const path = `/schemas/${encodeURIComponent(databaseId)}${params.size ? `?${params.toString()}` : ""}`;
1285
+ const res = await http.request("GET", path);
1286
+ return normalizeSchemaRevision(res, databaseId);
1287
+ }
1288
+ async getSchemaHistory() {
1289
+ const { http, databaseId } = await this.ensureClient();
1290
+ const path = `/schemas/history/${encodeURIComponent(databaseId)}`;
1291
+ const res = await http.request("GET", path);
1292
+ return Array.isArray(res) ? res.map((entry) => normalizeSchemaRevision(entry, databaseId)) : [];
1293
+ }
1294
+ async updateSchema(schema, options) {
1295
+ const { http, databaseId } = await this.ensureClient();
1296
+ const params = new URLSearchParams();
1297
+ if (options?.publish) params.append("publish", "true");
1298
+ const path = `/schemas/${encodeURIComponent(databaseId)}${params.size ? `?${params.toString()}` : ""}`;
1299
+ const body = stripEntityText({ ...schema, databaseId: schema.databaseId ?? databaseId });
1300
+ const res = await http.request(
1301
+ "PUT",
1302
+ path,
1303
+ serializeDates(body)
1304
+ );
1305
+ return normalizeSchemaRevision(res, databaseId);
1306
+ }
1307
+ async validateSchema(schema) {
1308
+ const { http, databaseId } = await this.ensureClient();
1309
+ const path = `/schemas/${encodeURIComponent(databaseId)}/validate`;
1310
+ const body = stripEntityText({ ...schema, databaseId: schema.databaseId ?? databaseId });
1311
+ const res = await http.request(
1312
+ "POST",
1313
+ path,
1314
+ serializeDates(body)
1315
+ );
1316
+ const normalizedSchema = res.schema ? normalizeSchemaRevision(res.schema, databaseId) : void 0;
1317
+ return {
1318
+ ...res,
1319
+ valid: res.valid ?? true,
1320
+ schema: normalizedSchema
1321
+ };
1322
+ }
1323
+ async listSecrets() {
1324
+ const { http, databaseId } = await this.ensureClient();
1325
+ const path = `/database/${encodeURIComponent(databaseId)}/secret`;
1326
+ const response = await http.request(
1327
+ "GET",
1328
+ path,
1329
+ void 0,
1330
+ { "Content-Type": "application/json" }
1331
+ );
1332
+ const records = (response.records ?? []).map(normalizeSecretMetadata);
1333
+ return {
1334
+ ...response,
1335
+ records,
1336
+ meta: response.meta ?? { totalRecords: records.length }
1337
+ };
1338
+ }
1339
+ async getSecret(key) {
1340
+ const { http, databaseId } = await this.ensureClient();
1341
+ const path = `/database/${encodeURIComponent(databaseId)}/secret/${encodeURIComponent(key)}`;
1342
+ const record = await http.request("GET", path, void 0, {
1343
+ "Content-Type": "application/json"
1344
+ });
1345
+ return normalizeSecretRecord(record);
1346
+ }
1347
+ async putSecret(key, input) {
1348
+ const { http, databaseId } = await this.ensureClient();
1349
+ const path = `/database/${encodeURIComponent(databaseId)}/secret/${encodeURIComponent(key)}`;
1350
+ const response = await http.request("PUT", path, serializeDates(input));
1351
+ return normalizeSecretMetadata(response);
1352
+ }
1353
+ async deleteSecret(key) {
1354
+ const { http, databaseId } = await this.ensureClient();
1355
+ const path = `/database/${encodeURIComponent(databaseId)}/secret/${encodeURIComponent(key)}`;
1356
+ const response = await http.request(
1357
+ "DELETE",
1358
+ path
1359
+ );
1360
+ const deletedKey = response && typeof response === "object" && "key" in response ? response.key : void 0;
1361
+ return { key: deletedKey ?? key };
1362
+ }
1363
+ close() {
1364
+ for (const h of Array.from(this.streams)) {
1365
+ try {
1366
+ h.cancel();
1367
+ } catch {
1368
+ } finally {
1369
+ this.streams.delete(h);
1370
+ }
1371
+ }
1372
+ }
1373
+ /** -------- internal helpers used by builders -------- */
1374
+ async _count(table, select, partition) {
1375
+ const { http, databaseId } = await this.ensureClient();
1376
+ const params = new URLSearchParams();
1377
+ const p = partition ?? this.defaultPartition;
1378
+ if (p) params.append("partition", p);
1379
+ const path = `/data/${encodeURIComponent(databaseId)}/query/count/${encodeURIComponent(
1380
+ table
1381
+ )}${params.toString() ? `?${params.toString()}` : ""}`;
1382
+ return http.request("PUT", path, serializeDates(select));
1383
+ }
1384
+ async _queryPage(table, select, opts = {}) {
1385
+ const { http, databaseId } = await this.ensureClient();
1386
+ const params = new URLSearchParams();
1387
+ if (opts.pageSize != null) params.append("pageSize", String(opts.pageSize));
1388
+ if (opts.nextPage) params.append("nextPage", opts.nextPage);
1389
+ const p = opts.partition ?? this.defaultPartition;
1390
+ if (p) params.append("partition", p);
1391
+ const path = `/data/${encodeURIComponent(databaseId)}/query/${encodeURIComponent(
1392
+ table
1393
+ )}${params.toString() ? `?${params.toString()}` : ""}`;
1394
+ return http.request("PUT", path, serializeDates(select));
1395
+ }
1396
+ async _update(table, update, partition) {
1397
+ const { http, databaseId } = await this.ensureClient();
1398
+ const params = new URLSearchParams();
1399
+ const p = partition ?? this.defaultPartition;
1400
+ if (p) params.append("partition", p);
1401
+ const path = `/data/${encodeURIComponent(databaseId)}/query/update/${encodeURIComponent(
1402
+ table
1403
+ )}${params.toString() ? `?${params.toString()}` : ""}`;
1404
+ return http.request("PUT", path, serializeDates(update));
1405
+ }
1406
+ async _deleteByQuery(table, select, partition) {
1407
+ const { http, databaseId } = await this.ensureClient();
1408
+ const params = new URLSearchParams();
1409
+ const p = partition ?? this.defaultPartition;
1410
+ if (p) params.append("partition", p);
1411
+ const path = `/data/${encodeURIComponent(databaseId)}/query/delete/${encodeURIComponent(
1412
+ table
1413
+ )}${params.toString() ? `?${params.toString()}` : ""}`;
1414
+ return http.request("PUT", path, serializeDates(select));
1415
+ }
1416
+ async _stream(table, select, includeQueryResults, keepAlive, handlers) {
1417
+ const { http, baseUrl, databaseId, fetchImpl } = await this.ensureClient();
1418
+ const params = new URLSearchParams();
1419
+ if (includeQueryResults) params.append("includeQueryResults", "true");
1420
+ if (keepAlive) params.append("keepAlive", "true");
1421
+ const url = `${baseUrl}/data/${encodeURIComponent(databaseId)}/query/stream/${encodeURIComponent(
1422
+ table
1423
+ )}${params.toString() ? `?${params.toString()}` : ""}`;
1424
+ const handle = await openJsonLinesStream(
1425
+ fetchImpl,
1426
+ url,
1427
+ {
1428
+ method: "PUT",
1429
+ headers: http.headers({
1430
+ Accept: "application/x-ndjson",
1431
+ "Content-Type": "application/json"
1432
+ }),
1433
+ body: JSON.stringify(serializeDates(select))
1434
+ },
1435
+ handlers
1436
+ );
1437
+ return this.registerStream(handle);
1438
+ }
1439
+ async _saveInternal(table, entityOrEntities, options) {
1440
+ const { http, databaseId } = await this.ensureClient();
1441
+ const params = new URLSearchParams();
1442
+ if (options?.relationships?.length) {
1443
+ params.append("relationships", options.relationships.map(encodeURIComponent).join(","));
1444
+ }
1445
+ const path = `/data/${encodeURIComponent(databaseId)}/${encodeURIComponent(table)}${params.toString() ? `?${params.toString()}` : ""}`;
1446
+ return http.request("PUT", path, serializeDates(entityOrEntities));
1447
+ }
1448
+ };
1449
+ var QueryBuilderImpl = class {
1450
+ db;
1451
+ table;
1452
+ fields = null;
1453
+ resolvers = null;
1454
+ conditions = null;
1455
+ sort = null;
1456
+ limitValue = null;
1457
+ distinctValue = false;
1458
+ groupByValues = null;
1459
+ partitionValue;
1460
+ pageSizeValue = null;
1461
+ nextPageValue = null;
1462
+ mode = "select";
1463
+ updates = null;
1464
+ onItemAddedListener = null;
1465
+ onItemUpdatedListener = null;
1466
+ onItemDeletedListener = null;
1467
+ onItemListener = null;
1468
+ constructor(db, table, partition) {
1469
+ this.db = db;
1470
+ this.table = table;
1471
+ this.partitionValue = partition;
1472
+ }
1473
+ ensureTable() {
1474
+ if (!this.table) throw new Error("Table is not defined. Call from(<table>) first.");
1475
+ return this.table;
1476
+ }
1477
+ serializableConditions() {
1478
+ return normalizeCondition(this.conditions);
1479
+ }
1480
+ toSelectQuery() {
1481
+ return {
1482
+ type: "SelectQuery",
1483
+ fields: this.fields,
1484
+ conditions: this.serializableConditions(),
1485
+ sort: this.sort,
1486
+ limit: this.limitValue,
1487
+ distinct: this.distinctValue,
1488
+ groupBy: this.groupByValues,
1489
+ partition: this.partitionValue ?? null,
1490
+ resolvers: this.resolvers
1491
+ };
1492
+ }
1493
+ toUpdateQuery() {
1494
+ return {
1495
+ type: "UpdateQuery",
1496
+ conditions: this.serializableConditions(),
1497
+ updates: this.updates ?? {},
1498
+ sort: this.sort,
1499
+ limit: this.limitValue,
1500
+ partition: this.partitionValue ?? null
1501
+ };
1502
+ }
1503
+ toSerializableQueryObject() {
1504
+ const table = this.ensureTable();
1505
+ const payload = this.mode === "update" ? this.toUpdateQuery() : this.toSelectQuery();
1506
+ return { ...payload, table };
1507
+ }
1508
+ from(table) {
1509
+ this.table = table;
1510
+ return this;
1511
+ }
1512
+ select(...fields) {
1513
+ const flat = flattenStrings(fields);
1514
+ this.fields = flat.length > 0 ? flat : null;
1515
+ return this;
1516
+ }
1517
+ resolve(...values) {
1518
+ const flat = flattenStrings(values);
1519
+ this.resolvers = flat.length > 0 ? flat : null;
1520
+ return this;
1521
+ }
1522
+ where(condition) {
1523
+ const c = toCondition(condition);
1524
+ if (!this.conditions) {
1525
+ this.conditions = c;
1526
+ } else {
1527
+ this.conditions = {
1528
+ conditionType: "CompoundCondition",
1529
+ operator: "AND",
1530
+ conditions: [this.conditions, c]
1531
+ };
1532
+ }
1533
+ return this;
1534
+ }
1535
+ and(condition) {
1536
+ const c = toCondition(condition);
1537
+ if (!this.conditions) {
1538
+ this.conditions = c;
1539
+ } else if (this.conditions.conditionType === "CompoundCondition" && this.conditions.operator === "AND") {
1540
+ this.conditions.conditions.push(c);
1541
+ } else {
1542
+ this.conditions = {
1543
+ conditionType: "CompoundCondition",
1544
+ operator: "AND",
1545
+ conditions: [this.conditions, c]
1546
+ };
1547
+ }
1548
+ return this;
1549
+ }
1550
+ or(condition) {
1551
+ const c = toCondition(condition);
1552
+ if (!this.conditions) {
1553
+ this.conditions = c;
1554
+ } else if (this.conditions.conditionType === "CompoundCondition" && this.conditions.operator === "OR") {
1555
+ this.conditions.conditions.push(c);
1556
+ } else {
1557
+ this.conditions = {
1558
+ conditionType: "CompoundCondition",
1559
+ operator: "OR",
1560
+ conditions: [this.conditions, c]
1561
+ };
1562
+ }
1563
+ return this;
1564
+ }
1565
+ orderBy(...sorts) {
1566
+ this.sort = sorts;
1567
+ return this;
1568
+ }
1569
+ groupBy(...fields) {
1570
+ this.groupByValues = fields.length ? fields : null;
1571
+ return this;
1572
+ }
1573
+ distinct() {
1574
+ this.distinctValue = true;
1575
+ return this;
1576
+ }
1577
+ limit(n) {
1578
+ this.limitValue = n;
1579
+ return this;
1580
+ }
1581
+ inPartition(partition) {
1582
+ this.partitionValue = partition;
1583
+ return this;
1584
+ }
1585
+ pageSize(n) {
1586
+ this.pageSizeValue = n;
1587
+ return this;
1588
+ }
1589
+ nextPage(token) {
1590
+ this.nextPageValue = token;
1591
+ return this;
1592
+ }
1593
+ setUpdates(updates) {
1594
+ this.mode = "update";
1595
+ this.updates = updates;
1596
+ return this;
1597
+ }
1598
+ async count() {
1599
+ if (this.mode !== "select") throw new Error("Cannot call count() in update mode.");
1600
+ const table = this.ensureTable();
1601
+ return this.db._count(table, this.toSelectQuery(), this.partitionValue);
1602
+ }
1603
+ async page(options = {}) {
1604
+ if (this.mode !== "select") throw new Error("Cannot call page() in update mode.");
1605
+ const table = this.ensureTable();
1606
+ const final = {
1607
+ pageSize: this.pageSizeValue ?? options.pageSize,
1608
+ nextPage: this.nextPageValue ?? options.nextPage,
1609
+ partition: this.partitionValue
1610
+ };
1611
+ return this.db._queryPage(table, this.toSelectQuery(), final);
1612
+ }
1613
+ list(options = {}) {
1614
+ const size = this.pageSizeValue ?? options.pageSize;
1615
+ const pgPromise = this.page(options).then((pg) => {
1616
+ const fetcher = (token) => this.nextPage(token).list({ pageSize: size });
1617
+ return new QueryResults(Array.isArray(pg.records) ? pg.records : [], pg.nextPage ?? null, fetcher);
1618
+ });
1619
+ for (const m of Object.getOwnPropertyNames(QueryResults.prototype)) {
1620
+ if (m === "constructor") continue;
1621
+ pgPromise[m] = (...args) => pgPromise.then((res) => res[m](...args));
1622
+ }
1623
+ return pgPromise;
1624
+ }
1625
+ async firstOrNull() {
1626
+ if (this.mode !== "select") throw new Error("Cannot call firstOrNull() in update mode.");
1627
+ if (!this.conditions) throw new OnyxError("firstOrNull() requires a where() clause.");
1628
+ this.limitValue = 1;
1629
+ const pg = await this.page();
1630
+ return Array.isArray(pg.records) && pg.records.length > 0 ? pg.records[0] : null;
1631
+ }
1632
+ async one() {
1633
+ return this.firstOrNull();
1634
+ }
1635
+ async delete() {
1636
+ if (this.mode !== "select") throw new Error("delete() is only applicable in select mode.");
1637
+ const table = this.ensureTable();
1638
+ return this.db._deleteByQuery(table, this.toSelectQuery(), this.partitionValue);
1639
+ }
1640
+ async update() {
1641
+ if (this.mode !== "update") throw new Error("Call setUpdates(...) before update().");
1642
+ const table = this.ensureTable();
1643
+ const update = this.toUpdateQuery();
1644
+ return this.db._update(table, update, this.partitionValue);
1645
+ }
1646
+ onItemAdded(listener) {
1647
+ this.onItemAddedListener = listener;
1648
+ return this;
1649
+ }
1650
+ onItemUpdated(listener) {
1651
+ this.onItemUpdatedListener = listener;
1652
+ return this;
1653
+ }
1654
+ onItemDeleted(listener) {
1655
+ this.onItemDeletedListener = listener;
1656
+ return this;
1657
+ }
1658
+ onItem(listener) {
1659
+ this.onItemListener = listener;
1660
+ return this;
1661
+ }
1662
+ async streamEventsOnly(keepAlive = true) {
1663
+ return this.stream(false, keepAlive);
1664
+ }
1665
+ async streamWithQueryResults(keepAlive = false) {
1666
+ return this.stream(true, keepAlive);
1667
+ }
1668
+ async stream(includeQueryResults = true, keepAlive = false) {
1669
+ if (this.mode !== "select") throw new Error("Streaming is only applicable in select mode.");
1670
+ const table = this.ensureTable();
1671
+ return this.db._stream(table, this.toSelectQuery(), includeQueryResults, keepAlive, {
1672
+ onItemAdded: this.onItemAddedListener ?? void 0,
1673
+ onItemUpdated: this.onItemUpdatedListener ?? void 0,
1674
+ onItemDeleted: this.onItemDeletedListener ?? void 0,
1675
+ onItem: this.onItemListener ?? void 0
1676
+ });
1677
+ }
1678
+ };
1679
+ var SaveBuilderImpl = class {
1680
+ db;
1681
+ table;
1682
+ relationships = null;
1683
+ constructor(db, table) {
1684
+ this.db = db;
1685
+ this.table = table;
1686
+ }
1687
+ cascade(...relationships) {
1688
+ this.relationships = relationships.flat();
1689
+ return this;
1690
+ }
1691
+ one(entity) {
1692
+ const opts = this.relationships ? { relationships: this.relationships } : void 0;
1693
+ return this.db._saveInternal(this.table, entity, opts);
1694
+ }
1695
+ many(entities) {
1696
+ const opts = this.relationships ? { relationships: this.relationships } : void 0;
1697
+ return this.db._saveInternal(this.table, entities, opts);
1698
+ }
1699
+ };
1700
+ var CascadeBuilderImpl = class {
1701
+ db;
1702
+ rels = null;
1703
+ constructor(db) {
1704
+ this.db = db;
1705
+ }
1706
+ cascade(...relationships) {
1707
+ this.rels = relationships.flat();
1708
+ return this;
1709
+ }
1710
+ save(table, entityOrEntities) {
1711
+ const opts = this.rels ? { relationships: this.rels } : void 0;
1712
+ return this.db._saveInternal(String(table), entityOrEntities, opts);
1713
+ }
1714
+ delete(table, primaryKey) {
1715
+ const opts = this.rels ? { relationships: this.rels } : void 0;
1716
+ return this.db.delete(table, primaryKey, opts);
1717
+ }
1718
+ };
1719
+ var onyx = {
1720
+ init(config) {
1721
+ return new OnyxDatabaseImpl(config);
1722
+ },
1723
+ clearCacheConfig
1724
+ };
497
1725
 
498
1726
  // gen/generate.ts
499
1727
  var DEFAULT_SCHEMA_PATH = "./onyx.schema.json";
@@ -539,58 +1767,85 @@ async function writeFile(path, data, overwrite) {
539
1767
  function isIntrospection(x) {
540
1768
  return !!x && typeof x === "object" && Array.isArray(x.tables);
541
1769
  }
542
- async function fetchSchemaFromApi(http, databaseId, candidates) {
543
- const defaultCandidates = [
544
- `/schema/${encodeURIComponent(databaseId)}`,
545
- `/data/${encodeURIComponent(databaseId)}/schema`,
546
- `/meta/schema/${encodeURIComponent(databaseId)}`,
547
- `/schema?databaseId=${encodeURIComponent(databaseId)}`,
548
- `/meta/schema?databaseId=${encodeURIComponent(databaseId)}`
549
- ];
550
- const paths = candidates && candidates.length ? candidates : defaultCandidates;
551
- let lastErr;
552
- for (const p of paths) {
553
- try {
554
- const res = await http.request("GET", p);
555
- if (isIntrospection(res)) return res;
556
- } catch (e) {
557
- lastErr = e;
558
- }
1770
+ function normalizeAttributeType(raw) {
1771
+ const t = typeof raw === "string" ? raw : "";
1772
+ switch (t) {
1773
+ case "String":
1774
+ return "String";
1775
+ case "Boolean":
1776
+ return "Boolean";
1777
+ case "Timestamp":
1778
+ return "Timestamp";
1779
+ case "EmbeddedList":
1780
+ return "EmbeddedList";
1781
+ case "EmbeddedObject":
1782
+ return "EmbeddedObject";
1783
+ case "Int":
1784
+ case "Byte":
1785
+ case "Short":
1786
+ case "Float":
1787
+ case "Double":
1788
+ case "Long":
1789
+ return "Int";
1790
+ default:
1791
+ return "EmbeddedObject";
559
1792
  }
560
- const err = lastErr instanceof Error ? lastErr.message : String(lastErr ?? "Unknown error");
561
- throw new Error(
562
- `Unable to fetch schema from API. Tried: ${paths.join(", ")}. Last error: ${err}`
563
- );
1793
+ }
1794
+ function toOnyxIntrospectionFromEntities(entities) {
1795
+ return {
1796
+ tables: entities.map((entity) => ({
1797
+ name: entity.name,
1798
+ attributes: (entity.attributes ?? []).map((attr) => ({
1799
+ name: attr.name,
1800
+ type: normalizeAttributeType(attr.type),
1801
+ isNullable: Boolean(attr.isNullable)
1802
+ }))
1803
+ }))
1804
+ };
1805
+ }
1806
+ function normalizeIntrospection(raw) {
1807
+ if (isIntrospection(raw)) return raw;
1808
+ const entities = raw.entities;
1809
+ if (Array.isArray(entities)) {
1810
+ return toOnyxIntrospectionFromEntities(entities);
1811
+ }
1812
+ throw new Error('Invalid schema: missing "tables" array.');
1813
+ }
1814
+ async function fetchSchemaFromApi(config) {
1815
+ const db = onyx.init({
1816
+ baseUrl: config.baseUrl,
1817
+ databaseId: config.databaseId,
1818
+ apiKey: config.apiKey,
1819
+ apiSecret: config.apiSecret,
1820
+ fetch: config.fetch
1821
+ });
1822
+ const schema = await db.getSchema();
1823
+ return normalizeIntrospection(schema);
564
1824
  }
565
1825
  async function generateTypes(options) {
566
1826
  const path = await import('path');
567
1827
  const opts = { ...DEFAULTS2, ...options ?? {} };
568
1828
  const resolvedSchemaPath = opts.schemaPath ?? (opts.source === "file" ? DEFAULT_SCHEMA_PATH : void 0);
569
- let schema = null;
1829
+ let schemaInput = null;
570
1830
  if (opts.source === "file" || opts.source === "auto" && resolvedSchemaPath) {
571
1831
  if (!resolvedSchemaPath) throw new Error('schemaPath is required when source="file"');
572
1832
  if (!opts.quiet)
573
1833
  process__default.default.stderr.write(`[onyx-gen] reading schema from file: ${resolvedSchemaPath}
574
1834
  `);
575
- schema = await readFileJson(resolvedSchemaPath);
1835
+ schemaInput = await readFileJson(resolvedSchemaPath);
576
1836
  }
577
- if (!schema) {
1837
+ if (!schemaInput) {
578
1838
  if (opts.source === "file") throw new Error("Failed to read schema from file");
579
1839
  const cfg = await resolveConfig({});
580
- const http = new HttpClient({
581
- baseUrl: cfg.baseUrl,
582
- apiKey: cfg.apiKey,
583
- apiSecret: cfg.apiSecret,
584
- fetchImpl: cfg.fetch
585
- });
1840
+ if (!cfg.databaseId) {
1841
+ throw new Error("Missing databaseId. Set ONYX_DATABASE_ID or pass to onyx.init().");
1842
+ }
586
1843
  if (!opts.quiet)
587
1844
  process__default.default.stderr.write(`[onyx-gen] fetching schema from API for db ${cfg.databaseId}
588
1845
  `);
589
- schema = await fetchSchemaFromApi(http, cfg.databaseId, options?.apiPaths);
590
- }
591
- if (!isIntrospection(schema)) {
592
- throw new Error('Invalid schema: missing "tables" array.');
1846
+ schemaInput = await fetchSchemaFromApi(cfg);
593
1847
  }
1848
+ const schema = normalizeIntrospection(schemaInput);
594
1849
  const outTargets = [
595
1850
  ...toArray(opts.typesOutFile),
596
1851
  ...toArray(opts.typesOutDir),