@onyx.dev/onyx-database 0.3.0 → 1.0.2

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.
@@ -78,7 +78,7 @@ function readEnv(targetId) {
78
78
  return res;
79
79
  }
80
80
  async function readProjectFile(databaseId) {
81
- if (!isNode) return {};
81
+ if (!isNode) return { config: {} };
82
82
  const fs = await nodeImport("node:fs/promises");
83
83
  const path2 = await nodeImport("node:path");
84
84
  const cwd = gProcess?.cwd?.() ?? ".";
@@ -87,7 +87,7 @@ async function readProjectFile(databaseId) {
87
87
  const sanitized = txt.replace(/[\r\n]+/g, "");
88
88
  const json = dropUndefined(JSON.parse(sanitized));
89
89
  dbg("project file:", p, "\u2192", mask(json));
90
- return json;
90
+ return { config: json, path: p };
91
91
  };
92
92
  if (databaseId) {
93
93
  const specific = path2.resolve(cwd, `onyx-database-${databaseId}.json`);
@@ -102,11 +102,11 @@ async function readProjectFile(databaseId) {
102
102
  return await tryRead(fallback);
103
103
  } catch {
104
104
  dbg("project file not found:", fallback);
105
- return {};
105
+ return { config: {} };
106
106
  }
107
107
  }
108
108
  async function readHomeProfile(databaseId) {
109
- if (!isNode) return {};
109
+ if (!isNode) return { config: {} };
110
110
  const fs = await nodeImport("node:fs/promises");
111
111
  const os = await nodeImport("node:os");
112
112
  const path2 = await nodeImport("node:path");
@@ -126,7 +126,7 @@ async function readHomeProfile(databaseId) {
126
126
  const sanitized = txt.replace(/[\r\n]+/g, "");
127
127
  const json = dropUndefined(JSON.parse(sanitized));
128
128
  dbg("home profile used:", p, "\u2192", mask(json));
129
- return json;
129
+ return { config: json, path: p };
130
130
  } catch (e) {
131
131
  const msg = e instanceof Error ? e.message : String(e);
132
132
  throw new OnyxConfigError(`Failed to read ${p}: ${msg}`);
@@ -145,7 +145,7 @@ async function readHomeProfile(databaseId) {
145
145
  dbg("no home-root fallback:", defaultInHome);
146
146
  if (!await fileExists(dir)) {
147
147
  dbg("~/.onyx does not exist:", dir);
148
- return {};
148
+ return { config: {} };
149
149
  }
150
150
  const files = await fs.readdir(dir).catch(() => []);
151
151
  const matches = files.filter((f) => f.startsWith("onyx-database-") && f.endsWith(".json"));
@@ -159,10 +159,10 @@ async function readHomeProfile(databaseId) {
159
159
  );
160
160
  }
161
161
  dbg("no usable home profiles found in", dir);
162
- return {};
162
+ return { config: {} };
163
163
  }
164
164
  async function readConfigPath(p) {
165
- if (!isNode) return {};
165
+ if (!isNode) return { config: {} };
166
166
  const fs = await nodeImport("node:fs/promises");
167
167
  const path2 = await nodeImport("node:path");
168
168
  const cwd = gProcess?.cwd?.() ?? ".";
@@ -172,7 +172,7 @@ async function readConfigPath(p) {
172
172
  const sanitized = txt.replace(/[\r\n]+/g, "");
173
173
  const json = dropUndefined(JSON.parse(sanitized));
174
174
  dbg("config path:", resolved, "\u2192", mask(json));
175
- return json;
175
+ return { config: json, path: resolved };
176
176
  } catch (e) {
177
177
  const msg = e instanceof Error ? e.message : String(e);
178
178
  throw new OnyxConfigError(`Failed to read ${resolved}: ${msg}`);
@@ -183,7 +183,8 @@ async function resolveConfig(input) {
183
183
  const env = readEnv(input?.databaseId);
184
184
  let cfgPath = {};
185
185
  if (configPath) {
186
- cfgPath = await readConfigPath(configPath);
186
+ const cfgRes = await readConfigPath(configPath);
187
+ cfgPath = cfgRes.config;
187
188
  }
188
189
  const targetId = input?.databaseId ?? env.databaseId ?? cfgPath.databaseId;
189
190
  let haveDbId = !!(input?.databaseId ?? env.databaseId ?? cfgPath.databaseId);
@@ -191,14 +192,16 @@ async function resolveConfig(input) {
191
192
  let haveApiSecret = !!(input?.apiSecret ?? env.apiSecret ?? cfgPath.apiSecret);
192
193
  let project = {};
193
194
  if (!(haveDbId && haveApiKey && haveApiSecret)) {
194
- project = await readProjectFile(targetId);
195
+ const projRes = await readProjectFile(targetId);
196
+ project = projRes.config;
195
197
  if (project.databaseId) haveDbId = true;
196
198
  if (project.apiKey) haveApiKey = true;
197
199
  if (project.apiSecret) haveApiSecret = true;
198
200
  }
199
201
  let home = {};
200
202
  if (!(haveDbId && haveApiKey && haveApiSecret)) {
201
- home = await readHomeProfile(targetId);
203
+ const homeRes = await readHomeProfile(targetId);
204
+ home = homeRes.config;
202
205
  }
203
206
  const merged = {
204
207
  baseUrl: DEFAULT_BASE_URL,
@@ -249,6 +252,27 @@ async function resolveConfig(input) {
249
252
  dbg("resolved:", mask(resolved));
250
253
  return resolved;
251
254
  }
255
+ async function resolveConfigWithSource(input) {
256
+ const configPathEnv = gProcess?.env?.ONYX_CONFIG_PATH;
257
+ const env = readEnv(input?.databaseId);
258
+ const cfgPathRes = configPathEnv ? await readConfigPath(configPathEnv) : { config: {} };
259
+ const cfgPath = cfgPathRes.config;
260
+ const projectRes = await readProjectFile(env.databaseId ?? cfgPath.databaseId);
261
+ const project = projectRes.config;
262
+ const homeRes = await readHomeProfile(env.databaseId ?? cfgPath.databaseId);
263
+ const home = homeRes.config;
264
+ const base = await resolveConfig(input);
265
+ const sources = {
266
+ baseUrl: input?.baseUrl ? "explicit config" : env.baseUrl ? "env" : cfgPath.baseUrl ? "env ONYX_CONFIG_PATH" : project.baseUrl ? "project file" : home.baseUrl ? "home profile" : "default",
267
+ databaseId: input?.databaseId ? "explicit config" : env.databaseId ? "env" : cfgPath.databaseId ? "env ONYX_CONFIG_PATH" : project.databaseId ? "project file" : home.databaseId ? "home profile" : "unknown",
268
+ apiKey: input?.apiKey ? "explicit config" : env.apiKey ? "env" : cfgPath.apiKey ? "env ONYX_CONFIG_PATH" : project.apiKey ? "project file" : home.apiKey ? "home profile" : "unknown",
269
+ apiSecret: input?.apiSecret ? "explicit config" : env.apiSecret ? "env" : cfgPath.apiSecret ? "env ONYX_CONFIG_PATH" : project.apiSecret ? "project file" : home.apiSecret ? "home profile" : "unknown",
270
+ configPath: cfgPathRes.path,
271
+ projectFile: projectRes.path,
272
+ homeProfile: homeRes.path
273
+ };
274
+ return { ...base, sources };
275
+ }
252
276
  function mask(obj) {
253
277
  if (!obj) return obj;
254
278
  const clone = { ...obj };
@@ -842,6 +866,56 @@ var QueryResults = class extends Array {
842
866
  }
843
867
  };
844
868
 
869
+ // src/helpers/condition-normalizer.ts
870
+ function isQueryBuilderLike(value) {
871
+ return !!value && typeof value.toSerializableQueryObject === "function";
872
+ }
873
+ function normalizeCriteriaValue(value) {
874
+ if (Array.isArray(value)) {
875
+ let changed = false;
876
+ const normalized = value.map((item) => {
877
+ const result = normalizeCriteriaValue(item);
878
+ if (result.changed) changed = true;
879
+ return result.value;
880
+ });
881
+ if (!changed) {
882
+ for (let i = 0; i < normalized.length; i += 1) {
883
+ if (normalized[i] !== value[i]) {
884
+ changed = true;
885
+ break;
886
+ }
887
+ }
888
+ }
889
+ return { value: changed ? normalized : value, changed };
890
+ }
891
+ if (isQueryBuilderLike(value)) {
892
+ return { value: value.toSerializableQueryObject(), changed: true };
893
+ }
894
+ return { value, changed: false };
895
+ }
896
+ function normalizeConditionInternal(condition) {
897
+ if (condition.conditionType === "SingleCondition") {
898
+ const { value, changed: changed2 } = normalizeCriteriaValue(condition.criteria.value);
899
+ if (!changed2) return condition;
900
+ return {
901
+ ...condition,
902
+ criteria: { ...condition.criteria, value }
903
+ };
904
+ }
905
+ let changed = false;
906
+ const normalizedConditions = condition.conditions.map((child) => {
907
+ const normalized = normalizeConditionInternal(child);
908
+ if (normalized !== child) changed = true;
909
+ return normalized;
910
+ });
911
+ if (!changed) return condition;
912
+ return { ...condition, conditions: normalizedConditions };
913
+ }
914
+ function normalizeCondition(condition) {
915
+ if (!condition) return condition;
916
+ return normalizeConditionInternal(condition);
917
+ }
918
+
845
919
  // src/builders/cascade-relationship-builder.ts
846
920
  var CascadeRelationshipBuilder = class {
847
921
  graphName;
@@ -916,6 +990,297 @@ var OnyxError = class extends Error {
916
990
  }
917
991
  };
918
992
 
993
+ // src/helpers/schema-diff.ts
994
+ function mapByName(items) {
995
+ const map = /* @__PURE__ */ new Map();
996
+ for (const item of items ?? []) {
997
+ if (!item?.name) continue;
998
+ map.set(item.name, item);
999
+ }
1000
+ return map;
1001
+ }
1002
+ function normalizeEntities(schema) {
1003
+ if (Array.isArray(schema.entities)) {
1004
+ return schema.entities ?? [];
1005
+ }
1006
+ const tables = schema.tables;
1007
+ if (!Array.isArray(tables)) return [];
1008
+ return tables.map((table) => ({
1009
+ name: table.name,
1010
+ attributes: table.attributes ?? []
1011
+ }));
1012
+ }
1013
+ function normalizePartition(partition) {
1014
+ if (partition == null) return "";
1015
+ const trimmed = partition.trim();
1016
+ return trimmed;
1017
+ }
1018
+ function identifiersEqual(a, b) {
1019
+ if (!a && !b) return true;
1020
+ if (!a || !b) return false;
1021
+ return a.name === b.name && a.generator === b.generator && a.type === b.type;
1022
+ }
1023
+ function diffAttributes(apiAttrs, localAttrs) {
1024
+ const apiMap = mapByName(apiAttrs);
1025
+ const localMap = mapByName(localAttrs);
1026
+ const added = [];
1027
+ const removed = [];
1028
+ const changed = [];
1029
+ for (const [name, local] of localMap.entries()) {
1030
+ if (!apiMap.has(name)) {
1031
+ added.push(local);
1032
+ continue;
1033
+ }
1034
+ const api = apiMap.get(name);
1035
+ const apiNull = Boolean(api.isNullable);
1036
+ const localNull = Boolean(local.isNullable);
1037
+ if (api.type !== local.type || apiNull !== localNull) {
1038
+ changed.push({
1039
+ name,
1040
+ from: { type: api.type, isNullable: apiNull },
1041
+ to: { type: local.type, isNullable: localNull }
1042
+ });
1043
+ }
1044
+ }
1045
+ for (const name of apiMap.keys()) {
1046
+ if (!localMap.has(name)) removed.push(name);
1047
+ }
1048
+ added.sort((a, b) => a.name.localeCompare(b.name));
1049
+ removed.sort();
1050
+ changed.sort((a, b) => a.name.localeCompare(b.name));
1051
+ if (!added.length && !removed.length && !changed.length) return null;
1052
+ return { added, removed, changed };
1053
+ }
1054
+ function diffIndexes(apiIndexes, localIndexes) {
1055
+ const apiMap = mapByName(apiIndexes);
1056
+ const localMap = mapByName(localIndexes);
1057
+ const added = [];
1058
+ const removed = [];
1059
+ const changed = [];
1060
+ for (const [name, local] of localMap.entries()) {
1061
+ if (!apiMap.has(name)) {
1062
+ added.push(local);
1063
+ continue;
1064
+ }
1065
+ const api = apiMap.get(name);
1066
+ const apiType = api.type ?? "DEFAULT";
1067
+ const localType = local.type ?? "DEFAULT";
1068
+ const apiScore = api.minimumScore;
1069
+ const localScore = local.minimumScore;
1070
+ if (apiType !== localType || apiScore !== localScore) {
1071
+ changed.push({ name, from: api, to: local });
1072
+ }
1073
+ }
1074
+ for (const name of apiMap.keys()) {
1075
+ if (!localMap.has(name)) removed.push(name);
1076
+ }
1077
+ added.sort((a, b) => a.name.localeCompare(b.name));
1078
+ removed.sort();
1079
+ changed.sort((a, b) => a.name.localeCompare(b.name));
1080
+ if (!added.length && !removed.length && !changed.length) return null;
1081
+ return { added, removed, changed };
1082
+ }
1083
+ function diffResolvers(apiResolvers, localResolvers) {
1084
+ const apiMap = mapByName(apiResolvers);
1085
+ const localMap = mapByName(localResolvers);
1086
+ const added = [];
1087
+ const removed = [];
1088
+ const changed = [];
1089
+ for (const [name, local] of localMap.entries()) {
1090
+ if (!apiMap.has(name)) {
1091
+ added.push(local);
1092
+ continue;
1093
+ }
1094
+ const api = apiMap.get(name);
1095
+ if (api.resolver !== local.resolver) {
1096
+ changed.push({ name, from: api, to: local });
1097
+ }
1098
+ }
1099
+ for (const name of apiMap.keys()) {
1100
+ if (!localMap.has(name)) removed.push(name);
1101
+ }
1102
+ added.sort((a, b) => a.name.localeCompare(b.name));
1103
+ removed.sort();
1104
+ changed.sort((a, b) => a.name.localeCompare(b.name));
1105
+ if (!added.length && !removed.length && !changed.length) return null;
1106
+ return { added, removed, changed };
1107
+ }
1108
+ function diffTriggers(apiTriggers, localTriggers) {
1109
+ const apiMap = mapByName(apiTriggers);
1110
+ const localMap = mapByName(localTriggers);
1111
+ const added = [];
1112
+ const removed = [];
1113
+ const changed = [];
1114
+ for (const [name, local] of localMap.entries()) {
1115
+ if (!apiMap.has(name)) {
1116
+ added.push(local);
1117
+ continue;
1118
+ }
1119
+ const api = apiMap.get(name);
1120
+ if (api.event !== local.event || api.trigger !== local.trigger) {
1121
+ changed.push({ name, from: api, to: local });
1122
+ }
1123
+ }
1124
+ for (const name of apiMap.keys()) {
1125
+ if (!localMap.has(name)) removed.push(name);
1126
+ }
1127
+ added.sort((a, b) => a.name.localeCompare(b.name));
1128
+ removed.sort();
1129
+ changed.sort((a, b) => a.name.localeCompare(b.name));
1130
+ if (!added.length && !removed.length && !changed.length) return null;
1131
+ return { added, removed, changed };
1132
+ }
1133
+ function computeSchemaDiff(apiSchema, localSchema) {
1134
+ const apiEntities = normalizeEntities(apiSchema);
1135
+ const localEntities = normalizeEntities(localSchema);
1136
+ const apiMap = mapByName(apiEntities);
1137
+ const localMap = mapByName(localEntities);
1138
+ const newTables = [];
1139
+ const removedTables = [];
1140
+ const changedTables = [];
1141
+ for (const [name, localEntity] of localMap.entries()) {
1142
+ if (!apiMap.has(name)) {
1143
+ newTables.push(name);
1144
+ continue;
1145
+ }
1146
+ const apiEntity = apiMap.get(name);
1147
+ const tableDiff = { name };
1148
+ const partitionFrom = normalizePartition(apiEntity.partition);
1149
+ const partitionTo = normalizePartition(localEntity.partition);
1150
+ if (partitionFrom !== partitionTo) {
1151
+ tableDiff.partition = { from: partitionFrom || null, to: partitionTo || null };
1152
+ }
1153
+ if (!identifiersEqual(apiEntity.identifier, localEntity.identifier)) {
1154
+ tableDiff.identifier = {
1155
+ from: apiEntity.identifier ?? null,
1156
+ to: localEntity.identifier ?? null
1157
+ };
1158
+ }
1159
+ const attrs = diffAttributes(apiEntity.attributes, localEntity.attributes);
1160
+ if (attrs) tableDiff.attributes = attrs;
1161
+ const indexes = diffIndexes(apiEntity.indexes, localEntity.indexes);
1162
+ if (indexes) tableDiff.indexes = indexes;
1163
+ const resolvers = diffResolvers(apiEntity.resolvers, localEntity.resolvers);
1164
+ if (resolvers) tableDiff.resolvers = resolvers;
1165
+ const triggers = diffTriggers(apiEntity.triggers, localEntity.triggers);
1166
+ if (triggers) tableDiff.triggers = triggers;
1167
+ const hasChange = tableDiff.partition || tableDiff.identifier || tableDiff.attributes || tableDiff.indexes || tableDiff.resolvers || tableDiff.triggers;
1168
+ if (hasChange) {
1169
+ changedTables.push(tableDiff);
1170
+ }
1171
+ }
1172
+ for (const name of apiMap.keys()) {
1173
+ if (!localMap.has(name)) removedTables.push(name);
1174
+ }
1175
+ newTables.sort();
1176
+ removedTables.sort();
1177
+ changedTables.sort((a, b) => a.name.localeCompare(b.name));
1178
+ return { newTables, removedTables, changedTables };
1179
+ }
1180
+ function isScalar(value) {
1181
+ return value == null || value instanceof Date || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1182
+ }
1183
+ function formatScalar(value) {
1184
+ if (value === null || value === void 0) return "null";
1185
+ if (value instanceof Date) return JSON.stringify(value.toISOString());
1186
+ if (typeof value === "string") return JSON.stringify(value);
1187
+ return String(value);
1188
+ }
1189
+ function isRecord(value) {
1190
+ return !!value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
1191
+ }
1192
+ function toYamlLines(value, indent = 0) {
1193
+ const pad = " ".repeat(indent);
1194
+ if (Array.isArray(value)) {
1195
+ if (!value.length) return [`${pad}[]`];
1196
+ const lines = [];
1197
+ for (const item of value) {
1198
+ if (isScalar(item)) {
1199
+ lines.push(`${pad}- ${formatScalar(item)}`);
1200
+ continue;
1201
+ }
1202
+ const nested = toYamlLines(item, indent + 2);
1203
+ const [first, ...rest] = nested;
1204
+ lines.push(`${pad}- ${first ? first.trimStart() : "{}"}`);
1205
+ for (const line of rest) lines.push(line);
1206
+ }
1207
+ return lines;
1208
+ }
1209
+ if (isRecord(value)) {
1210
+ const entries = Object.entries(value).filter(([, v]) => v !== void 0);
1211
+ if (!entries.length) return [`${pad}{}`];
1212
+ const lines = [];
1213
+ for (const [key, val] of entries) {
1214
+ if (Array.isArray(val) && val.length === 0) {
1215
+ lines.push(`${pad}${key}: []`);
1216
+ continue;
1217
+ }
1218
+ if (isScalar(val)) {
1219
+ lines.push(`${pad}${key}: ${formatScalar(val)}`);
1220
+ } else {
1221
+ lines.push(`${pad}${key}:`);
1222
+ lines.push(...toYamlLines(val, indent + 2));
1223
+ }
1224
+ }
1225
+ return lines;
1226
+ }
1227
+ return [`${pad}${formatScalar(value)}`];
1228
+ }
1229
+ function pruneTableDiff(table) {
1230
+ const pruned = { name: table.name };
1231
+ if (table.partition && table.partition.from !== table.partition.to) {
1232
+ pruned.partition = table.partition;
1233
+ }
1234
+ if (table.identifier && !identifiersEqual(table.identifier.from ?? void 0, table.identifier.to ?? void 0)) {
1235
+ pruned.identifier = table.identifier;
1236
+ }
1237
+ if (table.attributes) {
1238
+ const { added = [], removed = [], changed = [] } = table.attributes;
1239
+ if (added.length || removed.length || changed.length) {
1240
+ pruned.attributes = { added, removed, changed };
1241
+ }
1242
+ }
1243
+ if (table.indexes) {
1244
+ const { added = [], removed = [], changed = [] } = table.indexes;
1245
+ if (added.length || removed.length || changed.length) {
1246
+ pruned.indexes = { added, removed, changed };
1247
+ }
1248
+ }
1249
+ if (table.resolvers) {
1250
+ const { added = [], removed = [], changed = [] } = table.resolvers;
1251
+ if (added.length || removed.length || changed.length) {
1252
+ pruned.resolvers = { added, removed, changed };
1253
+ }
1254
+ }
1255
+ if (table.triggers) {
1256
+ const { added = [], removed = [], changed = [] } = table.triggers;
1257
+ if (added.length || removed.length || changed.length) {
1258
+ pruned.triggers = { added, removed, changed };
1259
+ }
1260
+ }
1261
+ const hasChange = pruned.partition || pruned.identifier || pruned.attributes && (pruned.attributes.added.length || pruned.attributes.removed.length || pruned.attributes.changed.length) || pruned.indexes && (pruned.indexes.added.length || pruned.indexes.removed.length || pruned.indexes.changed.length) || pruned.resolvers && (pruned.resolvers.added.length || pruned.resolvers.removed.length || pruned.resolvers.changed.length) || pruned.triggers && (pruned.triggers.added.length || pruned.triggers.removed.length || pruned.triggers.changed.length);
1262
+ return hasChange ? pruned : null;
1263
+ }
1264
+ function formatSchemaDiff(diff, filePath) {
1265
+ const hasChanges = diff.newTables.length || diff.removedTables.length || diff.changedTables.length;
1266
+ if (!hasChanges) {
1267
+ return `No differences found between API schema and ${filePath ?? "local schema"}.
1268
+ `;
1269
+ }
1270
+ const header = filePath ? `# Diff between API schema and ${filePath}
1271
+ ` : "# Schema diff";
1272
+ const prunedTables = diff.changedTables.map(pruneTableDiff).filter((t) => Boolean(t));
1273
+ const yamlObject = {
1274
+ newTables: [...diff.newTables],
1275
+ removedTables: [...diff.removedTables],
1276
+ changedTables: prunedTables
1277
+ };
1278
+ const bodyLines = toYamlLines(yamlObject);
1279
+ return `${header}
1280
+ ${bodyLines.join("\n")}
1281
+ `;
1282
+ }
1283
+
919
1284
  // src/impl/onyx.ts
920
1285
  var DEFAULT_CACHE_TTL = 5 * 60 * 1e3;
921
1286
  var cachedCfg = null;
@@ -969,6 +1334,10 @@ function serializeDates(value) {
969
1334
  }
970
1335
  return value;
971
1336
  }
1337
+ function stripEntityText(input) {
1338
+ const { entityText, ...rest } = input;
1339
+ return rest;
1340
+ }
972
1341
  function normalizeSecretMetadata(input) {
973
1342
  return { ...input, updatedAt: new Date(input.updatedAt) };
974
1343
  }
@@ -982,7 +1351,20 @@ function normalizeDate(value) {
982
1351
  return Number.isNaN(ts.getTime()) ? void 0 : ts;
983
1352
  }
984
1353
  function normalizeSchemaRevision(input, fallbackDatabaseId) {
985
- const { meta, createdAt, publishedAt, revisionId, ...rest } = input;
1354
+ const {
1355
+ meta,
1356
+ createdAt,
1357
+ publishedAt,
1358
+ revisionId,
1359
+ entityText,
1360
+ databaseId,
1361
+ entities,
1362
+ revisionDescription,
1363
+ ...rest
1364
+ } = input;
1365
+ const dbId = typeof databaseId === "string" ? databaseId : fallbackDatabaseId;
1366
+ const entityList = Array.isArray(entities) ? entities : [];
1367
+ const revisionDesc = typeof revisionDescription === "string" ? revisionDescription : void 0;
986
1368
  const mergedMeta = {
987
1369
  revisionId: meta?.revisionId ?? revisionId,
988
1370
  createdAt: normalizeDate(meta?.createdAt ?? createdAt),
@@ -990,10 +1372,11 @@ function normalizeSchemaRevision(input, fallbackDatabaseId) {
990
1372
  };
991
1373
  const cleanedMeta = mergedMeta.revisionId || mergedMeta.createdAt || mergedMeta.publishedAt ? mergedMeta : void 0;
992
1374
  return {
993
- ...rest,
994
- databaseId: input.databaseId ?? fallbackDatabaseId,
1375
+ databaseId: dbId,
1376
+ revisionDescription: revisionDesc,
1377
+ entities: entityList,
995
1378
  meta: cleanedMeta,
996
- entities: input.entities ?? []
1379
+ ...rest
997
1380
  };
998
1381
  }
999
1382
  var OnyxDatabaseImpl = class {
@@ -1105,7 +1488,8 @@ var OnyxDatabaseImpl = class {
1105
1488
  const path2 = `/data/${encodeURIComponent(databaseId)}/${encodeURIComponent(
1106
1489
  table
1107
1490
  )}/${encodeURIComponent(primaryKey)}${params.toString() ? `?${params.toString()}` : ""}`;
1108
- return http.request("DELETE", path2);
1491
+ await http.request("DELETE", path2);
1492
+ return true;
1109
1493
  }
1110
1494
  async saveDocument(doc) {
1111
1495
  const { http, databaseId } = await this.ensureClient();
@@ -1148,20 +1532,32 @@ var OnyxDatabaseImpl = class {
1148
1532
  const res = await http.request("GET", path2);
1149
1533
  return Array.isArray(res) ? res.map((entry) => normalizeSchemaRevision(entry, databaseId)) : [];
1150
1534
  }
1535
+ async diffSchema(localSchema) {
1536
+ const apiSchema = await this.getSchema();
1537
+ return computeSchemaDiff(apiSchema, localSchema);
1538
+ }
1151
1539
  async updateSchema(schema, options) {
1152
1540
  const { http, databaseId } = await this.ensureClient();
1153
1541
  const params = new URLSearchParams();
1154
1542
  if (options?.publish) params.append("publish", "true");
1155
1543
  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));
1544
+ const body = stripEntityText({ ...schema, databaseId: schema.databaseId ?? databaseId });
1545
+ const res = await http.request(
1546
+ "PUT",
1547
+ path2,
1548
+ serializeDates(body)
1549
+ );
1158
1550
  return normalizeSchemaRevision(res, databaseId);
1159
1551
  }
1160
1552
  async validateSchema(schema) {
1161
1553
  const { http, databaseId } = await this.ensureClient();
1162
1554
  const path2 = `/schemas/${encodeURIComponent(databaseId)}/validate`;
1163
- const body = { ...schema, databaseId: schema.databaseId ?? databaseId };
1164
- const res = await http.request("POST", path2, serializeDates(body));
1555
+ const body = stripEntityText({ ...schema, databaseId: schema.databaseId ?? databaseId });
1556
+ const res = await http.request(
1557
+ "POST",
1558
+ path2,
1559
+ serializeDates(body)
1560
+ );
1165
1561
  const normalizedSchema = res.schema ? normalizeSchemaRevision(res.schema, databaseId) : void 0;
1166
1562
  return {
1167
1563
  ...res,
@@ -1323,11 +1719,14 @@ var QueryBuilderImpl = class {
1323
1719
  if (!this.table) throw new Error("Table is not defined. Call from(<table>) first.");
1324
1720
  return this.table;
1325
1721
  }
1722
+ serializableConditions() {
1723
+ return normalizeCondition(this.conditions);
1724
+ }
1326
1725
  toSelectQuery() {
1327
1726
  return {
1328
1727
  type: "SelectQuery",
1329
1728
  fields: this.fields,
1330
- conditions: this.conditions,
1729
+ conditions: this.serializableConditions(),
1331
1730
  sort: this.sort,
1332
1731
  limit: this.limitValue,
1333
1732
  distinct: this.distinctValue,
@@ -1336,6 +1735,21 @@ var QueryBuilderImpl = class {
1336
1735
  resolvers: this.resolvers
1337
1736
  };
1338
1737
  }
1738
+ toUpdateQuery() {
1739
+ return {
1740
+ type: "UpdateQuery",
1741
+ conditions: this.serializableConditions(),
1742
+ updates: this.updates ?? {},
1743
+ sort: this.sort,
1744
+ limit: this.limitValue,
1745
+ partition: this.partitionValue ?? null
1746
+ };
1747
+ }
1748
+ toSerializableQueryObject() {
1749
+ const table = this.ensureTable();
1750
+ const payload = this.mode === "update" ? this.toUpdateQuery() : this.toSelectQuery();
1751
+ return { ...payload, table };
1752
+ }
1339
1753
  from(table) {
1340
1754
  this.table = table;
1341
1755
  return this;
@@ -1471,14 +1885,7 @@ var QueryBuilderImpl = class {
1471
1885
  async update() {
1472
1886
  if (this.mode !== "update") throw new Error("Call setUpdates(...) before update().");
1473
1887
  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
- };
1888
+ const update = this.toUpdateQuery();
1482
1889
  return this.db._update(table, update, this.partitionValue);
1483
1890
  }
1484
1891
  onItemAdded(listener) {
@@ -1570,10 +1977,13 @@ Usage:
1570
1977
  onyx-schema publish [file]
1571
1978
  onyx-schema get [file] [--tables tableA,tableB]
1572
1979
  onyx-schema validate [file]
1980
+ onyx-schema diff [file]
1981
+ onyx-schema info
1573
1982
 
1574
1983
  Options:
1575
1984
  [file] Path to schema JSON (default: ./onyx.schema.json)
1576
1985
  --tables <list> Comma-separated list of tables to fetch (for get)
1986
+ --print Print the fetched schema to stdout instead of writing a file
1577
1987
  -h, --help Show this help message
1578
1988
  `);
1579
1989
  }
@@ -1585,7 +1995,7 @@ function parseTables(value) {
1585
1995
  function parseArgs(argv) {
1586
1996
  const cmd = (argv[2] ?? "").toLowerCase();
1587
1997
  let command = "help";
1588
- if (cmd === "publish" || cmd === "get" || cmd === "validate") {
1998
+ if (cmd === "publish" || cmd === "get" || cmd === "validate" || cmd === "diff" || cmd === "info") {
1589
1999
  command = cmd;
1590
2000
  }
1591
2001
  let idx = 3;
@@ -1595,6 +2005,7 @@ function parseArgs(argv) {
1595
2005
  idx++;
1596
2006
  }
1597
2007
  let tables;
2008
+ let print = false;
1598
2009
  for (; idx < argv.length; idx++) {
1599
2010
  const arg = argv[idx];
1600
2011
  switch (arg) {
@@ -1602,6 +2013,9 @@ function parseArgs(argv) {
1602
2013
  tables = parseTables(argv[idx + 1]);
1603
2014
  idx++;
1604
2015
  break;
2016
+ case "--print":
2017
+ print = true;
2018
+ break;
1605
2019
  case "-h":
1606
2020
  case "--help":
1607
2021
  command = "help";
@@ -1616,7 +2030,7 @@ function parseArgs(argv) {
1616
2030
  }
1617
2031
  }
1618
2032
  }
1619
- return { command, filePath, tables };
2033
+ return { command, filePath, tables, print };
1620
2034
  }
1621
2035
  async function readFileJson(filePath) {
1622
2036
  const fs = await import('fs/promises');
@@ -1631,10 +2045,10 @@ async function writeFileJson(filePath, data) {
1631
2045
  `;
1632
2046
  await fs.writeFile(resolved, serialized, "utf8");
1633
2047
  }
1634
- async function fetchSchema(filePath, tables) {
2048
+ async function fetchSchema(filePath, tables, print) {
1635
2049
  const db = onyx.init();
1636
2050
  const schema = await db.getSchema({ tables });
1637
- if (tables?.length) {
2051
+ if (tables?.length || print) {
1638
2052
  process__default.default.stdout.write(`${JSON.stringify(schema, null, 2)}
1639
2053
  `);
1640
2054
  return;
@@ -1670,6 +2084,47 @@ ${formatSchemaErrors(result.errors)}`);
1670
2084
  process__default.default.stdout.write(`Schema published for database ${revision.databaseId} from ${filePath}.
1671
2085
  `);
1672
2086
  }
2087
+ async function diffSchema(filePath) {
2088
+ const db = onyx.init();
2089
+ const localSchema = await readFileJson(filePath);
2090
+ const diff = await db.diffSchema(localSchema);
2091
+ const output = formatSchemaDiff(diff, filePath);
2092
+ process__default.default.stdout.write(output);
2093
+ }
2094
+ function maskValue(value) {
2095
+ if (!value) return "";
2096
+ if (value.length <= 4) return value;
2097
+ return `${value.slice(0, 2)}...${value.slice(-2)}`;
2098
+ }
2099
+ function formatSource(source) {
2100
+ if (!source) return "unknown";
2101
+ if (source.includes("env")) return "env";
2102
+ if (source.includes("file") || source.includes("profile") || source.includes("config")) return "file";
2103
+ if (source === "explicit config") return "explicit";
2104
+ return source;
2105
+ }
2106
+ async function printInfo() {
2107
+ const resolved = await resolveConfigWithSource();
2108
+ const db = onyx.init();
2109
+ const cfgFile = resolved.sources.configPath ?? resolved.sources.projectFile ?? resolved.sources.homeProfile ?? "(none)";
2110
+ let connection = "ok";
2111
+ try {
2112
+ await db.getSchema({ tables: [] });
2113
+ } catch (err) {
2114
+ const msg = err instanceof Error ? err.message : String(err);
2115
+ connection = msg;
2116
+ }
2117
+ const lines = [
2118
+ `Database ID: ${resolved.databaseId} (source: ${formatSource(resolved.sources.databaseId)})`,
2119
+ `Base URL : ${resolved.baseUrl} (source: ${formatSource(resolved.sources.baseUrl)})`,
2120
+ `API Key : ${maskValue(resolved.apiKey)} (source: ${formatSource(resolved.sources.apiKey)})`,
2121
+ `API Secret : ${maskValue(resolved.apiSecret)} (source: ${formatSource(resolved.sources.apiSecret)})`,
2122
+ `Config file: ${cfgFile}`,
2123
+ `Connection : ${connection}`
2124
+ ];
2125
+ process__default.default.stdout.write(`${lines.join("\n")}
2126
+ `);
2127
+ }
1673
2128
  (async () => {
1674
2129
  try {
1675
2130
  const parsed = parseArgs(process__default.default.argv);
@@ -1678,11 +2133,17 @@ ${formatSchemaErrors(result.errors)}`);
1678
2133
  await publishSchema(parsed.filePath);
1679
2134
  break;
1680
2135
  case "get":
1681
- await fetchSchema(parsed.filePath, parsed.tables);
2136
+ await fetchSchema(parsed.filePath, parsed.tables, parsed.print);
1682
2137
  break;
1683
2138
  case "validate":
1684
2139
  await validateSchema(parsed.filePath);
1685
2140
  break;
2141
+ case "diff":
2142
+ await diffSchema(parsed.filePath);
2143
+ break;
2144
+ case "info":
2145
+ await printInfo();
2146
+ break;
1686
2147
  default:
1687
2148
  printHelp();
1688
2149
  return;