@onyx.dev/onyx-database 0.3.0 → 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.
@@ -842,6 +842,56 @@ var QueryResults = class extends Array {
842
842
  }
843
843
  };
844
844
 
845
+ // src/helpers/condition-normalizer.ts
846
+ function isQueryBuilderLike(value) {
847
+ return !!value && typeof value.toSerializableQueryObject === "function";
848
+ }
849
+ function normalizeCriteriaValue(value) {
850
+ if (Array.isArray(value)) {
851
+ let changed = false;
852
+ const normalized = value.map((item) => {
853
+ const result = normalizeCriteriaValue(item);
854
+ if (result.changed) changed = true;
855
+ return result.value;
856
+ });
857
+ if (!changed) {
858
+ for (let i = 0; i < normalized.length; i += 1) {
859
+ if (normalized[i] !== value[i]) {
860
+ changed = true;
861
+ break;
862
+ }
863
+ }
864
+ }
865
+ return { value: changed ? normalized : value, changed };
866
+ }
867
+ if (isQueryBuilderLike(value)) {
868
+ return { value: value.toSerializableQueryObject(), changed: true };
869
+ }
870
+ return { value, changed: false };
871
+ }
872
+ function normalizeConditionInternal(condition) {
873
+ if (condition.conditionType === "SingleCondition") {
874
+ const { value, changed: changed2 } = normalizeCriteriaValue(condition.criteria.value);
875
+ if (!changed2) return condition;
876
+ return {
877
+ ...condition,
878
+ criteria: { ...condition.criteria, value }
879
+ };
880
+ }
881
+ let changed = false;
882
+ const normalizedConditions = condition.conditions.map((child) => {
883
+ const normalized = normalizeConditionInternal(child);
884
+ if (normalized !== child) changed = true;
885
+ return normalized;
886
+ });
887
+ if (!changed) return condition;
888
+ return { ...condition, conditions: normalizedConditions };
889
+ }
890
+ function normalizeCondition(condition) {
891
+ if (!condition) return condition;
892
+ return normalizeConditionInternal(condition);
893
+ }
894
+
845
895
  // src/builders/cascade-relationship-builder.ts
846
896
  var CascadeRelationshipBuilder = class {
847
897
  graphName;
@@ -969,6 +1019,10 @@ function serializeDates(value) {
969
1019
  }
970
1020
  return value;
971
1021
  }
1022
+ function stripEntityText(input) {
1023
+ const { entityText, ...rest } = input;
1024
+ return rest;
1025
+ }
972
1026
  function normalizeSecretMetadata(input) {
973
1027
  return { ...input, updatedAt: new Date(input.updatedAt) };
974
1028
  }
@@ -982,7 +1036,7 @@ function normalizeDate(value) {
982
1036
  return Number.isNaN(ts.getTime()) ? void 0 : ts;
983
1037
  }
