@onyx.dev/onyx-database 2.3.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/edge.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // package.json
4
4
  var name = "@onyx.dev/onyx-database";
5
- var version = "2.3.0";
5
+ var version = "2.4.1";
6
6
 
7
7
  // src/config/defaults.ts
8
8
  var DEFAULT_BASE_URL = "https://api.onyx.dev";
@@ -1046,6 +1046,374 @@ function computeSchemaDiff(apiSchema, localSchema) {
1046
1046
  return { newTables, removedTables, changedTables };
1047
1047
  }
1048
1048
 
1049
+ // src/helpers/query-formatters.ts
1050
+ var DEFAULT_TABLE_OPTIONS = {
1051
+ headers: true,
1052
+ maxColumnWidth: 80,
1053
+ flattenNestedObjects: false,
1054
+ nestedSeparator: ".",
1055
+ nullValue: ""
1056
+ };
1057
+ var DEFAULT_TREE_OPTIONS = {
1058
+ rootLabel: "results",
1059
+ keyField: "",
1060
+ includeRoot: true,
1061
+ maxDepth: Number.POSITIVE_INFINITY,
1062
+ nullValue: ""
1063
+ };
1064
+ var DEFAULT_CSV_OPTIONS = {
1065
+ headers: true,
1066
+ delimiter: ",",
1067
+ quote: '"',
1068
+ escape: '"',
1069
+ newline: "\n",
1070
+ flattenNestedObjects: true,
1071
+ nestedSeparator: ".",
1072
+ nullValue: ""
1073
+ };
1074
+ var DEFAULT_JSON_OPTIONS = {
1075
+ pretty: true,
1076
+ indent: 2
1077
+ };
1078
+ function isDate(value) {
1079
+ return value instanceof Date;
1080
+ }
1081
+ function isRecord(value) {
1082
+ return value !== null && typeof value === "object" && !Array.isArray(value) && !isDate(value);
1083
+ }
1084
+ function normalizeRecord(value) {
1085
+ if (isRecord(value)) {
1086
+ return value;
1087
+ }
1088
+ return { value };
1089
+ }
1090
+ function normalizeScalar(value, nullValue) {
1091
+ if (value == null) return nullValue;
1092
+ if (isDate(value)) return value.toISOString();
1093
+ if (typeof value === "string") return value;
1094
+ if (typeof value === "bigint") return value.toString();
1095
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
1096
+ return JSON.stringify(value);
1097
+ }
1098
+ function inlineValue(value, nullValue) {
1099
+ if (value == null) return nullValue;
1100
+ if (isDate(value)) return value.toISOString();
1101
+ if (Array.isArray(value)) {
1102
+ return value.length === 0 ? "[]" : `[${value.map((entry) => inlineValue(entry, nullValue)).join(", ")}]`;
1103
+ }
1104
+ if (isRecord(value)) {
1105
+ const entries = Object.entries(value);
1106
+ if (entries.length === 0) return "{}";
1107
+ return entries.map(([key, entry]) => `${key}=${inlineValue(entry, nullValue)}`).join(", ");
1108
+ }
1109
+ if (typeof value === "bigint") return value.toString();
1110
+ return String(value);
1111
+ }
1112
+ function escapeTableCell(value) {
1113
+ return value.replace(/\r/g, "\\r").replace(/\n/g, "\\n");
1114
+ }
1115
+ function truncate(value, maxWidth) {
1116
+ if (maxWidth < 1) return "";
1117
+ if (value.length <= maxWidth) return value;
1118
+ if (maxWidth <= 3) return ".".repeat(maxWidth);
1119
+ return `${value.slice(0, Math.max(0, maxWidth - 3))}...`;
1120
+ }
1121
+ function padRight(value, width) {
1122
+ if (value.length >= width) return value;
1123
+ return value + " ".repeat(width - value.length);
1124
+ }
1125
+ function flattenValue(value, separator, prefix, out) {
1126
+ if (isRecord(value)) {
1127
+ const entries = Object.entries(value);
1128
+ if (entries.length === 0) {
1129
+ out[prefix] = {};
1130
+ return;
1131
+ }
1132
+ for (const [key, entry] of entries) {
1133
+ const path = prefix ? `${prefix}${separator}${key}` : key;
1134
+ flattenValue(entry, separator, path, out);
1135
+ }
1136
+ return;
1137
+ }
1138
+ if (Array.isArray(value)) {
1139
+ if (value.length === 0) {
1140
+ out[prefix] = [];
1141
+ return;
1142
+ }
1143
+ value.forEach((entry, index) => {
1144
+ const path = prefix ? `${prefix}${separator}${index}` : String(index);
1145
+ flattenValue(entry, separator, path, out);
1146
+ });
1147
+ return;
1148
+ }
1149
+ out[prefix] = value;
1150
+ }
1151
+ function toFlatRow(record, separator) {
1152
+ const out = {};
1153
+ for (const [key, value] of Object.entries(record)) {
1154
+ flattenValue(value, separator, key, out);
1155
+ }
1156
+ return out;
1157
+ }
1158
+ function discoveredColumns(rows) {
1159
+ const columns = [];
1160
+ const seen = /* @__PURE__ */ new Set();
1161
+ for (const row of rows) {
1162
+ for (const key of Object.keys(row)) {
1163
+ if (!seen.has(key)) {
1164
+ seen.add(key);
1165
+ columns.push(key);
1166
+ }
1167
+ }
1168
+ }
1169
+ return columns;
1170
+ }
1171
+ function orderColumns(discovered, preferred, flattenNestedObjects, separator) {
1172
+ if (!preferred || preferred.length === 0) return discovered;
1173
+ const ordered = [];
1174
+ const seen = /* @__PURE__ */ new Set();
1175
+ const addColumn = (column) => {
1176
+ if (!seen.has(column)) {
1177
+ seen.add(column);
1178
+ ordered.push(column);
1179
+ }
1180
+ };
1181
+ for (const field of preferred) {
1182
+ if (flattenNestedObjects) {
1183
+ let matched = false;
1184
+ for (const column of discovered) {
1185
+ if (column === field || column.startsWith(`${field}${separator}`)) {
1186
+ addColumn(column);
1187
+ matched = true;
1188
+ }
1189
+ }
1190
+ if (!matched) {
1191
+ addColumn(field);
1192
+ }
1193
+ continue;
1194
+ }
1195
+ addColumn(field);
1196
+ }
1197
+ for (const column of discovered) {
1198
+ addColumn(column);
1199
+ }
1200
+ return ordered;
1201
+ }
1202
+ function normalizeTableRows(records, preferredColumns, options) {
1203
+ const normalized = records.map(normalizeRecord);
1204
+ if (options.flattenNestedObjects) {
1205
+ const flatRows = normalized.map((record) => toFlatRow(record, options.nestedSeparator));
1206
+ const columns2 = orderColumns(
1207
+ discoveredColumns(flatRows),
1208
+ preferredColumns,
1209
+ true,
1210
+ options.nestedSeparator
1211
+ );
1212
+ const rows2 = flatRows.map((row) => columns2.map((column) => escapeTableCell(truncate(
1213
+ inlineValue(row[column], options.nullValue),
1214
+ options.maxColumnWidth
1215
+ ))));
1216
+ return { columns: columns2, rows: rows2 };
1217
+ }
1218
+ const topLevelRows = normalized.map((record) => ({ ...record }));
1219
+ const columns = orderColumns(
1220
+ discoveredColumns(topLevelRows),
1221
+ preferredColumns,
1222
+ false,
1223
+ options.nestedSeparator
1224
+ );
1225
+ const rows = topLevelRows.map((row) => columns.map((column) => escapeTableCell(truncate(
1226
+ inlineValue(row[column], options.nullValue),
1227
+ options.maxColumnWidth
1228
+ ))));
1229
+ return { columns, rows };
1230
+ }
1231
+ function formatQueryResultsAsTable(records, options, preferredColumns) {
1232
+ const final = { ...DEFAULT_TABLE_OPTIONS, ...options };
1233
+ const { columns, rows } = normalizeTableRows(records, preferredColumns, final);
1234
+ if (columns.length === 0) return "";
1235
+ const headerCells = columns.map((column) => truncate(column, final.maxColumnWidth));
1236
+ const widths = columns.map((_, index) => {
1237
+ const headerWidth = final.headers ? headerCells[index].length : 0;
1238
+ const rowWidth = rows.reduce((max2, row) => Math.max(max2, row[index].length), 0);
1239
+ return Math.max(headerWidth, rowWidth);
1240
+ });
1241
+ const border = (left, join, right) => `${left}${widths.map((width) => "\u2500".repeat(width + 2)).join(join)}${right}`;
1242
+ const renderRow = (cells) => `\u2502 ${cells.map((cell, index) => padRight(cell, widths[index])).join(" \u2502 ")} \u2502`;
1243
+ const lines = [border("\u250C", "\u252C", "\u2510")];
1244
+ if (final.headers) {
1245
+ lines.push(renderRow(headerCells));
1246
+ lines.push(border("\u251C", "\u253C", "\u2524"));
1247
+ }
1248
+ for (const row of rows) {
1249
+ lines.push(renderRow(row));
1250
+ }
1251
+ lines.push(border("\u2514", "\u2534", "\u2518"));
1252
+ return lines.join("\n");
1253
+ }
1254
+ function encodeCsvCell(value, options) {
1255
+ const escapedQuote = `${options.escape}${options.quote}`;
1256
+ const escaped = value.split(options.quote).join(escapedQuote);
1257
+ const needsQuoting = escaped.includes(options.delimiter) || escaped.includes(options.quote) || escaped.includes("\n") || escaped.includes("\r");
1258
+ if (!needsQuoting) return escaped;
1259
+ return `${options.quote}${escaped}${options.quote}`;
1260
+ }
1261
+ function normalizeCsvRows(records, preferredColumns, options) {
1262
+ const normalized = records.map(normalizeRecord);
1263
+ if (options.flattenNestedObjects) {
1264
+ const flatRows = normalized.map((record) => toFlatRow(record, options.nestedSeparator));
1265
+ const columns2 = orderColumns(
1266
+ discoveredColumns(flatRows),
1267
+ preferredColumns,
1268
+ true,
1269
+ options.nestedSeparator
1270
+ );
1271
+ const rows2 = flatRows.map((row) => columns2.map((column) => normalizeScalar(row[column], options.nullValue)));
1272
+ return { columns: columns2, rows: rows2 };
1273
+ }
1274
+ const topLevelRows = normalized.map((record) => ({ ...record }));
1275
+ const columns = orderColumns(
1276
+ discoveredColumns(topLevelRows),
1277
+ preferredColumns,
1278
+ false,
1279
+ options.nestedSeparator
1280
+ );
1281
+ const rows = topLevelRows.map((row) => columns.map((column) => inlineValue(row[column], options.nullValue)));
1282
+ return { columns, rows };
1283
+ }
1284
+ function formatQueryResultsAsCsv(records, options, preferredColumns) {
1285
+ const final = { ...DEFAULT_CSV_OPTIONS, ...options };
1286
+ const { columns, rows } = normalizeCsvRows(records, preferredColumns, final);
1287
+ const lines = [];
1288
+ if (final.headers && columns.length > 0) {
1289
+ lines.push(columns.map((column) => encodeCsvCell(column, final)).join(final.delimiter));
1290
+ }
1291
+ for (const row of rows) {
1292
+ lines.push(row.map((cell) => encodeCsvCell(cell, final)).join(final.delimiter));
1293
+ }
1294
+ return lines.join(final.newline);
1295
+ }
1296
+ function normalizeJsonValue(value) {
1297
+ if (value === void 0) return null;
1298
+ if (value === null) return null;
1299
+ if (isDate(value)) return value.toISOString();
1300
+ if (typeof value === "bigint") return value.toString();
1301
+ if (Array.isArray(value)) return value.map((entry) => normalizeJsonValue(entry));
1302
+ if (isRecord(value)) {
1303
+ const out = {};
1304
+ for (const [key, entry] of Object.entries(value)) {
1305
+ out[key] = normalizeJsonValue(entry);
1306
+ }
1307
+ return out;
1308
+ }
1309
+ return value;
1310
+ }
1311
+ function formatQueryResultsAsJson(records, options) {
1312
+ const final = { ...DEFAULT_JSON_OPTIONS, ...options };
1313
+ const normalized = records.map((record) => normalizeJsonValue(normalizeRecord(record)));
1314
+ return final.pretty ? JSON.stringify(normalized, null, final.indent) : JSON.stringify(normalized);
1315
+ }
1316
+ function findDefaultTreeKeyField(record) {
1317
+ for (const candidate of ["code", "id", "name"]) {
1318
+ if (candidate in record) return candidate;
1319
+ }
1320
+ const keys = Object.keys(record);
1321
+ return keys.length > 0 ? keys[0] : void 0;
1322
+ }
1323
+ function buildTreeNodesFromValue(value, options, depth) {
1324
+ if (depth >= options.maxDepth) {
1325
+ return [{ label: inlineValue(value, options.nullValue) }];
1326
+ }
1327
+ if (Array.isArray(value)) {
1328
+ return value.map((entry, index) => {
1329
+ if (!isRecord(entry) && !Array.isArray(entry)) {
1330
+ return { label: `[${index}]: ${inlineValue(entry, options.nullValue)}` };
1331
+ }
1332
+ return {
1333
+ label: `[${index}]`,
1334
+ children: buildTreeNodesFromValue(entry, options, depth + 1)
1335
+ };
1336
+ });
1337
+ }
1338
+ return Object.entries(value).map(([key, entry]) => {
1339
+ if (isRecord(entry) || Array.isArray(entry)) {
1340
+ if (depth + 1 >= options.maxDepth) {
1341
+ return { label: `${key}: ${inlineValue(entry, options.nullValue)}` };
1342
+ }
1343
+ return {
1344
+ label: key,
1345
+ children: buildTreeNodesFromValue(entry, options, depth + 1)
1346
+ };
1347
+ }
1348
+ return { label: `${key}: ${inlineValue(entry, options.nullValue)}` };
1349
+ });
1350
+ }
1351
+ function buildRecordTreeNode(record, index, options) {
1352
+ const keyField = options.keyField || findDefaultTreeKeyField(record);
1353
+ const entries = Object.entries(record);
1354
+ if (!keyField || !(keyField in record)) {
1355
+ return {
1356
+ label: `row ${index + 1}`,
1357
+ children: buildTreeNodesFromValue(record, options, 0)
1358
+ };
1359
+ }
1360
+ const labelValue = inlineValue(record[keyField], options.nullValue);
1361
+ const childEntries = entries.filter(([key]) => key !== keyField);
1362
+ return {
1363
+ label: labelValue,
1364
+ children: childEntries.length > 0 ? childEntries.map(([key, value]) => {
1365
+ if (isRecord(value) || Array.isArray(value)) {
1366
+ if (1 > options.maxDepth) {
1367
+ return { label: `${key}: ${inlineValue(value, options.nullValue)}` };
1368
+ }
1369
+ return {
1370
+ label: key,
1371
+ children: buildTreeNodesFromValue(value, options, 1)
1372
+ };
1373
+ }
1374
+ return { label: `${key}: ${inlineValue(value, options.nullValue)}` };
1375
+ }) : void 0
1376
+ };
1377
+ }
1378
+ function renderTreeNodes(nodes, prefix = "") {
1379
+ const lines = [];
1380
+ nodes.forEach((node, index) => {
1381
+ const isLast = index === nodes.length - 1;
1382
+ const branch = isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
1383
+ lines.push(`${prefix}${branch}${node.label}`);
1384
+ if (node.children && node.children.length > 0) {
1385
+ const childPrefix = `${prefix}${isLast ? " " : "\u2502 "}`;
1386
+ lines.push(...renderTreeNodes(node.children, childPrefix));
1387
+ }
1388
+ });
1389
+ return lines;
1390
+ }
1391
+ function formatQueryResultsAsTree(records, options) {
1392
+ const final = { ...DEFAULT_TREE_OPTIONS, ...options };
1393
+ const nodes = records.map((record, index) => buildRecordTreeNode(normalizeRecord(record), index, final));
1394
+ if (!final.includeRoot) {
1395
+ return renderTreeNodes(nodes).join("\n");
1396
+ }
1397
+ const lines = [final.rootLabel];
1398
+ if (nodes.length > 0) {
1399
+ lines.push(...renderTreeNodes(nodes));
1400
+ }
1401
+ return lines.join("\n");
1402
+ }
1403
+ async function collectAllQueryRecords(getPage, initialNextPage) {
1404
+ const records = [];
1405
+ let nextPage = initialNextPage;
1406
+ while (true) {
1407
+ const page = await getPage(nextPage);
1408
+ if (Array.isArray(page.records)) {
1409
+ records.push(...page.records);
1410
+ }
1411
+ if (!page.nextPage) break;
1412
+ nextPage = page.nextPage;
1413
+ }
1414
+ return records;
1415
+ }
1416
+
1049
1417
  // src/impl/onyx-core.ts
