turbine-orm 0.30.0 → 0.32.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.
@@ -339,6 +339,27 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
339
339
  buildCount(args?: CountArgs<T>): DeferredQuery<number>;
340
340
  groupBy(args: GroupByArgs<T>): Promise<Record<string, unknown>[]>;
341
341
  buildGroupBy(args: GroupByArgs<T>): DeferredQuery<Record<string, unknown>[]>;
342
+ /**
343
+ * Validate a JSON-path target (group key or aggregate target) in groupBy:
344
+ * the field must resolve to a real json/jsonb column and the path must be a
345
+ * non-empty array of keys/indexes. Returns the resolved snake_case column.
346
+ */
347
+ private resolveJsonPathTarget;
348
+ /**
349
+ * Build the `distinctOn` row source for groupBy (PostgreSQL only: other
350
+ * engines throw {@link UnsupportedFeatureError} E017):
351
+ *
352
+ * ```sql
353
+ * (SELECT DISTINCT ON ("c1") * FROM "table"<WHERE> ORDER BY "c1", <orderBy>) AS "table"
354
+ * ```
355
+ *
356
+ * The wrapper is aliased as the table name so every outer expression (group
357
+ * keys, aggregates, HAVING, ORDER BY) is byte-identical to the plain path.
358
+ * `distinctOn.orderBy` is required (it decides which row survives) and
359
+ * supports plain columns, {@link OrderBySpec} nulls, and JSON-path specs;
360
+ * JSON paths push their text[] param here, after the WHERE params.
361
+ */
362
+ private buildDistinctOnSource;
342
363
  /**
343
364
  * Build the SQL fragments for a {@link HavingClause}.
344
365
  *
@@ -349,6 +370,12 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
349
370
  * comparison value is pushed onto the shared `params` array and referenced by
350
371
  * a `$N` placeholder via {@link buildHavingNumericClauses} — there is no string
351
372
  * interpolation of user values.
373
+ *
374
+ * `jsonAggExprs` (from {@link buildGroupBy}) maps `alias:aggKey` to the
375
+ * exact aggregate expression a JSON-path aggregate emitted in SELECT
376
+ * (including its already-bound path placeholder), so HAVING on a JSON-path
377
+ * aggregate alias reuses the same expression instead of resolving the alias
378
+ * as a column.
352
379
  */
353
380
  private buildHavingClauses;
354
381
  /**
@@ -436,7 +463,12 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
436
463
  */
437
464
  private collectRelationFilterParams;
438
465
  private collectRelFilterParams;
439
- /** Collect params from operator clauses. Mirrors buildOperatorClauses. */
466
+ /**
467
+ * Collect params from operator clauses. Mirrors buildOperatorClauses:
468
+ * {@link ColumnRef} values compile into the SQL text, so they push NOTHING -
469
+ * but they re-run the same validation (unknown ref / insensitive mode) so a
470
+ * warmed cache can never skip a check the build path enforces.
471
+ */
440
472
  private collectOperatorParams;
441
473
  /**
442
474
  * Collect params from JSON filter. Mirrors buildJsonFilterClauses exactly:
@@ -448,10 +480,10 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
448
480
  /** Collect params from array filter. Mirrors buildArrayFilterClauses. */
449
481
  private collectArrayFilterParams;
450
482
  /**
451
- * Collect params for an orderBy clause. Only vector KNN ordering pushes a
452
- * param (the `$n::vector` query vector); plain direction ordering is
453
- * parameterless. Mirrors buildOrderBy's push order exactly so the cached-SQL
454
- * param re-collection stays in lockstep.
483
+ * Collect params for an orderBy clause. Vector KNN ordering pushes the
484
+ * `$n::vector` query vector and JSON-path ordering pushes its text[] path;
485
+ * plain direction ordering is parameterless. Mirrors buildOrderBy's push
486
+ * order exactly so the cached-SQL param re-collection stays in lockstep.
455
487
  */
456
488
  private collectOrderByParams;
457
489
  /**
@@ -611,9 +643,28 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
611
643
  * differently-shaped wheres would share one cached SQL string.
612
644
  */
613
645
  private fingerprintAliasWhere;
