@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.d.cts CHANGED
@@ -908,6 +908,10 @@ declare abstract class BaseCrudDialect<Schema extends SchemaDef> {
908
908
  * Builds a VALUES table and select all fields from it.
909
909
  */
910
910
  abstract buildValuesTableSelect(fields: FieldDef[], rows: unknown[][]): SelectQueryBuilder<any, any, any>;
911
+ /**
912
+ * Builds a binary comparison expression between two operands.
913
+ */
914
+ buildComparison(left: Expression<unknown>, _leftFieldDef: FieldDef | undefined, op: string, right: Expression<unknown>, _rightFieldDef: FieldDef | undefined): Expression<SqlBool>;
911
915
  /**
912
916
  * Builds a JSON path selection expression.
913
917
  */
package/dist/index.d.ts CHANGED
@@ -908,6 +908,10 @@ declare abstract class BaseCrudDialect<Schema extends SchemaDef> {
908
908
  * Builds a VALUES table and select all fields from it.
909
909
  */
910
910
  abstract buildValuesTableSelect(fields: FieldDef[], rows: unknown[][]): SelectQueryBuilder<any, any, any>;
911
+ /**
912
+ * Builds a binary comparison expression between two operands.
913
+ */
914
+ buildComparison(left: Expression<unknown>, _leftFieldDef: FieldDef | undefined, op: string, right: Expression<unknown>, _rightFieldDef: FieldDef | undefined): Expression<SqlBool>;
911
915
  /**
912
916
  * Builds a JSON path selection expression.
913
917
  */
package/dist/index.js CHANGED
@@ -1597,6 +1597,12 @@ var BaseCrudDialect = class {
1597
1597
  buildExistsExpression(innerQuery) {
1598
1598
  return this.eb.exists(innerQuery);
1599
1599
  }
1600
+ /**
1601
+ * Builds a binary comparison expression between two operands.
1602
+ */
1603
+ buildComparison(left, _leftFieldDef, op, right, _rightFieldDef) {
1604
+ return this.eb(left, op, right);
1605
+ }
1600
1606
  };
1601
1607
 
1602
1608
  // src/client/crud/dialects/lateral-join-dialect-base.ts
@@ -1652,13 +1658,53 @@ var LateralJoinDialectBase = class extends BaseCrudDialect {
1652
1658
  qb = qb.select((eb) => {
1653
1659
  const objArgs = this.buildRelationObjectArgs(relationModel, relationModelAlias, eb, payload, parentResultName);
1654
1660
  if (relationFieldDef.array) {
1655
- return this.buildArrayAgg(this.buildJsonObject(objArgs)).as("$data");
1661
+ const orderBy = this.buildRelationOrderByExpressions(relationModel, relationModelAlias, payload);
1662
+ return this.buildArrayAgg(this.buildJsonObject(objArgs), orderBy).as("$data");
1656
1663
  } else {
1657
1664
  return this.buildJsonObject(objArgs).as("$data");
1658
1665
  }
1659
1666
  });
1660
1667
  return qb;
1661
1668
  }
1669
+ /**
1670
+ * Extracts scalar `orderBy` clauses from the relation payload and maps them to
1671
+ * the array-aggregation ordering format.
1672
+ *
1673
+ * For to-many relations aggregated into a JSON array (via lateral joins), this
1674
+ * lets us preserve a stable ordering by passing `{ expr, sort, nulls? }` into
1675
+ * the dialect's `buildArrayAgg` implementation.
1676
+ */
1677
+ buildRelationOrderByExpressions(model, modelAlias, payload) {
1678
+ if (payload === true || !payload.orderBy) {
1679
+ return void 0;
1680
+ }
1681
+ const items = [];
1682
+ for (const orderBy of ensureArray(payload.orderBy)) {
1683
+ for (const [field, value] of Object.entries(orderBy)) {
1684
+ if (!value || requireField(this.schema, model, field).relation) {
1685
+ continue;
1686
+ }
1687
+ const expr = this.fieldRef(model, field, modelAlias);
1688
+ let sort = typeof value === "string" ? value : value.sort;
1689
+ if (payload.take !== void 0 && payload.take < 0) {
1690
+ sort = this.negateSort(sort, true);
1691
+ }
1692
+ if (typeof value === "string") {
1693
+ items.push({
1694
+ expr,
1695
+ sort
1696
+ });
1697
+ } else {
1698
+ items.push({
1699
+ expr,
1700
+ sort,
1701
+ nulls: value.nulls
1702
+ });
1703
+ }
1704
+ }
1705
+ }
1706
+ return items.length > 0 ? items : void 0;
1707
+ }
1662
1708
  buildRelationObjectArgs(relationModel, relationModelAlias, eb, payload, parentResultName) {
1663
1709
  const relationModelDef = requireModel(this.schema, relationModel);
1664
1710
  const objArgs = {};
@@ -1844,7 +1890,7 @@ var MySqlCrudDialect = class extends LateralJoinDialectBase {
1844
1890
  buildExistsExpression(innerQuery) {
1845
1891
  return this.eb.exists(this.eb.selectFrom(innerQuery.as("$exists_sub")).select(this.eb.lit(1).as("_")));
1846
1892
  }
1847
- buildArrayAgg(arg) {
1893
+ buildArrayAgg(arg, _orderBy) {
1848
1894
  return this.eb.fn.coalesce(sql2`JSON_ARRAYAGG(${arg})`, sql2`JSON_ARRAY()`);
1849
1895
  }
1850
1896
  buildSkipTake(query, skip, take) {
@@ -1991,6 +2037,33 @@ var PostgresCrudDialect = class _PostgresCrudDialect extends LateralJoinDialectB
1991
2037
  Bytes: "bytea",
1992
2038
  Json: "jsonb"
1993
2039
  };
2040
+ // Maps @db.* attribute names to PostgreSQL SQL types for use in VALUES table casts
2041
+ static dbAttributeToSqlTypeMap = {
2042
+ "@db.Uuid": "uuid",
2043
+ "@db.Citext": "citext",
2044
+ "@db.Inet": "inet",
2045
+ "@db.Bit": "bit",
2046
+ "@db.VarBit": "varbit",
2047
+ "@db.Xml": "xml",
2048
+ "@db.Json": "json",
2049
+ "@db.JsonB": "jsonb",
2050
+ "@db.ByteA": "bytea",
2051
+ "@db.Text": "text",
2052
+ "@db.Char": "bpchar",
2053
+ "@db.VarChar": "varchar",
2054
+ "@db.Date": "date",
2055
+ "@db.Time": "time",
2056
+ "@db.Timetz": "timetz",
2057
+ "@db.Timestamp": "timestamp",
2058
+ "@db.Timestamptz": "timestamptz",
2059
+ "@db.SmallInt": "smallint",
2060
+ "@db.Integer": "integer",
2061
+ "@db.BigInt": "bigint",
2062
+ "@db.Real": "real",
2063
+ "@db.DoublePrecision": "double precision",
2064
+ "@db.Decimal": "decimal",
2065
+ "@db.Boolean": "boolean"
2066
+ };
1994
2067
  constructor(schema, options) {
1995
2068
  super(schema, options);
1996
2069
  this.overrideTypeParsers();
@@ -2156,8 +2229,16 @@ var PostgresCrudDialect = class _PostgresCrudDialect extends LateralJoinDialectB
2156
2229
  }
2157
2230
  // #endregion
2158
2231
  // #region other overrides
2159
- buildArrayAgg(arg) {
2160
- return this.eb.fn.coalesce(sql3`jsonb_agg(${arg})`, sql3`'[]'::jsonb`);
2232
+ buildArrayAgg(arg, orderBy) {
2233
+ if (!orderBy || orderBy.length === 0) {
2234
+ return this.eb.fn.coalesce(sql3`jsonb_agg(${arg})`, sql3`'[]'::jsonb`);
2235
+ }
2236
+ const orderBySql = sql3.join(orderBy.map(({ expr, sort, nulls }) => {
2237
+ const dir = sql3.raw(sort.toUpperCase());
2238
+ const nullsSql = nulls ? sql3` NULLS ${sql3.raw(nulls.toUpperCase())}` : sql3``;
2239
+ return sql3`${expr} ${dir}${nullsSql}`;
2240
+ }), sql3.raw(", "));
2241
+ return this.eb.fn.coalesce(sql3`jsonb_agg(${arg} ORDER BY ${orderBySql})`, sql3`'[]'::jsonb`);
2161
2242
  }
2162
2243
  buildSkipTake(query, skip, take) {
2163
2244
  if (take !== void 0) {
@@ -2247,13 +2328,55 @@ var PostgresCrudDialect = class _PostgresCrudDialect extends LateralJoinDialectB
2247
2328
  receiver
2248
2329
  ]).as("$items")).select(this.eb.lit(1).as("_")).where(buildFilter(this.eb.ref("$items.value"))));
2249
2330
  }
2250
- getSqlType(zmodelType) {
2331
+ getSqlType(zmodelType, attributes) {
2332
+ if (attributes) {
2333
+ for (const attr of attributes) {
2334
+ const mapped = _PostgresCrudDialect.dbAttributeToSqlTypeMap[attr.name];
2335
+ if (mapped) {
2336
+ return mapped;
2337
+ }
2338
+ }
2339
+ }
2251
2340
  if (isEnum(this.schema, zmodelType)) {
2252
2341
  return "text";
2253
2342
  } else {
2254
2343
  return this.zmodelToSqlTypeMap[zmodelType] ?? "text";
2255
2344
  }
2256
2345
  }
2346
+ // Resolves the effective SQL type for a field: the native type from any @db.* attribute,
2347
+ // or the base ZModel SQL type if no attribute is present, or undefined if the field is unknown.
2348
+ resolveFieldSqlType(fieldDef) {
2349
+ if (!fieldDef) {
2350
+ return {
2351
+ sqlType: void 0,
2352
+ hasDbOverride: false
2353
+ };
2354
+ }
2355
+ const dbAttr = fieldDef.attributes?.find((a) => a.name.startsWith("@db."));
2356
+ if (dbAttr) {
2357
+ return {
2358
+ sqlType: _PostgresCrudDialect.dbAttributeToSqlTypeMap[dbAttr.name],
2359
+ hasDbOverride: true
2360
+ };
2361
+ }
2362
+ return {
2363
+ sqlType: this.getSqlType(fieldDef.type),
2364
+ hasDbOverride: false
2365
+ };
2366
+ }
2367
+ buildComparison(left, leftFieldDef, op, right, rightFieldDef) {
2368
+ const leftResolved = this.resolveFieldSqlType(leftFieldDef);
2369
+ const rightResolved = this.resolveFieldSqlType(rightFieldDef);
2370
+ if (leftResolved.sqlType !== rightResolved.sqlType && (leftResolved.hasDbOverride || rightResolved.hasDbOverride)) {
2371
+ if (leftResolved.hasDbOverride) {
2372
+ left = this.eb.cast(left, sql3.raw(this.getSqlType(leftFieldDef.type)));
2373
+ }
2374
+ if (rightResolved.hasDbOverride) {
2375
+ right = this.eb.cast(right, sql3.raw(this.getSqlType(rightFieldDef.type)));
2376
+ }
2377
+ }
2378
+ return super.buildComparison(left, leftFieldDef, op, right, rightFieldDef);
2379
+ }
2257
2380
  getStringCasingBehavior() {
2258
2381
  return {
2259
2382
  supportsILike: true,
@@ -2275,7 +2398,7 @@ var PostgresCrudDialect = class _PostgresCrudDialect extends LateralJoinDialectB
2275
2398
  }
2276
2399
  const eb = expressionBuilder3();
2277
2400
  return eb.selectFrom(sql3`(VALUES ${sql3.join(rows.map((row) => sql3`(${sql3.join(row.map((v) => sql3.val(v)))})`), sql3.raw(", "))})`.as("$values")).select(fields.map((f, i) => {
2278
- const mappedType = this.getSqlType(f.type);
2401
+ const mappedType = this.getSqlType(f.type, f.attributes);
2279
2402
  const castType = f.array ? sql3`${sql3.raw(mappedType)}[]` : sql3.raw(mappedType);
2280
2403
  return this.eb.cast(sql3.ref(`$values.column${i + 1}`), castType).as(f.name);
2281
2404
  }));