@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.
package/README.md CHANGED
@@ -174,6 +174,10 @@ onyx-schema get --tables=User,Profile
174
174
 
175
175
  # Validate a schema file without publishing
176
176
  onyx-schema validate ./onyx.schema.json
177
+
178
+ # Diff local schema vs API
179
+ onyx-schema diff ./onyx.schema.json
180
+ # Prints added/removed/changed tables and attribute differences between the API schema and your local file.
177
181
  ```
178
182
 
179
183
  When `--tables` is provided, the subset is printed to stdout instead of writing a
@@ -322,7 +326,9 @@ Importable helpers for conditions and sort:
322
326
 
323
327
  ```ts
324
328
  import {
325
- eq, neq, inOp, notIn, between,
329
+ eq, neq, within, notWithin, // preferred aliases for IN/NOT IN
330
+ inOp, notIn,
331
+ between,
326
332
  gt, gte, lt, lte,
327
333
  like, notLike, contains, notContains,
328
334
  startsWith, notStartsWith, matches, notMatches,
@@ -331,6 +337,41 @@ import {
331
337
  } from '@onyx.dev/onyx-database';
332
338
  ```
333
339
 
340
+ - Prefer `within`/`notWithin` for inclusion checks (supports arrays, comma-separated strings, or inner queries).
341
+ - `inOp`/`notIn` remain available for backward compatibility and are exact aliases.
342
+
343
+ ### Inner queries (IN/NOT IN with sub-selects)
344
+
345
+ You can pass another query builder to `within` or `notWithin` to create nested filters. The SDK serializes the inner query (including its table) before sending the request.
346
+
347
+ ```ts
348
+ import { onyx, within, notWithin, eq, tables, Schema } from '@onyx.dev/onyx-database';
349
+
350
+ const db = onyx.init<Schema>();
351
+
352
+ // Users that HAVE the admin role
353
+ const usersWithAdmin = await db
354
+ .from(tables.User)
355
+ .where(
356
+ within(
357
+ 'id',
358
+ db.select('userId').from(tables.UserRole).where(eq('roleId', 'role-admin')),
359
+ ),
360
+ )
361
+ .list();
362
+
363
+ // Roles that DO NOT include a specific permission
364
+ const rolesMissingPermission = await db
365
+ .from(tables.Role)
366
+ .where(
367
+ notWithin(
368
+ 'id',
369
+ db.from(tables.RolePermission).where(eq('permissionId', 'perm-manage-users')),
370
+ ),
371
+ )
372
+ .list();
373
+ ```
374
+
334
375
  ---
335
376
 
336
377
  ## Usage examples with `User`, `Role`, `Permission`
@@ -931,6 +931,56 @@ var QueryResults = class extends Array {
931
931
  }
932
932
  };
933
933
 
934
+ // src/helpers/condition-normalizer.ts
935
+ function isQueryBuilderLike(value) {
936
+ return !!value && typeof value.toSerializableQueryObject === "function";
937
+ }
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 };
955
+ }
956
+ if (isQueryBuilderLike(value)) {
957
+ return { value: value.toSerializableQueryObject(), changed: true };
958
+ }
959
+ return { value, changed: false };
960
+ }
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 };
978
+ }
979
+ function normalizeCondition(condition) {
980
+ if (!condition) return condition;
981
+ return normalizeConditionInternal(condition);
982
+ }
983
+
934
984
  // src/builders/cascade-relationship-builder.ts
935
985
  var CascadeRelationshipBuilder = class {
936
986
  graphName;
@@ -1058,6 +1108,10 @@ function serializeDates(value) {
1058
1108
  }
1059
1109
  return value;
1060
1110
  }
1111
+ function stripEntityText(input) {
1112
+ const { entityText, ...rest } = input;
1113
+ return rest;
1114
+ }
1061
1115
  function normalizeSecretMetadata(input) {
1062
1116
  return { ...input, updatedAt: new Date(input.updatedAt) };
1063
1117
  }
@@ -1071,7 +1125,7 @@ function normalizeDate(value) {
1071
1125
  return Number.isNaN(ts.getTime()) ? void 0 : ts;
1072
1126
  }
1073
1127
  function normalizeSchemaRevision(input, fallbackDatabaseId) {
1074
- const { meta, createdAt, publishedAt, revisionId, ...rest } = input;
1128
+ const { meta, createdAt, publishedAt, revisionId, entityText, ...rest } = input;
1075
1129
  const mergedMeta = {
1076
1130
  revisionId: meta?.revisionId ?? revisionId,
1077
1131
  createdAt: normalizeDate(meta?.createdAt ?? createdAt),
@@ -1242,15 +1296,23 @@ var OnyxDatabaseImpl = class {
1242
1296
  const params = new URLSearchParams();
1243
1297
  if (options?.publish) params.append("publish", "true");
1244
1298
  const path = `/schemas/${encodeURIComponent(databaseId)}${params.size ? `?${params.toString()}` : ""}`;
1245
- const body = { ...schema, databaseId: schema.databaseId ?? databaseId };
1246
- const res = await http.request("PUT", path, serializeDates(body));
1299
+ const body = stripEntityText({ ...schema, databaseId: schema.databaseId ?? databaseId });
1300
+ const res = await http.request(
1301
+ "PUT",
1302
+ path,
1303
+ serializeDates(body)
1304
+ );
1247
1305
  return normalizeSchemaRevision(res, databaseId);
1248
1306
  }
1249
1307
  async validateSchema(schema) {
1250
1308
  const { http, databaseId } = await this.ensureClient();
1251
1309
  const path = `/schemas/${encodeURIComponent(databaseId)}/validate`;
1252
- const body = { ...schema, databaseId: schema.databaseId ?? databaseId };
1253
- const res = await http.request("POST", path, serializeDates(body));
1310
+ const body = stripEntityText({ ...schema, databaseId: schema.databaseId ?? databaseId });
1311
+ const res = await http.request(
1312
+ "POST",
1313
+ path,
1314
+ serializeDates(body)
1315
+ );
1254
1316
  const normalizedSchema = res.schema ? normalizeSchemaRevision(res.schema, databaseId) : void 0;
1255
1317
  return {
1256
1318
  ...res,
@@ -1412,11 +1474,14 @@ var QueryBuilderImpl = class {
1412
1474
  if (!this.table) throw new Error("Table is not defined. Call from(<table>) first.");
1413
1475
  return this.table;
1414
1476
  }
1477
+ serializableConditions() {
1478
+ return normalizeCondition(this.conditions);
1479
+ }
1415
1480
  toSelectQuery() {
1416
1481
  return {
1417
1482
  type: "SelectQuery",
1418
1483
  fields: this.fields,
1419
- conditions: this.conditions,
1484
+ conditions: this.serializableConditions(),
1420
1485
  sort: this.sort,
1421
1486
  limit: this.limitValue,
1422
1487
  distinct: this.distinctValue,
@@ -1425,6 +1490,21 @@ var QueryBuilderImpl = class {
1425
1490
  resolvers: this.resolvers
1426
1491
  };
1427
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
+ }
1428
1508
  from(table) {
1429
1509
  this.table = table;
1430
1510
  return this;
@@ -1560,14 +1640,7 @@ var QueryBuilderImpl = class {
1560
1640
  async update() {
1561
1641
  if (this.mode !== "update") throw new Error("Call setUpdates(...) before update().");
1562
1642
  const table = this.ensureTable();
1563
- const update = {
1564
- type: "UpdateQuery",
1565
- conditions: this.conditions,
1566
- updates: this.updates ?? {},
1567
- sort: this.sort,
1568
- limit: this.limitValue,
1569
- partition: this.partitionValue ?? null
1570
- };
1643
+ const update = this.toUpdateQuery();
1571
1644
  return this.db._update(table, update, this.partitionValue);
1572
1645
  }
1573
1646
  onItemAdded(listener) {