646
+ /**
647
+ * Validate a `{ col }` column reference against its table and return the
648
+ * resolved snake_case column name. Shared by the SQL-build path
649
+ * ({@link buildOperatorClauses}) and the cache-hit param-collect path
650
+ * (`collectOperatorParams`) so both always throw identically: a warmed
651
+ * cache can never skip the check.
652
+ */
653
+ private resolveColumnRef;
654
+ /**
655
+ * Compile a `{ col }` reference to its quoted, prefix-matched SQL identifier.
656
+ * NO param is bound: the referenced column is part of the SQL text (and of
657
+ * the where fingerprint, see {@link fingerprintOperatorShape}).
658
+ */
659
+ private columnRefSql;
614
660
  /**
615
661
  * Build SQL clauses for a single operator object on a column.
616
662
  * Each operator key becomes its own clause, all ANDed together.
663
+ *
664
+ * `equals`/`not`/`gt`/`gte`/`lt`/`lte` also accept a {@link ColumnRef}
665
+ * (`{ col: 'otherField' }`) which compiles to a column-to-column comparison
666
+ * against `refCtx`: no param bound, so `collectOperatorParams` mirrors by
667
+ * pushing nothing and the referenced name lives in the fingerprint.
617
668
  */
618
669
  private buildOperatorClauses;
619
670
  /**
@@ -647,6 +698,33 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
647
698
  * {@link UnsupportedFeatureError} (E017) instead of broken SQL.
648
699
  */
649
700
  private nullsSuffix;
701
+ /**
702
+ * Resolve an orderBy key to its snake_case column via the table's columnMap
703
+ * (camelToSnake fallback), throwing the SAME unknown-field E003 the top-level
704
+ * where path uses. Shared by top-level JSON-path ordering and every nested
705
+ * relation orderBy path so nested orderBy accepts exactly what top-level
706
+ * accepts (the 0.30.x bug: nested orderBy skipped the columnMap and rejected
707
+ * camelCase-named DB columns like "sortOrder").
708
+ */
709
+ private resolveOrderByColumn;
710
+ /**
711
+ * Validate a {@link JsonPathOrderBy} entry: column must exist AND be
712
+ * json/jsonb, path must be a non-empty array of keys/indexes: and return
713
+ * the resolved column. Shared by the SQL-build path
714
+ * ({@link buildJsonPathOrderEntry}) and the cache-hit param-collect mirrors
715
+ * so both always throw identically.
716
+ */
717
+ private validateJsonPathOrderBy;
718
+ /**
719
+ * Compile one {@link JsonPathOrderBy} entry:
720
+ * `("col" #>> $n::text[])::numeric ASC`: the numeric cast only with
721
+ * `type: 'numeric'` (default is text comparison), the extraction routed
722
+ * through the dialect's JSON hook exactly like the JSON where-filters, the
723
+ * path bound as ONE text[] param (mirrored by the order-param collectors).
724
+ * `prefix` scopes the column (`''` top-level, `t0.` inside a relation
725
+ * subquery).
726
+ */
727
+ private buildJsonPathOrderEntry;
650
728
  /**
651
729
  * Compile a relation ordering term. For a to-many relation the only allowed
652
730
  * key is `_count`, which becomes a correlated `COUNT(*)` subquery. For a
@@ -655,8 +733,77 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
655
733
  *
656
734
  * Validation: relation must exist (E005); to-many only allows `_count`, and
657
735
  * to-one only allows real target columns (E003).
736
+ *
737
+ * `ctx` generalizes the term beyond the root table: inside a relation
738
+ * subquery's orderBy the relations live on the TARGET table's metadata and
739
+ * the correlation parent is the relation's alias, not `this.table`.
658
740
  */
659
741
  private buildRelationOrderBy;
