Skip to content

v0.4.2

Choose a tag to compare

@brunolau brunolau released this 09 Feb 12:52
· 211 commits to main since this release

0.4.2

This release fixes bug when using sql template literal as left-hand side operator in where clauses. It also adds navigation property support for literal/constant values in composite keys for discriminator-style joins

Navigation Key Constants & Composite Keys

Constant values in navigation key definitions

Navigation properties now support literal/constant values in composite keys for discriminator-style joins:

entity.hasOne(e => e.activePrice, () => ProductPrice)
    .withForeignKey(e => [e.productId, e.isActive])
    .withPrincipalKey(e => [e.id, true]);
// → LEFT JOIN product_prices ON products.product_id = product_prices.id AND products.is_active = true

Supported constant types: number, string, boolean, and sql tagged template literals.

entity.hasOne(e => e.activePrice, () => ProductPrice)
    .withForeignKey(e => [e.productId, e.isActive])
    .withPrincipalKey(e => [e.id, sql`TRUE`]);
// sql`` fragments also work as the sole key value:
entity.hasOne(e => e.latest, () => Snapshot)
    .withForeignKey(e => e.snapshotId)
    .withPrincipalKey(e => sql`5`);

Composite keys via entity builder

Composite foreign keys defined through the entity builder (e.g. .withForeignKey(e => [e.col1, e.col2])) now work correctly. Previously, composite keys were silently broken — the comma-joined property names were passed as a single column lookup which would fail.

Fix: SqlFragment as field in comparison operators

Using sql tagged templates as the left-hand side of comparison operators (like, eq, gt, etc.) previously generated invalid double-quoted SQL:

// Before (broken): ""product"."internal_id"::varchar(255)" LIKE $1
like(sql`${p.internalId}::varchar(255)`, `%${search}%`)
// After (fixed):   "product"."internal_id"::varchar(255) LIKE $1

WhereComparisonBase.getDbColumnName() now detects SqlFragment and calls buildSql() instead of wrapping it in quotes. Affects all comparison operators: eq, ne, gt, lt, gte, lte, like, ilike, in, notIn, between.

Files Changed

  • src/entity/entity-base.ts — NavigationMetadata.foreignKey/principalKey changed from string to string[] (foreignKeys/principalKeys)
  • src/entity/entity-builder.ts — extractKeyPartsFromSelector() detects constants in array selectors via proxy identity check; supports SqlFragment (sql tagged templates) both as single values and in arrays; type signatures accept number | string | boolean | SqlFragment in arrays
  • src/entity/model-config.ts — Resolves composite key parts individually; creates literal FieldRefs with __isLiteral flag; filters literals from FK constraints
  • src/schema/navigation.ts — Preserves __LIT: prefix when converting literal FieldRefs to column name strings
  • src/query/join-utils.ts — New shared formatJoinValue() helper
  • src/query/query-builder.ts, src/query/grouped-query.ts, src/query/strategies/cte-collection-strategy.ts, src/query/strategies/lateral-collection-strategy.ts — All 12 JOIN builder locations updated to use formatJoinValue()
  • src/query/conditions.ts — WhereComparisonBase.getDbColumnName() handles SqlFragment field; getFieldRefs() extracts refs from SqlFragment fields; all buildSql() overrides pass context to getDbColumnName()