turbine-orm 0.25.0 → 0.26.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.
@@ -5,7 +5,7 @@
5
5
  * `import { … } from './query/index.js'` is a drop-in replacement for the
6
6
  * former monolithic `import { … } from './query.js'`.
7
7
  */
8
- export type { AggregateArgs, AggregateResult, ArrayFilter, ConnectOrCreateOp, CountArgs, CreateArgs, CreateDataInput, CreateManyArgs, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GroupByArgs, HavingClause, JsonFilter, NestedCreateOp, NestedUpdateOp, NestedUpdateOpItem, NestedUpsertOpItem, OmitResult, OrderByClause, OrderDirection, QueryResult, RelationDescriptor, RelationFilter, SelectResult, TextSearchFilter, TypedWithClause, UpdateArgs, UpdateDataInput, UpdateInput, UpdateManyArgs, UpdateOperatorInput, UpsertArgs, VectorDistanceFilter, VectorFilter, VectorMetric, VectorOrderBy, VectorOrderByDistance, WhereClause, WhereOperator, WhereValue, WithClause, WithOptions, WithResult, } from './types.js';
8
+ export type { AggregateArgs, AggregateResult, ArrayFilter, ConnectOrCreateOp, CountArgs, CreateArgs, CreateDataInput, CreateManyArgs, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GroupByArgs, HavingClause, JsonFilter, NestedCreateOp, NestedUpdateOp, NestedUpdateOpItem, NestedUpsertOpItem, OmitResult, OrderByClause, OrderDirection, QueryResult, RelationDescriptor, RelationFilter, RelationLoadStrategy, SelectResult, TextSearchFilter, TypedWithClause, UpdateArgs, UpdateDataInput, UpdateInput, UpdateManyArgs, UpdateOperatorInput, UpsertArgs, VectorDistanceFilter, VectorFilter, VectorMetric, VectorOrderBy, VectorOrderByDistance, WhereClause, WhereOperator, WhereValue, WithClause, WithOptions, WithResult, } from './types.js';
9
9
  export type { BuiltStatement, BulkInsertStatementInput, ColumnDefinitionInput, ColumnTypeInput, CreateIndexStatementInput, CreateTableStatementInput, Dialect, InsertStatementInput, UpsertStatementInput, } from '../dialect.js';
10
10
  export { postgresDialect } from '../dialect.js';
11
11
  export type { SqlCacheEntry } from './utils.js';
@@ -4,6 +4,20 @@
4
4
  * All exported type and interface definitions for the query builder module.
5
5
  */
6
6
  export type OrderDirection = 'asc' | 'desc';
7
+ /**
8
+ * How a query resolves its `with` relations.
9
+ *
10
+ * - `'join'` (default) — one SQL statement with correlated
11
+ * `json_agg(json_build_object(...))` subqueries. One round-trip; an index
12
+ * seek per parent row when the child FK is indexed.
13
+ * - `'batched'` — run the base query, then ONE flat follow-up query per
14
+ * relation (`WHERE fk = ANY($1)`), stitching children client-side. D levels
15
+ * cost D extra round-trips, but each is a single key-set lookup and rows come
16
+ * back flat — a win when FK columns are unindexed or result sets are huge.
17
+ *
18
+ * Precedence: per-query arg > client `relationLoadStrategy` config > `'join'`.
19
+ */
20
+ export type RelationLoadStrategy = 'join' | 'batched';
7
21
  /** Operator object for advanced where filtering */
