turbine-orm 0.40.1 → 0.41.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.
- package/README.md +22 -4
- package/dist/cjs/cli/config.js +3 -0
- package/dist/cjs/cli/index.js +179 -0
- package/dist/cjs/cli/prisma-report.js +216 -0
- package/dist/cjs/cli/prisma-resolve.js +335 -0
- package/dist/cjs/cli/prisma-schema.js +484 -0
- package/dist/cjs/client.js +1 -0
- package/dist/cjs/generate.js +279 -22
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/introspect.js +203 -26
- package/dist/cjs/mssql.js +9 -10
- package/dist/cjs/mysql.js +3 -9
- package/dist/cjs/powdb-introspect.js +5 -10
- package/dist/cjs/powql.js +13 -0
- package/dist/cjs/prisma-compat.js +1147 -0
- package/dist/cjs/query/aggregates.js +67 -7
- package/dist/cjs/query/builder.js +388 -17
- package/dist/cjs/query/compound-unique.js +0 -0
- package/dist/cjs/query/relations.js +7 -5
- package/dist/cjs/query/warn-registry.js +98 -0
- package/dist/cjs/query/writes.js +13 -5
- package/dist/cjs/schema.js +47 -0
- package/dist/cjs/sqlite.js +4 -9
- package/dist/cli/config.d.ts +26 -0
- package/dist/cli/config.js +3 -0
- package/dist/cli/index.d.ts +11 -0
- package/dist/cli/index.js +180 -1
- package/dist/cli/prisma-report.d.ts +19 -0
- package/dist/cli/prisma-report.js +211 -0
- package/dist/cli/prisma-resolve.d.ts +87 -0
- package/dist/cli/prisma-resolve.js +330 -0
- package/dist/cli/prisma-schema.d.ts +116 -0
- package/dist/cli/prisma-schema.js +479 -0
- package/dist/cli/ui.d.ts +1 -1
- package/dist/client.d.ts +18 -2
- package/dist/client.js +1 -0
- package/dist/generate.d.ts +80 -1
- package/dist/generate.js +277 -25
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/introspect.d.ts +92 -2
- package/dist/introspect.js +198 -26
- package/dist/mssql.js +10 -11
- package/dist/mysql.js +4 -10
- package/dist/powdb-introspect.js +5 -10
- package/dist/powql.js +13 -0
- package/dist/prisma-compat.d.ts +281 -0
- package/dist/prisma-compat.js +1143 -0
- package/dist/query/aggregates.js +67 -7
- package/dist/query/builder.d.ts +77 -4
- package/dist/query/builder.js +390 -19
- package/dist/query/compound-unique.d.ts +49 -0
- package/dist/query/compound-unique.js +0 -0
- package/dist/query/deferred.d.ts +18 -0
- package/dist/query/relations.js +7 -5
- package/dist/query/types.d.ts +70 -9
- package/dist/query/warn-registry.d.ts +57 -0
- package/dist/query/warn-registry.js +92 -0
- package/dist/query/writes.js +13 -5
- package/dist/schema.d.ts +75 -0
- package/dist/schema.js +46 -0
- package/dist/sqlite.js +5 -10
- package/package.json +6 -1
package/dist/query/types.d.ts
CHANGED
|
@@ -15,13 +15,30 @@ export type OrderDirection = 'asc' | 'desc';
|
|
|
15
15
|
* relation (`WHERE fk = ANY($1)`), stitching children client-side. D levels
|
|
16
16
|
* cost D extra round-trips, but each is a single key-set lookup and rows come
|
|
17
17
|
* back flat (a win when FK columns are unindexed or result sets are huge).
|
|
18
|
+
* - `'auto'` (the implicit default on SQL engines since 0.41): per relation,
|
|
19
|
+
* use `'join'` unless the introspected metadata PROVES the probe columns are
|
|
20
|
+
* unindexed, in which case that relation falls back to the batched loader
|
|
21
|
+
* (where a correlated per-parent scan would be pathological). Requires
|
|
22
|
+
* DB-backed index metadata (a generated / introspected client); a code-first
|
|
23
|
+
* `defineSchema`-only client has no index info to prove anything, so `'auto'`
|
|
24
|
+
* behaves exactly like `'join'` there. An EXPLICIT `'join'` or `'batched'` at
|
|
25
|
+
* the client or query level always wins and disables the per-relation
|
|
26
|
+
* fallback. Output is byte-for-byte identical to `'join'` (the batched loader
|
|
27
|
+
* guarantees the same result SHAPE); child-array order for a to-many relation
|
|
28
|
+
* WITHOUT an `orderBy` may differ from the join plan (order was never
|
|
29
|
+
* guaranteed without `orderBy`, see `stableRelationOrder` to pin it).
|
|
30
|
+
* Composite-key relations stay on the join plan (the batched loader does not
|
|
31
|
+
* support them). Engagement emits a once-per-relation dev note and a
|
|
32
|
+
* `strategy` tag on the query event.
|
|
18
33
|
*
|
|
19
34
|
* Precedence: per-query arg > client `relationLoadStrategy` config > the engine
|
|
20
|
-
* default. On SQL engines the default is `'
|
|
35
|
+
* default. On SQL engines the default is `'auto'`; on PowDB the default is the
|
|
21
36
|
* batched loaders (an ineligible relation falls back to them per-relation and
|
|
22
|
-
* silently even when `'join'` is requested).
|
|
37
|
+
* silently even when `'join'` is requested). On PowDB, `'auto'` resolves to
|
|
38
|
+
* PowDB's own existing default (loaders / nested projections); it never selects
|
|
39
|
+
* a distinct PowQL code path.
|
|
23
40
|
*/
|
|
24
|
-
export type RelationLoadStrategy = 'join' | 'batched';
|
|
41
|
+
export type RelationLoadStrategy = 'join' | 'batched' | 'auto';
|
|
25
42
|
/**
|
|
26
43
|
* Reference to ANOTHER COLUMN of the same table inside a where operator,
|
|
27
44
|
* enabling column-to-column comparison:
|
|
@@ -330,6 +347,8 @@ export interface FindUniqueArgs<T, R extends object = {}, W extends TypedWithCla
|
|
|
330
347
|
timeout?: number;
|
|
331
348
|
/** Override the client's relation-loading strategy for this query. See {@link RelationLoadStrategy}. */
|
|
332
349
|
relationLoadStrategy?: RelationLoadStrategy;
|
|
350
|
+
/** Override the client's {@link TurbineConfig.stableRelationOrder} for this query. */
|
|
351
|
+
stableRelationOrder?: boolean;
|
|
333
352
|
/** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
|
|
334
353
|
skipGlobalFilters?: SkipGlobalFilters;
|
|
335
354
|
/** Include PII-tagged columns in the result. See {@link FindManyArgs.includePii}. */
|
|
@@ -353,6 +372,15 @@ export interface FindManyArgs<T, R extends object = {}, W extends TypedWithClaus
|
|
|
353
372
|
timeout?: number;
|
|
354
373
|
/** Override the client's relation-loading strategy for this query. See {@link RelationLoadStrategy}. */
|
|
355
374
|
relationLoadStrategy?: RelationLoadStrategy;
|
|
375
|
+
/**
|
|
376
|
+
* Override the client's {@link TurbineConfig.stableRelationOrder} for this
|
|
377
|
+
* query. When `true`, every to-many `with` relation that has no explicit
|
|
378
|
+
* `orderBy` is loaded ordered by the target table's primary key ascending, so
|
|
379
|
+
* unordered child arrays come back in a deterministic order. An explicit
|
|
380
|
+
* per-relation `orderBy` always wins. Off by default; when off the emitted SQL
|
|
381
|
+
* is byte-identical to before.
|
|
382
|
+
*/
|
|
383
|
+
stableRelationOrder?: boolean;
|
|
356
384
|
/** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
|
|
357
385
|
skipGlobalFilters?: SkipGlobalFilters;
|
|
358
386
|
/**
|
|
@@ -732,8 +760,16 @@ export interface GroupByArgs<T> {
|
|
|
732
760
|
* (`SELECT DISTINCT ON … ORDER BY …` row source). See {@link GroupByDistinctOn}.
|
|
733
761
|
*/
|
|
734
762
|
distinctOn?: GroupByDistinctOn<T>;
|
|
735
|
-
/**
|
|
736
|
-
|
|
763
|
+
/**
|
|
764
|
+
* Count each group. `true` (or omitted) → `_count: number` (COUNT(*)). The
|
|
765
|
+
* record form counts per selection: the reserved `_all: true` key → COUNT(*),
|
|
766
|
+
* and each entity field key → COUNT(that column), yielding
|
|
767
|
+
* `_count: { _all: n, field: n }` (Prisma parity). Ordering/HAVING on `_count`
|
|
768
|
+
* stays available whenever COUNT(*) is selected (`true`, omitted, or `_all`).
|
|
769
|
+
*/
|
|
770
|
+
_count?: true | ({
|
|
771
|
+
_all?: true;
|
|
772
|
+
} & Partial<Record<keyof T & string, boolean>>);
|
|
737
773
|
/** Sum of numeric fields (or JSON paths: see {@link JsonPathAggregateTarget}) in each group */
|
|
738
774
|
_sum?: GroupByAggregateSpec<T>;
|
|
739
775
|
/** Average of numeric fields (or JSON paths) in each group */
|
|
@@ -817,14 +853,39 @@ type GroupByMinMaxPart<T, A, Key extends '_min' | '_max'> = A extends {
|
|
|
817
853
|
*/
|
|
818
854
|
export type GroupByResult<T, A> = {
|
|
819
855
|
[K in GroupByFieldKeys<T, A>]: T[K];
|
|
820
|
-
} &
|
|
856
|
+
} & GroupByCountPart<A> & GroupBySumAvgPart<A, '_sum'> & GroupBySumAvgPart<A, '_avg'> & GroupByMinMaxPart<T, A, '_min'> & GroupByMinMaxPart<T, A, '_max'>;
|
|
857
|
+
/**
|
|
858
|
+
* The `_count` block on a groupBy result row. Scalar `_count: true` (or an
|
|
859
|
+
* omitted `_count`, which still selects COUNT(*) by default) yields a plain
|
|
860
|
+
* `number`; the record form (`_count: { _all: true, field: true }`) yields a
|
|
861
|
+
* per-selection object `{ _all: number, field: number }` (Prisma parity). Kept
|
|
862
|
+
* additive: existing `_count: true` / no-`_count` calls still infer `number`.
|
|
863
|
+
*/
|
|
864
|
+
type GroupByCountPart<A> = A extends {
|
|
865
|
+
_count: infer C;
|
|
866
|
+
} ? C extends true ? {
|
|
867
|
+
_count: number;
|
|
868
|
+
} : [C] extends [object] ? {
|
|
869
|
+
_count: {
|
|
870
|
+
[K in keyof C & string]: number;
|
|
871
|
+
};
|
|
872
|
+
} : {
|
|
873
|
+
_count: number;
|
|
874
|
+
} : {
|
|
821
875
|
_count: number;
|
|
822
|
-
}
|
|
876
|
+
};
|
|
823
877
|
/** Arguments for the standalone aggregate method */
|
|
824
878
|
export interface AggregateArgs<T> {
|
|
825
879
|
where?: WhereClause<T>;
|
|
826
|
-
/**
|
|
827
|
-
|
|
880
|
+
/**
|
|
881
|
+
* Count rows. `true` → `_count: number` (COUNT(*)). The record form counts per
|
|
882
|
+
* selection: the reserved `_all: true` key → COUNT(*), and each entity field
|
|
883
|
+
* key → COUNT(that column) (non-null count), yielding
|
|
884
|
+
* `_count: { _all: n, field: n }` (Prisma parity).
|
|
885
|
+
*/
|
|
886
|
+
_count?: true | ({
|
|
887
|
+
_all?: true;
|
|
888
|
+
} & Partial<Record<keyof T & string, boolean>>);
|
|
828
889
|
/** Sum of numeric fields */
|
|
829
890
|
_sum?: Partial<Record<keyof T & string, boolean>>;
|
|
830
891
|
/** Average of numeric fields */
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turbine-orm, process-wide once-per-key dev-warning dedupe registry.
|
|
3
|
+
*
|
|
4
|
+
* Several dev-only diagnostics (the missing-FK-index warning in relations.ts,
|
|
5
|
+
* the `relationLoadStrategy: 'auto'` engagement note, the deep-`with` warning)
|
|
6
|
+
* must fire AT MOST ONCE per distinct key for the life of the process. A
|
|
7
|
+
* module-level `Set` almost does this, but it is defeated by the two field
|
|
8
|
+
* realities this package actually ships into:
|
|
9
|
+
*
|
|
10
|
+
* 1. **Dual-package loading.** Turbine ships ESM (`dist/`) AND CJS
|
|
11
|
+
* (`dist/cjs/`). A mixed `require`/`import` graph (a compat layer, a tool
|
|
12
|
+
* that loads both) instantiates the module twice, giving two independent
|
|
13
|
+
* `Set`s that each warn once, a double warning.
|
|
14
|
+
* 2. **Bundler / HMR re-evaluation.** Under Next.js dev the module is
|
|
15
|
+
* re-evaluated per recompile, resetting a module-level `Set` and making the
|
|
16
|
+
* warning appear to fire every time.
|
|
17
|
+
*
|
|
18
|
+
* Hanging the registry off `globalThis` under a `Symbol.for(...)` key gives every
|
|
19
|
+
* module copy in the realm ONE shared registry (cross-copy identity without
|
|
20
|
+
* polluting enumerable globals), and `globalThis` survives webpack recompiles
|
|
21
|
+
* because the realm persists, which is exactly what fixes the every-recompile
|
|
22
|
+
* firing in dev servers. Per-process firing (worker threads, separate processes)
|
|
23
|
+
* is acceptable and stays.
|
|
24
|
+
*
|
|
25
|
+
* Bounded: each namespace stops recording AND stops warning once it reaches
|
|
26
|
+
* {@link WARN_ONCE_CAP} distinct keys. A schema with 500+ distinct unindexed
|
|
27
|
+
* relations has long since gotten the message, and the cap prevents unbounded
|
|
28
|
+
* growth if metadata objects are churned dynamically. (Clearing on overflow
|
|
29
|
+
* would be wrong, it would re-warn.)
|
|
30
|
+
*/
|
|
31
|
+
/** Per-namespace cap on distinct recorded keys (see module doc). */
|
|
32
|
+
export declare const WARN_ONCE_CAP = 500;
|
|
33
|
+
/**
|
|
34
|
+
* Record `(ns, key)` and report whether THIS call is the first to see it
|
|
35
|
+
* process-wide. Returns `true` exactly once per distinct key (the caller should
|
|
36
|
+
* emit its warning then), `false` on every subsequent call for that key, and
|
|
37
|
+
* `false` once the namespace has recorded {@link WARN_ONCE_CAP} distinct keys
|
|
38
|
+
* (bounded growth; the warning simply stops rather than re-firing).
|
|
39
|
+
*/
|
|
40
|
+
export declare function shouldWarnOnce(ns: string, key: string): boolean;
|
|
41
|
+
/** True when `(ns, key)` has already been recorded (no mutation). */
|
|
42
|
+
export declare function hasWarnedOnce(ns: string, key: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* @internal Test-only: clear one namespace, or the whole registry when `ns` is
|
|
45
|
+
* omitted. Lets a single test process verify that a warning fires once and then
|
|
46
|
+
* re-verify after a reset without spawning a new process.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resetWarnOnce(ns?: string): void;
|
|
49
|
+
/** Namespace constants so callers never typo a bare string. */
|
|
50
|
+
export declare const WARN_NS: {
|
|
51
|
+
/** Missing-FK-index runtime warning (relations.ts `buildRelationSubquery`). */
|
|
52
|
+
readonly unindexedRelation: "unindexedRelation";
|
|
53
|
+
/** `relationLoadStrategy: 'auto'` batched-fallback engagement note. */
|
|
54
|
+
readonly autoStrategy: "autoStrategy";
|
|
55
|
+
/** Deep-`with` (depth > 5) advisory (builder.ts `findMany`). */
|
|
56
|
+
readonly deepWith: "deepWith";
|
|
57
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turbine-orm, process-wide once-per-key dev-warning dedupe registry.
|
|
3
|
+
*
|
|
4
|
+
* Several dev-only diagnostics (the missing-FK-index warning in relations.ts,
|
|
5
|
+
* the `relationLoadStrategy: 'auto'` engagement note, the deep-`with` warning)
|
|
6
|
+
* must fire AT MOST ONCE per distinct key for the life of the process. A
|
|
7
|
+
* module-level `Set` almost does this, but it is defeated by the two field
|
|
8
|
+
* realities this package actually ships into:
|
|
9
|
+
*
|
|
10
|
+
* 1. **Dual-package loading.** Turbine ships ESM (`dist/`) AND CJS
|
|
11
|
+
* (`dist/cjs/`). A mixed `require`/`import` graph (a compat layer, a tool
|
|
12
|
+
* that loads both) instantiates the module twice, giving two independent
|
|
13
|
+
* `Set`s that each warn once, a double warning.
|
|
14
|
+
* 2. **Bundler / HMR re-evaluation.** Under Next.js dev the module is
|
|
15
|
+
* re-evaluated per recompile, resetting a module-level `Set` and making the
|
|
16
|
+
* warning appear to fire every time.
|
|
17
|
+
*
|
|
18
|
+
* Hanging the registry off `globalThis` under a `Symbol.for(...)` key gives every
|
|
19
|
+
* module copy in the realm ONE shared registry (cross-copy identity without
|
|
20
|
+
* polluting enumerable globals), and `globalThis` survives webpack recompiles
|
|
21
|
+
* because the realm persists, which is exactly what fixes the every-recompile
|
|
22
|
+
* firing in dev servers. Per-process firing (worker threads, separate processes)
|
|
23
|
+
* is acceptable and stays.
|
|
24
|
+
*
|
|
25
|
+
* Bounded: each namespace stops recording AND stops warning once it reaches
|
|
26
|
+
* {@link WARN_ONCE_CAP} distinct keys. A schema with 500+ distinct unindexed
|
|
27
|
+
* relations has long since gotten the message, and the cap prevents unbounded
|
|
28
|
+
* growth if metadata objects are churned dynamically. (Clearing on overflow
|
|
29
|
+
* would be wrong, it would re-warn.)
|
|
30
|
+
*/
|
|
31
|
+
const REGISTRY_KEY = Symbol.for('turbine.warnOnce.registry');
|
|
32
|
+
/** Per-namespace cap on distinct recorded keys (see module doc). */
|
|
33
|
+
export const WARN_ONCE_CAP = 500;
|
|
34
|
+
function registry() {
|
|
35
|
+
const g = globalThis;
|
|
36
|
+
let reg = g[REGISTRY_KEY];
|
|
37
|
+
if (!reg) {
|
|
38
|
+
reg = Object.create(null);
|
|
39
|
+
g[REGISTRY_KEY] = reg;
|
|
40
|
+
}
|
|
41
|
+
return reg;
|
|
42
|
+
}
|
|
43
|
+
function namespaceSet(ns) {
|
|
44
|
+
const reg = registry();
|
|
45
|
+
let set = reg[ns];
|
|
46
|
+
if (!set) {
|
|
47
|
+
set = new Set();
|
|
48
|
+
reg[ns] = set;
|
|
49
|
+
}
|
|
50
|
+
return set;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Record `(ns, key)` and report whether THIS call is the first to see it
|
|
54
|
+
* process-wide. Returns `true` exactly once per distinct key (the caller should
|
|
55
|
+
* emit its warning then), `false` on every subsequent call for that key, and
|
|
56
|
+
* `false` once the namespace has recorded {@link WARN_ONCE_CAP} distinct keys
|
|
57
|
+
* (bounded growth; the warning simply stops rather than re-firing).
|
|
58
|
+
*/
|
|
59
|
+
export function shouldWarnOnce(ns, key) {
|
|
60
|
+
const set = namespaceSet(ns);
|
|
61
|
+
if (set.has(key))
|
|
62
|
+
return false;
|
|
63
|
+
if (set.size >= WARN_ONCE_CAP)
|
|
64
|
+
return false;
|
|
65
|
+
set.add(key);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
/** True when `(ns, key)` has already been recorded (no mutation). */
|
|
69
|
+
export function hasWarnedOnce(ns, key) {
|
|
70
|
+
return namespaceSet(ns).has(key);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* @internal Test-only: clear one namespace, or the whole registry when `ns` is
|
|
74
|
+
* omitted. Lets a single test process verify that a warning fires once and then
|
|
75
|
+
* re-verify after a reset without spawning a new process.
|
|
76
|
+
*/
|
|
77
|
+
export function resetWarnOnce(ns) {
|
|
78
|
+
if (ns === undefined) {
|
|
79
|
+
globalThis[REGISTRY_KEY] = undefined;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
registry()[ns] = undefined;
|
|
83
|
+
}
|
|
84
|
+
/** Namespace constants so callers never typo a bare string. */
|
|
85
|
+
export const WARN_NS = {
|
|
86
|
+
/** Missing-FK-index runtime warning (relations.ts `buildRelationSubquery`). */
|
|
87
|
+
unindexedRelation: 'unindexedRelation',
|
|
88
|
+
/** `relationLoadStrategy: 'auto'` batched-fallback engagement note. */
|
|
89
|
+
autoStrategy: 'autoStrategy',
|
|
90
|
+
/** Deep-`with` (depth > 5) advisory (builder.ts `findMany`). */
|
|
91
|
+
deepWith: 'deepWith',
|
|
92
|
+
};
|
package/dist/query/writes.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { NotFoundError, OptimisticLockError, ValidationError } from '../errors.js';
|
|
14
14
|
import { camelToSnake, snakeToCamel } from '../schema.js';
|
|
15
|
+
import { expandCompoundUniqueWhere } from './compound-unique.js';
|
|
15
16
|
import { UPDATE_OPERATOR_KEYS } from './filters.js';
|
|
16
17
|
import * as whereMod from './where.js';
|
|
17
18
|
/**
|
|
@@ -133,7 +134,10 @@ export function buildUpdate(qi, args) {
|
|
|
133
134
|
qi.currentSkip = args.skipGlobalFilters;
|
|
134
135
|
const dataObj = args.data;
|
|
135
136
|
assertNoGeneratedColumns(qi, dataObj, 'update');
|
|
136
|
-
|
|
137
|
+
// Prisma compound-unique selector (e.g. `{ orgId_userId: { orgId, userId } }`)
|
|
138
|
+
// → the column conjunction, before the empty-`where` guard so the expanded
|
|
139
|
+
// members count as a real predicate.
|
|
140
|
+
const userWhere = expandCompoundUniqueWhere(qi.tableMeta, args.where);
|
|
137
141
|
const lock = args.optimisticLock;
|
|
138
142
|
// The empty-`where` guard checks the USER predicate only — a global filter
|
|
139
143
|
// must never turn an unguarded mass update into an allowed one.
|
|
@@ -239,9 +243,11 @@ export function buildUpdate(qi, args) {
|
|
|
239
243
|
export function buildDelete(qi, args) {
|
|
240
244
|
assertWritable(qi, 'delete');
|
|
241
245
|
qi.currentSkip = args.skipGlobalFilters;
|
|
246
|
+
// Prisma compound-unique selector → the column conjunction (before the guard).
|
|
247
|
+
const userWhere = expandCompoundUniqueWhere(qi.tableMeta, args.where);
|
|
242
248
|
// Guard the USER predicate (a global filter must not satisfy the guard).
|
|
243
|
-
whereMod.assertMutationHasPredicate(qi, 'delete', whereMod.userPredicateIsEmpty(qi,
|
|
244
|
-
const whereObj = (whereMod.mergeGlobalFilter(qi,
|
|
249
|
+
whereMod.assertMutationHasPredicate(qi, 'delete', whereMod.userPredicateIsEmpty(qi, userWhere) ? '' : ' WHERE x', args.allowFullTableScan);
|
|
250
|
+
const whereObj = (whereMod.mergeGlobalFilter(qi, userWhere) ?? {});
|
|
245
251
|
const whereFp = whereMod.fingerprintWhere(qi, whereObj);
|
|
246
252
|
const ck = `d:${whereFp}${whereMod.globalFilterCacheSegment(qi)}`;
|
|
247
253
|
const params = [];
|
|
@@ -292,6 +298,8 @@ export function buildUpsert(qi, args) {
|
|
|
292
298
|
assertNoGeneratedColumns(qi, args.create, 'upsert');
|
|
293
299
|
assertNoGeneratedColumns(qi, args.update, 'upsert');
|
|
294
300
|
qi.currentSkip = args.skipGlobalFilters;
|
|
301
|
+
// Prisma compound-unique selector on the conflict target → its member columns.
|
|
302
|
+
const upsertWhere = expandCompoundUniqueWhere(qi.tableMeta, args.where);
|
|
295
303
|
// Build the INSERT part from create data
|
|
296
304
|
const createEntries = Object.entries(args.create).filter(([, v]) => v !== undefined);
|
|
297
305
|
const columns = createEntries.map(([k]) => qi.toSqlColumn(k));
|
|
@@ -299,7 +307,7 @@ export function buildUpsert(qi, args) {
|
|
|
299
307
|
// Enum columns get an explicit `::"EnumName"` cast (see enumTypeForColumn).
|
|
300
308
|
const placeholders = createEntries.map(([k], i) => `${qi.p(i + 1)}${whereMod.enumCastSuffix(qi, qi.toColumn(k))}`);
|
|
301
309
|
// The conflict target comes from `where` keys — must be unique/PK columns
|
|
302
|
-
const conflictKeys = Object.keys(
|
|
310
|
+
const conflictKeys = Object.keys(upsertWhere).filter((k) => upsertWhere[k] !== undefined);
|
|
303
311
|
const conflictColumns = conflictKeys.map((k) => qi.toSqlColumn(k));
|
|
304
312
|
// Build the UPDATE SET part
|
|
305
313
|
const updateEntries = Object.entries(args.update).filter(([, v]) => v !== undefined);
|
|
@@ -350,7 +358,7 @@ export function buildUpsert(qi, args) {
|
|
|
350
358
|
reselect: qi.dialect.resultStrategy === 'reselect'
|
|
351
359
|
? async (exec) => {
|
|
352
360
|
await exec(sql, params);
|
|
353
|
-
const sel = buildReselectByWhere(qi, (whereMod.mergeGlobalFilter(qi,
|
|
361
|
+
const sel = buildReselectByWhere(qi, (whereMod.mergeGlobalFilter(qi, upsertWhere) ?? {}));
|
|
354
362
|
return exec(sel.sql, sel.params);
|
|
355
363
|
}
|
|
356
364
|
: undefined,
|
package/dist/schema.d.ts
CHANGED
|
@@ -220,3 +220,78 @@ export declare function camelToSnake(s: string): string;
|
|
|
220
220
|
export declare function snakeToPascal(s: string): string;
|
|
221
221
|
/** Naive singularize: "posts" → "post", "categories" → "category" */
|
|
222
222
|
export declare function singularize(s: string): string;
|
|
223
|
+
/**
|
|
224
|
+
* Return a copy of `schema` in which every column's TypeScript **field** name is
|
|
225
|
+
* the raw database column name (snake_case) instead of the camelCased default,
|
|
226
|
+
* i.e. `user_id` stays `user_id` rather than becoming `userId`.
|
|
227
|
+
*
|
|
228
|
+
* This is a PURE, generate-time transform with ZERO runtime changes: it only
|
|
229
|
+
* rewrites `column.field` and rebuilds each table's `columnMap` /
|
|
230
|
+
* `reverseColumnMap` as IDENTITY maps (`user_id → user_id`). Because every
|
|
231
|
+
* runtime surface resolves column names through those maps (`toColumn`,
|
|
232
|
+
* `parseRow`, relation `json_build_object` keys, batched-loader stitching,
|
|
233
|
+
* positional decoding, aggregate/groupBy naming), the generated client returns
|
|
234
|
+
* rows keyed by DB column names and accepts DB column names in
|
|
235
|
+
* `where`/`orderBy`/`select` with no code path aware of the difference.
|
|
236
|
+
*
|
|
237
|
+
* Deliberately left untouched (the flag is literally about column names):
|
|
238
|
+
* - `name`, `allColumns`, `dateColumns`, `dialectTypes`, `pgTypes`,
|
|
239
|
+
* `primaryKey`, `uniqueColumns`, `indexes`, `checks`, `isView` (all already
|
|
240
|
+
* keyed by snake_case column names);
|
|
241
|
+
* - `relations` (relation PROPERTY names are synthetic introspection names
|
|
242
|
+
* with no DB column equivalent, and `foreignKey`/`referenceKey`/`through`
|
|
243
|
+
* already hold DB column names);
|
|
244
|
+
* - entity type names, table accessors, and `enums`.
|
|
245
|
+
*
|
|
246
|
+
* Every non-PII field of each {@link ColumnMetadata} (`pii`, `pgType`,
|
|
247
|
+
* `dialectType`, `nullable`, …) is preserved. Exported from the package root so
|
|
248
|
+
* runtime-introspection and serverless users can apply the same identity mapping
|
|
249
|
+
* to a schema they build at runtime, e.g. `turbineHttp(pool,
|
|
250
|
+
* withDbFieldNames(schema))`.
|
|
251
|
+
*/
|
|
252
|
+
export declare function withDbFieldNames(schema: SchemaMetadata): SchemaMetadata;
|
|
253
|
+
/**
|
|
254
|
+
* A typed name map from a Prisma schema onto a Turbine client, produced by
|
|
255
|
+
* `turbine migrate-from-prisma` (`generatePrismaMap` writes it as a
|
|
256
|
+
* `prisma-map.ts` module next to the generated client). It is library-side so
|
|
257
|
+
* the phase-2 `turbine-orm/prisma-compat` runtime adapter can consume the same
|
|
258
|
+
* shape to translate Prisma model/field/relation/compound-unique names onto the
|
|
259
|
+
* Turbine surface without re-parsing anything.
|
|
260
|
+
*
|
|
261
|
+
* Every name it carries was RESOLVED against live introspected metadata: a model
|
|
262
|
+
* or field that could not be matched is omitted from the map and listed in the
|
|
263
|
+
* migration report instead, so the map only ever contains verified mappings.
|
|
264
|
+
*/
|
|
265
|
+
export interface PrismaCompatMap {
|
|
266
|
+
/** Prisma model name → its resolved mapping. */
|
|
267
|
+
models: Record<string, PrismaModelMap>;
|
|
268
|
+
/** Prisma enum name → resolved database enum-type name. */
|
|
269
|
+
enums: Record<string, string>;
|
|
270
|
+
}
|
|
271
|
+
/** One Prisma model's resolved mapping onto a Turbine table + client accessor. */
|
|
272
|
+
export interface PrismaModelMap {
|
|
273
|
+
/** Resolved snake_case database table name. */
|
|
274
|
+
table: string;
|
|
275
|
+
/** camelCase `TurbineClient` accessor (`db.<accessor>`). */
|
|
276
|
+
accessor: string;
|
|
277
|
+
/** Prisma field name → Turbine field name (camelCase). Relation fields excluded. */
|
|
278
|
+
fields: Record<string, string>;
|
|
279
|
+
/** Prisma relation-field name → resolved Turbine relation + cardinality. */
|
|
280
|
+
relations: Record<string, PrismaRelationMap>;
|
|
281
|
+
/**
|
|
282
|
+
* Prisma compound-unique/compound-id selector name → the Turbine field names
|
|
283
|
+
* (in declared order). The selector name is Prisma's: the explicit
|
|
284
|
+
* `@@unique(name:)` / `@@id(name:)` argument, else the field names joined with
|
|
285
|
+
* `_`. Consumed by the phase-2 client to translate
|
|
286
|
+
* `where: { <selector>: { ... } }`, including custom `@@unique(name:)` names
|
|
287
|
+
* the core `findUnique`-family derivation cannot know.
|
|
288
|
+
*/
|
|
289
|
+
compoundUniques: Record<string, string[]>;
|
|
290
|
+
}
|
|
291
|
+
/** A resolved Prisma relation field → Turbine relation. */
|
|
292
|
+
export interface PrismaRelationMap {
|
|
293
|
+
/** Turbine relation name (the `with` clause key). */
|
|
294
|
+
name: string;
|
|
295
|
+
/** `'one'` (to-one) or `'many'` (to-many, including m2m). */
|
|
296
|
+
cardinality: 'one' | 'many';
|
|
297
|
+
}
|
package/dist/schema.js
CHANGED
|
@@ -146,3 +146,49 @@ export function singularize(s) {
|
|
|
146
146
|
return s.slice(0, -1);
|
|
147
147
|
return s;
|
|
148
148
|
}
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// keepColumnNames transform (F4)
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
/**
|
|
153
|
+
* Return a copy of `schema` in which every column's TypeScript **field** name is
|
|
154
|
+
* the raw database column name (snake_case) instead of the camelCased default,
|
|
155
|
+
* i.e. `user_id` stays `user_id` rather than becoming `userId`.
|
|
156
|
+
*
|
|
157
|
+
* This is a PURE, generate-time transform with ZERO runtime changes: it only
|
|
158
|
+
* rewrites `column.field` and rebuilds each table's `columnMap` /
|
|
159
|
+
* `reverseColumnMap` as IDENTITY maps (`user_id → user_id`). Because every
|
|
160
|
+
* runtime surface resolves column names through those maps (`toColumn`,
|
|
161
|
+
* `parseRow`, relation `json_build_object` keys, batched-loader stitching,
|
|
162
|
+
* positional decoding, aggregate/groupBy naming), the generated client returns
|
|
163
|
+
* rows keyed by DB column names and accepts DB column names in
|
|
164
|
+
* `where`/`orderBy`/`select` with no code path aware of the difference.
|
|
165
|
+
*
|
|
166
|
+
* Deliberately left untouched (the flag is literally about column names):
|
|
167
|
+
* - `name`, `allColumns`, `dateColumns`, `dialectTypes`, `pgTypes`,
|
|
168
|
+
* `primaryKey`, `uniqueColumns`, `indexes`, `checks`, `isView` (all already
|
|
169
|
+
* keyed by snake_case column names);
|
|
170
|
+
* - `relations` (relation PROPERTY names are synthetic introspection names
|
|
171
|
+
* with no DB column equivalent, and `foreignKey`/`referenceKey`/`through`
|
|
172
|
+
* already hold DB column names);
|
|
173
|
+
* - entity type names, table accessors, and `enums`.
|
|
174
|
+
*
|
|
175
|
+
* Every non-PII field of each {@link ColumnMetadata} (`pii`, `pgType`,
|
|
176
|
+
* `dialectType`, `nullable`, …) is preserved. Exported from the package root so
|
|
177
|
+
* runtime-introspection and serverless users can apply the same identity mapping
|
|
178
|
+
* to a schema they build at runtime, e.g. `turbineHttp(pool,
|
|
179
|
+
* withDbFieldNames(schema))`.
|
|
180
|
+
*/
|
|
181
|
+
export function withDbFieldNames(schema) {
|
|
182
|
+
const tables = {};
|
|
183
|
+
for (const [tableKey, table] of Object.entries(schema.tables)) {
|
|
184
|
+
const columns = table.columns.map((col) => ({ ...col, field: col.name }));
|
|
185
|
+
const columnMap = {};
|
|
186
|
+
const reverseColumnMap = {};
|
|
187
|
+
for (const col of columns) {
|
|
188
|
+
columnMap[col.name] = col.name;
|
|
189
|
+
reverseColumnMap[col.name] = col.name;
|
|
190
|
+
}
|
|
191
|
+
tables[tableKey] = { ...table, columns, columnMap, reverseColumnMap };
|
|
192
|
+
}
|
|
193
|
+
return { ...schema, tables };
|
|
194
|
+
}
|
package/dist/sqlite.js
CHANGED
|
@@ -49,7 +49,7 @@ import { createRequire } from 'node:module';
|
|
|
49
49
|
import { TurbineClient } from './client.js';
|
|
50
50
|
import { postgresDialect, } from './dialect.js';
|
|
51
51
|
import { ConnectionError } from './errors.js';
|
|
52
|
-
import { deriveEngineRelations } from './introspect.js';
|
|
52
|
+
import { applyTableFilters, deriveEngineRelations } from './introspect.js';
|
|
53
53
|
import { isDateType, snakeToCamel, } from './schema.js';
|
|
54
54
|
let cachedDatabaseSync;
|
|
55
55
|
/**
|
|
@@ -550,15 +550,10 @@ function pragma(db, sql) {
|
|
|
550
550
|
*/
|
|
551
551
|
export function introspectSqliteDatabase(db, options = {}) {
|
|
552
552
|
// ----- Tables (skip SQLite internal + the migration tracking table) -----
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
}
|
|
558
|
-
if (options.exclude?.length) {
|
|
559
|
-
const exc = new Set(options.exclude);
|
|
560
|
-
tableNames = tableNames.filter((t) => !exc.has(t));
|
|
561
|
-
}
|
|
553
|
+
const candidateTables = pragma(db, "SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name").map((r) => r.name);
|
|
554
|
+
// include / exclude + default bookkeeping-table exclusions (F12), shared with
|
|
555
|
+
// every other introspector via applyTableFilters.
|
|
556
|
+
const tableNames = applyTableFilters(candidateTables, options);
|
|
562
557
|
const tableSet = new Set(tableNames);
|
|
563
558
|
const columnsByTable = new Map();
|
|
564
559
|
const pkByTable = new Map();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "turbine-orm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.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": {
|
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
"import": "./dist/serverless.js",
|
|
15
15
|
"require": "./dist/cjs/serverless.js"
|
|
16
16
|
},
|
|
17
|
+
"./prisma-compat": {
|
|
18
|
+
"types": "./dist/prisma-compat.d.ts",
|
|
19
|
+
"import": "./dist/prisma-compat.js",
|
|
20
|
+
"require": "./dist/cjs/prisma-compat.js"
|
|
21
|
+
},
|
|
17
22
|
"./sqlite": {
|
|
18
23
|
"types": "./dist/sqlite.d.ts",
|
|
19
24
|
"import": "./dist/sqlite.js",
|