742
+ /**
743
+ * Validate a {@link RelationPickOrderBy} entry's scope and shape. Shared by
744
+ * the SQL-build path ({@link buildRelationPickOrderBy}) and the cache-hit
745
+ * param-collect mirror ({@link collectRelationPickOrderParams}) so both
746
+ * always throw identically:
747
+ *
748
+ * - `nested` (inside a relation subquery's orderBy or a pick.orderBy):
749
+ * top-level findMany only in this release (E003),
750
+ * - manyToMany: not supported (E003 naming the limitation),
751
+ * - to-one: order by the target column directly instead (E003),
752
+ * - `pick.orderBy` is REQUIRED (deterministic row choice),
753
+ * - `by` must be a target column name or a `{ field, path }` JSON-path spec.
754
+ */
755
+ private pickOrderNestedError;
756
+ private validatePickOrderBy;
757
+ /**
758
+ * Compile a {@link RelationPickOrderBy} term: a correlated scalar subquery
759
+ * that picks ONE related row (`ORDER BY <pick.orderBy> LIMIT 1`, optionally
760
+ * filtered by `pick.where` and the target's global filter) and surfaces one
761
+ * value from it (a plain target column or a JSON-path extraction) as the
762
+ * parent ORDER BY key:
763
+ *
764
+ * ```sql
765
+ * (SELECT ord0."data" #>> $1::text[] FROM "versions" ord0
766
+ * WHERE ord0."instance_id" = "instances"."id" AND ord0."is_current" = $2
767
+ * ORDER BY ord0."created_at" DESC LIMIT 1) ASC NULLS LAST
768
+ * ```
769
+ *
770
+ * Param-push order (mirrored EXACTLY by
771
+ * {@link collectRelationPickOrderParams}): `by` JSON path (if any) →
772
+ * target global filter → `pick.where` → `pick.orderBy` JSON paths.
773
+ */
774
+ private buildRelationPickOrderBy;
775
+ /**
776
+ * Param-collect mirror of {@link buildRelationPickOrderBy}: re-runs the same
777
+ * validation (a warmed cache can never skip it), then pushes in the same
778
+ * order: `by` JSON path → target global filter → `pick.where` →
779
+ * `pick.orderBy` JSON paths.
780
+ */
781
+ private collectRelationPickOrderParams;
782
+ /**
783
+ * Compile the ORDER BY terms of a relation `with` clause against the
784
+ * relation's table alias. One unified path for every relation shape
785
+ * (hasMany / manyToMany / belongsTo / hasOne) supporting exactly what the
786
+ * top-level orderBy accepts at this level:
787
+ *
788
+ * - scalar columns via columnMap resolution (camelToSnake fallback) with
789
+ * {@link OrderBySpec} nulls placement,
790
+ * - {@link JsonPathOrderBy} entries (path bound as one text[] param),
791
+ * - relation ordering on the TARGET's relations (`_count` for to-many, a
792
+ * target column for to-one), correlated to the relation alias,
793
+ * - vector KNN ordering stays top-level-only (E003, same as before).
794
+ *
795
+ * Param pushes (JSON paths, relation-order global filters) MUST be mirrored,
796
+ * in the same order, by {@link collectRelationOrderParams}.
797
+ */
798
+ private buildRelationOrderClause;
799
+ /**
800
+ * Param-collect mirror of {@link buildRelationOrderClause}: JSON-path
801
+ * entries push their path (one text[] param each); relation-order entries
802
+ * mirror {@link collectOrderByParams}' relation branch (count / to-one
803
+ * global-filter params); scalar entries push nothing but re-run the same
804
+ * column validation so a warmed cache can never skip it.
805
+ */
806
+ private collectRelationOrderParams;
660
807
  /**
661
808
  * Build a correlated `(SELECT COUNT(*) …)` scalar subquery for a to-many
662
809
  * relation, correlated to `parentRef`. hasMany counts child rows via the FK;
@@ -929,6 +1076,13 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
929
1076
  * Used to detect JSONB/array columns for specialized operators.
930
1077
  * Uses pre-computed Map for O(1) lookup instead of linear scan.
931
1078
  */
1079
+ /**
1080
+ * Case-insensitive json/jsonb column-type check. Postgres reports lowercase
1081
+ * udt_names, but SQLite/MySQL introspection surfaces the DECLARED type
1082
+ * (e.g. `JSON`), so every JSON-feature gate compares through this predicate
1083
+ * — build and collect sides alike, keeping the SQL-cache lockstep.
1084
+ */
1085
+ private isJsonColumnType;
932
1086
  private getColumnPgType;
933
1087
  /**
934
1088
  * Get the Postgres base element type for an array column.
@@ -953,6 +1107,18 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
953
1107
  * stays byte-identical to {@link collectJsonFilterParams}.
954
1108
  */
955
1109
  private buildJsonFilterClauses;
1110
+ /**
1111
+ * Bind value for a JSON path parameter, encoded per dialect. PostgreSQL's
1112
+ * `#>>` takes a `text[]` (the segments as strings — or `nativeForm` when the
1113
+ * caller has a specific native binding, e.g. JsonFilter's raw path array).
1114
+ * Every other engine's JSON function (`json_extract` / `JSON_EXTRACT` /
1115
+ * `JSON_VALUE`) takes a `'$'`-rooted JSONPath STRING: binding the raw array
1116
+ * would arrive as `'["a"]'` (the driver shims JSON.stringify non-primitive
1117
+ * params) and fail at runtime with the engine's bad-JSON-path error. The
1118
+ * encoded path stays a bound parameter — never spliced into SQL text — so
1119
+ * the build/collect param mirrors stay in lockstep and injection-safe.
1120
+ */
1121
+ private jsonPathParam;
956
1122
  /**
957
1123
  * Cast an extracted JSON path text value to a numeric type for range
958
1124
  * comparison. PostgreSQL uses `(expr)::numeric` (exact — the right way to