8
22
  export interface WhereOperator<V = unknown> {
9
23
  /**
@@ -36,7 +50,21 @@ export interface WhereOperator<V = unknown> {
36
50
  * - A text search filter object ({ search, config? })
37
51
  * - A vector distance filter object ({ distance: { to, metric, lt } }) for pgvector columns
38
52
  */
39
- export type WhereValue<V = unknown> = V | WhereOperator<V> | JsonFilter | ArrayFilter | TextSearchFilter | VectorFilter | null;
53
+ export type WhereValue<V = unknown> = (V extends Array<infer U> ? TypedRelationFilter<U> : V extends Date ? V : V extends object ? V | TypedToOneFilter<V> | WhereClause<V> : V) | WhereOperator<V> | JsonFilter | ArrayFilter | TextSearchFilter | VectorFilter | null;
54
+ /** Relation filter on a to-many relation property. */
55
+ export interface TypedRelationFilter<U> {
56
+ some?: WhereClause<U>;
57
+ every?: WhereClause<U>;
58
+ none?: WhereClause<U>;
59
+ }
60
+ /**
61
+ * Relation filter on a to-one relation property. A bare object on a to-one
62
+ * relation key is also accepted at runtime (implicit `is`, Prisma-compatible).
63
+ */
64
+ export interface TypedToOneFilter<V> {
65
+ is?: WhereClause<V>;
66
+ isNot?: WhereClause<V>;
67
+ }
40
68
  /**
41
69
  * Where clause type: each field can be a plain value, null, or operator object.
42
70
  * Special keys: OR for disjunctive conditions.
@@ -192,7 +220,7 @@ export type FieldResult<T, S extends Record<string, boolean> | undefined, O exte
192
220
  * Short-circuits to plain WithResult when neither select nor omit is provided,
193
221
  * preserving exact type equality with the pre-narrowing era.
194
222
  */
195
- export type QueryResult<T, R extends object, W, S extends Record<string, boolean> | undefined, O extends Record<string, boolean> | undefined> = S extends undefined ? O extends undefined ? WithResult<T, R, W> : O extends Record<string, boolean> ? Omit<WithResult<T, R, W>, Extract<keyof T, TrueKeys<O>>> : WithResult<T, R, W> : S extends Record<string, boolean> ? Pick<WithResult<T, R, W>, Extract<keyof T, TrueKeys<S>> | Exclude<keyof WithResult<T, R, W>, keyof T>> : WithResult<T, R, W>;
223
+ export type QueryResult<T, R extends object, W, S extends Record<string, boolean> | undefined, O extends Record<string, boolean> | undefined> = S extends undefined ? O extends undefined ? WithResult<T, R, W> : O extends Record<string, boolean> ? Omit<WithResult<T, R, W>, Exclude<Extract<keyof T, TrueKeys<O>>, keyof W>> : WithResult<T, R, W> : S extends Record<string, boolean> ? Pick<WithResult<T, R, W>, Extract<keyof T, TrueKeys<S>> | Exclude<keyof WithResult<T, R, W>, keyof T> | Extract<keyof W, keyof WithResult<T, R, W>>> : WithResult<T, R, W>;
196
224
  export interface FindUniqueArgs<T, R extends object = {}, W extends TypedWithClause<R> = TypedWithClause<R>, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined> {
197
225
  where: WhereClause<T>;
198
226
  select?: S;
@@ -200,6 +228,8 @@ export interface FindUniqueArgs<T, R extends object = {}, W extends TypedWithCla
200
228
  with?: W;
201
229
  /** Query timeout in milliseconds. Rejects with an error if exceeded. */
202
230
  timeout?: number;
231
+ /** Override the client's relation-loading strategy for this query. See {@link RelationLoadStrategy}. */
232
+ relationLoadStrategy?: RelationLoadStrategy;
203
233
  }
204
234
  export interface FindManyArgs<T, R extends object = {}, W extends TypedWithClause<R> = TypedWithClause<R>, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined> {
205
235
  where?: WhereClause<T>;
@@ -217,6 +247,8 @@ export interface FindManyArgs<T, R extends object = {}, W extends TypedWithClaus
217
247
  distinct?: (keyof T & string)[];
218
248
  /** Query timeout in milliseconds. Rejects with an error if exceeded. */
219
249
  timeout?: number;
250
+ /** Override the client's relation-loading strategy for this query. See {@link RelationLoadStrategy}. */
251
+ relationLoadStrategy?: RelationLoadStrategy;
220
252
  }
221
253
  export interface FindManyStreamArgs<T, R extends object = {}, W extends TypedWithClause<R> = TypedWithClause<R>, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined> extends FindManyArgs<T, R, W, S, O> {
222
254
  /**
@@ -505,6 +537,9 @@ export interface RelationFilter {
505
537
  some?: Record<string, unknown>;
506
538
  every?: Record<string, unknown>;
507
539
  none?: Record<string, unknown>;
540
+ /** To-one relation match (bare objects on to-one keys are implicit `is`). */
541
+ is?: Record<string, unknown>;
542
+ isNot?: Record<string, unknown>;
508
543
  }
509
544
  /** JSONB query operators for where clauses */
510
545
  export interface JsonFilter {
@@ -65,3 +65,19 @@ export declare const OPERATOR_KEYS: Set<string>;
65
65
  * For multi-column: `"alias"."col_a" = "parent"."ref_a" AND "alias"."col_b" = "parent"."ref_b"`
66
66
  */
67
67
  export declare function buildCorrelation(leftRef: string, leftColumns: string | string[], rightRef: string, rightColumns: string | string[]): string;
68
+ /**
69
+ * Parse a database date-time string deterministically.
70
+ *
71
+ * Postgres `timestamp` (without time zone) values arrive with no offset —
72
+ * both from the driver and from `json_agg`/`json_build_object` subquery JSON
73
+ * (`2026-07-07T17:15:41.896`). JavaScript's `new Date()` interprets such
74
+ * strings in the SERVER'S LOCAL TIME ZONE, so the same row parses to a
75
+ * different instant depending on where the code runs. The universal ORM
76
+ * convention (Prisma, Rails, Django) is to treat offset-less timestamps as
77
+ * UTC — that is also the only interpretation that round-trips: Postgres
78
+ * stores exactly the wall-clock fields you sent.
79
+ *
80
+ * Strings that carry an explicit offset (`timestamptz` output) are parsed
81
+ * as-is.
82
+ */
83
+ export declare function parseDbDate(value: string): Date;
@@ -129,3 +129,38 @@ export function buildCorrelation(leftRef, leftColumns, rightRef, rightColumns) {
129
129
  .map((col, i) => `${leftRef}.${quoteIdent(col)} = ${rightRef}.${quoteIdent(rightCols[i])}`)
130
130
  .join(' AND ');
131
131
  }
132
+ /**
133
+ * Matches an explicit timezone suffix on a date-time string: a trailing `Z`
134
+ * or a `±HH`, `±HHMM`, `±HH:MM` offset.
135
+ */
136
+ const TZ_SUFFIX_RE = /(?:Z|[+-]\d{2}(?::?\d{2})?)$/;
137
+ /**
138
+ * Parse a database date-time string deterministically.
139
+ *
140
+ * Postgres `timestamp` (without time zone) values arrive with no offset —
141
+ * both from the driver and from `json_agg`/`json_build_object` subquery JSON
142
+ * (`2026-07-07T17:15:41.896`). JavaScript's `new Date()` interprets such
143
+ * strings in the SERVER'S LOCAL TIME ZONE, so the same row parses to a
144
+ * different instant depending on where the code runs. The universal ORM
145
+ * convention (Prisma, Rails, Django) is to treat offset-less timestamps as
146
+ * UTC — that is also the only interpretation that round-trips: Postgres
147
+ * stores exactly the wall-clock fields you sent.
148
+ *
149
+ * Strings that carry an explicit offset (`timestamptz` output) are parsed
150
+ * as-is.
151
+ */
152
+ export function parseDbDate(value) {
153
+ // Date-only values (`2026-07-07`, from `date` columns in json_agg output)
154
+ // have no time to zone-pin — and their `-07` tail must not be read as an
155
+ // offset. JS parses bare ISO dates as UTC midnight already.
156
+ if (!value.includes(':'))
157
+ return new Date(value);
158
+ if (TZ_SUFFIX_RE.test(value)) {
159
+ // JS Date can't parse colon-less (`-0430`) or bare-hour (`+02`) offsets —
160
+ // normalize both to `±HH:MM`. Postgres emits the bare-hour form for
161
+ // whole-hour zones in some text outputs.
162
+ return new Date(value.replace(/([+-]\d{2})(\d{2})$/, '$1:$2').replace(/([+-]\d{2})$/, '$1:00'));
163
+ }
164
+ // normalize `YYYY-MM-DD HH:MM:SS` (driver form) to ISO before pinning UTC
165
+ return new Date(`${value.replace(' ', 'T')}Z`);
166
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turbine-orm",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "Postgres-native TypeScript ORM — runs on Neon, Vercel Postgres, Cloudflare, Supabase. Streaming cursors, typed errors, single-query nested relations. One dependency, no WASM engine",
5
5
  "type": "module",
6
6
  "exports": {