@zenstackhq/orm 3.5.1 → 3.5.3

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/index.cjs CHANGED
@@ -1643,6 +1643,12 @@ var BaseCrudDialect = class {
1643
1643
  buildExistsExpression(innerQuery) {
1644
1644
  return this.eb.exists(innerQuery);
1645
1645
  }
1646
+ /**
1647
+ * Builds a binary comparison expression between two operands.
1648
+ */
1649
+ buildComparison(left, _leftFieldDef, op, right, _rightFieldDef) {
1650
+ return this.eb(left, op, right);
1651
+ }
1646
1652
  };
1647
1653
 
1648
1654
  // src/client/crud/dialects/lateral-join-dialect-base.ts
@@ -1698,13 +1704,53 @@ var LateralJoinDialectBase = class extends BaseCrudDialect {
1698
1704
  qb = qb.select((eb) => {
1699
1705
  const objArgs = this.buildRelationObjectArgs(relationModel, relationModelAlias, eb, payload, parentResultName);
1700
1706
  if (relationFieldDef.array) {
1701
- return this.buildArrayAgg(this.buildJsonObject(objArgs)).as("$data");
1707
+ const orderBy = this.buildRelationOrderByExpressions(relationModel, relationModelAlias, payload);
1708
+ return this.buildArrayAgg(this.buildJsonObject(objArgs), orderBy).as("$data");
1702
1709
  } else {
1703
1710
  return this.buildJsonObject(objArgs).as("$data");
1704
1711
  }
1705
1712
  });
1706
1713
  return qb;
1707
1714
  }
