turbine-orm 0.61.0 → 0.62.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 +65 -21
- package/dist/cjs/cli/config.d.ts +40 -0
- package/dist/cjs/cli/config.js +74 -2
- package/dist/cjs/cli/index.d.ts +85 -1
- package/dist/cjs/cli/index.js +323 -24
- package/dist/cjs/cli/mcp.d.ts +8 -0
- package/dist/cjs/cli/mcp.js +448 -29
- package/dist/cjs/cli/pii-tags.d.ts +64 -9
- package/dist/cjs/cli/pii-tags.js +218 -39
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/studio.d.ts +23 -0
- package/dist/cjs/cli/studio.js +126 -53
- package/dist/cjs/cli/ui.d.ts +15 -1
- package/dist/cjs/cli/ui.js +19 -5
- package/dist/cjs/client.js +186 -3
- package/dist/cjs/errors.d.ts +38 -1
- package/dist/cjs/errors.js +235 -24
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.js +7 -2
- package/dist/cjs/pipeline.js +15 -2
- package/dist/cjs/powql.d.ts +12 -0
- package/dist/cjs/powql.js +46 -21
- package/dist/cjs/prisma-compat.d.ts +15 -5
- package/dist/cjs/prisma-compat.js +273 -78
- package/dist/cjs/query/aggregates.d.ts +1 -1
- package/dist/cjs/query/aggregates.js +24 -10
- package/dist/cjs/query/batched-loader.d.ts +9 -4
- package/dist/cjs/query/batched-loader.js +4 -1
- package/dist/cjs/query/builder.d.ts +47 -0
- package/dist/cjs/query/builder.js +125 -21
- package/dist/cjs/query/index.d.ts +3 -1
- package/dist/cjs/query/index.js +7 -1
- package/dist/cjs/query/option-surface.d.ts +11 -0
- package/dist/cjs/query/option-surface.js +13 -0
- package/dist/cjs/query/relations.d.ts +8 -0
- package/dist/cjs/query/relations.js +21 -1
- package/dist/cjs/query/types.d.ts +152 -18
- package/dist/cjs/query/types.js +212 -1
- package/dist/cjs/query/where.d.ts +3 -3
- package/dist/cjs/query/where.js +8 -2
- package/dist/cjs/query/writes.js +10 -9
- package/dist/cli/config.d.ts +40 -0
- package/dist/cli/config.js +73 -2
- package/dist/cli/index.d.ts +85 -1
- package/dist/cli/index.js +321 -26
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +448 -29
- package/dist/cli/pii-tags.d.ts +64 -9
- package/dist/cli/pii-tags.js +217 -39
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/studio.d.ts +23 -0
- package/dist/cli/studio.js +125 -53
- package/dist/cli/ui.d.ts +15 -1
- package/dist/cli/ui.js +18 -4
- package/dist/client.js +187 -4
- package/dist/errors.d.ts +38 -1
- package/dist/errors.js +234 -23
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -2
- package/dist/pipeline.js +15 -2
- package/dist/powql.d.ts +12 -0
- package/dist/powql.js +46 -21
- package/dist/prisma-compat.d.ts +15 -5
- package/dist/prisma-compat.js +274 -79
- package/dist/query/aggregates.d.ts +1 -1
- package/dist/query/aggregates.js +24 -10
- package/dist/query/batched-loader.d.ts +9 -4
- package/dist/query/batched-loader.js +4 -1
- package/dist/query/builder.d.ts +47 -0
- package/dist/query/builder.js +124 -21
- package/dist/query/index.d.ts +3 -1
- package/dist/query/index.js +2 -0
- package/dist/query/option-surface.d.ts +11 -0
- package/dist/query/option-surface.js +13 -0
- package/dist/query/relations.d.ts +8 -0
- package/dist/query/relations.js +21 -1
- package/dist/query/types.d.ts +152 -18
- package/dist/query/types.js +207 -2
- package/dist/query/where.d.ts +3 -3
- package/dist/query/where.js +8 -2
- package/dist/query/writes.js +10 -9
- package/package.json +13 -3
|
@@ -41,6 +41,11 @@
|
|
|
41
41
|
* by this layer, and a key that is neither a Prisma arg nor a turbine option
|
|
42
42
|
* gets a dev-mode warning instead of vanishing (see
|
|
43
43
|
* {@link PRISMA_ARG_KEYS} and `warnUnknownQueryOptions`).
|
|
44
|
+
* The three PRIVILEGE options among them (`skipGlobalFilters`, `includePii`,
|
|
45
|
+
* `allowFullTableScan`) are forwarded VERBATIM, which is what makes them safe
|
|
46
|
+
* here: core accepts only the `UNSAFE` sentinel, so a Prisma-shaped args
|
|
47
|
+
* object carrying `includePii: true` reaches core and is refused there, and
|
|
48
|
+
* this layer never needs its own copy of that rule.
|
|
44
49
|
*
|
|
45
50
|
* ## What it deliberately does NOT do (documented divergences)
|
|
46
51
|
*
|
|
@@ -76,11 +81,16 @@
|
|
|
76
81
|
* - **`limit` on `updateMany` / `deleteMany`** (Prisma 6.7+) throws. Turbine has
|
|
77
82
|
* no row-bounded mass mutation, and dropping a SAFETY BOUND with a warning
|
|
78
83
|
* would turn "change at most 10 rows" into "change every matching row".
|
|
79
|
-
* - **Write projections
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
+
* - **Write projections**: `select` / `omit` on create/update/delete/upsert
|
|
85
|
+
* narrow the RETURNED OBJECT (see {@link resolveWriteProjection}); they do
|
|
86
|
+
* NOT narrow the statement's `RETURNING` list, so the column still crosses
|
|
87
|
+
* the wire, and `pii: true` remains the SQL-level control. A relation key in
|
|
88
|
+
* either (or any `include` on a write) THROWS, since a write returns its own
|
|
89
|
+
* row only. `omit` on READS is translated to core's own `omit`, see
|
|
90
|
+
* {@link translateOmit}.
|
|
91
|
+
* - **`select` on `count`** and **`orderBy` / `cursor` / `take` / `skip` on
|
|
92
|
+
* `aggregate`** are accepted and IGNORED (they are legitimate Prisma, so they
|
|
93
|
+
* never warn); a plain number / the aggregate comes back.
|
|
84
94
|
*
|
|
85
95
|
* ## Type dependencies (0.41.0)
|
|
86
96
|
*
|
|
@@ -638,16 +648,53 @@ function mapTake(take) {
|
|
|
638
648
|
return take;
|
|
639
649
|
}
|
|
640
650
|
/**
|
|
641
|
-
* Translate Prisma `
|
|
642
|
-
*
|
|
643
|
-
*
|
|
651
|
+
* Translate a Prisma `omit` (scalar field names → true) into Turbine's `omit`.
|
|
652
|
+
*
|
|
653
|
+
* This used to be DROPPED silently, which made it a data-exposure bug rather
|
|
654
|
+
* than an ergonomics one: `omit` is the idiom for a sensitive-but-untagged
|
|
655
|
+
* column (`passwordHash`, `resetToken`), so a caller who asked for it got the
|
|
656
|
+
* column back anyway, in a response they believed was already filtered. Turbine
|
|
657
|
+
* core has supported `omit` on reads all along; only this translation was
|
|
658
|
+
* missing.
|
|
659
|
+
*
|
|
660
|
+
* A relation name is refused rather than forwarded: core's `omit` narrows the
|
|
661
|
+
* SCALAR projection, so `omit: { posts: true }` would have compiled to nothing
|
|
662
|
+
* and dropped the caller's intent a second time. Prisma rejects it too.
|
|
663
|
+
*/
|
|
664
|
+
function translateOmit(ctx, mm, omit) {
|
|
665
|
+
const out = {};
|
|
666
|
+
let any = false;
|
|
667
|
+
for (const [key, val] of Object.entries(omit)) {
|
|
668
|
+
if (val === false || val == null)
|
|
669
|
+
continue;
|
|
670
|
+
if (mm.relations[key]) {
|
|
671
|
+
throw new errors_js_1.ValidationError(`[turbine] prisma-compat: \`omit\` on model "${modelName(ctx, mm)}" takes scalar fields, but "${key}" ` +
|
|
672
|
+
'is a relation. Leave the relation out of `include` instead.');
|
|
673
|
+
}
|
|
674
|
+
out[renameField(mm, key)] = true;
|
|
675
|
+
any = true;
|
|
676
|
+
}
|
|
677
|
+
return any ? out : undefined;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Translate Prisma `include` / `select` / `omit` into Turbine
|
|
681
|
+
* `{ select, omit, with }`. `include` keeps all scalars and adds relations;
|
|
682
|
+
* `select` narrows scalars and may also pull relations + `_count`. `include`
|
|
683
|
+
* and `select` are mutually exclusive, and so are `select` and `omit` (a
|
|
684
|
+
* narrowed projection minus fields is ambiguous; Prisma refuses the pair too).
|
|
644
685
|
*/
|
|
645
686
|
function translateProjection(ctx, mm, args) {
|
|
646
687
|
const include = args.include;
|
|
647
688
|
const select = args.select;
|
|
689
|
+
const omitArg = args.omit;
|
|
648
690
|
if (include && select) {
|
|
649
691
|
throw new errors_js_1.ValidationError('[turbine] prisma-compat: `include` and `select` are mutually exclusive.');
|
|
650
692
|
}
|
|
693
|
+
if (select && omitArg) {
|
|
694
|
+
throw new errors_js_1.ValidationError('[turbine] prisma-compat: `select` and `omit` are mutually exclusive. ' +
|
|
695
|
+
'A `select` already lists exactly the fields you want.');
|
|
696
|
+
}
|
|
697
|
+
const omit = omitArg ? translateOmit(ctx, mm, omitArg) : undefined;
|
|
651
698
|
const withClause = {};
|
|
652
699
|
let hasWith = false;
|
|
653
700
|
if (include) {
|
|
@@ -666,7 +713,7 @@ function translateProjection(ctx, mm, args) {
|
|
|
666
713
|
withClause[rel.name] = translateWithOption(ctx, mm, rel.name, val);
|
|
667
714
|
hasWith = true;
|
|
668
715
|
}
|
|
669
|
-
return { with: hasWith ? withClause : undefined };
|
|
716
|
+
return { omit, with: hasWith ? withClause : undefined };
|
|
670
717
|
}
|
|
671
718
|
if (select) {
|
|
672
719
|
const scalar = {};
|
|
@@ -690,7 +737,130 @@ function translateProjection(ctx, mm, args) {
|
|
|
690
737
|
}
|
|
691
738
|
return { select: hasScalar ? scalar : undefined, with: hasWith ? withClause : undefined };
|
|
692
739
|
}
|
|
693
|
-
return {};
|
|
740
|
+
return { omit };
|
|
741
|
+
}
|
|
742
|
+
const NO_WRITE_PROJECTION = {};
|
|
743
|
+
/**
|
|
744
|
+
* Refuse a write-projection key that names no field on the model.
|
|
745
|
+
*
|
|
746
|
+
* Unlike a READ projection, this one never reaches core: `select` / `omit` on a
|
|
747
|
+
* write are applied to the already-returned object by
|
|
748
|
+
* {@link applyWriteProjection}, so nothing downstream can notice the name is
|
|
749
|
+
* wrong. A typo therefore failed silently in BOTH directions, and the `omit`
|
|
750
|
+
* direction is the one that matters: `omit: { ssn: true }` misspelled as
|
|
751
|
+
* `snn` returned the column, and the call site that asked for it hidden had no
|
|
752
|
+
* signal at all. (`pick` failed the other way, quietly dropping the field from
|
|
753
|
+
* the result.)
|
|
754
|
+
*
|
|
755
|
+
* The read path has never had this hole: core validates every projection key
|
|
756
|
+
* and throws E003 on an unknown one, which is the behaviour a caller has
|
|
757
|
+
* already observed on `findMany` before writing the same clause on `create`.
|
|
758
|
+
* So this is not a new rule, it is the read rule reaching the one surface that
|
|
759
|
+
* bypassed it. Errors match core's shape, including the suggestion.
|
|
760
|
+
*/
|
|
761
|
+
function assertWriteProjectionField(ctx, mm, op, clause, key) {
|
|
762
|
+
if (Object.hasOwn(mm.fields, key))
|
|
763
|
+
return;
|
|
764
|
+
const known = Object.keys(mm.fields);
|
|
765
|
+
const suggestion = (0, utils_js_1.suggestKey)(key, new Set(known));
|
|
766
|
+
throw new errors_js_1.ValidationError(`[turbine] prisma-compat: unknown field "${key}" in \`${clause}\` on ${modelName(ctx, mm)}.${op}().` +
|
|
767
|
+
(suggestion ? ` Did you mean "${suggestion}"?` : '') +
|
|
768
|
+
` Known fields: ${known.join(', ') || '(none)'}.`);
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Resolve `select` / `omit` / `include` on create / update / delete / upsert.
|
|
772
|
+
*
|
|
773
|
+
* All three used to be accepted and IGNORED here. For `omit` that is the exact
|
|
774
|
+
* data-exposure bug that was fixed for reads: `omit` is the idiom for a
|
|
775
|
+
* sensitive-but-untagged column, and because it demonstrably WORKS on
|
|
776
|
+
* `findMany`, a caller has positive evidence for assuming it works on `create`
|
|
777
|
+
* too. `select` is the same class in the other spelling (the caller asked for
|
|
778
|
+
* three fields and got the whole row).
|
|
779
|
+
*
|
|
780
|
+
* SCOPE, stated exactly rather than implied: this narrows the OBJECT this
|
|
781
|
+
* adapter returns. Core's write args carry no projection, so the column is
|
|
782
|
+
* still in the statement's `RETURNING` list and still crosses the wire. The
|
|
783
|
+
* SQL-level control is `pii: true` on the column, which is enforced in core and
|
|
784
|
+
* is unaffected by any of this.
|
|
785
|
+
*
|
|
786
|
+
* A relation (or `_count`) key THROWS instead: a write returns its own row and
|
|
787
|
+
* nothing else, so serving it is impossible, and the alternative is handing
|
|
788
|
+
* back a row whose relation property is silently `undefined`, which is the
|
|
789
|
+
* failure mode this whole round exists to remove.
|
|
790
|
+
*/
|
|
791
|
+
function resolveWriteProjection(ctx, mm, op, args) {
|
|
792
|
+
if (!args)
|
|
793
|
+
return NO_WRITE_PROJECTION;
|
|
794
|
+
const include = args.include;
|
|
795
|
+
const select = args.select;
|
|
796
|
+
const omitArg = args.omit;
|
|
797
|
+
if (include && select) {
|
|
798
|
+
throw new errors_js_1.ValidationError('[turbine] prisma-compat: `include` and `select` are mutually exclusive.');
|
|
799
|
+
}
|
|
800
|
+
if (select && omitArg) {
|
|
801
|
+
throw new errors_js_1.ValidationError('[turbine] prisma-compat: `select` and `omit` are mutually exclusive. ' +
|
|
802
|
+
'A `select` already lists exactly the fields you want.');
|
|
803
|
+
}
|
|
804
|
+
if (include) {
|
|
805
|
+
for (const [key, val] of Object.entries(include)) {
|
|
806
|
+
if (val === false || val == null)
|
|
807
|
+
continue;
|
|
808
|
+
throw new errors_js_1.UnsupportedFeatureError(`include ("${key}") on ${op}`, 'prisma-compat', `a write on model "${modelName(ctx, mm)}" returns its own row only; read the relation back with a ` +
|
|
809
|
+
'separate findUnique inside the same $transaction.');
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (select) {
|
|
813
|
+
const pick = new Set();
|
|
814
|
+
for (const [key, val] of Object.entries(select)) {
|
|
815
|
+
if (val === false || val == null)
|
|
816
|
+
continue;
|
|
817
|
+
if (key === '_count' || mm.relations[key]) {
|
|
818
|
+
throw new errors_js_1.UnsupportedFeatureError(`select of "${key}" on ${op}`, 'prisma-compat', `"${key}" is a relation on model "${modelName(ctx, mm)}"; a write returns its own row only, so read ` +
|
|
819
|
+
'it back with a separate findUnique inside the same $transaction.');
|
|
820
|
+
}
|
|
821
|
+
assertWriteProjectionField(ctx, mm, op, 'select', key);
|
|
822
|
+
pick.add(key);
|
|
823
|
+
}
|
|
824
|
+
// An all-false `select` names nothing. Reads treat that as "no projection"
|
|
825
|
+
// (translateProjection returns select: undefined), so writes do too rather
|
|
826
|
+
// than inventing a second rule for input Prisma rejects anyway.
|
|
827
|
+
return pick.size ? { pick } : NO_WRITE_PROJECTION;
|
|
828
|
+
}
|
|
829
|
+
if (omitArg) {
|
|
830
|
+
const omit = new Set();
|
|
831
|
+
for (const [key, val] of Object.entries(omitArg)) {
|
|
832
|
+
if (val === false || val == null)
|
|
833
|
+
continue;
|
|
834
|
+
if (mm.relations[key]) {
|
|
835
|
+
throw new errors_js_1.ValidationError(`[turbine] prisma-compat: \`omit\` on model "${modelName(ctx, mm)}" takes scalar fields, but "${key}" ` +
|
|
836
|
+
'is a relation.');
|
|
837
|
+
}
|
|
838
|
+
assertWriteProjectionField(ctx, mm, op, 'omit', key);
|
|
839
|
+
omit.add(key);
|
|
840
|
+
}
|
|
841
|
+
return omit.size ? { omit } : NO_WRITE_PROJECTION;
|
|
842
|
+
}
|
|
843
|
+
return NO_WRITE_PROJECTION;
|
|
844
|
+
}
|
|
845
|
+
/** Apply a {@link WriteProjection} to one already-reshaped write result row. */
|
|
846
|
+
function applyWriteProjection(proj, row) {
|
|
847
|
+
if (!isPlainObject(row))
|
|
848
|
+
return row;
|
|
849
|
+
if (proj.pick) {
|
|
850
|
+
const out = {};
|
|
851
|
+
for (const key of proj.pick) {
|
|
852
|
+
if (key in row)
|
|
853
|
+
out[key] = row[key];
|
|
854
|
+
}
|
|
855
|
+
return out;
|
|
856
|
+
}
|
|
857
|
+
if (proj.omit) {
|
|
858
|
+
const out = { ...row };
|
|
859
|
+
for (const key of proj.omit)
|
|
860
|
+
delete out[key];
|
|
861
|
+
return out;
|
|
862
|
+
}
|
|
863
|
+
return row;
|
|
694
864
|
}
|
|
695
865
|
/** Translate a Prisma relation include payload into a Turbine `WithOptions`. */
|
|
696
866
|
function translateWithOption(ctx, mm, turbineRel, val) {
|
|
@@ -709,10 +879,14 @@ function translateWithOption(ctx, mm, turbineRel, val) {
|
|
|
709
879
|
if (val.skip !== undefined) {
|
|
710
880
|
throw new errors_js_1.UnsupportedFeatureError('skip (offset) on a nested relation include', 'prisma-compat', "Turbine's `with` clause has no offset, page the relation with a separate query.");
|
|
711
881
|
}
|
|
712
|
-
if (target && (val.select !== undefined || val.include !== undefined)) {
|
|
882
|
+
if (target && (val.select !== undefined || val.include !== undefined || val.omit !== undefined)) {
|
|
713
883
|
const proj = translateProjection(ctx, target, val);
|
|
714
884
|
if (proj.select)
|
|
715
885
|
opt.select = proj.select;
|
|
886
|
+
// Same silent drop as the top level: a nested `omit` names the sensitive
|
|
887
|
+
// column on the CHILD rows, which are just as exposed.
|
|
888
|
+
if (proj.omit)
|
|
889
|
+
opt.omit = proj.omit;
|
|
716
890
|
if (proj.with)
|
|
717
891
|
opt.with = proj.with;
|
|
718
892
|
}
|
|
@@ -759,6 +933,8 @@ function translateReadArgs(ctx, mm, prismaArgs, kind) {
|
|
|
759
933
|
const proj = translateProjection(ctx, mm, prismaArgs);
|
|
760
934
|
if (proj.select)
|
|
761
935
|
t.select = proj.select;
|
|
936
|
+
if (proj.omit)
|
|
937
|
+
t.omit = proj.omit;
|
|
762
938
|
if (proj.with)
|
|
763
939
|
t.with = proj.with;
|
|
764
940
|
if (Array.isArray(prismaArgs.distinct)) {
|
|
@@ -1520,15 +1696,22 @@ function makeDelegate(ctx, mm, getQI, runInTx) {
|
|
|
1520
1696
|
findUnique: (args) => defer('findUnique', args, () => translateReadArgs(ctx, mm, requireWhere(args, 'findUnique'), 'unique'), (qi, t) => qi.findUnique(t).then((r) => reshapeRowOrNull(ctx, mm, r)), { build: (qi, t) => qi.buildFindUnique(t), reshape: (raw) => reshapeRowOrNull(ctx, mm, raw) }),
|
|
1521
1697
|
findFirstOrThrow: (args = {}) => defer('findFirstOrThrow', args, () => translateReadArgs(ctx, mm, args, 'first'), (qi, t) => qi.findFirstOrThrow(t).then((r) => reshapeRow(ctx, mm, r)), { build: (qi, t) => qi.buildFindFirstOrThrow(t), reshape: (raw) => reshapeRow(ctx, mm, raw) }),
|
|
1522
1698
|
findUniqueOrThrow: (args) => defer('findUniqueOrThrow', args, () => translateReadArgs(ctx, mm, requireWhere(args, 'findUniqueOrThrow'), 'unique'), (qi, t) => qi.findUniqueOrThrow(t).then((r) => reshapeRow(ctx, mm, r)), { build: (qi, t) => qi.buildFindUniqueOrThrow(t), reshape: (raw) => reshapeRow(ctx, mm, raw) }),
|
|
1523
|
-
create: (args) =>
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1699
|
+
create: (args) => {
|
|
1700
|
+
// Resolved inside the translate thunk below (so it validates before the
|
|
1701
|
+
// write runs and stays deferred), read back by every result path.
|
|
1702
|
+
let proj = NO_WRITE_PROJECTION;
|
|
1703
|
+
const shape = (raw) => applyWriteProjection(proj, reshapeRow(ctx, mm, raw));
|
|
1704
|
+
return defer('create', args, () => {
|
|
1705
|
+
proj = resolveWriteProjection(ctx, mm, 'create', args);
|
|
1706
|
+
const t = { data: translateWriteData(ctx, mm, applyCreateDefaults(mm, args.data)) };
|
|
1707
|
+
(0, index_js_1.applyNativeOptions)(index_js_1.CREATE_OPTIONS, args, t);
|
|
1708
|
+
return t;
|
|
1709
|
+
}, (qi, t) => qi.create(t).then(shape), {
|
|
1710
|
+
build: (qi, t) => qi.buildCreate(t),
|
|
1711
|
+
reshape: shape,
|
|
1712
|
+
nested: (t) => hasNestedKeys(ctx, mm, t.data),
|
|
1713
|
+
});
|
|
1714
|
+
},
|
|
1532
1715
|
createMany: (args) => defer('createMany', args, () => {
|
|
1533
1716
|
const data = args.data;
|
|
1534
1717
|
const rows = Array.isArray(data)
|
|
@@ -1556,28 +1739,33 @@ function makeDelegate(ctx, mm, getQI, runInTx) {
|
|
|
1556
1739
|
nested: (t) => createManyRunsOf(t).length > 1,
|
|
1557
1740
|
execInTx: (table, t) => createManyByRun(table(mm.table), t, createManyRunsOf(t)),
|
|
1558
1741
|
}),
|
|
1559
|
-
update: (args) =>
|
|
1560
|
-
|
|
1561
|
-
const
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
// and break on any model whose column is `@map`ped.
|
|
1569
|
-
if (isPlainObject(a.optimisticLock)) {
|
|
1570
|
-
t.optimisticLock = {
|
|
1571
|
-
...a.optimisticLock,
|
|
1572
|
-
field: renameField(mm, String(a.optimisticLock.field)),
|
|
1742
|
+
update: (args) => {
|
|
1743
|
+
let proj = NO_WRITE_PROJECTION;
|
|
1744
|
+
const shape = (raw) => applyWriteProjection(proj, reshapeRow(ctx, mm, raw));
|
|
1745
|
+
return defer('update', args, () => {
|
|
1746
|
+
const a = requireWhere(args, 'update');
|
|
1747
|
+
proj = resolveWriteProjection(ctx, mm, 'update', a);
|
|
1748
|
+
const t = {
|
|
1749
|
+
where: translateWhere(ctx, mm, a.where),
|
|
1750
|
+
data: translateWriteData(ctx, mm, applyUpdateTouch(mm, a.data)),
|
|
1573
1751
|
};
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1752
|
+
(0, index_js_1.applyNativeOptions)(index_js_1.UPDATE_OPTIONS, a, t);
|
|
1753
|
+
// `optimisticLock.field` is a FIELD NAME, so it is renamed rather than
|
|
1754
|
+
// copied: a blind passthrough would send the Prisma spelling into core
|
|
1755
|
+
// and break on any model whose column is `@map`ped.
|
|
1756
|
+
if (isPlainObject(a.optimisticLock)) {
|
|
1757
|
+
t.optimisticLock = {
|
|
1758
|
+
...a.optimisticLock,
|
|
1759
|
+
field: renameField(mm, String(a.optimisticLock.field)),
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
return t;
|
|
1763
|
+
}, (qi, t) => qi.update(t).then(shape), {
|
|
1764
|
+
build: (qi, t) => qi.buildUpdate(t),
|
|
1765
|
+
reshape: shape,
|
|
1766
|
+
nested: (t) => hasNestedKeys(ctx, mm, t.data),
|
|
1767
|
+
});
|
|
1768
|
+
},
|
|
1581
1769
|
updateMany: (args) => defer('updateMany', args, () => {
|
|
1582
1770
|
const a = args;
|
|
1583
1771
|
refuseRowLimit(modelName(ctx, mm), 'updateMany', a);
|
|
@@ -1590,15 +1778,20 @@ function makeDelegate(ctx, mm, getQI, runInTx) {
|
|
|
1590
1778
|
// row, and an explicit `allowFullTableScan: false` must not be able to
|
|
1591
1779
|
// turn that parity into a thrown empty-where guard.
|
|
1592
1780
|
if (a.where === undefined)
|
|
1593
|
-
t.allowFullTableScan =
|
|
1781
|
+
t.allowFullTableScan = index_js_1.UNSAFE;
|
|
1594
1782
|
return t;
|
|
1595
1783
|
}, (qi, t) => qi.updateMany(t), { build: (qi, t) => qi.buildUpdateMany(t), reshape: (raw) => raw }),
|
|
1596
|
-
delete: (args) =>
|
|
1597
|
-
|
|
1598
|
-
const
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1784
|
+
delete: (args) => {
|
|
1785
|
+
let proj = NO_WRITE_PROJECTION;
|
|
1786
|
+
const shape = (raw) => applyWriteProjection(proj, reshapeRow(ctx, mm, raw));
|
|
1787
|
+
return defer('delete', args, () => {
|
|
1788
|
+
const a = requireWhere(args, 'delete');
|
|
1789
|
+
proj = resolveWriteProjection(ctx, mm, 'delete', a);
|
|
1790
|
+
const t = { where: translateWhere(ctx, mm, a.where) };
|
|
1791
|
+
(0, index_js_1.applyNativeOptions)(index_js_1.DELETE_OPTIONS, a, t);
|
|
1792
|
+
return t;
|
|
1793
|
+
}, (qi, t) => qi.delete(t).then(shape), { build: (qi, t) => qi.buildDelete(t), reshape: shape });
|
|
1794
|
+
},
|
|
1602
1795
|
deleteMany: (args = {}) => defer('deleteMany', args, () => {
|
|
1603
1796
|
const a = args;
|
|
1604
1797
|
refuseRowLimit(modelName(ctx, mm), 'deleteMany', a);
|
|
@@ -1606,40 +1799,42 @@ function makeDelegate(ctx, mm, getQI, runInTx) {
|
|
|
1606
1799
|
(0, index_js_1.applyNativeOptions)(index_js_1.DELETE_MANY_OPTIONS, a, t);
|
|
1607
1800
|
// LAST, so it wins. See the same note on updateMany.
|
|
1608
1801
|
if (a.where === undefined)
|
|
1609
|
-
t.allowFullTableScan =
|
|
1802
|
+
t.allowFullTableScan = index_js_1.UNSAFE;
|
|
1610
1803
|
return t;
|
|
1611
1804
|
}, (qi, t) => qi.deleteMany(t), { build: (qi, t) => qi.buildDeleteMany(t), reshape: (raw) => raw }),
|
|
1612
|
-
upsert: (args) =>
|
|
1613
|
-
|
|
1614
|
-
const
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
const row = await upsertLookupFirst(table(mm.table), t);
|
|
1630
|
-
return reshapeRow(ctx, mm, row);
|
|
1631
|
-
});
|
|
1632
|
-
}, {
|
|
1633
|
-
build: (qi, t) => qi.buildUpsert(t),
|
|
1634
|
-
reshape: (raw) => reshapeRow(ctx, mm, raw),
|
|
1635
|
-
nested: (t) => !upsertKeysMatch(t) || hasNestedKeys(ctx, mm, t.create) || hasNestedKeys(ctx, mm, t.update),
|
|
1636
|
-
execInTx: async (table, t) => {
|
|
1805
|
+
upsert: (args) => {
|
|
1806
|
+
let proj = NO_WRITE_PROJECTION;
|
|
1807
|
+
const shape = (raw) => applyWriteProjection(proj, reshapeRow(ctx, mm, raw));
|
|
1808
|
+
return defer('upsert', args, () => {
|
|
1809
|
+
const a = requireWhere(args, 'upsert');
|
|
1810
|
+
proj = resolveWriteProjection(ctx, mm, 'upsert', a);
|
|
1811
|
+
const t = {
|
|
1812
|
+
where: translateWhere(ctx, mm, a.where),
|
|
1813
|
+
create: translateWriteData(ctx, mm, applyCreateDefaults(mm, a.create)),
|
|
1814
|
+
update: translateWriteData(ctx, mm, applyUpdateTouch(mm, a.update)),
|
|
1815
|
+
};
|
|
1816
|
+
(0, index_js_1.applyNativeOptions)(index_js_1.UPSERT_OPTIONS, a, t);
|
|
1817
|
+
return t;
|
|
1818
|
+
}, (qi, t) => {
|
|
1819
|
+
// Native ON CONFLICT upsert is only Prisma-equivalent when the where
|
|
1820
|
+
// key values equal the create values AND no nested write data is
|
|
1821
|
+
// present; otherwise emulate Prisma's lookup-first atomically.
|
|
1637
1822
|
if (upsertKeysMatch(t) && !hasNestedKeys(ctx, mm, t.create) && !hasNestedKeys(ctx, mm, t.update)) {
|
|
1638
|
-
return
|
|
1823
|
+
return qi.upsert(t).then(shape);
|
|
1639
1824
|
}
|
|
1640
|
-
return
|
|
1641
|
-
},
|
|
1642
|
-
|
|
1825
|
+
return runInTx(async (table) => shape(await upsertLookupFirst(table(mm.table), t)));
|
|
1826
|
+
}, {
|
|
1827
|
+
build: (qi, t) => qi.buildUpsert(t),
|
|
1828
|
+
reshape: shape,
|
|
1829
|
+
nested: (t) => !upsertKeysMatch(t) || hasNestedKeys(ctx, mm, t.create) || hasNestedKeys(ctx, mm, t.update),
|
|
1830
|
+
execInTx: async (table, t) => {
|
|
1831
|
+
if (upsertKeysMatch(t) && !hasNestedKeys(ctx, mm, t.create) && !hasNestedKeys(ctx, mm, t.update)) {
|
|
1832
|
+
return shape(await table(mm.table).upsert(t));
|
|
1833
|
+
}
|
|
1834
|
+
return shape(await upsertLookupFirst(table(mm.table), t));
|
|
1835
|
+
},
|
|
1836
|
+
});
|
|
1837
|
+
},
|
|
1643
1838
|
count: (args = {}) => defer('count', args, () => {
|
|
1644
1839
|
const t = {};
|
|
1645
1840
|
(0, index_js_1.applyNativeOptions)(index_js_1.COUNT_OPTIONS, args, t);
|
|
@@ -18,7 +18,7 @@ import type { BuilderCtx } from './where.js';
|
|
|
18
18
|
* projection, and a value-returning aggregate is a projection by another name:
|
|
19
19
|
* `groupBy({ by: ['email'] })` emits one row per distinct plaintext email, and
|
|
20
20
|
* `_min`/`_max` return a stored cell verbatim. Both therefore REQUIRE the same
|
|
21
|
-
* `includePii
|
|
21
|
+
* `includePii` opt-in (the UNSAFE sentinel) that reads use.
|
|
22
22
|
*
|
|
23
23
|
* Deliberately NOT gated: `_count` (a count, never a value), `_sum` / `_avg`
|
|
24
24
|
* (a computed total across many rows, not a stored cell), and `where` /
|
|
@@ -54,6 +54,7 @@ exports.buildAggregate = buildAggregate;
|
|
|
54
54
|
const errors_js_1 = require("../errors.js");
|
|
55
55
|
const schema_js_1 = require("../schema.js");
|
|
56
56
|
const filters_js_1 = require("./filters.js");
|
|
57
|
+
const types_js_1 = require("./types.js");
|
|
57
58
|
const utils_js_1 = require("./utils.js");
|
|
58
59
|
const whereMod = __importStar(require("./where.js"));
|
|
59
60
|
/**
|
|
@@ -62,7 +63,7 @@ const whereMod = __importStar(require("./where.js"));
|
|
|
62
63
|
* projection, and a value-returning aggregate is a projection by another name:
|
|
63
64
|
* `groupBy({ by: ['email'] })` emits one row per distinct plaintext email, and
|
|
64
65
|
* `_min`/`_max` return a stored cell verbatim. Both therefore REQUIRE the same
|
|
65
|
-
* `includePii
|
|
66
|
+
* `includePii` opt-in (the UNSAFE sentinel) that reads use.
|
|
66
67
|
*
|
|
67
68
|
* Deliberately NOT gated: `_count` (a count, never a value), `_sum` / `_avg`
|
|
68
69
|
* (a computed total across many rows, not a stored cell), and `where` /
|
|
@@ -80,7 +81,8 @@ function assertAggregatePiiOptIn(table, meta, field, column, usage, includePii)
|
|
|
80
81
|
return;
|
|
81
82
|
throw new errors_js_1.ValidationError(`[turbine] ${usage} on column "${field}" of table "${table}" is refused: that column is ` +
|
|
82
83
|
'PII-tagged (`pii: true`), and this aggregate returns its stored values, which are excluded ' +
|
|
83
|
-
'from every default projection. Pass `includePii:
|
|
84
|
+
'from every default projection. Pass `includePii: UNSAFE` on this call to opt in ' +
|
|
85
|
+
"(import { UNSAFE } from 'turbine-orm'). " +
|
|
84
86
|
'`_count` over a PII column (a count, not a value) and `where` / `orderBy` / `having` on PII ' +
|
|
85
87
|
'columns need no opt-in.');
|
|
86
88
|
}
|
|
@@ -93,7 +95,11 @@ function buildGroupBy(qi, args) {
|
|
|
93
95
|
}
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
|
-
qi.currentSkip = args.skipGlobalFilters;
|
|
98
|
+
qi.currentSkip = (0, types_js_1.resolveSkipGlobalFilters)(args.skipGlobalFilters);
|
|
99
|
+
// Resolve the PII opt-in ONCE, here, so the sentinel check runs on every
|
|
100
|
+
// groupBy (including one whose `by` names no PII column) rather than only on
|
|
101
|
+
// the paths that happen to consult it.
|
|
102
|
+
const includePii = (0, types_js_1.resolveUnsafeFlag)(args.includePii, 'includePii');
|
|
97
103
|
const gbWhere = whereMod.mergeGlobalFilter(qi, args.where);
|
|
98
104
|
const { sql: whereSql, params } = gbWhere
|
|
99
105
|
? whereMod.buildWhere(qi, gbWhere)
|
|
@@ -143,7 +149,7 @@ function buildGroupBy(qi, args) {
|
|
|
143
149
|
for (const entry of args.by) {
|
|
144
150
|
if (typeof entry === 'string') {
|
|
145
151
|
const col = qi.toColumn(entry);
|
|
146
|
-
assertAggregatePiiOptIn(qi.table, meta, entry, col, 'groupBy `by` key',
|
|
152
|
+
assertAggregatePiiOptIn(qi.table, meta, entry, col, 'groupBy `by` key', includePii);
|
|
147
153
|
claimResultKey(entry, `column "${col}"`);
|
|
148
154
|
// The emitted output column is the snake_case name; claim it too (when
|
|
149
155
|
// it differs from the result key) so a JSON alias like 'created_at'
|
|
@@ -158,7 +164,7 @@ function buildGroupBy(qi, args) {
|
|
|
158
164
|
}
|
|
159
165
|
else {
|
|
160
166
|
const col = resolveJsonPathTarget(qi, 'group key', entry.field, entry.path);
|
|
161
|
-
assertAggregatePiiOptIn(qi.table, meta, entry.field, col, 'groupBy JSON `by` key',
|
|
167
|
+
assertAggregatePiiOptIn(qi.table, meta, entry.field, col, 'groupBy JSON `by` key', includePii);
|
|
162
168
|
params.push(whereMod.jsonPathParam(qi, entry.path));
|
|
163
169
|
const extract = qi.dialect.buildJsonPathExtract(qi.q(col), qi.p(params.length));
|
|
164
170
|
const alias = entry.alias ?? String(entry.path[entry.path.length - 1]);
|
|
@@ -226,7 +232,7 @@ function buildGroupBy(qi, args) {
|
|
|
226
232
|
if (target === true) {
|
|
227
233
|
const col = qi.toColumn(key);
|
|
228
234
|
if (aggKey === '_min' || aggKey === '_max') {
|
|
229
|
-
assertAggregatePiiOptIn(qi.table, meta, key, col, `groupBy ${aggKey}`,
|
|
235
|
+
assertAggregatePiiOptIn(qi.table, meta, key, col, `groupBy ${aggKey}`, includePii);
|
|
230
236
|
}
|
|
231
237
|
// Aggregate output aliases share the same output-name namespace as
|
|
232
238
|
// the group keys: `_sum: { totalPrice: true, total_price: {json} }`
|
|
@@ -240,7 +246,7 @@ function buildGroupBy(qi, args) {
|
|
|
240
246
|
}
|
|
241
247
|
const col = resolveJsonPathTarget(qi, `${aggKey} target "${key}"`, target.field, target.path);
|
|
242
248
|
if (aggKey === '_min' || aggKey === '_max') {
|
|
243
|
-
assertAggregatePiiOptIn(qi.table, meta, target.field, col, `groupBy ${aggKey} JSON target`,
|
|
249
|
+
assertAggregatePiiOptIn(qi.table, meta, target.field, col, `groupBy ${aggKey} JSON target`, includePii);
|
|
244
250
|
}
|
|
245
251
|
const alwaysNumeric = aggKey === '_sum' || aggKey === '_avg';
|
|
246
252
|
if (alwaysNumeric && target.type === 'text') {
|
|
@@ -412,6 +418,9 @@ function buildGroupByOrderBy(qi, orderBy, byOrderExprs, aggOrderExprs) {
|
|
|
412
418
|
throw new errors_js_1.ValidationError(`[turbine] Cannot order groupBy by "_count" on table "${qi.table}": _count is not selected. ` +
|
|
413
419
|
`Orderable keys: ${validKeys()}.`);
|
|
414
420
|
}
|
|
421
|
+
// Refuse a direction that is neither asc nor desc BEFORE normalizeOrderBy,
|
|
422
|
+
// whose `=== 'desc' ? DESC : ASC` would silently sort ascending.
|
|
423
|
+
(0, types_js_1.assertOrderDirection)(value, `groupBy orderBy "_count" on table "${qi.table}"`);
|
|
415
424
|
const { dir, nulls } = (0, filters_js_1.normalizeOrderBy)(value);
|
|
416
425
|
parts.push(`${expr} ${dir}${qi.nullsSuffix(nulls)}`);
|
|
417
426
|
continue;
|
|
@@ -429,6 +438,7 @@ function buildGroupByOrderBy(qi, orderBy, byOrderExprs, aggOrderExprs) {
|
|
|
429
438
|
throw new errors_js_1.ValidationError(`[turbine] Cannot order groupBy by "${key}.${field}" on table "${qi.table}": ` +
|
|
430
439
|
`that aggregate is not requested in this call. Orderable keys: ${validKeys()}.`);
|
|
431
440
|
}
|
|
441
|
+
(0, types_js_1.assertOrderDirection)(dirSpec, `groupBy orderBy "${key}.${field}" on table "${qi.table}"`);
|
|
432
442
|
const { dir, nulls } = (0, filters_js_1.normalizeOrderBy)(dirSpec);
|
|
433
443
|
parts.push(`${expr} ${dir}${qi.nullsSuffix(nulls)}`);
|
|
434
444
|
}
|
|
@@ -440,6 +450,7 @@ function buildGroupByOrderBy(qi, orderBy, byOrderExprs, aggOrderExprs) {
|
|
|
440
450
|
throw new errors_js_1.ValidationError(`[turbine] Unknown field "${key}" in groupBy orderBy on table "${qi.table}". ` +
|
|
441
451
|
`Orderable keys: ${validKeys()}.`);
|
|
442
452
|
}
|
|
453
|
+
(0, types_js_1.assertOrderDirection)(value, `groupBy orderBy "${key}" on table "${qi.table}"`);
|
|
443
454
|
const { dir, nulls } = (0, filters_js_1.normalizeOrderBy)(value);
|
|
444
455
|
parts.push(`${expr} ${dir}${qi.nullsSuffix(nulls)}`);
|
|
445
456
|
}
|
|
@@ -508,6 +519,7 @@ function buildDistinctOnSource(qi, distinctOn, whereSql, params) {
|
|
|
508
519
|
'sort specs, and JSON-path orderings only.');
|
|
509
520
|
}
|
|
510
521
|
const col = qi.resolveOrderByColumn(qi.table, qi.tableMeta, key);
|
|
522
|
+
(0, types_js_1.assertOrderDirection)(value, `groupBy distinctOn.orderBy "${key}" on table "${qi.table}"`);
|
|
511
523
|
const { dir, nulls } = (0, filters_js_1.normalizeOrderBy)(value);
|
|
512
524
|
orderParts.push(`${qi.q(col)} ${dir}${qi.nullsSuffix(nulls)}`);
|
|
513
525
|
}
|
|
@@ -803,7 +815,9 @@ function temporalAggValue(qi, col, value) {
|
|
|
803
815
|
return value === '-infinity' ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY;
|
|
804
816
|
}
|
|
805
817
|
function buildAggregate(qi, args) {
|
|
806
|
-
qi.currentSkip = args.skipGlobalFilters;
|
|
818
|
+
qi.currentSkip = (0, types_js_1.resolveSkipGlobalFilters)(args.skipGlobalFilters);
|
|
819
|
+
// Resolved once, up front: see buildGroupBy.
|
|
820
|
+
const includePii = (0, types_js_1.resolveUnsafeFlag)(args.includePii, 'includePii');
|
|
807
821
|
const aggWhere = whereMod.mergeGlobalFilter(qi, args.where);
|
|
808
822
|
const { sql: whereSql, params } = aggWhere
|
|
809
823
|
? whereMod.buildWhere(qi, aggWhere)
|
|
@@ -874,7 +888,7 @@ function buildAggregate(qi, args) {
|
|
|
874
888
|
for (const [field, enabled] of Object.entries(args._min)) {
|
|
875
889
|
if (enabled) {
|
|
876
890
|
const col = qi.toColumn(field);
|
|
877
|
-
assertAggregatePiiOptIn(qi.table, meta, field, col, 'aggregate _min',
|
|
891
|
+
assertAggregatePiiOptIn(qi.table, meta, field, col, 'aggregate _min', includePii);
|
|
878
892
|
selectExprs.push(`MIN(${qi.q(col)}) AS ${qi.q(`_min_${col}`)}`);
|
|
879
893
|
}
|
|
880
894
|
}
|
|
@@ -884,7 +898,7 @@ function buildAggregate(qi, args) {
|
|
|
884
898
|
for (const [field, enabled] of Object.entries(args._max)) {
|
|
885
899
|
if (enabled) {
|
|
886
900
|
const col = qi.toColumn(field);
|
|
887
|
-
assertAggregatePiiOptIn(qi.table, meta, field, col, 'aggregate _max',
|
|
901
|
+
assertAggregatePiiOptIn(qi.table, meta, field, col, 'aggregate _max', includePii);
|
|
888
902
|
selectExprs.push(`MAX(${qi.q(col)}) AS ${qi.q(`_max_${col}`)}`);
|
|
889
903
|
}
|
|
890
904
|
}
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
import type pg from 'pg';
|
|
57
57
|
import { type RelationDef, type SchemaMetadata, type TableMetadata } from '../schema.js';
|
|
58
58
|
import type { ReselectExecutor } from './builder.js';
|
|
59
|
-
import type { SkipGlobalFilters, WithClause, WithCount } from './types.js';
|
|
59
|
+
import type { SkipGlobalFilters, Unsafe, WithClause, WithCount } from './types.js';
|
|
60
60
|
/**
|
|
61
61
|
* A DeferredQuery, minimally typed for what the loader consumes. Kept local to
|
|
62
62
|
* avoid a value import of builder.ts (which imports this module).
|
|
@@ -104,9 +104,14 @@ export interface RelationLoadContext {
|
|
|
104
104
|
/**
|
|
105
105
|
* The query's `includePii` opt-in, threaded onto every child `buildFindMany`
|
|
106
106
|
* so a batched relation load excludes (or includes) PII-tagged columns exactly
|
|
107
|
-
* as the join strategy does at every nested level.
|
|
107
|
+
* as the join strategy does at every nested level. Absent by default.
|
|
108
|
+
*
|
|
109
|
+
* Typed as the SENTINEL, not a boolean, and that is a contract not a style
|
|
110
|
+
* choice: this value is copied verbatim onto child `FindManyArgs`, where a
|
|
111
|
+
* plain `true` is refused as a privilege escalation. A boolean here would
|
|
112
|
+
* type-check and then throw on the first relation follow-up.
|
|
108
113
|
*/
|
|
109
|
-
includePii?:
|
|
114
|
+
includePii?: Unsafe;
|
|
110
115
|
/**
|
|
111
116
|
* Render `table`'s global filter against `alias` for a raw follow-up query
|
|
112
117
|
* (the batched `_count`), numbering its `$n` placeholders AFTER
|
|
@@ -128,7 +133,7 @@ export interface RelationLoadContext {
|
|
|
128
133
|
* keep the `select: undefined, omit: undefined` fast path and the emitted SQL
|
|
129
134
|
* stays byte-identical.
|
|
130
135
|
*/
|
|
131
|
-
export declare function defaultProjectionFields(meta: TableMetadata, includePii: boolean | undefined): {
|
|
136
|
+
export declare function defaultProjectionFields(meta: TableMetadata, includePii: boolean | Unsafe | undefined): {
|
|
132
137
|
hidden: ReadonlySet<string>;
|
|
133
138
|
visible: string[];
|
|
134
139
|
} | undefined;
|
|
@@ -86,7 +86,10 @@ const MAX_DEPTH = 10;
|
|
|
86
86
|
* keep the `select: undefined, omit: undefined` fast path and the emitted SQL
|
|
87
87
|
* stays byte-identical.
|
|
88
88
|
*/
|
|
89
|
-
function defaultProjectionFields(meta,
|
|
89
|
+
function defaultProjectionFields(meta,
|
|
90
|
+
// Either the already-resolved boolean (top-level callers) or the raw sentinel
|
|
91
|
+
// (the relation-load context); both are only ever tested for truthiness.
|
|
92
|
+
includePii) {
|
|
90
93
|
if (includePii)
|
|
91
94
|
return undefined;
|
|
92
95
|
const hidden = new Set();
|