984
1038
  function normalizeSchemaRevision(input, fallbackDatabaseId) {
985
- const { meta, createdAt, publishedAt, revisionId, ...rest } = input;
1039
+ const { meta, createdAt, publishedAt, revisionId, entityText, ...rest } = input;
986
1040
  const mergedMeta = {
987
1041
  revisionId: meta?.revisionId ?? revisionId,
988
1042
  createdAt: normalizeDate(meta?.createdAt ?? createdAt),
@@ -1153,15 +1207,23 @@ var OnyxDatabaseImpl = class {
1153
1207
  const params = new URLSearchParams();
1154
1208
  if (options?.publish) params.append("publish", "true");
1155
1209
  const path2 = `/schemas/${encodeURIComponent(databaseId)}${params.size ? `?${params.toString()}` : ""}`;
1156
- const body = { ...schema, databaseId: schema.databaseId ?? databaseId };
1157
- const res = await http.request("PUT", path2, serializeDates(body));
1210
+ const body = stripEntityText({ ...schema, databaseId: schema.databaseId ?? databaseId });
1211
+ const res = await http.request(
1212
+ "PUT",
1213
+ path2,
1214
+ serializeDates(body)
1215
+ );
1158
1216
  return normalizeSchemaRevision(res, databaseId);
1159
1217
  }
1160
1218
  async validateSchema(schema) {
1161
1219
  const { http, databaseId } = await this.ensureClient();
1162
1220
  const path2 = `/schemas/${encodeURIComponent(databaseId)}/validate`;
1163
- const body = { ...schema, databaseId: schema.databaseId ?? databaseId };
1164
- const res = await http.request("POST", path2, serializeDates(body));
1221
+ const body = stripEntityText({ ...schema, databaseId: schema.databaseId ?? databaseId });
1222
+ const res = await http.request(
1223
+ "POST",
1224
+ path2,
1225
+ serializeDates(body)
1226
+ );
1165
1227
  const normalizedSchema = res.schema ? normalizeSchemaRevision(res.schema, databaseId) : void 0;
1166
1228
  return {
1167
1229
  ...res,
@@ -1323,11 +1385,14 @@ var QueryBuilderImpl = class {
1323
1385
  if (!this.table) throw new Error("Table is not defined. Call from(<table>) first.");
1324
1386
  return this.table;
1325
1387
  }
1388
+ serializableConditions() {
1389
+ return normalizeCondition(this.conditions);
1390
+ }
1326
1391
  toSelectQuery() {
1327
1392
  return {
1328
1393
  type: "SelectQuery",
1329
1394
  fields: this.fields,
1330
- conditions: this.conditions,
1395
+ conditions: this.serializableConditions(),
1331
1396
  sort: this.sort,
1332
1397
  limit: this.limitValue,
1333
1398
  distinct: this.distinctValue,
@@ -1336,6 +1401,21 @@ var QueryBuilderImpl = class {
1336
1401
  resolvers: this.resolvers
1337
1402
  };
1338
1403
  }
1404
+ toUpdateQuery() {
1405
+ return {
1406
+ type: "UpdateQuery",
1407
+ conditions: this.serializableConditions(),
1408
+ updates: this.updates ?? {},
1409
+ sort: this.sort,
1410
+ limit: this.limitValue,
1411
+ partition: this.partitionValue ?? null
1412
+ };
1413
+ }
1414
+ toSerializableQueryObject() {
1415
+ const table = this.ensureTable();
1416
+ const payload = this.mode === "update" ? this.toUpdateQuery() : this.toSelectQuery();
1417
+ return { ...payload, table };
1418
+ }
1339
1419
  from(table) {
1340
1420
  this.table = table;
1341
1421
  return this;
@@ -1471,14 +1551,7 @@ var QueryBuilderImpl = class {
1471
1551
  async update() {
1472
1552
  if (this.mode !== "update") throw new Error("Call setUpdates(...) before update().");
1473
1553
  const table = this.ensureTable();
1474
- const update = {
1475
- type: "UpdateQuery",
1476
- conditions: this.conditions,
1477
- updates: this.updates ?? {},
1478
- sort: this.sort,
1479
- limit: this.limitValue,
1480
- partition: this.partitionValue ?? null
1481
- };
1554
+ const update = this.toUpdateQuery();
1482
1555
  return this.db._update(table, update, this.partitionValue);
1483
1556
  }
1484
1557
  onItemAdded(listener) {
@@ -1561,6 +1634,212 @@ var onyx = {
1561
1634
  clearCacheConfig
1562
1635
  };
1563
1636
 
1637
+ // schema/cli/diff.ts
1638
+ function mapByName(items) {
1639
+ const map = /* @__PURE__ */ new Map();
1640
+ for (const item of items ?? []) {
1641
+ if (!item?.name) continue;
1642
+ map.set(item.name, item);
1643
+ }
1644
+ return map;
1645
+ }
1646
+ function normalizeEntities(schema) {
1647
+ if (Array.isArray(schema.entities)) {
1648
+ return schema.entities ?? [];
1649
+ }
1650
+ const tables = schema.tables;
1651
+ if (!Array.isArray(tables)) return [];
1652
+ return tables.map((table) => ({
1653
+ name: table.name,
1654
+ attributes: table.attributes ?? []
1655
+ }));
1656
+ }
1657
+ function formatIdentifier(id) {
1658
+ if (!id) return "";
1659
+ const parts = [id.name ? `name=${id.name}` : null, id.generator ? `generator=${id.generator}` : null, id.type ? `type=${id.type}` : null].filter(Boolean);
1660
+ return parts.join(", ");
1661
+ }
1662
+ function describeIdentifierChange(api, local) {
1663
+ if (!api && !local) return null;
1664
+ if (!api && local) return "identifier removed";
1665
+ if (api && !local) return "identifier added";
1666
+ if (!api || !local) return null;
1667
+ if (api.name === local.name && api.generator === local.generator && api.type === local.type) {
1668
+ return null;
1669
+ }
1670
+ return `identifier: ${formatIdentifier(api) || "none"} -> ${formatIdentifier(local) || "none"}`;
1671
+ }
1672
+ function describePartitionChange(api, local) {
1673
+ const norm = (v) => v == null || v === "" ? "" : v;
1674
+ if (norm(api) === norm(local)) return null;
1675
+ return `partition: ${norm(api) || "none"} -> ${norm(local) || "none"}`;
1676
+ }
1677
+ function describeAttribute(attr) {
1678
+ const nullable = attr.isNullable ? "true" : "false";
1679
+ return `${attr.name} (${attr.type ?? "unknown"}, nullable: ${nullable})`;
1680
+ }
1681
+ function describeAttributeChange(api, local) {
1682
+ const diffs = [];
1683
+ if (api.type !== local.type) {
1684
+ diffs.push(`type ${api.type ?? "unknown"} -> ${local.type ?? "unknown"}`);
1685
+ }
1686
+ const apiNull = Boolean(api.isNullable);
1687
+ const localNull = Boolean(local.isNullable);
1688
+ if (apiNull !== localNull) {
1689
+ diffs.push(`nullable ${apiNull} -> ${localNull}`);
1690
+ }
1691
+ if (!diffs.length) return null;
1692
+ return `${local.name}: ${diffs.join("; ")}`;
1693
+ }
1694
+ function describeIndex(idx) {
1695
+ const parts = [`type: ${idx.type ?? "DEFAULT"}`];
1696
+ if (idx.minimumScore != null) parts.push(`minScore: ${idx.minimumScore}`);
1697
+ return `${idx.name} (${parts.join(", ")})`;
1698
+ }
1699
+ function describeIndexChange(api, local) {
1700
+ const diffs = [];
1701
+ if (api.type !== local.type) {
1702
+ diffs.push(`type ${api.type ?? "DEFAULT"} -> ${local.type ?? "DEFAULT"}`);
1703
+ }
1704
+ if (api.minimumScore !== local.minimumScore) {
1705
+ diffs.push(`minScore ${api.minimumScore ?? "none"} -> ${local.minimumScore ?? "none"}`);
1706
+ }
1707
+ if (!diffs.length) return null;
1708
+ return `${local.name}: ${diffs.join("; ")}`;
1709
+ }
1710
+ function describeResolverChange(api, local) {
1711
+ if (api.resolver === local.resolver) return null;
1712
+ return `${local.name}: resolver changed`;
1713
+ }
1714
+ function describeTrigger(trigger) {
1715
+ return `${trigger.name} (${trigger.event})`;
1716
+ }
1717
+ function describeTriggerChange(api, local) {
1718
+ const diffs = [];
1719
+ if (api.event !== local.event) {
1720
+ diffs.push(`event ${api.event ?? "none"} -> ${local.event ?? "none"}`);
1721
+ }
1722
+ if (api.trigger !== local.trigger) {
1723
+ diffs.push("trigger changed");
1724
+ }
1725
+ if (!diffs.length) return null;
1726
+ return `${local.name}: ${diffs.join("; ")}`;
1727
+ }
1728
+ function diffCollections(apiItems, localItems, describeAdd, describeChange) {
1729
+ const apiMap = mapByName(apiItems);
1730
+ const localMap = mapByName(localItems);
1731
+ const added = [];
1732
+ const removed = [];
1733
+ const changed = [];
1734
+ for (const [name, local] of localMap.entries()) {
1735
+ if (!apiMap.has(name)) {
1736
+ added.push(describeAdd(local));
1737
+ continue;
1738
+ }
1739
+ if (describeChange) {
1740
+ const detail = describeChange(apiMap.get(name), local);
1741
+ if (detail) changed.push(detail);
1742
+ }
1743
+ }
1744
+ for (const name of apiMap.keys()) {
1745
+ if (!localMap.has(name)) removed.push(name);
1746
+ }
1747
+ return { added, removed, changed };
1748
+ }
1749
+ function computeSchemaDiff(apiSchema, localSchema) {
1750
+ const apiEntities = normalizeEntities(apiSchema);
1751
+ const localEntities = normalizeEntities(localSchema);
1752
+ const apiMap = mapByName(apiEntities);
1753
+ const localMap = mapByName(localEntities);
1754
+ const newTables = [];
1755
+ const removedTables = [];
1756
+ const changedTables = [];
1757
+ for (const [name, localEntity] of localMap.entries()) {
1758
+ if (!apiMap.has(name)) {
1759
+ newTables.push(name);
1760
+ continue;
1761
+ }
1762
+ const apiEntity = apiMap.get(name);
1763
+ const details = [];
1764
+ const partitionChange = describePartitionChange(apiEntity.partition, localEntity.partition);
1765
+ if (partitionChange) details.push(partitionChange);
1766
+ const idChange = describeIdentifierChange(apiEntity.identifier, localEntity.identifier);
1767
+ if (idChange) details.push(idChange);
1768
+ const attrDiff = diffCollections(apiEntity.attributes, localEntity.attributes, (attr) => `+ ${describeAttribute(attr)}`, describeAttributeChange);
1769
+ if (attrDiff.added.length || attrDiff.removed.length || attrDiff.changed.length) {
1770
+ details.push("attributes:");
1771
+ for (const a of attrDiff.added) details.push(` ${a}`);
1772
+ for (const c of attrDiff.changed) details.push(` ~ ${c}`);
1773
+ for (const r of attrDiff.removed) details.push(` - ${r}`);
1774
+ }
1775
+ const idxDiff = diffCollections(apiEntity.indexes, localEntity.indexes, (idx) => `+ ${describeIndex(idx)}`, describeIndexChange);
1776
+ if (idxDiff.added.length || idxDiff.removed.length || idxDiff.changed.length) {
1777
+ details.push("indexes:");
1778
+ for (const a of idxDiff.added) details.push(` ${a}`);
1779
+ for (const c of idxDiff.changed) details.push(` ~ ${c}`);
1780
+ for (const r of idxDiff.removed) details.push(` - ${r}`);
1781
+ }
1782
+ const resolverDiff = diffCollections(apiEntity.resolvers, localEntity.resolvers, (resolver) => `+ ${resolver.name}`, describeResolverChange);
1783
+ if (resolverDiff.added.length || resolverDiff.removed.length || resolverDiff.changed.length) {
1784
+ details.push("resolvers:");
1785
+ for (const a of resolverDiff.added) details.push(` ${a}`);
1786
+ for (const c of resolverDiff.changed) details.push(` ~ ${c}`);
1787
+ for (const r of resolverDiff.removed) details.push(` - ${r}`);
1788
+ }
1789
+ const triggerDiff = diffCollections(apiEntity.triggers, localEntity.triggers, (trigger) => `+ ${describeTrigger(trigger)}`, describeTriggerChange);
1790
+ if (triggerDiff.added.length || triggerDiff.removed.length || triggerDiff.changed.length) {
1791
+ details.push("triggers:");
1792
+ for (const a of triggerDiff.added) details.push(` ${a}`);
1793
+ for (const c of triggerDiff.changed) details.push(` ~ ${c}`);
1794
+ for (const r of triggerDiff.removed) details.push(` - ${r}`);
1795
+ }
1796
+ if (details.length) {
1797
+ changedTables.push({ name, details });
1798
+ }
1799
+ }
1800
+ for (const name of apiMap.keys()) {
1801
+ if (!localMap.has(name)) removedTables.push(name);
1802
+ }
1803
+ newTables.sort();
1804
+ removedTables.sort();
1805
+ changedTables.sort((a, b) => a.name.localeCompare(b.name));
1806
+ return { newTables, removedTables, changedTables };
1807
+ }
1808
+ function formatSchemaDiff(diff, filePath) {
1809
+ const lines = [];
1810
+ const hasChanges = diff.newTables.length || diff.removedTables.length || diff.changedTables.length;
1811
+ if (!hasChanges) {
1812
+ return `No differences found between API schema and ${filePath ?? "local file"}.
1813
+ `;
1814
+ }
1815
+ if (filePath) lines.push(`Comparing API schema to ${filePath}`);
1816
+ lines.push("New Tables:");
1817
+ if (diff.newTables.length) {
1818
+ for (const t of diff.newTables) lines.push(` ${t}`);
1819
+ } else {
1820
+ lines.push(" (none)");
1821
+ }
1822
+ lines.push("Removed Tables:");
1823
+ if (diff.removedTables.length) {
1824
+ for (const t of diff.removedTables) lines.push(` ${t}`);
1825
+ } else {
1826
+ lines.push(" (none)");
1827
+ }
1828
+ lines.push("Changes:");
1829
+ if (diff.changedTables.length) {
1830
+ for (const table of diff.changedTables) {
1831
+ lines.push(` ${table.name}`);
1832
+ for (const detail of table.details) {
1833
+ lines.push(` ${detail}`);
1834
+ }
1835
+ }
1836
+ } else {
1837
+ lines.push(" (none)");
1838
+ }
1839
+ return `${lines.join("\n")}
1840
+ `;
1841
+ }
1842
+
1564
1843
  // schema/cli/schema.ts
1565
1844
  var DEFAULT_SCHEMA_PATH = "./onyx.schema.json";
1566
1845
  function printHelp() {
@@ -1570,6 +1849,7 @@ Usage:
1570
1849
  onyx-schema publish [file]
1571
1850
  onyx-schema get [file] [--tables tableA,tableB]
1572
1851
  onyx-schema validate [file]
1852
+ onyx-schema diff [file]
1573
1853
 
1574
1854
  Options:
1575
1855
  [file] Path to schema JSON (default: ./onyx.schema.json)
@@ -1585,7 +1865,7 @@ function parseTables(value) {
1585
1865
  function parseArgs(argv) {
1586
1866
  const cmd = (argv[2] ?? "").toLowerCase();
1587
1867
  let command = "help";
1588
- if (cmd === "publish" || cmd === "get" || cmd === "validate") {
1868
+ if (cmd === "publish" || cmd === "get" || cmd === "validate" || cmd === "diff") {
1589
1869
  command = cmd;
1590
1870
  }
1591
1871
  let idx = 3;
@@ -1670,6 +1950,16 @@ ${formatSchemaErrors(result.errors)}`);
1670
1950
  process__default.default.stdout.write(`Schema published for database ${revision.databaseId} from ${filePath}.
1671
1951
  `);
1672
1952
  }
1953
+ async function diffSchema(filePath) {
1954
+ const db = onyx.init();
1955
+ const [apiSchema, localSchema] = await Promise.all([
1956
+ db.getSchema(),
1957
+ readFileJson(filePath)
1958
+ ]);
1959
+ const diff = computeSchemaDiff(apiSchema, localSchema);
1960
+ const output = formatSchemaDiff(diff, filePath);
1961
+ process__default.default.stdout.write(output);
1962
+ }
1673
1963
  (async () => {
1674
1964
  try {
1675
1965
  const parsed = parseArgs(process__default.default.argv);
@@ -1683,6 +1973,9 @@ ${formatSchemaErrors(result.errors)}`);
1683
1973
  case "validate":
1684
1974
  await validateSchema(parsed.filePath);
1685
1975
  break;
1976
+ case "diff":
1977
+ await diffSchema(parsed.filePath);
1978
+ break;
1686
1979
  default:
1687
1980
  printHelp();
1688
1981
  return;