1715
+ /**
1716
+ * Extracts scalar `orderBy` clauses from the relation payload and maps them to
1717
+ * the array-aggregation ordering format.
1718
+ *
1719
+ * For to-many relations aggregated into a JSON array (via lateral joins), this
1720
+ * lets us preserve a stable ordering by passing `{ expr, sort, nulls? }` into
1721
+ * the dialect's `buildArrayAgg` implementation.
1722
+ */
1723
+ buildRelationOrderByExpressions(model, modelAlias, payload) {
1724
+ if (payload === true || !payload.orderBy) {
1725
+ return void 0;
1726
+ }
1727
+ const items = [];
1728
+ for (const orderBy of ensureArray(payload.orderBy)) {
1729
+ for (const [field, value] of Object.entries(orderBy)) {
1730
+ if (!value || requireField(this.schema, model, field).relation) {
1731
+ continue;
1732
+ }
1733
+ const expr = this.fieldRef(model, field, modelAlias);
1734
+ let sort = typeof value === "string" ? value : value.sort;
1735
+ if (payload.take !== void 0 && payload.take < 0) {
1736
+ sort = this.negateSort(sort, true);
1737
+ }
1738
+ if (typeof value === "string") {
1739
+ items.push({
1740
+ expr,
1741
+ sort
1742
+ });
1743
+ } else {
1744
+ items.push({
1745
+ expr,
1746
+ sort,
1747
+ nulls: value.nulls
1748
+ });
1749
+ }
1750
+ }
1751
+ }
1752
+ return items.length > 0 ? items : void 0;
1753
+ }
1708
1754
  buildRelationObjectArgs(relationModel, relationModelAlias, eb, payload, parentResultName) {
1709
1755
  const relationModelDef = requireModel(this.schema, relationModel);
1710
1756
  const objArgs = {};
@@ -1890,7 +1936,7 @@ var MySqlCrudDialect = class extends LateralJoinDialectBase {
1890
1936
  buildExistsExpression(innerQuery) {
1891
1937
  return this.eb.exists(this.eb.selectFrom(innerQuery.as("$exists_sub")).select(this.eb.lit(1).as("_")));
1892
1938
  }
1893
- buildArrayAgg(arg) {
1939
+ buildArrayAgg(arg, _orderBy) {
1894
1940
  return this.eb.fn.coalesce(import_kysely3.sql`JSON_ARRAYAGG(${arg})`, import_kysely3.sql`JSON_ARRAY()`);
1895
1941
  }
1896
1942
  buildSkipTake(query, skip, take) {
@@ -2037,6 +2083,33 @@ var PostgresCrudDialect = class _PostgresCrudDialect extends LateralJoinDialectB
2037
2083
  Bytes: "bytea",
2038
2084
  Json: "jsonb"
2039
2085
  };
2086
+ // Maps @db.* attribute names to PostgreSQL SQL types for use in VALUES table casts
2087
+ static dbAttributeToSqlTypeMap = {
2088
+ "@db.Uuid": "uuid",
2089
+ "@db.Citext": "citext",
2090
+ "@db.Inet": "inet",
2091
+ "@db.Bit": "bit",
2092
+ "@db.VarBit": "varbit",
2093
+ "@db.Xml": "xml",
2094
+ "@db.Json": "json",
2095
+ "@db.JsonB": "jsonb",
2096
+ "@db.ByteA": "bytea",
2097
+ "@db.Text": "text",
2098
+ "@db.Char": "bpchar",
2099
+ "@db.VarChar": "varchar",
2100
+ "@db.Date": "date",
2101
+ "@db.Time": "time",
2102
+ "@db.Timetz": "timetz",
2103
+ "@db.Timestamp": "timestamp",
2104
+ "@db.Timestamptz": "timestamptz",
2105
+ "@db.SmallInt": "smallint",
2106
+ "@db.Integer": "integer",
2107
+ "@db.BigInt": "bigint",
2108
+ "@db.Real": "real",
2109
+ "@db.DoublePrecision": "double precision",
2110
+ "@db.Decimal": "decimal",
2111
+ "@db.Boolean": "boolean"
2112
+ };
2040
2113
  constructor(schema, options) {
2041
2114
  super(schema, options);
2042
2115
  this.overrideTypeParsers();
@@ -2202,8 +2275,16 @@ var PostgresCrudDialect = class _PostgresCrudDialect extends LateralJoinDialectB
2202
2275
  }
2203
2276
  // #endregion
2204
2277
  // #region other overrides
2205
- buildArrayAgg(arg) {
2206
- return this.eb.fn.coalesce(import_kysely4.sql`jsonb_agg(${arg})`, import_kysely4.sql`'[]'::jsonb`);
2278
+ buildArrayAgg(arg, orderBy) {
2279
+ if (!orderBy || orderBy.length === 0) {
2280
+ return this.eb.fn.coalesce(import_kysely4.sql`jsonb_agg(${arg})`, import_kysely4.sql`'[]'::jsonb`);
2281
+ }
2282
+ const orderBySql = import_kysely4.sql.join(orderBy.map(({ expr, sort, nulls }) => {
2283
+ const dir = import_kysely4.sql.raw(sort.toUpperCase());
2284
+ const nullsSql = nulls ? import_kysely4.sql` NULLS ${import_kysely4.sql.raw(nulls.toUpperCase())}` : import_kysely4.sql``;
2285
+ return import_kysely4.sql`${expr} ${dir}${nullsSql}`;
2286
+ }), import_kysely4.sql.raw(", "));
2287
+ return this.eb.fn.coalesce(import_kysely4.sql`jsonb_agg(${arg} ORDER BY ${orderBySql})`, import_kysely4.sql`'[]'::jsonb`);
2207
2288
  }
2208
2289
  buildSkipTake(query, skip, take) {
2209
2290
  if (take !== void 0) {
@@ -2293,13 +2374,55 @@ var PostgresCrudDialect = class _PostgresCrudDialect extends LateralJoinDialectB
2293
2374
  receiver
2294
2375
  ]).as("$items")).select(this.eb.lit(1).as("_")).where(buildFilter(this.eb.ref("$items.value"))));
2295
2376
  }
2296
- getSqlType(zmodelType) {
2377
+ getSqlType(zmodelType, attributes) {
2378
+ if (attributes) {
2379
+ for (const attr of attributes) {
2380
+ const mapped = _PostgresCrudDialect.dbAttributeToSqlTypeMap[attr.name];
2381
+ if (mapped) {
2382
+ return mapped;
2383
+ }
2384
+ }
2385
+ }
2297
2386
  if (isEnum(this.schema, zmodelType)) {
2298
2387
  return "text";
2299
2388
  } else {
2300
2389
  return this.zmodelToSqlTypeMap[zmodelType] ?? "text";
2301
2390
  }
2302
2391
  }
2392
+ // Resolves the effective SQL type for a field: the native type from any @db.* attribute,
2393
+ // or the base ZModel SQL type if no attribute is present, or undefined if the field is unknown.
2394
+ resolveFieldSqlType(fieldDef) {
2395
+ if (!fieldDef) {
2396
+ return {
2397
+ sqlType: void 0,
2398
+ hasDbOverride: false
2399
+ };
2400
+ }
2401
+ const dbAttr = fieldDef.attributes?.find((a) => a.name.startsWith("@db."));
2402
+ if (dbAttr) {
2403
+ return {
2404
+ sqlType: _PostgresCrudDialect.dbAttributeToSqlTypeMap[dbAttr.name],
2405
+ hasDbOverride: true
2406
+ };
2407
+ }
2408
+ return {
2409
+ sqlType: this.getSqlType(fieldDef.type),
2410
+ hasDbOverride: false
2411
+ };
2412
+ }
2413
+ buildComparison(left, leftFieldDef, op, right, rightFieldDef) {
2414
+ const leftResolved = this.resolveFieldSqlType(leftFieldDef);
2415
+ const rightResolved = this.resolveFieldSqlType(rightFieldDef);
2416
+ if (leftResolved.sqlType !== rightResolved.sqlType && (leftResolved.hasDbOverride || rightResolved.hasDbOverride)) {
2417
+ if (leftResolved.hasDbOverride) {
2418
+ left = this.eb.cast(left, import_kysely4.sql.raw(this.getSqlType(leftFieldDef.type)));
2419
+ }
2420
+ if (rightResolved.hasDbOverride) {
2421
+ right = this.eb.cast(right, import_kysely4.sql.raw(this.getSqlType(rightFieldDef.type)));
2422
+ }
2423
+ }
2424
+ return super.buildComparison(left, leftFieldDef, op, right, rightFieldDef);
2425
+ }
2303
2426
  getStringCasingBehavior() {
2304
2427
  return {
2305
2428
  supportsILike: true,
@@ -2321,7 +2444,7 @@ var PostgresCrudDialect = class _PostgresCrudDialect extends LateralJoinDialectB
2321
2444
  }
2322
2445
  const eb = (0, import_kysely4.expressionBuilder)();
2323
2446
  return eb.selectFrom(import_kysely4.sql`(VALUES ${import_kysely4.sql.join(rows.map((row) => import_kysely4.sql`(${import_kysely4.sql.join(row.map((v) => import_kysely4.sql.val(v)))})`), import_kysely4.sql.raw(", "))})`.as("$values")).select(fields.map((f, i) => {
2324
- const mappedType = this.getSqlType(f.type);
2447
+ const mappedType = this.getSqlType(f.type, f.attributes);
2325
2448
  const castType = f.array ? import_kysely4.sql`${import_kysely4.sql.raw(mappedType)}[]` : import_kysely4.sql.raw(mappedType);
2326
2449
  return this.eb.cast(import_kysely4.sql.ref(`$values.column${i + 1}`), castType).as(f.name);
2327
2450
  }));