turbine-orm 0.9.0 → 0.9.2

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.
@@ -0,0 +1,13 @@
1
+ /**
2
+ * turbine-orm — Query builder barrel
3
+ *
4
+ * Re-exports every public symbol from the query submodules so that
5
+ * `import { … } from './query/index.js'` is a drop-in replacement for the
6
+ * former monolithic `import { … } from './query.js'`.
7
+ */
8
+ export type { AggregateArgs, AggregateResult, ArrayFilter, CountArgs, CreateArgs, CreateManyArgs, DeleteArgs, DeleteManyArgs, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GroupByArgs, JsonFilter, OrderDirection, RelationDescriptor, RelationFilter, TypedWithClause, UpdateArgs, UpdateInput, UpdateManyArgs, UpdateOperatorInput, UpsertArgs, WhereClause, WhereOperator, WhereValue, WithClause, WithOptions, WithResult, } from './types.js';
9
+ export type { SqlCacheEntry } from './utils.js';
10
+ export { escapeLike, escSingleQuote, fnv1a64Hex, LRUCache, OPERATOR_KEYS, quoteIdent, sqlToPreparedName, } from './utils.js';
11
+ export type { DeferredQuery, MiddlewareFn, QueryInterfaceOptions } from './builder.js';
12
+ export { QueryInterface } from './builder.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * turbine-orm — Query builder barrel
3
+ *
4
+ * Re-exports every public symbol from the query submodules so that
5
+ * `import { … } from './query/index.js'` is a drop-in replacement for the
6
+ * former monolithic `import { … } from './query.js'`.
7
+ */
8
+ export { escapeLike, escSingleQuote, fnv1a64Hex, LRUCache, OPERATOR_KEYS, quoteIdent, sqlToPreparedName, } from './utils.js';
9
+ export { QueryInterface } from './builder.js';
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,365 @@
1
+ /**
2
+ * turbine-orm — Query builder types
3
+ *
4
+ * All exported type and interface definitions for the query builder module.
5
+ */
6
+ export type OrderDirection = 'asc' | 'desc';
7
+ /** Operator object for advanced where filtering */
8
+ export interface WhereOperator<V = unknown> {
9
+ gt?: V;
10
+ gte?: V;
11
+ lt?: V;
12
+ lte?: V;
13
+ not?: V | null;
14
+ in?: V[];
15
+ notIn?: V[];
16
+ contains?: string;
17
+ startsWith?: string;
18
+ endsWith?: string;
19
+ /** Set to 'insensitive' to use ILIKE instead of LIKE for string comparisons */
20
+ mode?: 'default' | 'insensitive';
21
+ }
22
+ /**
23
+ * A where value can be:
24
+ * - A plain value (equality: column = $N)
25
+ * - null (column IS NULL)
26
+ * - An operator object ({ gt: 5, lte: 10 })
27
+ * - A JSONB filter object ({ contains, equals, path, hasKey })
28
+ * - An array filter object ({ has, hasEvery, hasSome, isEmpty })
29
+ */
30
+ export type WhereValue<V = unknown> = V | WhereOperator<V> | JsonFilter | ArrayFilter | null;
31
+ /**
32
+ * Where clause type: each field can be a plain value, null, or operator object.
33
+ * Special keys: OR for disjunctive conditions.
34
+ * Relation names can be used with some/every/none sub-filters.
35
+ */
36
+ export type WhereClause<T> = {
37
+ [K in keyof T]?: WhereValue<T[K]>;
38
+ } & {
39
+ OR?: WhereClause<T>[];
40
+ AND?: WhereClause<T>[];
41
+ NOT?: WhereClause<T>;
42
+ /** Relation filters — keyed by relation name, value is { some, every, none } */
43
+ [relationName: string]: unknown;
44
+ };
45
+ /**
46
+ * Unparameterized with clause — accepts any relation name.
47
+ * Used internally by the query builder at runtime.
48
+ */
49
+ export interface WithClause {
50
+ [relation: string]: true | WithOptions;
51
+ }
52
+ /**
53
+ * Relation-aware with clause. When R (the relations map) is provided,
54
+ * only keys from R are autocompleted. Used in public method signatures
55
+ * so the compiler can narrow the return type.
56
+ *
57
+ * For typed maps, each relation accepts either `true` (default include) or a
58
+ * {@link WithOptions} object whose nested `with` is keyed against the relation
59
+ * target's own relations interface — this is what enables deep
60
+ * `WithResult` inference.
61
+ */
62
+ export type TypedWithClause<R extends object = {}> = [keyof R] extends [never] ? WithClause : {
63
+ [K in keyof R]?: true | WithOptions<RelationRelations<R[K]> & object>;
64
+ };
65
+ /**
66
+ * Options for an included relation.
67
+ *
68
+ * Generic over `NestedR` — the relations interface of the *target* entity —
69
+ * so the nested `with` clause is autocompleted with the correct relation keys
70
+ * and so {@link WithResult} can recursively infer the return type. Defaults to
71
+ * `{}` (no relation suggestions) for callers that use the unparameterized
72
+ * {@link WithClause}.
73
+ */
74
+ export interface WithOptions<NestedR extends object = {}> {
75
+ with?: TypedWithClause<NestedR>;
76
+ where?: Record<string, unknown>;
77
+ orderBy?: Record<string, OrderDirection>;
78
+ limit?: number;
79
+ /** Only include these fields from the relation */
80
+ select?: Record<string, boolean>;
81
+ /** Exclude these fields from the relation */
82
+ omit?: Record<string, boolean>;
83
+ }
84
+ /**
85
+ * A relation descriptor used by generated `*Relations` interfaces to make deep
86
+ * `with` clause inference work. It bundles three pieces of information that
87
+ * `WithResult` needs to recurse through nested relations:
88
+ *
89
+ * - `__target` — the target entity type (e.g. `Post`)
90
+ * - `__cardinality`— `'many'` for hasMany, `'one'` for belongsTo / hasOne
91
+ * - `__relations` — the target entity's relations interface (for further recursion)
92
+ *
93
+ * **Generator contract (Track 3):** the code generator emits `*Relations`
94
+ * interfaces in the following shape so that `WithResult` can walk arbitrary
95
+ * nesting depth:
96
+ *
97
+ * ```ts
98
+ * export interface UserRelations {
99
+ * posts: RelationDescriptor<Post, 'many', PostRelations>;
100
+ * profile: RelationDescriptor<Profile, 'one', ProfileRelations>;
101
+ * }
102
+ * ```
103
+ *
104
+ * The brand fields are phantom — they exist only for type inference and have
105
+ * no runtime representation. The runtime always sees the parsed entity values
106
+ * (arrays for hasMany, single object or null for belongsTo / hasOne) — see the
107
+ * cardinality projection inside {@link WithResult}.
108
+ *
109
+ * **Backward compatibility:** legacy generated code emitted bare types
110
+ * (`posts: Post[]`, `profile: Profile | null`). `WithResult` still accepts that
111
+ * shape via a fallback branch — it just cannot recurse into nested `with` for
112
+ * those relations until the generator is updated.
113
+ *
114
+ * @typeParam Target - The entity type the relation points at.
115
+ * @typeParam Cardinality - `'many'` (array) or `'one'` (single object | null).
116
+ * @typeParam Relations - The target entity's own `*Relations` interface, or
117
+ * `{}` if the target has no relations of its own.
118
+ */
119
+ export interface RelationDescriptor<Target, Cardinality extends 'one' | 'many', Relations extends object = {}> {
120
+ readonly __target?: Target;
121
+ readonly __cardinality?: Cardinality;
122
+ readonly __relations?: Relations;
123
+ }
124
+ /** Extract the target entity from a relation descriptor or bare relation type. */
125
+ type RelationTarget<Rel> = Rel extends RelationDescriptor<infer Target, infer _C, infer _R> ? Target : Rel extends Array<infer Item> ? Item : Rel extends infer One | null ? One : Rel;
126
+ /** Extract the target's relations map from a relation descriptor (or `{}` for bare types). */
127
+ type RelationRelations<Rel> = Rel extends RelationDescriptor<infer _T, infer _C, infer R> ? R : {};
128
+ /** Project the target type into its runtime shape (array for many, single for one). */
129
+ type ApplyCardinality<Rel, Resolved> = Rel extends RelationDescriptor<infer _T, infer Cardinality, infer _R> ? Cardinality extends 'many' ? Resolved[] : Resolved | null : Rel extends Array<infer _Item> ? Resolved[] : Resolved | null;
130
+ /**
131
+ * Compute the result type when relations are included via `with`.
132
+ *
133
+ * Recursively walks the `with` clause, looking up each relation in `R` and:
134
+ *
135
+ * 1. If the relation is included with `true` (or no nested `with`), the
136
+ * relation's bare resolved type is grafted onto `T` (e.g. `posts: Post[]`).
137
+ * 2. If the relation is included with a nested `with: {...}`, the recursion
138
+ * looks up the target entity's relations interface (via the
139
+ * {@link RelationDescriptor} brand fields the generator emits) and
140
+ * recursively applies `WithResult` to the nested target. Cardinality is
141
+ * re-applied at each level so a hasMany relation stays an array even after
142
+ * deep nesting.
143
+ *
144
+ * **When `R` is `{}` (the default):** the recursion short-circuits and the
145
+ * function returns plain `T` — preserving the existing untyped escape hatch
146
+ * for callers that have not generated typed clients.
147
+ *
148
+ * **When `R` does not contain the requested relations:** the unknown keys are
149
+ * ignored (the runtime will throw a `RelationError`, but the type system stays
150
+ * permissive so the legacy `WithClause` index signature still typechecks).
151
+ *
152
+ * @typeParam T - Base entity type (e.g. `User`).
153
+ * @typeParam R - Relations map for `T` (e.g.
154
+ * `{ posts: RelationDescriptor<Post, 'many', PostRelations>; ... }`).
155
+ * Legacy bare shapes (`{ posts: Post[]; profile: Profile | null }`)
156
+ * are also accepted, but cannot recurse beyond one level.
157
+ * @typeParam W - The `with` clause the user passed (e.g. `{ posts: true }` or
158
+ * `{ posts: { with: { comments: true } } }`).
159
+ */
160
+ export type WithResult<T, R extends object, W> = [keyof R] extends [never] ? T : W extends object ? [keyof W & keyof R] extends [never] ? T : T & {
161
+ [K in keyof W & keyof R]: W[K] extends true ? ApplyCardinality<R[K], RelationTarget<R[K]>> : W[K] extends {
162
+ with?: infer NestedW;
163
+ } ? NestedW extends object ? ApplyCardinality<R[K], WithResult<RelationTarget<R[K]>, RelationRelations<R[K]> & object, NestedW>> : ApplyCardinality<R[K], RelationTarget<R[K]>> : ApplyCardinality<R[K], RelationTarget<R[K]>>;
164
+ } : T;
165
+ export interface FindUniqueArgs<T, R extends object = {}, W extends TypedWithClause<R> = TypedWithClause<R>> {
166
+ where: WhereClause<T>;
167
+ select?: Record<string, boolean>;
168
+ omit?: Record<string, boolean>;
169
+ with?: W;
170
+ /** Query timeout in milliseconds. Rejects with an error if exceeded. */
171
+ timeout?: number;
172
+ }
173
+ export interface FindManyArgs<T, R extends object = {}, W extends TypedWithClause<R> = TypedWithClause<R>> {
174
+ where?: WhereClause<T>;
175
+ select?: Record<string, boolean>;
176
+ omit?: Record<string, boolean>;
177
+ orderBy?: Record<string, OrderDirection>;
178
+ limit?: number;
179
+ offset?: number;
180
+ with?: W;
181
+ /** Cursor-based pagination: start after this row */
182
+ cursor?: Partial<T>;
183
+ /** Number of records to take (used with cursor) */
184
+ take?: number;
185
+ /** De-duplicate results by specified fields */
186
+ distinct?: (keyof T & string)[];
187
+ /** Query timeout in milliseconds. Rejects with an error if exceeded. */
188
+ timeout?: number;
189
+ }
190
+ export interface FindManyStreamArgs<T, R extends object = {}, W extends TypedWithClause<R> = TypedWithClause<R>> extends FindManyArgs<T, R, W> {
191
+ /**
192
+ * Number of rows to fetch per internal FETCH batch (default: 1000).
193
+ *
194
+ * Trade-off: larger batches reduce network round-trips (important for
195
+ * high-latency connections like Neon) but increase per-batch memory.
196
+ * At 1000 rows x ~500 bytes/row the default is ~500 KB per batch.
197
+ *
198
+ * When the total result set fits within one batch, the stream avoids
199
+ * cursor overhead entirely (no BEGIN / DECLARE / CLOSE / COMMIT) by
200
+ * using a speculative `SELECT ... LIMIT batchSize+1` first.
201
+ */
202
+ batchSize?: number;
203
+ }
204
+ export interface CreateArgs<T> {
205
+ data: Partial<T>;
206
+ /** Query timeout in milliseconds. Rejects with an error if exceeded. */
207
+ timeout?: number;
208
+ }
209
+ export interface CreateManyArgs<T> {
210
+ data: Partial<T>[];
211
+ /** When true, adds ON CONFLICT DO NOTHING to skip duplicate rows */
212
+ skipDuplicates?: boolean;
213
+ /** Query timeout in milliseconds. Rejects with an error if exceeded. */
214
+ timeout?: number;
215
+ }
216
+ /**
217
+ * Atomic update operators for a field.
218
+ *
219
+ * `set` works on any type; `increment`, `decrement`, `multiply`, and `divide`
220
+ * are only valid on numeric fields. They generate SQL like
221
+ * `col = col + $n` (and the corresponding `-`, `*`, `/` variants) instead of
222
+ * plain absolute assignments, so they are safe against concurrent writers —
223
+ * the database performs the math atomically.
224
+ *
225
+ * @example
226
+ * db.posts.update({ where: { id: 5 }, data: { viewCount: { increment: 1 } } });
227
+ */
228
+ export type UpdateOperatorInput<V> = {
229
+ set: V;
230
+ } | (V extends number ? {
231
+ increment: number;
232
+ } : never) | (V extends number ? {
233
+ decrement: number;
234
+ } : never) | (V extends number ? {
235
+ multiply: number;
236
+ } : never) | (V extends number ? {
237
+ divide: number;
238
+ } : never);
239
+ /**
240
+ * Update data — each field can be a plain value or an atomic operator object.
241
+ * Back-compatible with `Partial<T>`: plain values still typecheck unchanged.
242
+ */
243
+ export type UpdateInput<T> = {
244
+ [K in keyof T]?: T[K] | UpdateOperatorInput<T[K]>;
245
+ };
246
+ export interface UpdateArgs<T> {
247
+ where: WhereClause<T>;
248
+ data: UpdateInput<T>;
249
+ /** Query timeout in milliseconds. Rejects with an error if exceeded. */
250
+ timeout?: number;
251
+ /**
252
+ * Opt in to running this mutation when `where` resolves to an empty
253
+ * predicate (e.g. `{}` or `{ id: undefined }`). Default `false` — an
254
+ * empty predicate throws `ValidationError` to catch the common case of
255
+ * a filter value accidentally being `undefined`. Set this to `true` only
256
+ * when an unconditional mutation is the intended behaviour.
257
+ */
258
+ allowFullTableScan?: boolean;
259
+ }
260
+ export interface UpdateManyArgs<T> {
261
+ where: WhereClause<T>;
262
+ data: UpdateInput<T>;
263
+ /** Query timeout in milliseconds. Rejects with an error if exceeded. */
264
+ timeout?: number;
265
+ /** See {@link UpdateArgs.allowFullTableScan}. */
266
+ allowFullTableScan?: boolean;
267
+ }
268
+ export interface DeleteArgs<T> {
269
+ where: WhereClause<T>;
270
+ /** Query timeout in milliseconds. Rejects with an error if exceeded. */
271
+ timeout?: number;
272
+ /** See {@link UpdateArgs.allowFullTableScan}. */
273
+ allowFullTableScan?: boolean;
274
+ }
275
+ export interface DeleteManyArgs<T> {
276
+ where: WhereClause<T>;
277
+ /** Query timeout in milliseconds. Rejects with an error if exceeded. */
278
+ timeout?: number;
279
+ /** See {@link UpdateArgs.allowFullTableScan}. */
280
+ allowFullTableScan?: boolean;
281
+ }
282
+ export interface UpsertArgs<T> {
283
+ where: WhereClause<T>;
284
+ create: Partial<T>;
285
+ update: Partial<T>;
286
+ /** Query timeout in milliseconds. Rejects with an error if exceeded. */
287
+ timeout?: number;
288
+ }
289
+ export interface CountArgs<T> {
290
+ where?: WhereClause<T>;
291
+ /** Query timeout in milliseconds. Rejects with an error if exceeded. */
292
+ timeout?: number;
293
+ }
294
+ export interface GroupByArgs<T> {
295
+ by: (keyof T & string)[];
296
+ where?: WhereClause<T>;
297
+ /** Include count of each group */
298
+ _count?: true;
299
+ /** Sum of numeric fields in each group */
300
+ _sum?: Partial<Record<keyof T & string, boolean>>;
301
+ /** Average of numeric fields in each group */
302
+ _avg?: Partial<Record<keyof T & string, boolean>>;
303
+ /** Minimum value of fields in each group */
304
+ _min?: Partial<Record<keyof T & string, boolean>>;
305
+ /** Maximum value of fields in each group */
306
+ _max?: Partial<Record<keyof T & string, boolean>>;
307
+ /** Order groups */
308
+ orderBy?: Record<string, OrderDirection>;
309
+ /** Query timeout in milliseconds. Rejects with an error if exceeded. */
310
+ timeout?: number;
311
+ }
312
+ /** Arguments for the standalone aggregate method */
313
+ export interface AggregateArgs<T> {
314
+ where?: WhereClause<T>;
315
+ /** Count all rows matching the filter */
316
+ _count?: true | Partial<Record<keyof T & string, boolean>>;
317
+ /** Sum of numeric fields */
318
+ _sum?: Partial<Record<keyof T & string, boolean>>;
319
+ /** Average of numeric fields */
320
+ _avg?: Partial<Record<keyof T & string, boolean>>;
321
+ /** Minimum value of fields */
322
+ _min?: Partial<Record<keyof T & string, boolean>>;
323
+ /** Maximum value of fields */
324
+ _max?: Partial<Record<keyof T & string, boolean>>;
325
+ /** Query timeout in milliseconds. Rejects with an error if exceeded. */
326
+ timeout?: number;
327
+ }
328
+ /** Result type for aggregate queries */
329
+ export interface AggregateResult<T> {
330
+ _count?: number | Record<string, number>;
331
+ _sum?: Partial<Record<keyof T & string, number | null>>;
332
+ _avg?: Partial<Record<keyof T & string, number | null>>;
333
+ _min?: Partial<Record<keyof T & string, unknown>>;
334
+ _max?: Partial<Record<keyof T & string, unknown>>;
335
+ }
336
+ /** Relation filter operators for where clauses */
337
+ export interface RelationFilter {
338
+ some?: Record<string, unknown>;
339
+ every?: Record<string, unknown>;
340
+ none?: Record<string, unknown>;
341
+ }
342
+ /** JSONB query operators for where clauses */
343
+ export interface JsonFilter {
344
+ /** Access nested path via #>> operator */
345
+ path?: string[];
346
+ /** Exact match: column @> value::jsonb (containment) */
347
+ equals?: unknown;
348
+ /** Containment check: column @> value::jsonb */
349
+ contains?: unknown;
350
+ /** Key existence check: column ? key */
351
+ hasKey?: string;
352
+ }
353
+ /** Array query operators for where clauses */
354
+ export interface ArrayFilter {
355
+ /** Check if array contains a single value: value = ANY(column) */
356
+ has?: unknown;
357
+ /** Check if array contains ALL values: column @> ARRAY[...] */
358
+ hasEvery?: unknown[];
359
+ /** Check if array has ANY of the values: column && ARRAY[...] */
360
+ hasSome?: unknown[];
361
+ /** Check if array is empty: array_length(column, 1) IS NULL */
362
+ isEmpty?: boolean;
363
+ }
364
+ export {};
365
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * turbine-orm — Query builder types
3
+ *
4
+ * All exported type and interface definitions for the query builder module.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,60 @@
1
+ /**
2
+ * turbine-orm — Query builder utilities
3
+ *
4
+ * Standalone utility functions and classes used by the query builder.
5
+ */
6
+ /**
7
+ * Quote a SQL identifier (table name, column name) using Postgres double-quote
8
+ * rules: wrap in double quotes, escape internal double quotes by doubling them.
9
+ *
10
+ * @example
11
+ * quoteIdent('users') → '"users"'
12
+ * quoteIdent('my"table') → '"my""table"'
13
+ * quoteIdent('user name') → '"user name"'
14
+ */
15
+ export declare function quoteIdent(name: string): string;
16
+ /**
17
+ * Escape single quotes for use as string keys in json_build_object().
18
+ * Doubles single quotes per SQL quoting rules.
19
+ */
20
+ export declare function escSingleQuote(s: string): string;
21
+ /**
22
+ * Escape LIKE pattern metacharacters: %, _, and \.
23
+ * Must be used with `ESCAPE '\'` in the LIKE clause.
24
+ */
25
+ export declare function escapeLike(value: string): string;
26
+ /**
27
+ * Simple LRU (Least Recently Used) cache with a fixed maximum size.
28
+ * When the cache exceeds maxSize, the oldest (least recently used) entry is evicted.
29
+ * Uses Map insertion order for O(1) eviction.
30
+ */
31
+ export declare class LRUCache<K, V> {
32
+ private maxSize;
33
+ private cache;
34
+ constructor(maxSize: number);
35
+ get(key: K): V | undefined;
36
+ set(key: K, value: V): void;
37
+ get size(): number;
38
+ }
39
+ /** Cached SQL template paired with its prepared-statement name. */
40
+ export interface SqlCacheEntry {
41
+ sql: string;
42
+ name: string;
43
+ }
44
+ /**
45
+ * FNV-1a 64-bit hash returning 16 lowercase hex chars.
46
+ * Single-loop string iteration. Uses BigInt for 64-bit math.
47
+ *
48
+ * @internal Exported for testing only.
49
+ */
50
+ export declare function fnv1a64Hex(s: string): string;
51
+ /**
52
+ * Derive a prepared-statement name from a SQL string.
53
+ * Format: `t_<16hex>` — always 18 chars, well under NAMEDATALEN (63).
54
+ *
55
+ * @internal Exported for testing only.
56
+ */
57
+ export declare function sqlToPreparedName(sql: string): string;
58
+ /** Known operator keys — used to detect operator objects vs plain values */
59
+ export declare const OPERATOR_KEYS: Set<string>;
60
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1,114 @@
1
+ /**
2
+ * turbine-orm — Query builder utilities
3
+ *
4
+ * Standalone utility functions and classes used by the query builder.
5
+ */
6
+ // ---------------------------------------------------------------------------
7
+ // Identifier quoting — prevents SQL injection via table/column names
8
+ // ---------------------------------------------------------------------------
9
+ /**
10
+ * Quote a SQL identifier (table name, column name) using Postgres double-quote
11
+ * rules: wrap in double quotes, escape internal double quotes by doubling them.
12
+ *
13
+ * @example
14
+ * quoteIdent('users') → '"users"'
15
+ * quoteIdent('my"table') → '"my""table"'
16
+ * quoteIdent('user name') → '"user name"'
17
+ */
18
+ export function quoteIdent(name) {
19
+ return `"${name.replace(/"/g, '""')}"`;
20
+ }
21
+ /**
22
+ * Escape single quotes for use as string keys in json_build_object().
23
+ * Doubles single quotes per SQL quoting rules.
24
+ */
25
+ export function escSingleQuote(s) {
26
+ return s.replace(/'/g, "''");
27
+ }
28
+ /**
29
+ * Escape LIKE pattern metacharacters: %, _, and \.
30
+ * Must be used with `ESCAPE '\'` in the LIKE clause.
31
+ */
32
+ export function escapeLike(value) {
33
+ return value.replace(/\\/g, '\\\\').replace(/%/g, '\\%').replace(/_/g, '\\_');
34
+ }
35
+ // ---------------------------------------------------------------------------
36
+ // LRU cache — bounded SQL template cache to prevent memory leaks
37
+ // ---------------------------------------------------------------------------
38
+ /**
39
+ * Simple LRU (Least Recently Used) cache with a fixed maximum size.
40
+ * When the cache exceeds maxSize, the oldest (least recently used) entry is evicted.
41
+ * Uses Map insertion order for O(1) eviction.
42
+ */
43
+ export class LRUCache {
44
+ maxSize;
45
+ cache = new Map();
46
+ constructor(maxSize) {
47
+ this.maxSize = maxSize;
48
+ }
49
+ get(key) {
50
+ const value = this.cache.get(key);
51
+ if (value !== undefined) {
52
+ // Move to end (most recently used)
53
+ this.cache.delete(key);
54
+ this.cache.set(key, value);
55
+ }
56
+ return value;
57
+ }
58
+ set(key, value) {
59
+ if (this.cache.has(key)) {
60
+ this.cache.delete(key);
61
+ }
62
+ else if (this.cache.size >= this.maxSize) {
63
+ // Delete oldest (first) entry
64
+ const firstKey = this.cache.keys().next().value;
65
+ if (firstKey !== undefined)
66
+ this.cache.delete(firstKey);
67
+ }
68
+ this.cache.set(key, value);
69
+ }
70
+ get size() {
71
+ return this.cache.size;
72
+ }
73
+ }
74
+ /**
75
+ * FNV-1a 64-bit hash returning 16 lowercase hex chars.
76
+ * Single-loop string iteration. Uses BigInt for 64-bit math.
77
+ *
78
+ * @internal Exported for testing only.
79
+ */
80
+ export function fnv1a64Hex(s) {
81
+ // FNV-1a offset basis and prime for 64-bit
82
+ let hash = 0xcbf29ce484222325n;
83
+ const prime = 0x100000001b3n;
84
+ const mask = 0xffffffffffffffffn; // 64-bit mask
85
+ for (let i = 0; i < s.length; i++) {
86
+ hash ^= BigInt(s.charCodeAt(i));
87
+ hash = (hash * prime) & mask;
88
+ }
89
+ return hash.toString(16).padStart(16, '0');
90
+ }
91
+ /**
92
+ * Derive a prepared-statement name from a SQL string.
93
+ * Format: `t_<16hex>` — always 18 chars, well under NAMEDATALEN (63).
94
+ *
95
+ * @internal Exported for testing only.
96
+ */
97
+ export function sqlToPreparedName(sql) {
98
+ return `t_${fnv1a64Hex(sql)}`;
99
+ }
100
+ /** Known operator keys — used to detect operator objects vs plain values */
101
+ export const OPERATOR_KEYS = new Set([
102
+ 'gt',
103
+ 'gte',
104
+ 'lt',
105
+ 'lte',
106
+ 'not',
107
+ 'in',
108
+ 'notIn',
109
+ 'contains',
110
+ 'startsWith',
111
+ 'endsWith',
112
+ 'mode',
113
+ ]);
114
+ //# sourceMappingURL=utils.js.map
@@ -5,7 +5,7 @@
5
5
  * Also provides diff and push commands for syncing schema to a live database.
6
6
  */
7
7
  import pg from 'pg';
8
- import { quoteIdent } from './query.js';
8
+ import { quoteIdent } from './query/index.js';
9
9
  import { camelToSnake } from './schema.js';
10
10
  // ---------------------------------------------------------------------------
11
11
  // SQL Generation — SchemaDef → CREATE TABLE statements
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turbine-orm",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "Postgres-native TypeScript ORM — runs on Neon, Vercel Postgres, Cloudflare, Supabase. Streaming cursors, typed errors, single-query nested relations. 1 dependency, ~110KB",
5
5
  "type": "module",
6
6
  "exports": {
@@ -51,6 +51,11 @@
51
51
  "format": "biome format --write src/",
52
52
  "prepublishOnly": "npm run build && npm run typecheck && npm run lint && npm run test:unit",
53
53
  "prepare": "husky",
54
+ "size": "size-limit",
55
+ "size:check": "size-limit",
56
+ "test:watch": "tsx --watch --test src/test/schema-builder.test.ts src/test/errors.test.ts src/test/stress.test.ts",
57
+ "db:seed": "psql $DATABASE_URL -v ON_ERROR_STOP=1 -f src/test/fixtures/seed.sql",
58
+ "db:reset": "psql $DATABASE_URL -c 'DROP SCHEMA public CASCADE; CREATE SCHEMA public;' && npm run db:seed",
54
59
  "site:dev": "cd site && npm run dev",
55
60
  "site:build": "cd site && npm run build",
56
61
  "site:deploy": "cd site && vercel --prod"
@@ -63,11 +68,13 @@
63
68
  },
64
69
  "devDependencies": {
65
70
  "@biomejs/biome": "^2.4.10",
71
+ "@size-limit/file": "^12.0.1",
66
72
  "@types/node": "^22.10.0",
67
73
  "@types/pg": "^8.11.11",
68
74
  "c8": "^11.0.0",
69
75
  "husky": "^9.1.7",
70
76
  "lint-staged": "^16.4.0",
77
+ "size-limit": "^12.0.1",
71
78
  "tsx": "^4.19.0",
72
79
  "typescript": "^5.7.0"
73
80
  },