1050
1418
  var DEFAULT_CACHE_TTL = 5 * 60 * 1e3;
1051
1419
  function toSingleCondition(criteria) {
@@ -1574,7 +1942,7 @@ var OnyxDatabaseImpl = class {
1574
1942
  };
1575
1943
  var QueryBuilderImpl = class {
1576
1944
  db;
1577
- table;
1945
+ tableName;
1578
1946
  fields = null;
1579
1947
  resolvers = null;
1580
1948
  conditions = null;
@@ -1593,12 +1961,12 @@ var QueryBuilderImpl = class {
1593
1961
  onItemListener = null;
1594
1962
  constructor(db, table, partition) {
1595
1963
  this.db = db;
1596
- this.table = table;
1964
+ this.tableName = table;
1597
1965
  this.partitionValue = partition;
1598
1966
  }
1599
1967
  ensureTable() {
1600
- if (!this.table) throw new Error("Table is not defined. Call from(<table>) first.");
1601
- return this.table;
1968
+ if (!this.tableName) throw new Error("Table is not defined. Call from(<table>) first.");
1969
+ return this.tableName;
1602
1970
  }
1603
1971
  serializableConditions() {
1604
1972
  return normalizeCondition(this.conditions);
@@ -1606,7 +1974,7 @@ var QueryBuilderImpl = class {
1606
1974
  toSelectQuery() {
1607
1975
  return {
1608
1976
  type: "SelectQuery",
1609
- table: this.table,
1977
+ table: this.tableName,
1610
1978
  fields: this.fields,
1611
1979
  conditions: this.serializableConditions(),
1612
1980
  sort: this.sort,
@@ -1627,13 +1995,25 @@ var QueryBuilderImpl = class {
1627
1995
  partition: this.partitionValue ?? null
1628
1996
  };
1629
1997
  }
1998
+ async getAllRecordsForFormatting() {
1999
+ if (this.mode !== "select") throw new Error("Formatting is only applicable in select mode.");
2000
+ const table = this.ensureTable();
2001
+ const select = this.toSelectQuery();
2002
+ const pageSize = this.pageSizeValue ?? void 0;
2003
+ const initialNextPage = this.nextPageValue ?? void 0;
2004
+ return collectAllQueryRecords((nextPage) => this.db._queryPage(table, select, {
2005
+ pageSize,
2006
+ nextPage,
2007
+ partition: this.partitionValue
2008
+ }), initialNextPage);
2009
+ }
1630
2010
  toSerializableQueryObject() {
1631
2011
  const table = this.ensureTable();
1632
2012
  const payload = this.mode === "update" ? this.toUpdateQuery() : this.toSelectQuery();
1633
2013
  return { ...payload, table };
1634
2014
  }
1635
2015
  from(table) {
1636
- this.table = table;
2016
+ this.tableName = table;
1637
2017
  return this;
1638
2018
  }
1639
2019
  select(...fields) {
@@ -1765,6 +2145,26 @@ var QueryBuilderImpl = class {
1765
2145
  async one() {
1766
2146
  return this.firstOrNull();
1767
2147
  }
2148
+ async table(options) {
2149
+ return formatQueryResultsAsTable(
2150
+ await this.getAllRecordsForFormatting(),
2151
+ options,
2152
+ this.fields ?? void 0
2153
+ );
2154
+ }
2155
+ async tree(options) {
2156
+ return formatQueryResultsAsTree(await this.getAllRecordsForFormatting(), options);
2157
+ }
2158
+ async csv(options) {
2159
+ return formatQueryResultsAsCsv(
2160
+ await this.getAllRecordsForFormatting(),
2161
+ options,
2162
+ this.fields ?? void 0
2163
+ );
2164
+ }
2165
+ async json(options) {
2166
+ return formatQueryResultsAsJson(await this.getAllRecordsForFormatting(), options);
2167
+ }
1768
2168
  async delete() {
1769
2169
  if (this.mode !== "select") throw new Error("delete() is only applicable in select mode.");
1770
2170
  const table = this.ensureTable();