@zenstackhq/runtime 3.0.0-beta.4 → 3.0.0-beta.6
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/dist/{contract-hoS-Sd87.d.cts → contract-BJce14-p.d.cts} +54 -12
- package/dist/{contract-hoS-Sd87.d.ts → contract-BJce14-p.d.ts} +54 -12
- package/dist/index.cjs +1211 -690
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1049 -528
- package/dist/index.js.map +1 -1
- package/dist/plugins/policy/index.cjs +835 -329
- package/dist/plugins/policy/index.cjs.map +1 -1
- package/dist/plugins/policy/index.d.cts +24 -4
- package/dist/plugins/policy/index.d.ts +24 -4
- package/dist/plugins/policy/index.js +736 -241
- package/dist/plugins/policy/index.js.map +1 -1
- package/package.json +11 -8
- package/dist/plugins/policy/plugin.zmodel +0 -33
package/dist/index.js
CHANGED
|
@@ -6,14 +6,15 @@ var __export = (target, all) => {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// src/client/client-impl.ts
|
|
9
|
-
import { invariant as
|
|
10
|
-
import { CompiledQuery, DefaultConnectionProvider, DefaultQueryExecutor as DefaultQueryExecutor2, Kysely, Log, sql as
|
|
9
|
+
import { invariant as invariant15 } from "@zenstackhq/common-helpers";
|
|
10
|
+
import { CompiledQuery, DefaultConnectionProvider, DefaultQueryExecutor as DefaultQueryExecutor2, Kysely, Log, sql as sql10, Transaction } from "kysely";
|
|
11
11
|
|
|
12
12
|
// src/client/crud/operations/aggregate.ts
|
|
13
|
-
import { sql as
|
|
13
|
+
import { sql as sql6 } from "kysely";
|
|
14
14
|
import { match as match10 } from "ts-pattern";
|
|
15
15
|
|
|
16
16
|
// src/client/query-utils.ts
|
|
17
|
+
import { invariant } from "@zenstackhq/common-helpers";
|
|
17
18
|
import { match } from "ts-pattern";
|
|
18
19
|
|
|
19
20
|
// src/schema/expression.ts
|
|
@@ -192,10 +193,19 @@ function requireField(schema, modelOrType, field) {
|
|
|
192
193
|
}
|
|
193
194
|
__name(requireField, "requireField");
|
|
194
195
|
function getIdFields(schema, model) {
|
|
195
|
-
const modelDef =
|
|
196
|
+
const modelDef = getModel(schema, model);
|
|
196
197
|
return modelDef?.idFields;
|
|
197
198
|
}
|
|
198
199
|
__name(getIdFields, "getIdFields");
|
|
200
|
+
function requireIdFields(schema, model) {
|
|
201
|
+
const modelDef = requireModel(schema, model);
|
|
202
|
+
const result = modelDef?.idFields;
|
|
203
|
+
if (!result) {
|
|
204
|
+
throw new InternalError(`Model "${model}" does not have ID field(s)`);
|
|
205
|
+
}
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
__name(requireIdFields, "requireIdFields");
|
|
199
209
|
function getRelationForeignKeyFieldPairs(schema, model, relationField) {
|
|
200
210
|
const fieldDef = requireField(schema, model, relationField);
|
|
201
211
|
if (!fieldDef?.relation) {
|
|
@@ -309,7 +319,7 @@ function buildFieldRef(schema, model, field, options, eb, modelAlias, inlineComp
|
|
|
309
319
|
throw new QueryError(`Computed field "${field}" implementation not provided for model "${model}"`);
|
|
310
320
|
}
|
|
311
321
|
return computer(eb, {
|
|
312
|
-
|
|
322
|
+
modelAlias
|
|
313
323
|
});
|
|
314
324
|
}
|
|
315
325
|
}
|
|
@@ -344,7 +354,7 @@ function buildJoinPairs(schema, model, modelAlias, relationField, relationModelA
|
|
|
344
354
|
}
|
|
345
355
|
__name(buildJoinPairs, "buildJoinPairs");
|
|
346
356
|
function makeDefaultOrderBy(schema, model) {
|
|
347
|
-
const idFields =
|
|
357
|
+
const idFields = requireIdFields(schema, model);
|
|
348
358
|
return idFields.map((f) => ({
|
|
349
359
|
[f]: "asc"
|
|
350
360
|
}));
|
|
@@ -383,11 +393,17 @@ function getManyToManyRelation(schema, model, field) {
|
|
|
383
393
|
"A"
|
|
384
394
|
];
|
|
385
395
|
}
|
|
396
|
+
const modelIdFields = requireIdFields(schema, model);
|
|
397
|
+
invariant(modelIdFields.length === 1, "Only single-field ID is supported for many-to-many relation");
|
|
398
|
+
const otherIdFields = requireIdFields(schema, fieldDef.type);
|
|
399
|
+
invariant(otherIdFields.length === 1, "Only single-field ID is supported for many-to-many relation");
|
|
386
400
|
return {
|
|
387
401
|
parentFkName: orderedFK[0],
|
|
402
|
+
parentPKName: modelIdFields[0],
|
|
388
403
|
otherModel: fieldDef.type,
|
|
389
404
|
otherField: fieldDef.relation.opposite,
|
|
390
405
|
otherFkName: orderedFK[1],
|
|
406
|
+
otherPKName: otherIdFields[0],
|
|
391
407
|
joinTable: fieldDef.relation.name ? `_${fieldDef.relation.name}` : `_${sortedModelNames[0]}To${sortedModelNames[1]}`
|
|
392
408
|
};
|
|
393
409
|
} else {
|
|
@@ -426,7 +442,7 @@ function ensureArray(value) {
|
|
|
426
442
|
}
|
|
427
443
|
__name(ensureArray, "ensureArray");
|
|
428
444
|
function extractIdFields(entity, schema, model) {
|
|
429
|
-
const idFields =
|
|
445
|
+
const idFields = requireIdFields(schema, model);
|
|
430
446
|
return extractFields(entity, idFields);
|
|
431
447
|
}
|
|
432
448
|
__name(extractIdFields, "extractIdFields");
|
|
@@ -463,35 +479,94 @@ __name(aggregate, "aggregate");
|
|
|
463
479
|
|
|
464
480
|
// src/client/crud/operations/base.ts
|
|
465
481
|
import { createId } from "@paralleldrive/cuid2";
|
|
466
|
-
import { invariant as
|
|
467
|
-
import { expressionBuilder as
|
|
482
|
+
import { invariant as invariant9, isPlainObject as isPlainObject3 } from "@zenstackhq/common-helpers";
|
|
483
|
+
import { expressionBuilder as expressionBuilder4, sql as sql5 } from "kysely";
|
|
468
484
|
import { nanoid } from "nanoid";
|
|
469
485
|
import { match as match9 } from "ts-pattern";
|
|
470
486
|
import { ulid } from "ulid";
|
|
471
487
|
import * as uuid from "uuid";
|
|
472
488
|
|
|
473
489
|
// src/plugins/policy/errors.ts
|
|
490
|
+
var RejectedByPolicyReason = /* @__PURE__ */ function(RejectedByPolicyReason2) {
|
|
491
|
+
RejectedByPolicyReason2["NO_ACCESS"] = "no-access";
|
|
492
|
+
RejectedByPolicyReason2["CANNOT_READ_BACK"] = "cannot-read-back";
|
|
493
|
+
RejectedByPolicyReason2["OTHER"] = "other";
|
|
494
|
+
return RejectedByPolicyReason2;
|
|
495
|
+
}({});
|
|
474
496
|
var RejectedByPolicyError = class extends Error {
|
|
475
497
|
static {
|
|
476
498
|
__name(this, "RejectedByPolicyError");
|
|
477
499
|
}
|
|
478
500
|
model;
|
|
479
501
|
reason;
|
|
480
|
-
constructor(model, reason) {
|
|
481
|
-
super(
|
|
502
|
+
constructor(model, reason = "no-access", message) {
|
|
503
|
+
super(message ?? `Operation rejected by policy${model ? ": " + model : ""}`), this.model = model, this.reason = reason;
|
|
482
504
|
}
|
|
483
505
|
};
|
|
484
506
|
|
|
507
|
+
// src/plugins/policy/functions.ts
|
|
508
|
+
import { invariant as invariant8 } from "@zenstackhq/common-helpers";
|
|
509
|
+
import { ExpressionWrapper as ExpressionWrapper2, ValueNode as ValueNode4 } from "kysely";
|
|
510
|
+
|
|
511
|
+
// src/client/contract.ts
|
|
512
|
+
var TransactionIsolationLevel = /* @__PURE__ */ function(TransactionIsolationLevel2) {
|
|
513
|
+
TransactionIsolationLevel2["ReadUncommitted"] = "read uncommitted";
|
|
514
|
+
TransactionIsolationLevel2["ReadCommitted"] = "read committed";
|
|
515
|
+
TransactionIsolationLevel2["RepeatableRead"] = "repeatable read";
|
|
516
|
+
TransactionIsolationLevel2["Serializable"] = "serializable";
|
|
517
|
+
TransactionIsolationLevel2["Snapshot"] = "snapshot";
|
|
518
|
+
return TransactionIsolationLevel2;
|
|
519
|
+
}({});
|
|
520
|
+
var CRUD = [
|
|
521
|
+
"create",
|
|
522
|
+
"read",
|
|
523
|
+
"update",
|
|
524
|
+
"delete"
|
|
525
|
+
];
|
|
526
|
+
|
|
527
|
+
// src/client/kysely-utils.ts
|
|
528
|
+
import { AliasNode, ColumnNode, ReferenceNode, TableNode } from "kysely";
|
|
529
|
+
function stripAlias(node) {
|
|
530
|
+
if (AliasNode.is(node)) {
|
|
531
|
+
return {
|
|
532
|
+
alias: node.alias,
|
|
533
|
+
node: node.node
|
|
534
|
+
};
|
|
535
|
+
} else {
|
|
536
|
+
return {
|
|
537
|
+
alias: void 0,
|
|
538
|
+
node
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
__name(stripAlias, "stripAlias");
|
|
543
|
+
function extractModelName(node) {
|
|
544
|
+
const { node: innerNode } = stripAlias(node);
|
|
545
|
+
return TableNode.is(innerNode) ? innerNode.table.identifier.name : void 0;
|
|
546
|
+
}
|
|
547
|
+
__name(extractModelName, "extractModelName");
|
|
548
|
+
function extractFieldName(node) {
|
|
549
|
+
if (ReferenceNode.is(node) && ColumnNode.is(node.column)) {
|
|
550
|
+
return node.column.column.name;
|
|
551
|
+
} else if (ColumnNode.is(node)) {
|
|
552
|
+
return node.column.name;
|
|
553
|
+
} else {
|
|
554
|
+
return void 0;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
__name(extractFieldName, "extractFieldName");
|
|
558
|
+
|
|
485
559
|
// src/plugins/policy/policy-handler.ts
|
|
486
|
-
import { invariant as
|
|
487
|
-
import { AliasNode as
|
|
560
|
+
import { invariant as invariant7 } from "@zenstackhq/common-helpers";
|
|
561
|
+
import { AliasNode as AliasNode4, BinaryOperationNode as BinaryOperationNode3, ColumnNode as ColumnNode3, DeleteQueryNode, expressionBuilder as expressionBuilder3, ExpressionWrapper, FromNode as FromNode2, FunctionNode as FunctionNode3, IdentifierNode as IdentifierNode2, InsertQueryNode, OperationNodeTransformer, OperatorNode as OperatorNode3, ParensNode as ParensNode2, PrimitiveValueListNode, RawNode, ReturningNode, SelectionNode as SelectionNode2, SelectQueryNode as SelectQueryNode2, sql as sql4, TableNode as TableNode4, UpdateQueryNode, ValueListNode as ValueListNode2, ValueNode as ValueNode3, ValuesNode, WhereNode as WhereNode2 } from "kysely";
|
|
488
562
|
import { match as match8 } from "ts-pattern";
|
|
489
563
|
|
|
490
564
|
// src/client/crud/dialects/index.ts
|
|
491
565
|
import { match as match5 } from "ts-pattern";
|
|
492
566
|
|
|
493
567
|
// src/client/crud/dialects/postgresql.ts
|
|
494
|
-
import { invariant as
|
|
568
|
+
import { invariant as invariant3 } from "@zenstackhq/common-helpers";
|
|
569
|
+
import Decimal from "decimal.js";
|
|
495
570
|
import { sql as sql2 } from "kysely";
|
|
496
571
|
import { match as match3 } from "ts-pattern";
|
|
497
572
|
|
|
@@ -517,7 +592,7 @@ var AGGREGATE_OPERATORS = [
|
|
|
517
592
|
];
|
|
518
593
|
|
|
519
594
|
// src/client/crud/dialects/base-dialect.ts
|
|
520
|
-
import { invariant, isPlainObject } from "@zenstackhq/common-helpers";
|
|
595
|
+
import { invariant as invariant2, isPlainObject } from "@zenstackhq/common-helpers";
|
|
521
596
|
import { expressionBuilder, sql } from "kysely";
|
|
522
597
|
import { match as match2, P } from "ts-pattern";
|
|
523
598
|
|
|
@@ -549,6 +624,9 @@ var BaseCrudDialect = class {
|
|
|
549
624
|
transformPrimitive(value, _type, _forArrayField) {
|
|
550
625
|
return value;
|
|
551
626
|
}
|
|
627
|
+
transformOutput(value, _type) {
|
|
628
|
+
return value;
|
|
629
|
+
}
|
|
552
630
|
// #region common query builders
|
|
553
631
|
buildSelectModel(eb, model, modelAlias) {
|
|
554
632
|
const modelDef = requireModel(this.schema, model);
|
|
@@ -716,9 +794,11 @@ var BaseCrudDialect = class {
|
|
|
716
794
|
const buildPkFkWhereRefs = /* @__PURE__ */ __name((eb2) => {
|
|
717
795
|
const m2m = getManyToManyRelation(this.schema, model, field);
|
|
718
796
|
if (m2m) {
|
|
719
|
-
const
|
|
720
|
-
|
|
721
|
-
|
|
797
|
+
const modelIdFields = requireIdFields(this.schema, model);
|
|
798
|
+
invariant2(modelIdFields.length === 1, "many-to-many relation must have exactly one id field");
|
|
799
|
+
const relationIdFields = requireIdFields(this.schema, relationModel);
|
|
800
|
+
invariant2(relationIdFields.length === 1, "many-to-many relation must have exactly one id field");
|
|
801
|
+
return eb2(sql.ref(`${relationFilterSelectAlias}.${relationIdFields[0]}`), "in", eb2.selectFrom(m2m.joinTable).select(`${m2m.joinTable}.${m2m.otherFkName}`).whereRef(sql.ref(`${m2m.joinTable}.${m2m.parentFkName}`), "=", sql.ref(`${modelAlias}.${modelIdFields[0]}`)));
|
|
722
802
|
} else {
|
|
723
803
|
const relationKeyPairs = getRelationForeignKeyFieldPairs(this.schema, model, field);
|
|
724
804
|
let result2 = this.true(eb2);
|
|
@@ -828,14 +908,14 @@ var BaseCrudDialect = class {
|
|
|
828
908
|
}
|
|
829
909
|
const rhs = Array.isArray(value) ? value.map(getRhs) : getRhs(value);
|
|
830
910
|
const condition = match2(op).with("equals", () => rhs === null ? eb(lhs, "is", null) : eb(lhs, "=", rhs)).with("in", () => {
|
|
831
|
-
|
|
911
|
+
invariant2(Array.isArray(rhs), "right hand side must be an array");
|
|
832
912
|
if (rhs.length === 0) {
|
|
833
913
|
return this.false(eb);
|
|
834
914
|
} else {
|
|
835
915
|
return eb(lhs, "in", rhs);
|
|
836
916
|
}
|
|
837
917
|
}).with("notIn", () => {
|
|
838
|
-
|
|
918
|
+
invariant2(Array.isArray(rhs), "right hand side must be an array");
|
|
839
919
|
if (rhs.length === 0) {
|
|
840
920
|
return this.true(eb);
|
|
841
921
|
} else {
|
|
@@ -953,18 +1033,18 @@ var BaseCrudDialect = class {
|
|
|
953
1033
|
"_min",
|
|
954
1034
|
"_max"
|
|
955
1035
|
].includes(field)) {
|
|
956
|
-
|
|
1036
|
+
invariant2(value && typeof value === "object", `invalid orderBy value for field "${field}"`);
|
|
957
1037
|
for (const [k, v] of Object.entries(value)) {
|
|
958
|
-
|
|
1038
|
+
invariant2(v === "asc" || v === "desc", `invalid orderBy value for field "${field}"`);
|
|
959
1039
|
result = result.orderBy((eb) => aggregate(eb, this.fieldRef(model, k, eb, modelAlias), field), sql.raw(this.negateSort(v, negated)));
|
|
960
1040
|
}
|
|
961
1041
|
continue;
|
|
962
1042
|
}
|
|
963
1043
|
switch (field) {
|
|
964
1044
|
case "_count": {
|
|
965
|
-
|
|
1045
|
+
invariant2(value && typeof value === "object", 'invalid orderBy value for field "_count"');
|
|
966
1046
|
for (const [k, v] of Object.entries(value)) {
|
|
967
|
-
|
|
1047
|
+
invariant2(v === "asc" || v === "desc", `invalid orderBy value for field "${field}"`);
|
|
968
1048
|
result = result.orderBy((eb) => eb.fn.count(this.fieldRef(model, k, eb, modelAlias)), sql.raw(this.negateSort(v, negated)));
|
|
969
1049
|
}
|
|
970
1050
|
continue;
|
|
@@ -987,7 +1067,7 @@ var BaseCrudDialect = class {
|
|
|
987
1067
|
throw new QueryError(`invalid orderBy value for field "${field}"`);
|
|
988
1068
|
}
|
|
989
1069
|
if ("_count" in value) {
|
|
990
|
-
|
|
1070
|
+
invariant2(value._count === "asc" || value._count === "desc", 'invalid orderBy value for field "_count"');
|
|
991
1071
|
const sort = this.negateSort(value._count, negated);
|
|
992
1072
|
result = result.orderBy((eb) => {
|
|
993
1073
|
const subQueryAlias = `${modelAlias}$orderBy$${field}$count`;
|
|
@@ -1059,7 +1139,7 @@ var BaseCrudDialect = class {
|
|
|
1059
1139
|
}
|
|
1060
1140
|
}
|
|
1061
1141
|
buildDelegateJoin(thisModel, thisModelAlias, otherModelAlias, query) {
|
|
1062
|
-
const idFields =
|
|
1142
|
+
const idFields = requireIdFields(this.schema, thisModel);
|
|
1063
1143
|
query = query.leftJoin(otherModelAlias, (qb) => {
|
|
1064
1144
|
for (const idField of idFields) {
|
|
1065
1145
|
qb = qb.onRef(`${thisModelAlias}.${idField}`, "=", `${otherModelAlias}.${idField}`);
|
|
@@ -1081,10 +1161,16 @@ var BaseCrudDialect = class {
|
|
|
1081
1161
|
for (const [field, value] of Object.entries(selections.select)) {
|
|
1082
1162
|
const fieldDef = requireField(this.schema, model, field);
|
|
1083
1163
|
const fieldModel = fieldDef.type;
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
fieldCountQuery =
|
|
1164
|
+
let fieldCountQuery;
|
|
1165
|
+
const m2m = getManyToManyRelation(this.schema, model, field);
|
|
1166
|
+
if (m2m) {
|
|
1167
|
+
fieldCountQuery = eb.selectFrom(fieldModel).innerJoin(m2m.joinTable, (join) => join.onRef(`${m2m.joinTable}.${m2m.otherFkName}`, "=", `${fieldModel}.${m2m.otherPKName}`).onRef(`${m2m.joinTable}.${m2m.parentFkName}`, "=", `${parentAlias}.${m2m.parentPKName}`)).select(eb.fn.countAll().as(`_count$${field}`));
|
|
1168
|
+
} else {
|
|
1169
|
+
fieldCountQuery = eb.selectFrom(fieldModel).select(eb.fn.countAll().as(`_count$${field}`));
|
|
1170
|
+
const joinPairs = buildJoinPairs(this.schema, model, parentAlias, field, fieldModel);
|
|
1171
|
+
for (const [left, right] of joinPairs) {
|
|
1172
|
+
fieldCountQuery = fieldCountQuery.whereRef(left, "=", right);
|
|
1173
|
+
}
|
|
1088
1174
|
}
|
|
1089
1175
|
if (value && typeof value === "object" && "where" in value && value.where && typeof value.where === "object") {
|
|
1090
1176
|
const filter = this.buildFilter(eb, fieldModel, fieldModel, value.where);
|
|
@@ -1164,6 +1250,9 @@ var PostgresCrudDialect = class extends BaseCrudDialect {
|
|
|
1164
1250
|
static {
|
|
1165
1251
|
__name(this, "PostgresCrudDialect");
|
|
1166
1252
|
}
|
|
1253
|
+
constructor(schema, options) {
|
|
1254
|
+
super(schema, options);
|
|
1255
|
+
}
|
|
1167
1256
|
get provider() {
|
|
1168
1257
|
return "postgresql";
|
|
1169
1258
|
}
|
|
@@ -1178,9 +1267,41 @@ var PostgresCrudDialect = class extends BaseCrudDialect {
|
|
|
1178
1267
|
return value.map((v) => this.transformPrimitive(v, type, false));
|
|
1179
1268
|
}
|
|
1180
1269
|
} else {
|
|
1181
|
-
return match3(type).with("DateTime", () => value instanceof Date ? value : typeof value === "string" ? new Date(value) : value).with("Decimal", () => value !== null ? value.toString() : value).otherwise(() => value);
|
|
1270
|
+
return match3(type).with("DateTime", () => value instanceof Date ? value.toISOString() : typeof value === "string" ? new Date(value).toISOString() : value).with("Decimal", () => value !== null ? value.toString() : value).otherwise(() => value);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
transformOutput(value, type) {
|
|
1274
|
+
if (value === null || value === void 0) {
|
|
1275
|
+
return value;
|
|
1276
|
+
}
|
|
1277
|
+
return match3(type).with("DateTime", () => this.transformOutputDate(value)).with("Bytes", () => this.transformOutputBytes(value)).with("BigInt", () => this.transformOutputBigInt(value)).with("Decimal", () => this.transformDecimal(value)).otherwise(() => super.transformOutput(value, type));
|
|
1278
|
+
}
|
|
1279
|
+
transformOutputBigInt(value) {
|
|
1280
|
+
if (typeof value === "bigint") {
|
|
1281
|
+
return value;
|
|
1282
|
+
}
|
|
1283
|
+
invariant3(typeof value === "string" || typeof value === "number", `Expected string or number, got ${typeof value}`);
|
|
1284
|
+
return BigInt(value);
|
|
1285
|
+
}
|
|
1286
|
+
transformDecimal(value) {
|
|
1287
|
+
if (value instanceof Decimal) {
|
|
1288
|
+
return value;
|
|
1289
|
+
}
|
|
1290
|
+
invariant3(typeof value === "string" || typeof value === "number" || value instanceof Decimal, `Expected string, number or Decimal, got ${typeof value}`);
|
|
1291
|
+
return new Decimal(value);
|
|
1292
|
+
}
|
|
1293
|
+
transformOutputDate(value) {
|
|
1294
|
+
if (typeof value === "string") {
|
|
1295
|
+
return new Date(value);
|
|
1296
|
+
} else if (value instanceof Date && this.options.fixPostgresTimezone !== false) {
|
|
1297
|
+
return new Date(value.getTime() - value.getTimezoneOffset() * 60 * 1e3);
|
|
1298
|
+
} else {
|
|
1299
|
+
return value;
|
|
1182
1300
|
}
|
|
1183
1301
|
}
|
|
1302
|
+
transformOutputBytes(value) {
|
|
1303
|
+
return Buffer.isBuffer(value) ? Uint8Array.from(value) : value;
|
|
1304
|
+
}
|
|
1184
1305
|
buildRelationSelection(query, model, relationField, parentAlias, payload) {
|
|
1185
1306
|
const relationResultName = `${parentAlias}$${relationField}`;
|
|
1186
1307
|
const joinedQuery = this.buildRelationJSON(model, query, relationField, parentAlias, payload, relationResultName);
|
|
@@ -1211,10 +1332,10 @@ var PostgresCrudDialect = class extends BaseCrudDialect {
|
|
|
1211
1332
|
buildRelationJoinFilter(query, model, relationField, relationModel, relationModelAlias, parentAlias) {
|
|
1212
1333
|
const m2m = getManyToManyRelation(this.schema, model, relationField);
|
|
1213
1334
|
if (m2m) {
|
|
1214
|
-
const parentIds =
|
|
1215
|
-
const relationIds =
|
|
1216
|
-
|
|
1217
|
-
|
|
1335
|
+
const parentIds = requireIdFields(this.schema, model);
|
|
1336
|
+
const relationIds = requireIdFields(this.schema, relationModel);
|
|
1337
|
+
invariant3(parentIds.length === 1, "many-to-many relation must have exactly one id field");
|
|
1338
|
+
invariant3(relationIds.length === 1, "many-to-many relation must have exactly one id field");
|
|
1218
1339
|
query = query.where((eb) => eb(eb.ref(`${relationModelAlias}.${relationIds[0]}`), "in", eb.selectFrom(m2m.joinTable).select(`${m2m.joinTable}.${m2m.otherFkName}`).whereRef(`${parentAlias}.${parentIds[0]}`, "=", `${m2m.joinTable}.${m2m.parentFkName}`)));
|
|
1219
1340
|
} else {
|
|
1220
1341
|
const joinPairs = buildJoinPairs(this.schema, model, parentAlias, relationField, relationModelAlias);
|
|
@@ -1326,10 +1447,32 @@ var PostgresCrudDialect = class extends BaseCrudDialect {
|
|
|
1326
1447
|
get supportInsertWithDefault() {
|
|
1327
1448
|
return true;
|
|
1328
1449
|
}
|
|
1450
|
+
getFieldSqlType(fieldDef) {
|
|
1451
|
+
if (fieldDef.relation) {
|
|
1452
|
+
throw new QueryError("Cannot get SQL type of a relation field");
|
|
1453
|
+
}
|
|
1454
|
+
let result;
|
|
1455
|
+
if (this.schema.enums?.[fieldDef.type]) {
|
|
1456
|
+
result = "text";
|
|
1457
|
+
} else {
|
|
1458
|
+
result = match3(fieldDef.type).with("String", () => "text").with("Boolean", () => "boolean").with("Int", () => "integer").with("BigInt", () => "bigint").with("Float", () => "double precision").with("Decimal", () => "decimal").with("DateTime", () => "timestamp").with("Bytes", () => "bytea").with("Json", () => "jsonb").otherwise(() => "text");
|
|
1459
|
+
}
|
|
1460
|
+
if (fieldDef.array) {
|
|
1461
|
+
result += "[]";
|
|
1462
|
+
}
|
|
1463
|
+
return result;
|
|
1464
|
+
}
|
|
1465
|
+
getStringCasingBehavior() {
|
|
1466
|
+
return {
|
|
1467
|
+
supportsILike: true,
|
|
1468
|
+
likeCaseSensitive: true
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1329
1471
|
};
|
|
1330
1472
|
|
|
1331
1473
|
// src/client/crud/dialects/sqlite.ts
|
|
1332
|
-
import { invariant as
|
|
1474
|
+
import { invariant as invariant4 } from "@zenstackhq/common-helpers";
|
|
1475
|
+
import Decimal2 from "decimal.js";
|
|
1333
1476
|
import { sql as sql3 } from "kysely";
|
|
1334
1477
|
import { match as match4 } from "ts-pattern";
|
|
1335
1478
|
var SqliteCrudDialect = class extends BaseCrudDialect {
|
|
@@ -1349,10 +1492,58 @@ var SqliteCrudDialect = class extends BaseCrudDialect {
|
|
|
1349
1492
|
if (this.schema.typeDefs && type in this.schema.typeDefs) {
|
|
1350
1493
|
return JSON.stringify(value);
|
|
1351
1494
|
} else {
|
|
1352
|
-
return match4(type).with("Boolean", () => value ? 1 : 0).with("DateTime", () => value instanceof Date ? value.toISOString() : value).with("Decimal", () => value.toString()).with("Bytes", () => Buffer.from(value)).with("Json", () => JSON.stringify(value)).otherwise(() => value);
|
|
1495
|
+
return match4(type).with("Boolean", () => value ? 1 : 0).with("DateTime", () => value instanceof Date ? value.toISOString() : typeof value === "string" ? new Date(value).toISOString() : value).with("Decimal", () => value.toString()).with("Bytes", () => Buffer.from(value)).with("Json", () => JSON.stringify(value)).otherwise(() => value);
|
|
1353
1496
|
}
|
|
1354
1497
|
}
|
|
1355
1498
|
}
|
|
1499
|
+
transformOutput(value, type) {
|
|
1500
|
+
if (value === null || value === void 0) {
|
|
1501
|
+
return value;
|
|
1502
|
+
} else if (this.schema.typeDefs && type in this.schema.typeDefs) {
|
|
1503
|
+
return this.transformOutputJson(value);
|
|
1504
|
+
} else {
|
|
1505
|
+
return match4(type).with("Boolean", () => this.transformOutputBoolean(value)).with("DateTime", () => this.transformOutputDate(value)).with("Bytes", () => this.transformOutputBytes(value)).with("Decimal", () => this.transformOutputDecimal(value)).with("BigInt", () => this.transformOutputBigInt(value)).with("Json", () => this.transformOutputJson(value)).otherwise(() => super.transformOutput(value, type));
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
transformOutputDecimal(value) {
|
|
1509
|
+
if (value instanceof Decimal2) {
|
|
1510
|
+
return value;
|
|
1511
|
+
}
|
|
1512
|
+
invariant4(typeof value === "string" || typeof value === "number" || value instanceof Decimal2, `Expected string, number or Decimal, got ${typeof value}`);
|
|
1513
|
+
return new Decimal2(value);
|
|
1514
|
+
}
|
|
1515
|
+
transformOutputBigInt(value) {
|
|
1516
|
+
if (typeof value === "bigint") {
|
|
1517
|
+
return value;
|
|
1518
|
+
}
|
|
1519
|
+
invariant4(typeof value === "string" || typeof value === "number", `Expected string or number, got ${typeof value}`);
|
|
1520
|
+
return BigInt(value);
|
|
1521
|
+
}
|
|
1522
|
+
transformOutputBoolean(value) {
|
|
1523
|
+
return !!value;
|
|
1524
|
+
}
|
|
1525
|
+
transformOutputDate(value) {
|
|
1526
|
+
if (typeof value === "number") {
|
|
1527
|
+
return new Date(value);
|
|
1528
|
+
} else if (typeof value === "string") {
|
|
1529
|
+
return new Date(value);
|
|
1530
|
+
} else {
|
|
1531
|
+
return value;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
transformOutputBytes(value) {
|
|
1535
|
+
return Buffer.isBuffer(value) ? Uint8Array.from(value) : value;
|
|
1536
|
+
}
|
|
1537
|
+
transformOutputJson(value) {
|
|
1538
|
+
if (typeof value === "string") {
|
|
1539
|
+
try {
|
|
1540
|
+
return JSON.parse(value);
|
|
1541
|
+
} catch (e) {
|
|
1542
|
+
throw new QueryError("Invalid JSON returned", e);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
return value;
|
|
1546
|
+
}
|
|
1356
1547
|
buildRelationSelection(query, model, relationField, parentAlias, payload) {
|
|
1357
1548
|
return query.select((eb) => this.buildRelationJSON(model, eb, relationField, parentAlias, payload).as(relationField));
|
|
1358
1549
|
}
|
|
@@ -1434,10 +1625,10 @@ var SqliteCrudDialect = class extends BaseCrudDialect {
|
|
|
1434
1625
|
const relationModel = fieldDef.type;
|
|
1435
1626
|
const m2m = getManyToManyRelation(this.schema, model, relationField);
|
|
1436
1627
|
if (m2m) {
|
|
1437
|
-
const parentIds =
|
|
1438
|
-
const relationIds =
|
|
1439
|
-
|
|
1440
|
-
|
|
1628
|
+
const parentIds = requireIdFields(this.schema, model);
|
|
1629
|
+
const relationIds = requireIdFields(this.schema, relationModel);
|
|
1630
|
+
invariant4(parentIds.length === 1, "many-to-many relation must have exactly one id field");
|
|
1631
|
+
invariant4(relationIds.length === 1, "many-to-many relation must have exactly one id field");
|
|
1441
1632
|
selectModelQuery = selectModelQuery.where((eb) => eb(eb.ref(`${relationModelAlias}.${relationIds[0]}`), "in", eb.selectFrom(m2m.joinTable).select(`${m2m.joinTable}.${m2m.otherFkName}`).whereRef(`${parentAlias}.${parentIds[0]}`, "=", `${m2m.joinTable}.${m2m.parentFkName}`)));
|
|
1442
1633
|
} else {
|
|
1443
1634
|
const { keyPairs, ownedByModel } = getRelationForeignKeyFieldPairs(this.schema, model, relationField);
|
|
@@ -1489,6 +1680,24 @@ var SqliteCrudDialect = class extends BaseCrudDialect {
|
|
|
1489
1680
|
get supportInsertWithDefault() {
|
|
1490
1681
|
return false;
|
|
1491
1682
|
}
|
|
1683
|
+
getFieldSqlType(fieldDef) {
|
|
1684
|
+
if (fieldDef.relation) {
|
|
1685
|
+
throw new QueryError("Cannot get SQL type of a relation field");
|
|
1686
|
+
}
|
|
1687
|
+
if (fieldDef.array) {
|
|
1688
|
+
throw new QueryError("SQLite does not support scalar list type");
|
|
1689
|
+
}
|
|
1690
|
+
if (this.schema.enums?.[fieldDef.type]) {
|
|
1691
|
+
return "text";
|
|
1692
|
+
}
|
|
1693
|
+
return match4(fieldDef.type).with("String", () => "text").with("Boolean", () => "integer").with("Int", () => "integer").with("BigInt", () => "integer").with("Float", () => "real").with("Decimal", () => "decimal").with("DateTime", () => "numeric").with("Bytes", () => "blob").with("Json", () => "jsonb").otherwise(() => "text");
|
|
1694
|
+
}
|
|
1695
|
+
getStringCasingBehavior() {
|
|
1696
|
+
return {
|
|
1697
|
+
supportsILike: false,
|
|
1698
|
+
likeCaseSensitive: false
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1492
1701
|
};
|
|
1493
1702
|
|
|
1494
1703
|
// src/client/crud/dialects/index.ts
|
|
@@ -1816,12 +2025,12 @@ var ColumnCollector = class extends DefaultOperationNodeVisitor {
|
|
|
1816
2025
|
};
|
|
1817
2026
|
|
|
1818
2027
|
// src/plugins/policy/expression-transformer.ts
|
|
1819
|
-
import { invariant as
|
|
1820
|
-
import { AliasNode as
|
|
2028
|
+
import { invariant as invariant6 } from "@zenstackhq/common-helpers";
|
|
2029
|
+
import { AliasNode as AliasNode3, BinaryOperationNode as BinaryOperationNode2, ColumnNode as ColumnNode2, expressionBuilder as expressionBuilder2, FromNode, FunctionNode as FunctionNode2, IdentifierNode, OperatorNode as OperatorNode2, ReferenceNode as ReferenceNode3, SelectionNode, SelectQueryNode, TableNode as TableNode3, ValueListNode, ValueNode as ValueNode2, WhereNode } from "kysely";
|
|
1821
2030
|
import { match as match7 } from "ts-pattern";
|
|
1822
2031
|
|
|
1823
2032
|
// src/plugins/policy/expression-evaluator.ts
|
|
1824
|
-
import { invariant as
|
|
2033
|
+
import { invariant as invariant5 } from "@zenstackhq/common-helpers";
|
|
1825
2034
|
import { match as match6 } from "ts-pattern";
|
|
1826
2035
|
var ExpressionEvaluator = class {
|
|
1827
2036
|
static {
|
|
@@ -1865,18 +2074,18 @@ var ExpressionEvaluator = class {
|
|
|
1865
2074
|
const right = this.evaluate(expr2.right, context);
|
|
1866
2075
|
return match6(expr2.op).with("==", () => left === right).with("!=", () => left !== right).with(">", () => left > right).with(">=", () => left >= right).with("<", () => left < right).with("<=", () => left <= right).with("&&", () => left && right).with("||", () => left || right).with("in", () => {
|
|
1867
2076
|
const _right = right ?? [];
|
|
1868
|
-
|
|
2077
|
+
invariant5(Array.isArray(_right), 'expected array for "in" operator');
|
|
1869
2078
|
return _right.includes(left);
|
|
1870
2079
|
}).exhaustive();
|
|
1871
2080
|
}
|
|
1872
2081
|
evaluateCollectionPredicate(expr2, context) {
|
|
1873
2082
|
const op = expr2.op;
|
|
1874
|
-
|
|
2083
|
+
invariant5(op === "?" || op === "!" || op === "^", 'expected "?" or "!" or "^" operator');
|
|
1875
2084
|
const left = this.evaluate(expr2.left, context);
|
|
1876
2085
|
if (!left) {
|
|
1877
2086
|
return false;
|
|
1878
2087
|
}
|
|
1879
|
-
|
|
2088
|
+
invariant5(Array.isArray(left), "expected array");
|
|
1880
2089
|
return match6(op).with("?", () => left.some((item) => this.evaluate(expr2.right, {
|
|
1881
2090
|
...context,
|
|
1882
2091
|
thisValue: item
|
|
@@ -1891,7 +2100,7 @@ var ExpressionEvaluator = class {
|
|
|
1891
2100
|
};
|
|
1892
2101
|
|
|
1893
2102
|
// src/plugins/policy/utils.ts
|
|
1894
|
-
import { AliasNode, AndNode, BinaryOperationNode, FunctionNode, OperatorNode, OrNode, ParensNode, ReferenceNode, TableNode, UnaryOperationNode, ValueNode } from "kysely";
|
|
2103
|
+
import { AliasNode as AliasNode2, AndNode, BinaryOperationNode, FunctionNode, OperatorNode, OrNode, ParensNode, ReferenceNode as ReferenceNode2, TableNode as TableNode2, UnaryOperationNode, ValueNode } from "kysely";
|
|
1895
2104
|
function trueNode(dialect) {
|
|
1896
2105
|
return ValueNode.createImmediate(dialect.transformPrimitive(true, "Boolean", false));
|
|
1897
2106
|
}
|
|
@@ -1977,11 +2186,11 @@ function getTableName(node) {
|
|
|
1977
2186
|
if (!node) {
|
|
1978
2187
|
return node;
|
|
1979
2188
|
}
|
|
1980
|
-
if (
|
|
2189
|
+
if (TableNode2.is(node)) {
|
|
1981
2190
|
return node.table.identifier.name;
|
|
1982
|
-
} else if (
|
|
2191
|
+
} else if (AliasNode2.is(node)) {
|
|
1983
2192
|
return getTableName(node.node);
|
|
1984
|
-
} else if (
|
|
2193
|
+
} else if (ReferenceNode2.is(node) && node.table) {
|
|
1985
2194
|
return getTableName(node.table);
|
|
1986
2195
|
}
|
|
1987
2196
|
return void 0;
|
|
@@ -2014,16 +2223,21 @@ var ExpressionTransformer = class {
|
|
|
2014
2223
|
static {
|
|
2015
2224
|
__name(this, "ExpressionTransformer");
|
|
2016
2225
|
}
|
|
2017
|
-
|
|
2018
|
-
clientOptions;
|
|
2019
|
-
auth;
|
|
2226
|
+
client;
|
|
2020
2227
|
dialect;
|
|
2021
|
-
constructor(
|
|
2022
|
-
this.
|
|
2023
|
-
this.clientOptions = clientOptions;
|
|
2024
|
-
this.auth = auth;
|
|
2228
|
+
constructor(client) {
|
|
2229
|
+
this.client = client;
|
|
2025
2230
|
this.dialect = getCrudDialect(this.schema, this.clientOptions);
|
|
2026
2231
|
}
|
|
2232
|
+
get schema() {
|
|
2233
|
+
return this.client.$schema;
|
|
2234
|
+
}
|
|
2235
|
+
get clientOptions() {
|
|
2236
|
+
return this.client.$options;
|
|
2237
|
+
}
|
|
2238
|
+
get auth() {
|
|
2239
|
+
return this.client.$auth;
|
|
2240
|
+
}
|
|
2027
2241
|
get authType() {
|
|
2028
2242
|
if (!this.schema.authType) {
|
|
2029
2243
|
throw new InternalError('Schema does not have an "authType" specified');
|
|
@@ -2093,8 +2307,9 @@ var ExpressionTransformer = class {
|
|
|
2093
2307
|
if (op === "?" || op === "!" || op === "^") {
|
|
2094
2308
|
return this.transformCollectionPredicate(expr2, context);
|
|
2095
2309
|
}
|
|
2096
|
-
const
|
|
2097
|
-
const
|
|
2310
|
+
const { normalizedLeft, normalizedRight } = this.normalizeBinaryOperationOperands(expr2, context);
|
|
2311
|
+
const left = this.transform(normalizedLeft, context);
|
|
2312
|
+
const right = this.transform(normalizedRight, context);
|
|
2098
2313
|
if (op === "in") {
|
|
2099
2314
|
if (this.isNullNode(left)) {
|
|
2100
2315
|
return this.transformValue(false, "Boolean");
|
|
@@ -2109,29 +2324,65 @@ var ExpressionTransformer = class {
|
|
|
2109
2324
|
}
|
|
2110
2325
|
}
|
|
2111
2326
|
if (this.isNullNode(right)) {
|
|
2112
|
-
return
|
|
2327
|
+
return this.transformNullCheck(left, expr2.op);
|
|
2113
2328
|
} else if (this.isNullNode(left)) {
|
|
2114
|
-
return
|
|
2329
|
+
return this.transformNullCheck(right, expr2.op);
|
|
2330
|
+
} else {
|
|
2331
|
+
return BinaryOperationNode2.create(left, this.transformOperator(op), right);
|
|
2115
2332
|
}
|
|
2116
|
-
|
|
2333
|
+
}
|
|
2334
|
+
transformNullCheck(expr2, operator) {
|
|
2335
|
+
invariant6(operator === "==" || operator === "!=", 'operator must be "==" or "!=" for null comparison');
|
|
2336
|
+
if (ValueNode2.is(expr2)) {
|
|
2337
|
+
if (expr2.value === null) {
|
|
2338
|
+
return operator === "==" ? trueNode(this.dialect) : falseNode(this.dialect);
|
|
2339
|
+
} else {
|
|
2340
|
+
return operator === "==" ? falseNode(this.dialect) : trueNode(this.dialect);
|
|
2341
|
+
}
|
|
2342
|
+
} else {
|
|
2343
|
+
return operator === "==" ? BinaryOperationNode2.create(expr2, OperatorNode2.create("is"), ValueNode2.createImmediate(null)) : BinaryOperationNode2.create(expr2, OperatorNode2.create("is not"), ValueNode2.createImmediate(null));
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
normalizeBinaryOperationOperands(expr2, context) {
|
|
2347
|
+
let normalizedLeft = expr2.left;
|
|
2348
|
+
if (this.isRelationField(expr2.left, context.model)) {
|
|
2349
|
+
invariant6(ExpressionUtils.isNull(expr2.right), "only null comparison is supported for relation field");
|
|
2350
|
+
const leftRelDef = this.getFieldDefFromFieldRef(expr2.left, context.model);
|
|
2351
|
+
invariant6(leftRelDef, "failed to get relation field definition");
|
|
2352
|
+
const idFields = requireIdFields(this.schema, leftRelDef.type);
|
|
2353
|
+
normalizedLeft = this.makeOrAppendMember(normalizedLeft, idFields[0]);
|
|
2354
|
+
}
|
|
2355
|
+
let normalizedRight = expr2.right;
|
|
2356
|
+
if (this.isRelationField(expr2.right, context.model)) {
|
|
2357
|
+
invariant6(ExpressionUtils.isNull(expr2.left), "only null comparison is supported for relation field");
|
|
2358
|
+
const rightRelDef = this.getFieldDefFromFieldRef(expr2.right, context.model);
|
|
2359
|
+
invariant6(rightRelDef, "failed to get relation field definition");
|
|
2360
|
+
const idFields = requireIdFields(this.schema, rightRelDef.type);
|
|
2361
|
+
normalizedRight = this.makeOrAppendMember(normalizedRight, idFields[0]);
|
|
2362
|
+
}
|
|
2363
|
+
return {
|
|
2364
|
+
normalizedLeft,
|
|
2365
|
+
normalizedRight
|
|
2366
|
+
};
|
|
2117
2367
|
}
|
|
2118
2368
|
transformCollectionPredicate(expr2, context) {
|
|
2119
|
-
|
|
2369
|
+
invariant6(expr2.op === "?" || expr2.op === "!" || expr2.op === "^", 'expected "?" or "!" or "^" operator');
|
|
2120
2370
|
if (this.isAuthCall(expr2.left) || this.isAuthMember(expr2.left)) {
|
|
2121
2371
|
const value = new ExpressionEvaluator().evaluate(expr2, {
|
|
2122
2372
|
auth: this.auth
|
|
2123
2373
|
});
|
|
2124
2374
|
return this.transformValue(value, "Boolean");
|
|
2125
2375
|
}
|
|
2126
|
-
|
|
2376
|
+
invariant6(ExpressionUtils.isField(expr2.left) || ExpressionUtils.isMember(expr2.left), "left operand must be field or member access");
|
|
2127
2377
|
let newContextModel;
|
|
2128
|
-
|
|
2129
|
-
|
|
2378
|
+
const fieldDef = this.getFieldDefFromFieldRef(expr2.left, context.model);
|
|
2379
|
+
if (fieldDef) {
|
|
2380
|
+
invariant6(fieldDef.relation, `field is not a relation: ${JSON.stringify(expr2.left)}`);
|
|
2130
2381
|
newContextModel = fieldDef.type;
|
|
2131
2382
|
} else {
|
|
2132
|
-
|
|
2133
|
-
const
|
|
2134
|
-
newContextModel =
|
|
2383
|
+
invariant6(ExpressionUtils.isMember(expr2.left) && ExpressionUtils.isField(expr2.left.receiver), "left operand must be member access with field receiver");
|
|
2384
|
+
const fieldDef2 = requireField(this.schema, context.model, expr2.left.receiver.field);
|
|
2385
|
+
newContextModel = fieldDef2.type;
|
|
2135
2386
|
for (const member of expr2.left.members) {
|
|
2136
2387
|
const memberDef = requireField(this.schema, newContextModel, member);
|
|
2137
2388
|
newContextModel = memberDef.type;
|
|
@@ -2151,7 +2402,7 @@ var ExpressionTransformer = class {
|
|
|
2151
2402
|
const predicateResult = match7(expr2.op).with("?", () => BinaryOperationNode2.create(count, OperatorNode2.create(">"), ValueNode2.createImmediate(0))).with("!", () => BinaryOperationNode2.create(count, OperatorNode2.create("="), ValueNode2.createImmediate(0))).with("^", () => BinaryOperationNode2.create(count, OperatorNode2.create("="), ValueNode2.createImmediate(0))).exhaustive();
|
|
2152
2403
|
return this.transform(expr2.left, {
|
|
2153
2404
|
...context,
|
|
2154
|
-
memberSelect: SelectionNode.create(
|
|
2405
|
+
memberSelect: SelectionNode.create(AliasNode3.create(predicateResult, IdentifierNode.create("$t"))),
|
|
2155
2406
|
memberFilter: predicateFilter
|
|
2156
2407
|
});
|
|
2157
2408
|
}
|
|
@@ -2176,12 +2427,10 @@ var ExpressionTransformer = class {
|
|
|
2176
2427
|
throw new QueryError(`Unsupported use of \`auth()\` in policy of model "${context.model}", comparing with \`auth()\` is only possible when auth type is a model`);
|
|
2177
2428
|
}
|
|
2178
2429
|
const idFields = Object.values(authModel.fields).filter((f) => f.id).map((f) => f.name);
|
|
2179
|
-
|
|
2430
|
+
invariant6(idFields.length > 0, "auth type model must have at least one id field");
|
|
2180
2431
|
const conditions = idFields.map((fieldName) => ExpressionUtils.binary(ExpressionUtils.member(authExpr, [
|
|
2181
2432
|
fieldName
|
|
2182
|
-
]), "==",
|
|
2183
|
-
fieldName
|
|
2184
|
-
])));
|
|
2433
|
+
]), "==", this.makeOrAppendMember(other, fieldName)));
|
|
2185
2434
|
let result = this.buildAnd(conditions);
|
|
2186
2435
|
if (expr2.op === "!=") {
|
|
2187
2436
|
result = this.buildLogicalNot(result);
|
|
@@ -2189,11 +2438,29 @@ var ExpressionTransformer = class {
|
|
|
2189
2438
|
return this.transform(result, context);
|
|
2190
2439
|
}
|
|
2191
2440
|
}
|
|
2441
|
+
makeOrAppendMember(other, fieldName) {
|
|
2442
|
+
if (ExpressionUtils.isMember(other)) {
|
|
2443
|
+
return ExpressionUtils.member(other.receiver, [
|
|
2444
|
+
...other.members,
|
|
2445
|
+
fieldName
|
|
2446
|
+
]);
|
|
2447
|
+
} else {
|
|
2448
|
+
return ExpressionUtils.member(other, [
|
|
2449
|
+
fieldName
|
|
2450
|
+
]);
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2192
2453
|
transformValue(value, type) {
|
|
2193
|
-
|
|
2454
|
+
if (value === true) {
|
|
2455
|
+
return trueNode(this.dialect);
|
|
2456
|
+
} else if (value === false) {
|
|
2457
|
+
return falseNode(this.dialect);
|
|
2458
|
+
} else {
|
|
2459
|
+
return ValueNode2.create(this.dialect.transformPrimitive(value, type, false) ?? null);
|
|
2460
|
+
}
|
|
2194
2461
|
}
|
|
2195
2462
|
_unary(expr2, context) {
|
|
2196
|
-
|
|
2463
|
+
invariant6(expr2.op === "!", 'only "!" operator is supported');
|
|
2197
2464
|
return logicalNot(this.dialect, this.transform(expr2.operand, context));
|
|
2198
2465
|
}
|
|
2199
2466
|
transformOperator(op) {
|
|
@@ -2205,17 +2472,31 @@ var ExpressionTransformer = class {
|
|
|
2205
2472
|
return result.toOperationNode();
|
|
2206
2473
|
}
|
|
2207
2474
|
transformCall(expr2, context) {
|
|
2208
|
-
const func = this.
|
|
2475
|
+
const func = this.getFunctionImpl(expr2.function);
|
|
2209
2476
|
if (!func) {
|
|
2210
2477
|
throw new QueryError(`Function not implemented: ${expr2.function}`);
|
|
2211
2478
|
}
|
|
2212
2479
|
const eb = expressionBuilder2();
|
|
2213
2480
|
return func(eb, (expr2.args ?? []).map((arg) => this.transformCallArg(eb, arg, context)), {
|
|
2481
|
+
client: this.client,
|
|
2214
2482
|
dialect: this.dialect,
|
|
2215
2483
|
model: context.model,
|
|
2484
|
+
modelAlias: context.alias ?? context.model,
|
|
2216
2485
|
operation: context.operation
|
|
2217
2486
|
});
|
|
2218
2487
|
}
|
|
2488
|
+
getFunctionImpl(functionName) {
|
|
2489
|
+
let func = this.clientOptions.functions?.[functionName];
|
|
2490
|
+
if (!func) {
|
|
2491
|
+
for (const plugin of this.clientOptions.plugins ?? []) {
|
|
2492
|
+
if (plugin.functions?.[functionName]) {
|
|
2493
|
+
func = plugin.functions[functionName];
|
|
2494
|
+
break;
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
return func;
|
|
2499
|
+
}
|
|
2219
2500
|
transformCallArg(eb, arg, context) {
|
|
2220
2501
|
if (ExpressionUtils.isLiteral(arg)) {
|
|
2221
2502
|
return eb.val(arg.value);
|
|
@@ -2236,23 +2517,22 @@ var ExpressionTransformer = class {
|
|
|
2236
2517
|
if (this.isAuthCall(expr2.receiver)) {
|
|
2237
2518
|
return this.valueMemberAccess(this.auth, expr2, this.authType);
|
|
2238
2519
|
}
|
|
2239
|
-
|
|
2520
|
+
invariant6(ExpressionUtils.isField(expr2.receiver) || ExpressionUtils.isThis(expr2.receiver), 'expect receiver to be field expression or "this"');
|
|
2240
2521
|
let members = expr2.members;
|
|
2241
2522
|
let receiver;
|
|
2242
2523
|
const { memberFilter, memberSelect, ...restContext } = context;
|
|
2243
2524
|
if (ExpressionUtils.isThis(expr2.receiver)) {
|
|
2244
2525
|
if (expr2.members.length === 1) {
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2526
|
+
return this._field(ExpressionUtils.field(expr2.members[0]), context);
|
|
2527
|
+
} else {
|
|
2528
|
+
const firstMemberFieldDef = requireField(this.schema, context.model, expr2.members[0]);
|
|
2529
|
+
receiver = this.transformRelationAccess(expr2.members[0], firstMemberFieldDef.type, restContext);
|
|
2530
|
+
members = expr2.members.slice(1);
|
|
2248
2531
|
}
|
|
2249
|
-
const firstMemberFieldDef = requireField(this.schema, context.model, expr2.members[0]);
|
|
2250
|
-
receiver = this.transformRelationAccess(expr2.members[0], firstMemberFieldDef.type, restContext);
|
|
2251
|
-
members = expr2.members.slice(1);
|
|
2252
2532
|
} else {
|
|
2253
2533
|
receiver = this.transform(expr2.receiver, restContext);
|
|
2254
2534
|
}
|
|
2255
|
-
|
|
2535
|
+
invariant6(SelectQueryNode.is(receiver), "expected receiver to be select query");
|
|
2256
2536
|
let startType;
|
|
2257
2537
|
if (ExpressionUtils.isField(expr2.receiver)) {
|
|
2258
2538
|
const receiverField = requireField(this.schema, context.model, expr2.receiver.field);
|
|
@@ -2281,11 +2561,11 @@ var ExpressionTransformer = class {
|
|
|
2281
2561
|
alias: void 0
|
|
2282
2562
|
});
|
|
2283
2563
|
if (currNode) {
|
|
2284
|
-
|
|
2564
|
+
invariant6(SelectQueryNode.is(currNode), "expected select query node");
|
|
2285
2565
|
currNode = {
|
|
2286
2566
|
...relation,
|
|
2287
2567
|
selections: [
|
|
2288
|
-
SelectionNode.create(
|
|
2568
|
+
SelectionNode.create(AliasNode3.create(currNode, IdentifierNode.create(members[i + 1])))
|
|
2289
2569
|
]
|
|
2290
2570
|
};
|
|
2291
2571
|
} else {
|
|
@@ -2298,15 +2578,15 @@ var ExpressionTransformer = class {
|
|
|
2298
2578
|
};
|
|
2299
2579
|
}
|
|
2300
2580
|
} else {
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
currNode =
|
|
2581
|
+
invariant6(i === members.length - 1, "plain field access must be the last segment");
|
|
2582
|
+
invariant6(!currNode, "plain field access must be the last segment");
|
|
2583
|
+
currNode = ColumnNode2.create(member);
|
|
2304
2584
|
}
|
|
2305
2585
|
}
|
|
2306
2586
|
return {
|
|
2307
2587
|
...receiver,
|
|
2308
2588
|
selections: [
|
|
2309
|
-
SelectionNode.create(
|
|
2589
|
+
SelectionNode.create(AliasNode3.create(currNode, IdentifierNode.create("$t")))
|
|
2310
2590
|
]
|
|
2311
2591
|
};
|
|
2312
2592
|
}
|
|
@@ -2323,24 +2603,33 @@ var ExpressionTransformer = class {
|
|
|
2323
2603
|
return this.transformValue(fieldValue, fieldDef.type);
|
|
2324
2604
|
}
|
|
2325
2605
|
transformRelationAccess(field, relationModel, context) {
|
|
2606
|
+
const m2m = getManyToManyRelation(this.schema, context.model, field);
|
|
2607
|
+
if (m2m) {
|
|
2608
|
+
return this.transformManyToManyRelationAccess(m2m, context);
|
|
2609
|
+
}
|
|
2326
2610
|
const fromModel = context.model;
|
|
2327
2611
|
const { keyPairs, ownedByModel } = getRelationForeignKeyFieldPairs(this.schema, fromModel, field);
|
|
2328
2612
|
let condition;
|
|
2329
2613
|
if (ownedByModel) {
|
|
2330
|
-
condition = conjunction(this.dialect, keyPairs.map(({ fk, pk }) => BinaryOperationNode2.create(
|
|
2614
|
+
condition = conjunction(this.dialect, keyPairs.map(({ fk, pk }) => BinaryOperationNode2.create(ReferenceNode3.create(ColumnNode2.create(fk), TableNode3.create(context.alias ?? fromModel)), OperatorNode2.create("="), ReferenceNode3.create(ColumnNode2.create(pk), TableNode3.create(relationModel)))));
|
|
2331
2615
|
} else {
|
|
2332
|
-
condition = conjunction(this.dialect, keyPairs.map(({ fk, pk }) => BinaryOperationNode2.create(
|
|
2616
|
+
condition = conjunction(this.dialect, keyPairs.map(({ fk, pk }) => BinaryOperationNode2.create(ReferenceNode3.create(ColumnNode2.create(pk), TableNode3.create(context.alias ?? fromModel)), OperatorNode2.create("="), ReferenceNode3.create(ColumnNode2.create(fk), TableNode3.create(relationModel)))));
|
|
2333
2617
|
}
|
|
2334
2618
|
return {
|
|
2335
2619
|
kind: "SelectQueryNode",
|
|
2336
2620
|
from: FromNode.create([
|
|
2337
|
-
|
|
2621
|
+
TableNode3.create(relationModel)
|
|
2338
2622
|
]),
|
|
2339
2623
|
where: WhereNode.create(condition)
|
|
2340
2624
|
};
|
|
2341
2625
|
}
|
|
2626
|
+
transformManyToManyRelationAccess(m2m, context) {
|
|
2627
|
+
const eb = expressionBuilder2();
|
|
2628
|
+
const relationQuery = eb.selectFrom(m2m.otherModel).innerJoin(m2m.joinTable, (join) => join.onRef(`${m2m.otherModel}.${m2m.otherPKName}`, "=", `${m2m.joinTable}.${m2m.otherFkName}`).onRef(`${m2m.joinTable}.${m2m.parentFkName}`, "=", `${context.alias ?? context.model}.${m2m.parentPKName}`));
|
|
2629
|
+
return relationQuery.toOperationNode();
|
|
2630
|
+
}
|
|
2342
2631
|
createColumnRef(column, context) {
|
|
2343
|
-
return
|
|
2632
|
+
return ReferenceNode3.create(ColumnNode2.create(column), TableNode3.create(context.alias ?? context.model));
|
|
2344
2633
|
}
|
|
2345
2634
|
isAuthCall(value) {
|
|
2346
2635
|
return ExpressionUtils.isCall(value) && value.function === "auth";
|
|
@@ -2363,6 +2652,19 @@ var ExpressionTransformer = class {
|
|
|
2363
2652
|
return conditions.reduce((acc, condition) => ExpressionUtils.binary(acc, "&&", condition));
|
|
2364
2653
|
}
|
|
2365
2654
|
}
|
|
2655
|
+
isRelationField(expr2, model) {
|
|
2656
|
+
const fieldDef = this.getFieldDefFromFieldRef(expr2, model);
|
|
2657
|
+
return !!fieldDef?.relation;
|
|
2658
|
+
}
|
|
2659
|
+
getFieldDefFromFieldRef(expr2, model) {
|
|
2660
|
+
if (ExpressionUtils.isField(expr2)) {
|
|
2661
|
+
return requireField(this.schema, model, expr2.field);
|
|
2662
|
+
} else if (ExpressionUtils.isMember(expr2) && expr2.members.length === 1 && ExpressionUtils.isThis(expr2.receiver)) {
|
|
2663
|
+
return requireField(this.schema, model, expr2.members[0]);
|
|
2664
|
+
} else {
|
|
2665
|
+
return void 0;
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2366
2668
|
};
|
|
2367
2669
|
_ts_decorate([
|
|
2368
2670
|
expr("literal"),
|
|
@@ -2449,86 +2751,249 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
2449
2751
|
}
|
|
2450
2752
|
async handle(node, proceed) {
|
|
2451
2753
|
if (!this.isCrudQueryNode(node)) {
|
|
2452
|
-
throw new RejectedByPolicyError(void 0, "non-CRUD queries are not allowed");
|
|
2754
|
+
throw new RejectedByPolicyError(void 0, RejectedByPolicyReason.OTHER, "non-CRUD queries are not allowed");
|
|
2453
2755
|
}
|
|
2454
2756
|
if (!this.isMutationQueryNode(node)) {
|
|
2455
2757
|
return proceed(this.transformNode(node));
|
|
2456
2758
|
}
|
|
2457
|
-
|
|
2458
|
-
const mutationModel = this.getMutationModel(node);
|
|
2759
|
+
const { mutationModel } = this.getMutationModel(node);
|
|
2459
2760
|
if (InsertQueryNode.is(node)) {
|
|
2460
|
-
const
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2761
|
+
const isManyToManyJoinTable = this.isManyToManyJoinTable(mutationModel);
|
|
2762
|
+
let needCheckPreCreate = true;
|
|
2763
|
+
if (!isManyToManyJoinTable) {
|
|
2764
|
+
const constCondition = this.tryGetConstantPolicy(mutationModel, "create");
|
|
2765
|
+
if (constCondition === true) {
|
|
2766
|
+
needCheckPreCreate = false;
|
|
2767
|
+
} else if (constCondition === false) {
|
|
2768
|
+
throw new RejectedByPolicyError(mutationModel);
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
if (needCheckPreCreate) {
|
|
2772
|
+
await this.enforcePreCreatePolicy(node, mutationModel, isManyToManyJoinTable, proceed);
|
|
2465
2773
|
}
|
|
2466
2774
|
}
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
await this.enforcePreCreatePolicy(node, proceed);
|
|
2472
|
-
}
|
|
2473
|
-
const transformedNode = this.transformNode(node);
|
|
2474
|
-
const result = await proceed(transformedNode);
|
|
2475
|
-
if (!this.onlyReturningId(node)) {
|
|
2775
|
+
const result = await proceed(this.transformNode(node));
|
|
2776
|
+
if (!node.returning || this.onlyReturningId(node)) {
|
|
2777
|
+
return result;
|
|
2778
|
+
} else {
|
|
2476
2779
|
const readBackResult = await this.processReadBack(node, result, proceed);
|
|
2477
2780
|
if (readBackResult.rows.length !== result.rows.length) {
|
|
2478
|
-
throw new RejectedByPolicyError(mutationModel, "result is not allowed to be read back");
|
|
2781
|
+
throw new RejectedByPolicyError(mutationModel, RejectedByPolicyReason.CANNOT_READ_BACK, "result is not allowed to be read back");
|
|
2479
2782
|
}
|
|
2480
2783
|
return readBackResult;
|
|
2481
|
-
}
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
// #region overrides
|
|
2787
|
+
transformSelectQuery(node) {
|
|
2788
|
+
let whereNode = this.transformNode(node.where);
|
|
2789
|
+
const policyFilter = this.createPolicyFilterForFrom(node.from);
|
|
2790
|
+
if (policyFilter) {
|
|
2791
|
+
whereNode = WhereNode2.create(whereNode?.where ? conjunction(this.dialect, [
|
|
2792
|
+
whereNode.where,
|
|
2793
|
+
policyFilter
|
|
2794
|
+
]) : policyFilter);
|
|
2795
|
+
}
|
|
2796
|
+
const baseResult = super.transformSelectQuery({
|
|
2797
|
+
...node,
|
|
2798
|
+
where: void 0
|
|
2799
|
+
});
|
|
2800
|
+
return {
|
|
2801
|
+
...baseResult,
|
|
2802
|
+
where: whereNode
|
|
2803
|
+
};
|
|
2804
|
+
}
|
|
2805
|
+
transformJoin(node) {
|
|
2806
|
+
const table = this.extractTableName(node.table);
|
|
2807
|
+
if (!table) {
|
|
2808
|
+
return super.transformJoin(node);
|
|
2809
|
+
}
|
|
2810
|
+
const filter = this.buildPolicyFilter(table.model, table.alias, "read");
|
|
2811
|
+
const nestedSelect = {
|
|
2812
|
+
kind: "SelectQueryNode",
|
|
2813
|
+
from: FromNode2.create([
|
|
2814
|
+
node.table
|
|
2815
|
+
]),
|
|
2816
|
+
selections: [
|
|
2817
|
+
SelectionNode2.createSelectAll()
|
|
2818
|
+
],
|
|
2819
|
+
where: WhereNode2.create(filter)
|
|
2820
|
+
};
|
|
2821
|
+
return {
|
|
2822
|
+
...node,
|
|
2823
|
+
table: AliasNode4.create(ParensNode2.create(nestedSelect), IdentifierNode2.create(table.alias ?? table.model))
|
|
2824
|
+
};
|
|
2825
|
+
}
|
|
2826
|
+
transformInsertQuery(node) {
|
|
2827
|
+
let onConflict = node.onConflict;
|
|
2828
|
+
if (onConflict?.updates) {
|
|
2829
|
+
const { mutationModel, alias } = this.getMutationModel(node);
|
|
2830
|
+
const filter = this.buildPolicyFilter(mutationModel, alias, "update");
|
|
2831
|
+
if (onConflict.updateWhere) {
|
|
2832
|
+
onConflict = {
|
|
2833
|
+
...onConflict,
|
|
2834
|
+
updateWhere: WhereNode2.create(conjunction(this.dialect, [
|
|
2835
|
+
onConflict.updateWhere.where,
|
|
2836
|
+
filter
|
|
2837
|
+
]))
|
|
2838
|
+
};
|
|
2839
|
+
} else {
|
|
2840
|
+
onConflict = {
|
|
2841
|
+
...onConflict,
|
|
2842
|
+
updateWhere: WhereNode2.create(filter)
|
|
2843
|
+
};
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
const processedNode = onConflict ? {
|
|
2847
|
+
...node,
|
|
2848
|
+
onConflict
|
|
2849
|
+
} : node;
|
|
2850
|
+
const result = super.transformInsertQuery(processedNode);
|
|
2851
|
+
if (!node.returning) {
|
|
2482
2852
|
return result;
|
|
2483
2853
|
}
|
|
2854
|
+
if (this.onlyReturningId(node)) {
|
|
2855
|
+
return result;
|
|
2856
|
+
} else {
|
|
2857
|
+
const { mutationModel } = this.getMutationModel(node);
|
|
2858
|
+
const idFields = requireIdFields(this.client.$schema, mutationModel);
|
|
2859
|
+
return {
|
|
2860
|
+
...result,
|
|
2861
|
+
returning: ReturningNode.create(idFields.map((field) => SelectionNode2.create(ColumnNode3.create(field))))
|
|
2862
|
+
};
|
|
2863
|
+
}
|
|
2484
2864
|
}
|
|
2865
|
+
transformUpdateQuery(node) {
|
|
2866
|
+
const result = super.transformUpdateQuery(node);
|
|
2867
|
+
const { mutationModel, alias } = this.getMutationModel(node);
|
|
2868
|
+
let filter = this.buildPolicyFilter(mutationModel, alias, "update");
|
|
2869
|
+
if (node.from) {
|
|
2870
|
+
const joinFilter = this.createPolicyFilterForFrom(node.from);
|
|
2871
|
+
if (joinFilter) {
|
|
2872
|
+
filter = conjunction(this.dialect, [
|
|
2873
|
+
filter,
|
|
2874
|
+
joinFilter
|
|
2875
|
+
]);
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
return {
|
|
2879
|
+
...result,
|
|
2880
|
+
where: WhereNode2.create(result.where ? conjunction(this.dialect, [
|
|
2881
|
+
result.where.where,
|
|
2882
|
+
filter
|
|
2883
|
+
]) : filter)
|
|
2884
|
+
};
|
|
2885
|
+
}
|
|
2886
|
+
transformDeleteQuery(node) {
|
|
2887
|
+
const result = super.transformDeleteQuery(node);
|
|
2888
|
+
const { mutationModel, alias } = this.getMutationModel(node);
|
|
2889
|
+
let filter = this.buildPolicyFilter(mutationModel, alias, "delete");
|
|
2890
|
+
if (node.using) {
|
|
2891
|
+
const joinFilter = this.createPolicyFilterForTables(node.using.tables);
|
|
2892
|
+
if (joinFilter) {
|
|
2893
|
+
filter = conjunction(this.dialect, [
|
|
2894
|
+
filter,
|
|
2895
|
+
joinFilter
|
|
2896
|
+
]);
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
return {
|
|
2900
|
+
...result,
|
|
2901
|
+
where: WhereNode2.create(result.where ? conjunction(this.dialect, [
|
|
2902
|
+
result.where.where,
|
|
2903
|
+
filter
|
|
2904
|
+
]) : filter)
|
|
2905
|
+
};
|
|
2906
|
+
}
|
|
2907
|
+
// #endregion
|
|
2908
|
+
// #region helpers
|
|
2485
2909
|
onlyReturningId(node) {
|
|
2486
2910
|
if (!node.returning) {
|
|
2487
2911
|
return true;
|
|
2488
2912
|
}
|
|
2489
|
-
const
|
|
2913
|
+
const { mutationModel } = this.getMutationModel(node);
|
|
2914
|
+
const idFields = requireIdFields(this.client.$schema, mutationModel);
|
|
2490
2915
|
const collector = new ColumnCollector();
|
|
2491
2916
|
const selectedColumns = collector.collect(node.returning);
|
|
2492
2917
|
return selectedColumns.every((c) => idFields.includes(c));
|
|
2493
2918
|
}
|
|
2494
|
-
async enforcePreCreatePolicy(node, proceed) {
|
|
2495
|
-
const model = this.getMutationModel(node);
|
|
2919
|
+
async enforcePreCreatePolicy(node, mutationModel, isManyToManyJoinTable, proceed) {
|
|
2496
2920
|
const fields = node.columns?.map((c) => c.column.name) ?? [];
|
|
2497
|
-
const valueRows = node.values ? this.unwrapCreateValueRows(node.values,
|
|
2921
|
+
const valueRows = node.values ? this.unwrapCreateValueRows(node.values, mutationModel, fields, isManyToManyJoinTable) : [
|
|
2498
2922
|
[]
|
|
2499
2923
|
];
|
|
2500
2924
|
for (const values of valueRows) {
|
|
2501
|
-
|
|
2925
|
+
if (isManyToManyJoinTable) {
|
|
2926
|
+
await this.enforcePreCreatePolicyForManyToManyJoinTable(mutationModel, fields, values.map((v) => v.node), proceed);
|
|
2927
|
+
} else {
|
|
2928
|
+
await this.enforcePreCreatePolicyForOne(mutationModel, fields, values.map((v) => v.node), proceed);
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
async enforcePreCreatePolicyForManyToManyJoinTable(tableName, fields, values, proceed) {
|
|
2933
|
+
const m2m = this.resolveManyToManyJoinTable(tableName);
|
|
2934
|
+
invariant7(m2m);
|
|
2935
|
+
invariant7(fields.includes("A") && fields.includes("B"), "many-to-many join table must have A and B fk fields");
|
|
2936
|
+
const aIndex = fields.indexOf("A");
|
|
2937
|
+
const aNode = values[aIndex];
|
|
2938
|
+
const bIndex = fields.indexOf("B");
|
|
2939
|
+
const bNode = values[bIndex];
|
|
2940
|
+
invariant7(ValueNode3.is(aNode) && ValueNode3.is(bNode), "A and B values must be ValueNode");
|
|
2941
|
+
const aValue = aNode.value;
|
|
2942
|
+
const bValue = bNode.value;
|
|
2943
|
+
invariant7(aValue !== null && aValue !== void 0, "A value cannot be null or undefined");
|
|
2944
|
+
invariant7(bValue !== null && bValue !== void 0, "B value cannot be null or undefined");
|
|
2945
|
+
const eb = expressionBuilder3();
|
|
2946
|
+
const filterA = this.buildPolicyFilter(m2m.firstModel, void 0, "update");
|
|
2947
|
+
const queryA = eb.selectFrom(m2m.firstModel).where(eb(eb.ref(`${m2m.firstModel}.${m2m.firstIdField}`), "=", aValue)).select(() => new ExpressionWrapper(filterA).as("$t"));
|
|
2948
|
+
const filterB = this.buildPolicyFilter(m2m.secondModel, void 0, "update");
|
|
2949
|
+
const queryB = eb.selectFrom(m2m.secondModel).where(eb(eb.ref(`${m2m.secondModel}.${m2m.secondIdField}`), "=", bValue)).select(() => new ExpressionWrapper(filterB).as("$t"));
|
|
2950
|
+
const queryNode = {
|
|
2951
|
+
kind: "SelectQueryNode",
|
|
2952
|
+
selections: [
|
|
2953
|
+
SelectionNode2.create(AliasNode4.create(queryA.toOperationNode(), IdentifierNode2.create("$conditionA"))),
|
|
2954
|
+
SelectionNode2.create(AliasNode4.create(queryB.toOperationNode(), IdentifierNode2.create("$conditionB")))
|
|
2955
|
+
]
|
|
2956
|
+
};
|
|
2957
|
+
const result = await proceed(queryNode);
|
|
2958
|
+
if (!result.rows[0]?.$conditionA) {
|
|
2959
|
+
throw new RejectedByPolicyError(m2m.firstModel, RejectedByPolicyReason.CANNOT_READ_BACK, `many-to-many relation participant model "${m2m.firstModel}" not updatable`);
|
|
2960
|
+
}
|
|
2961
|
+
if (!result.rows[0]?.$conditionB) {
|
|
2962
|
+
throw new RejectedByPolicyError(m2m.secondModel, RejectedByPolicyReason.NO_ACCESS, `many-to-many relation participant model "${m2m.secondModel}" not updatable`);
|
|
2502
2963
|
}
|
|
2503
2964
|
}
|
|
2504
2965
|
async enforcePreCreatePolicyForOne(model, fields, values, proceed) {
|
|
2505
|
-
const allFields = Object.
|
|
2966
|
+
const allFields = Object.entries(requireModel(this.client.$schema, model).fields).filter(([, def]) => !def.relation);
|
|
2506
2967
|
const allValues = [];
|
|
2507
|
-
for (const
|
|
2508
|
-
const index = fields.indexOf(
|
|
2968
|
+
for (const [name, _def] of allFields) {
|
|
2969
|
+
const index = fields.indexOf(name);
|
|
2509
2970
|
if (index >= 0) {
|
|
2510
2971
|
allValues.push(values[index]);
|
|
2511
2972
|
} else {
|
|
2512
2973
|
allValues.push(ValueNode3.createImmediate(null));
|
|
2513
2974
|
}
|
|
2514
2975
|
}
|
|
2976
|
+
const eb = expressionBuilder3();
|
|
2515
2977
|
const constTable = {
|
|
2516
2978
|
kind: "SelectQueryNode",
|
|
2517
2979
|
from: FromNode2.create([
|
|
2518
|
-
|
|
2980
|
+
AliasNode4.create(ParensNode2.create(ValuesNode.create([
|
|
2519
2981
|
ValueListNode2.create(allValues)
|
|
2520
2982
|
])), IdentifierNode2.create("$t"))
|
|
2521
2983
|
]),
|
|
2522
|
-
selections: allFields.map((
|
|
2984
|
+
selections: allFields.map(([name, def], index) => {
|
|
2985
|
+
const castedColumnRef = sql4`CAST(${eb.ref(`column${index + 1}`)} as ${sql4.raw(this.dialect.getFieldSqlType(def))})`.as(name);
|
|
2986
|
+
return SelectionNode2.create(castedColumnRef.toOperationNode());
|
|
2987
|
+
})
|
|
2523
2988
|
};
|
|
2524
2989
|
const filter = this.buildPolicyFilter(model, void 0, "create");
|
|
2525
2990
|
const preCreateCheck = {
|
|
2526
2991
|
kind: "SelectQueryNode",
|
|
2527
2992
|
from: FromNode2.create([
|
|
2528
|
-
|
|
2993
|
+
AliasNode4.create(constTable, IdentifierNode2.create(model))
|
|
2529
2994
|
]),
|
|
2530
2995
|
selections: [
|
|
2531
|
-
SelectionNode2.create(
|
|
2996
|
+
SelectionNode2.create(AliasNode4.create(BinaryOperationNode3.create(FunctionNode3.create("COUNT", [
|
|
2532
2997
|
ValueNode3.createImmediate(1)
|
|
2533
2998
|
]), OperatorNode3.create(">"), ValueNode3.createImmediate(0)), IdentifierNode2.create("$condition")))
|
|
2534
2999
|
],
|
|
@@ -2539,31 +3004,35 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
2539
3004
|
throw new RejectedByPolicyError(model);
|
|
2540
3005
|
}
|
|
2541
3006
|
}
|
|
2542
|
-
unwrapCreateValueRows(node, model, fields) {
|
|
3007
|
+
unwrapCreateValueRows(node, model, fields, isManyToManyJoinTable) {
|
|
2543
3008
|
if (ValuesNode.is(node)) {
|
|
2544
|
-
return node.values.map((v) => this.unwrapCreateValueRow(v.values, model, fields));
|
|
3009
|
+
return node.values.map((v) => this.unwrapCreateValueRow(v.values, model, fields, isManyToManyJoinTable));
|
|
2545
3010
|
} else if (PrimitiveValueListNode.is(node)) {
|
|
2546
3011
|
return [
|
|
2547
|
-
this.unwrapCreateValueRow(node.values, model, fields)
|
|
3012
|
+
this.unwrapCreateValueRow(node.values, model, fields, isManyToManyJoinTable)
|
|
2548
3013
|
];
|
|
2549
3014
|
} else {
|
|
2550
3015
|
throw new InternalError(`Unexpected node kind: ${node.kind} for unwrapping create values`);
|
|
2551
3016
|
}
|
|
2552
3017
|
}
|
|
2553
|
-
unwrapCreateValueRow(data, model, fields) {
|
|
2554
|
-
|
|
3018
|
+
unwrapCreateValueRow(data, model, fields, isImplicitManyToManyJoinTable) {
|
|
3019
|
+
invariant7(data.length === fields.length, "data length must match fields length");
|
|
2555
3020
|
const result = [];
|
|
2556
3021
|
for (let i = 0; i < data.length; i++) {
|
|
2557
3022
|
const item = data[i];
|
|
2558
|
-
const fieldDef = requireField(this.client.$schema, model, fields[i]);
|
|
2559
3023
|
if (typeof item === "object" && item && "kind" in item) {
|
|
2560
|
-
|
|
3024
|
+
const fieldDef = requireField(this.client.$schema, model, fields[i]);
|
|
3025
|
+
invariant7(item.kind === "ValueNode", "expecting a ValueNode");
|
|
2561
3026
|
result.push({
|
|
2562
3027
|
node: ValueNode3.create(this.dialect.transformPrimitive(item.value, fieldDef.type, !!fieldDef.array)),
|
|
2563
3028
|
raw: item.value
|
|
2564
3029
|
});
|
|
2565
3030
|
} else {
|
|
2566
|
-
|
|
3031
|
+
let value = item;
|
|
3032
|
+
if (!isImplicitManyToManyJoinTable) {
|
|
3033
|
+
const fieldDef = requireField(this.client.$schema, model, fields[i]);
|
|
3034
|
+
value = this.dialect.transformPrimitive(item, fieldDef.type, !!fieldDef.array);
|
|
3035
|
+
}
|
|
2567
3036
|
if (Array.isArray(value)) {
|
|
2568
3037
|
result.push({
|
|
2569
3038
|
node: RawNode.createWithSql(this.dialect.buildArrayLiteralSQL(value)),
|
|
@@ -2607,16 +3076,13 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
2607
3076
|
if (!this.isMutationQueryNode(node) || !node.returning) {
|
|
2608
3077
|
return result;
|
|
2609
3078
|
}
|
|
2610
|
-
const
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
}
|
|
2614
|
-
const idConditions = this.buildIdConditions(table, result.rows);
|
|
2615
|
-
const policyFilter = this.buildPolicyFilter(table, void 0, "read");
|
|
3079
|
+
const { mutationModel } = this.getMutationModel(node);
|
|
3080
|
+
const idConditions = this.buildIdConditions(mutationModel, result.rows);
|
|
3081
|
+
const policyFilter = this.buildPolicyFilter(mutationModel, void 0, "read");
|
|
2616
3082
|
const select = {
|
|
2617
3083
|
kind: "SelectQueryNode",
|
|
2618
3084
|
from: FromNode2.create([
|
|
2619
|
-
|
|
3085
|
+
TableNode4.create(mutationModel)
|
|
2620
3086
|
]),
|
|
2621
3087
|
where: WhereNode2.create(conjunction(this.dialect, [
|
|
2622
3088
|
idConditions,
|
|
@@ -2628,15 +3094,31 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
2628
3094
|
return selectResult;
|
|
2629
3095
|
}
|
|
2630
3096
|
buildIdConditions(table, rows) {
|
|
2631
|
-
const idFields =
|
|
2632
|
-
return disjunction(this.dialect, rows.map((row) => conjunction(this.dialect, idFields.map((field) => BinaryOperationNode3.create(
|
|
3097
|
+
const idFields = requireIdFields(this.client.$schema, table);
|
|
3098
|
+
return disjunction(this.dialect, rows.map((row) => conjunction(this.dialect, idFields.map((field) => BinaryOperationNode3.create(ColumnNode3.create(field), OperatorNode3.create("="), ValueNode3.create(row[field]))))));
|
|
2633
3099
|
}
|
|
2634
3100
|
getMutationModel(node) {
|
|
2635
|
-
const r = match8(node).when(InsertQueryNode.is, (node2) =>
|
|
3101
|
+
const r = match8(node).when(InsertQueryNode.is, (node2) => ({
|
|
3102
|
+
mutationModel: getTableName(node2.into),
|
|
3103
|
+
alias: void 0
|
|
3104
|
+
})).when(UpdateQueryNode.is, (node2) => {
|
|
3105
|
+
if (!node2.table) {
|
|
3106
|
+
throw new QueryError("Update query must have a table");
|
|
3107
|
+
}
|
|
3108
|
+
const r2 = this.extractTableName(node2.table);
|
|
3109
|
+
return r2 ? {
|
|
3110
|
+
mutationModel: r2.model,
|
|
3111
|
+
alias: r2.alias
|
|
3112
|
+
} : void 0;
|
|
3113
|
+
}).when(DeleteQueryNode.is, (node2) => {
|
|
2636
3114
|
if (node2.from.froms.length !== 1) {
|
|
2637
|
-
throw new
|
|
3115
|
+
throw new QueryError("Only one from table is supported for delete");
|
|
2638
3116
|
}
|
|
2639
|
-
|
|
3117
|
+
const r2 = this.extractTableName(node2.from.froms[0]);
|
|
3118
|
+
return r2 ? {
|
|
3119
|
+
mutationModel: r2.model,
|
|
3120
|
+
alias: r2.alias
|
|
3121
|
+
} : void 0;
|
|
2640
3122
|
}).exhaustive();
|
|
2641
3123
|
if (!r) {
|
|
2642
3124
|
throw new InternalError(`Unable to get table name for query node: ${node}`);
|
|
@@ -2650,12 +3132,16 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
2650
3132
|
return InsertQueryNode.is(node) || UpdateQueryNode.is(node) || DeleteQueryNode.is(node);
|
|
2651
3133
|
}
|
|
2652
3134
|
buildPolicyFilter(model, alias, operation) {
|
|
3135
|
+
const m2mFilter = this.getModelPolicyFilterForManyToManyJoinTable(model, alias, operation);
|
|
3136
|
+
if (m2mFilter) {
|
|
3137
|
+
return m2mFilter;
|
|
3138
|
+
}
|
|
2653
3139
|
const policies = this.getModelPolicies(model, operation);
|
|
2654
3140
|
if (policies.length === 0) {
|
|
2655
3141
|
return falseNode(this.dialect);
|
|
2656
3142
|
}
|
|
2657
|
-
const allows = policies.filter((policy) => policy.kind === "allow").map((policy) => this.
|
|
2658
|
-
const denies = policies.filter((policy) => policy.kind === "deny").map((policy) => this.
|
|
3143
|
+
const allows = policies.filter((policy) => policy.kind === "allow").map((policy) => this.compilePolicyCondition(model, alias, operation, policy));
|
|
3144
|
+
const denies = policies.filter((policy) => policy.kind === "deny").map((policy) => this.compilePolicyCondition(model, alias, operation, policy));
|
|
2659
3145
|
let combinedPolicy;
|
|
2660
3146
|
if (allows.length === 0) {
|
|
2661
3147
|
combinedPolicy = falseNode(this.dialect);
|
|
@@ -2671,100 +3157,59 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
2671
3157
|
}
|
|
2672
3158
|
return combinedPolicy;
|
|
2673
3159
|
}
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
node.from?.froms.forEach((from) => {
|
|
2677
|
-
const extractResult = this.extractTableName(from);
|
|
2678
|
-
if (extractResult) {
|
|
2679
|
-
const { model, alias } = extractResult;
|
|
2680
|
-
const filter = this.buildPolicyFilter(model, alias, "read");
|
|
2681
|
-
whereNode = WhereNode2.create(whereNode?.where ? conjunction(this.dialect, [
|
|
2682
|
-
whereNode.where,
|
|
2683
|
-
filter
|
|
2684
|
-
]) : filter);
|
|
2685
|
-
}
|
|
2686
|
-
});
|
|
2687
|
-
const baseResult = super.transformSelectQuery({
|
|
2688
|
-
...node,
|
|
2689
|
-
where: void 0
|
|
2690
|
-
});
|
|
2691
|
-
return {
|
|
2692
|
-
...baseResult,
|
|
2693
|
-
where: whereNode
|
|
2694
|
-
};
|
|
2695
|
-
}
|
|
2696
|
-
transformInsertQuery(node) {
|
|
2697
|
-
const result = super.transformInsertQuery(node);
|
|
2698
|
-
if (!node.returning) {
|
|
2699
|
-
return result;
|
|
2700
|
-
}
|
|
2701
|
-
if (this.onlyReturningId(node)) {
|
|
2702
|
-
return result;
|
|
2703
|
-
} else {
|
|
2704
|
-
const idFields = getIdFields(this.client.$schema, this.getMutationModel(node));
|
|
2705
|
-
return {
|
|
2706
|
-
...result,
|
|
2707
|
-
returning: ReturningNode.create(idFields.map((field) => SelectionNode2.create(ColumnNode2.create(field))))
|
|
2708
|
-
};
|
|
2709
|
-
}
|
|
2710
|
-
}
|
|
2711
|
-
transformUpdateQuery(node) {
|
|
2712
|
-
const result = super.transformUpdateQuery(node);
|
|
2713
|
-
const mutationModel = this.getMutationModel(node);
|
|
2714
|
-
const filter = this.buildPolicyFilter(mutationModel, void 0, "update");
|
|
2715
|
-
return {
|
|
2716
|
-
...result,
|
|
2717
|
-
where: WhereNode2.create(result.where ? conjunction(this.dialect, [
|
|
2718
|
-
result.where.where,
|
|
2719
|
-
filter
|
|
2720
|
-
]) : filter)
|
|
2721
|
-
};
|
|
2722
|
-
}
|
|
2723
|
-
transformDeleteQuery(node) {
|
|
2724
|
-
const result = super.transformDeleteQuery(node);
|
|
2725
|
-
const mutationModel = this.getMutationModel(node);
|
|
2726
|
-
const filter = this.buildPolicyFilter(mutationModel, void 0, "delete");
|
|
2727
|
-
return {
|
|
2728
|
-
...result,
|
|
2729
|
-
where: WhereNode2.create(result.where ? conjunction(this.dialect, [
|
|
2730
|
-
result.where.where,
|
|
2731
|
-
filter
|
|
2732
|
-
]) : filter)
|
|
2733
|
-
};
|
|
2734
|
-
}
|
|
2735
|
-
extractTableName(from) {
|
|
2736
|
-
if (TableNode3.is(from)) {
|
|
3160
|
+
extractTableName(node) {
|
|
3161
|
+
if (TableNode4.is(node)) {
|
|
2737
3162
|
return {
|
|
2738
|
-
model:
|
|
3163
|
+
model: node.table.identifier.name
|
|
2739
3164
|
};
|
|
2740
3165
|
}
|
|
2741
|
-
if (
|
|
2742
|
-
const inner = this.extractTableName(
|
|
3166
|
+
if (AliasNode4.is(node)) {
|
|
3167
|
+
const inner = this.extractTableName(node.node);
|
|
2743
3168
|
if (!inner) {
|
|
2744
3169
|
return void 0;
|
|
2745
3170
|
}
|
|
2746
3171
|
return {
|
|
2747
3172
|
model: inner.model,
|
|
2748
|
-
alias: IdentifierNode2.is(
|
|
3173
|
+
alias: IdentifierNode2.is(node.alias) ? node.alias.name : void 0
|
|
2749
3174
|
};
|
|
2750
3175
|
} else {
|
|
2751
3176
|
return void 0;
|
|
2752
3177
|
}
|
|
2753
3178
|
}
|
|
2754
|
-
|
|
2755
|
-
|
|
3179
|
+
createPolicyFilterForFrom(node) {
|
|
3180
|
+
if (!node) {
|
|
3181
|
+
return void 0;
|
|
3182
|
+
}
|
|
3183
|
+
return this.createPolicyFilterForTables(node.froms);
|
|
3184
|
+
}
|
|
3185
|
+
createPolicyFilterForTables(tables) {
|
|
3186
|
+
return tables.reduce((acc, table) => {
|
|
3187
|
+
const extractResult = this.extractTableName(table);
|
|
3188
|
+
if (extractResult) {
|
|
3189
|
+
const { model, alias } = extractResult;
|
|
3190
|
+
const filter = this.buildPolicyFilter(model, alias, "read");
|
|
3191
|
+
return acc ? conjunction(this.dialect, [
|
|
3192
|
+
acc,
|
|
3193
|
+
filter
|
|
3194
|
+
]) : filter;
|
|
3195
|
+
}
|
|
3196
|
+
return acc;
|
|
3197
|
+
}, void 0);
|
|
3198
|
+
}
|
|
3199
|
+
compilePolicyCondition(model, alias, operation, policy) {
|
|
3200
|
+
return new ExpressionTransformer(this.client).transform(policy.condition, {
|
|
2756
3201
|
model,
|
|
2757
3202
|
alias,
|
|
2758
3203
|
operation,
|
|
2759
3204
|
auth: this.client.$auth
|
|
2760
3205
|
});
|
|
2761
3206
|
}
|
|
2762
|
-
getModelPolicies(
|
|
2763
|
-
const modelDef = requireModel(this.client.$schema,
|
|
3207
|
+
getModelPolicies(model, operation) {
|
|
3208
|
+
const modelDef = requireModel(this.client.$schema, model);
|
|
2764
3209
|
const result = [];
|
|
2765
3210
|
const extractOperations = /* @__PURE__ */ __name((expr2) => {
|
|
2766
|
-
|
|
2767
|
-
|
|
3211
|
+
invariant7(ExpressionUtils.isLiteral(expr2), "expecting a literal");
|
|
3212
|
+
invariant7(typeof expr2.value === "string", "expecting a string literal");
|
|
2768
3213
|
return expr2.value.split(",").filter((v) => !!v).map((v) => v.trim());
|
|
2769
3214
|
}, "extractOperations");
|
|
2770
3215
|
if (modelDef.attributes) {
|
|
@@ -2776,8 +3221,84 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
2776
3221
|
}
|
|
2777
3222
|
return result;
|
|
2778
3223
|
}
|
|
3224
|
+
resolveManyToManyJoinTable(tableName) {
|
|
3225
|
+
for (const model of Object.values(this.client.$schema.models)) {
|
|
3226
|
+
for (const field of Object.values(model.fields)) {
|
|
3227
|
+
const m2m = getManyToManyRelation(this.client.$schema, model.name, field.name);
|
|
3228
|
+
if (m2m?.joinTable === tableName) {
|
|
3229
|
+
const sortedRecord = [
|
|
3230
|
+
{
|
|
3231
|
+
model: model.name,
|
|
3232
|
+
field: field.name
|
|
3233
|
+
},
|
|
3234
|
+
{
|
|
3235
|
+
model: m2m.otherModel,
|
|
3236
|
+
field: m2m.otherField
|
|
3237
|
+
}
|
|
3238
|
+
].sort(this.manyToManySorter);
|
|
3239
|
+
const firstIdFields = requireIdFields(this.client.$schema, sortedRecord[0].model);
|
|
3240
|
+
const secondIdFields = requireIdFields(this.client.$schema, sortedRecord[1].model);
|
|
3241
|
+
invariant7(firstIdFields.length === 1 && secondIdFields.length === 1, "only single-field id is supported for implicit many-to-many join table");
|
|
3242
|
+
return {
|
|
3243
|
+
firstModel: sortedRecord[0].model,
|
|
3244
|
+
firstField: sortedRecord[0].field,
|
|
3245
|
+
firstIdField: firstIdFields[0],
|
|
3246
|
+
secondModel: sortedRecord[1].model,
|
|
3247
|
+
secondField: sortedRecord[1].field,
|
|
3248
|
+
secondIdField: secondIdFields[0]
|
|
3249
|
+
};
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
return void 0;
|
|
3254
|
+
}
|
|
3255
|
+
manyToManySorter(a, b) {
|
|
3256
|
+
return a.model !== b.model ? a.model.localeCompare(b.model) : a.field.localeCompare(b.field);
|
|
3257
|
+
}
|
|
3258
|
+
isManyToManyJoinTable(tableName) {
|
|
3259
|
+
return !!this.resolveManyToManyJoinTable(tableName);
|
|
3260
|
+
}
|
|
3261
|
+
getModelPolicyFilterForManyToManyJoinTable(tableName, alias, operation) {
|
|
3262
|
+
const m2m = this.resolveManyToManyJoinTable(tableName);
|
|
3263
|
+
if (!m2m) {
|
|
3264
|
+
return void 0;
|
|
3265
|
+
}
|
|
3266
|
+
const checkForOperation = operation === "read" ? "read" : "update";
|
|
3267
|
+
const eb = expressionBuilder3();
|
|
3268
|
+
const joinTable = alias ?? tableName;
|
|
3269
|
+
const aQuery = eb.selectFrom(m2m.firstModel).whereRef(`${m2m.firstModel}.${m2m.firstIdField}`, "=", `${joinTable}.A`).select(() => new ExpressionWrapper(this.buildPolicyFilter(m2m.firstModel, void 0, checkForOperation)).as("$conditionA"));
|
|
3270
|
+
const bQuery = eb.selectFrom(m2m.secondModel).whereRef(`${m2m.secondModel}.${m2m.secondIdField}`, "=", `${joinTable}.B`).select(() => new ExpressionWrapper(this.buildPolicyFilter(m2m.secondModel, void 0, checkForOperation)).as("$conditionB"));
|
|
3271
|
+
return eb.and([
|
|
3272
|
+
aQuery,
|
|
3273
|
+
bQuery
|
|
3274
|
+
]).toOperationNode();
|
|
3275
|
+
}
|
|
2779
3276
|
};
|
|
2780
3277
|
|
|
3278
|
+
// src/plugins/policy/functions.ts
|
|
3279
|
+
var check = /* @__PURE__ */ __name((eb, args, { client, model, modelAlias, operation }) => {
|
|
3280
|
+
invariant8(args.length === 1 || args.length === 2, '"check" function requires 1 or 2 arguments');
|
|
3281
|
+
const arg1Node = args[0].toOperationNode();
|
|
3282
|
+
const arg2Node = args.length === 2 ? args[1].toOperationNode() : void 0;
|
|
3283
|
+
if (arg2Node) {
|
|
3284
|
+
invariant8(ValueNode4.is(arg2Node) && typeof arg2Node.value === "string", '"operation" parameter must be a string literal when provided');
|
|
3285
|
+
invariant8(CRUD.includes(arg2Node.value), '"operation" parameter must be one of "create", "read", "update", "delete"');
|
|
3286
|
+
}
|
|
3287
|
+
const fieldName = extractFieldName(arg1Node);
|
|
3288
|
+
invariant8(fieldName, 'Failed to extract field name from the first argument of "check" function');
|
|
3289
|
+
const fieldDef = requireField(client.$schema, model, fieldName);
|
|
3290
|
+
invariant8(fieldDef.relation, `Field "${fieldName}" is not a relation field in model "${model}"`);
|
|
3291
|
+
invariant8(!fieldDef.array, `Field "${fieldName}" is a to-many relation, which is not supported by "check"`);
|
|
3292
|
+
const relationModel = fieldDef.type;
|
|
3293
|
+
const op = arg2Node ? arg2Node.value : operation;
|
|
3294
|
+
const policyHandler = new PolicyHandler(client);
|
|
3295
|
+
const joinPairs = buildJoinPairs(client.$schema, model, modelAlias, fieldName, relationModel);
|
|
3296
|
+
const joinCondition = joinPairs.length === 1 ? eb(eb.ref(joinPairs[0][0]), "=", eb.ref(joinPairs[0][1])) : eb.and(joinPairs.map(([left, right]) => eb(eb.ref(left), "=", eb.ref(right))));
|
|
3297
|
+
const policyCondition = policyHandler.buildPolicyFilter(relationModel, void 0, op);
|
|
3298
|
+
const result = eb.selectFrom(relationModel).where(joinCondition).select(new ExpressionWrapper2(policyCondition).as("$condition"));
|
|
3299
|
+
return result;
|
|
3300
|
+
}, "check");
|
|
3301
|
+
|
|
2781
3302
|
// src/plugins/policy/plugin.ts
|
|
2782
3303
|
var PolicyPlugin = class {
|
|
2783
3304
|
static {
|
|
@@ -2792,6 +3313,11 @@ var PolicyPlugin = class {
|
|
|
2792
3313
|
get description() {
|
|
2793
3314
|
return "Enforces access policies defined in the schema.";
|
|
2794
3315
|
}
|
|
3316
|
+
get functions() {
|
|
3317
|
+
return {
|
|
3318
|
+
check
|
|
3319
|
+
};
|
|
3320
|
+
}
|
|
2795
3321
|
onKyselyQuery({
|
|
2796
3322
|
query,
|
|
2797
3323
|
client,
|
|
@@ -2827,16 +3353,6 @@ function clone(value) {
|
|
|
2827
3353
|
}
|
|
2828
3354
|
__name(clone, "clone");
|
|
2829
3355
|
|
|
2830
|
-
// src/client/contract.ts
|
|
2831
|
-
var TransactionIsolationLevel = /* @__PURE__ */ function(TransactionIsolationLevel2) {
|
|
2832
|
-
TransactionIsolationLevel2["ReadUncommitted"] = "read uncommitted";
|
|
2833
|
-
TransactionIsolationLevel2["ReadCommitted"] = "read committed";
|
|
2834
|
-
TransactionIsolationLevel2["RepeatableRead"] = "repeatable read";
|
|
2835
|
-
TransactionIsolationLevel2["Serializable"] = "serializable";
|
|
2836
|
-
TransactionIsolationLevel2["Snapshot"] = "snapshot";
|
|
2837
|
-
return TransactionIsolationLevel2;
|
|
2838
|
-
}({});
|
|
2839
|
-
|
|
2840
3356
|
// src/client/crud/operations/base.ts
|
|
2841
3357
|
var BaseOperationHandler = class {
|
|
2842
3358
|
static {
|
|
@@ -2881,7 +3397,7 @@ var BaseOperationHandler = class {
|
|
|
2881
3397
|
return getField(this.schema, model, field);
|
|
2882
3398
|
}
|
|
2883
3399
|
async exists(kysely, model, filter) {
|
|
2884
|
-
const idFields =
|
|
3400
|
+
const idFields = requireIdFields(this.schema, model);
|
|
2885
3401
|
const _filter = flattenCompoundUniqueFilters(this.schema, model, filter);
|
|
2886
3402
|
const query = kysely.selectFrom(model).where((eb) => eb.and(_filter)).select(idFields.map((f) => kysely.dynamic.ref(f))).limit(1).modifyEnd(this.makeContextComment({
|
|
2887
3403
|
model,
|
|
@@ -2890,7 +3406,7 @@ var BaseOperationHandler = class {
|
|
|
2890
3406
|
return this.executeQueryTakeFirst(kysely, query, "exists");
|
|
2891
3407
|
}
|
|
2892
3408
|
async read(kysely, model, args) {
|
|
2893
|
-
let query = this.dialect.buildSelectModel(
|
|
3409
|
+
let query = this.dialect.buildSelectModel(expressionBuilder4(), model, model);
|
|
2894
3410
|
if (args) {
|
|
2895
3411
|
query = this.dialect.buildFilterSortTake(model, args, query, model);
|
|
2896
3412
|
}
|
|
@@ -2969,7 +3485,7 @@ var BaseOperationHandler = class {
|
|
|
2969
3485
|
if (!m2m) {
|
|
2970
3486
|
const { ownedByModel, keyPairs } = getRelationForeignKeyFieldPairs(this.schema, fromRelation?.model ?? "", fromRelation?.field ?? "");
|
|
2971
3487
|
if (!ownedByModel) {
|
|
2972
|
-
const parentFkFields = this.buildFkAssignments(fromRelation.model, fromRelation.field, fromRelation.ids);
|
|
3488
|
+
const parentFkFields = await this.buildFkAssignments(kysely, fromRelation.model, fromRelation.field, fromRelation.ids);
|
|
2973
3489
|
Object.assign(createFields, parentFkFields);
|
|
2974
3490
|
} else {
|
|
2975
3491
|
updateParent = /* @__PURE__ */ __name((entity) => {
|
|
@@ -3009,17 +3525,16 @@ var BaseOperationHandler = class {
|
|
|
3009
3525
|
createFields = baseCreateResult.remainingFields;
|
|
3010
3526
|
}
|
|
3011
3527
|
const updatedData = this.fillGeneratedAndDefaultValues(modelDef, createFields);
|
|
3012
|
-
const idFields =
|
|
3528
|
+
const idFields = requireIdFields(this.schema, model);
|
|
3013
3529
|
const query = kysely.insertInto(model).$if(Object.keys(updatedData).length === 0, (qb) => qb.defaultValues()).$if(Object.keys(updatedData).length > 0, (qb) => qb.values(updatedData)).returning(idFields).modifyEnd(this.makeContextComment({
|
|
3014
3530
|
model,
|
|
3015
3531
|
operation: "create"
|
|
3016
3532
|
}));
|
|
3017
3533
|
const createdEntity = await this.executeQueryTakeFirst(kysely, query, "create");
|
|
3018
3534
|
if (Object.keys(postCreateRelations).length > 0) {
|
|
3019
|
-
const
|
|
3020
|
-
|
|
3021
|
-
}
|
|
3022
|
-
await Promise.all(relationPromises);
|
|
3535
|
+
for (const [field, subPayload] of Object.entries(postCreateRelations)) {
|
|
3536
|
+
await this.processNoneOwnedRelationForCreate(kysely, model, field, subPayload, createdEntity);
|
|
3537
|
+
}
|
|
3023
3538
|
}
|
|
3024
3539
|
if (fromRelation && m2m) {
|
|
3025
3540
|
await this.handleManyToManyRelation(kysely, "connect", fromRelation.model, fromRelation.field, fromRelation.ids, m2m.otherModel, m2m.otherField, createdEntity, m2m.joinTable);
|
|
@@ -3041,7 +3556,7 @@ var BaseOperationHandler = class {
|
|
|
3041
3556
|
}
|
|
3042
3557
|
});
|
|
3043
3558
|
const discriminatorField = getDiscriminatorField(this.schema, model);
|
|
3044
|
-
|
|
3559
|
+
invariant9(discriminatorField, `Base model "${model}" must have a discriminator field`);
|
|
3045
3560
|
thisCreateFields[discriminatorField] = forModel;
|
|
3046
3561
|
const baseEntity = await this.create(kysely, model, thisCreateFields, void 0, true);
|
|
3047
3562
|
const idValues = extractIdFields(baseEntity, this.schema, model);
|
|
@@ -3051,14 +3566,24 @@ var BaseOperationHandler = class {
|
|
|
3051
3566
|
remainingFields
|
|
3052
3567
|
};
|
|
3053
3568
|
}
|
|
3054
|
-
buildFkAssignments(model, relationField, entity) {
|
|
3569
|
+
async buildFkAssignments(kysely, model, relationField, entity) {
|
|
3055
3570
|
const parentFkFields = {};
|
|
3056
|
-
|
|
3057
|
-
|
|
3571
|
+
invariant9(relationField, "parentField must be defined if parentModel is defined");
|
|
3572
|
+
invariant9(entity, "parentEntity must be defined if parentModel is defined");
|
|
3058
3573
|
const { keyPairs } = getRelationForeignKeyFieldPairs(this.schema, model, relationField);
|
|
3059
3574
|
for (const pair of keyPairs) {
|
|
3060
3575
|
if (!(pair.pk in entity)) {
|
|
3061
|
-
|
|
3576
|
+
const extraRead = await this.readUnique(kysely, model, {
|
|
3577
|
+
where: entity,
|
|
3578
|
+
select: {
|
|
3579
|
+
[pair.pk]: true
|
|
3580
|
+
}
|
|
3581
|
+
});
|
|
3582
|
+
if (!extraRead) {
|
|
3583
|
+
throw new QueryError(`Field "${pair.pk}" not found in parent created data`);
|
|
3584
|
+
} else {
|
|
3585
|
+
Object.assign(entity, extraRead);
|
|
3586
|
+
}
|
|
3062
3587
|
}
|
|
3063
3588
|
Object.assign(parentFkFields, {
|
|
3064
3589
|
[pair.fk]: entity[pair.pk]
|
|
@@ -3079,15 +3604,15 @@ var BaseOperationHandler = class {
|
|
|
3079
3604
|
entity: rightEntity
|
|
3080
3605
|
}
|
|
3081
3606
|
].sort((a, b) => (
|
|
3082
|
-
// the
|
|
3607
|
+
// the implicit m2m join table's "A", "B" fk fields' order is determined
|
|
3083
3608
|
// by model name's sort order, and when identical (for self-relations),
|
|
3084
3609
|
// field name's sort order
|
|
3085
3610
|
a.model !== b.model ? a.model.localeCompare(b.model) : a.field.localeCompare(b.field)
|
|
3086
3611
|
));
|
|
3087
|
-
const firstIds =
|
|
3088
|
-
const secondIds =
|
|
3089
|
-
|
|
3090
|
-
|
|
3612
|
+
const firstIds = requireIdFields(this.schema, sortedRecords[0].model);
|
|
3613
|
+
const secondIds = requireIdFields(this.schema, sortedRecords[1].model);
|
|
3614
|
+
invariant9(firstIds.length === 1, "many-to-many relation must have exactly one id field");
|
|
3615
|
+
invariant9(secondIds.length === 1, "many-to-many relation must have exactly one id field");
|
|
3091
3616
|
if (action === "connect") {
|
|
3092
3617
|
const result = await kysely.insertInto(joinTable).values({
|
|
3093
3618
|
A: sortedRecords[0].entity[firstIds[0]],
|
|
@@ -3098,17 +3623,17 @@ var BaseOperationHandler = class {
|
|
|
3098
3623
|
]).doNothing()).execute();
|
|
3099
3624
|
return result[0];
|
|
3100
3625
|
} else {
|
|
3101
|
-
const eb =
|
|
3626
|
+
const eb = expressionBuilder4();
|
|
3102
3627
|
const result = await kysely.deleteFrom(joinTable).where(eb(`${joinTable}.A`, "=", sortedRecords[0].entity[firstIds[0]])).where(eb(`${joinTable}.B`, "=", sortedRecords[1].entity[secondIds[0]])).execute();
|
|
3103
3628
|
return result[0];
|
|
3104
3629
|
}
|
|
3105
3630
|
}
|
|
3106
3631
|
resetManyToManyRelation(kysely, model, field, parentIds) {
|
|
3107
|
-
|
|
3632
|
+
invariant9(Object.keys(parentIds).length === 1, "parentIds must have exactly one field");
|
|
3108
3633
|
const parentId = Object.values(parentIds)[0];
|
|
3109
3634
|
const m2m = getManyToManyRelation(this.schema, model, field);
|
|
3110
|
-
|
|
3111
|
-
const eb =
|
|
3635
|
+
invariant9(m2m, "not a many-to-many relation");
|
|
3636
|
+
const eb = expressionBuilder4();
|
|
3112
3637
|
return kysely.deleteFrom(m2m.joinTable).where(eb(`${m2m.joinTable}.${m2m.parentFkName}`, "=", parentId)).execute();
|
|
3113
3638
|
}
|
|
3114
3639
|
async processOwnedRelationForCreate(kysely, relationField, payload) {
|
|
@@ -3129,7 +3654,7 @@ var BaseOperationHandler = class {
|
|
|
3129
3654
|
}
|
|
3130
3655
|
case "connect": {
|
|
3131
3656
|
const referencedPkFields = relationField.relation.references;
|
|
3132
|
-
|
|
3657
|
+
invariant9(referencedPkFields, "relation must have fields info");
|
|
3133
3658
|
const extractedFks = extractFields(subPayload, referencedPkFields);
|
|
3134
3659
|
if (Object.keys(extractedFks).length === referencedPkFields.length) {
|
|
3135
3660
|
result = extractedFks;
|
|
@@ -3161,10 +3686,9 @@ var BaseOperationHandler = class {
|
|
|
3161
3686
|
}
|
|
3162
3687
|
return result;
|
|
3163
3688
|
}
|
|
3164
|
-
processNoneOwnedRelationForCreate(kysely, contextModel, relationFieldName, payload, parentEntity) {
|
|
3689
|
+
async processNoneOwnedRelationForCreate(kysely, contextModel, relationFieldName, payload, parentEntity) {
|
|
3165
3690
|
const relationFieldDef = this.requireField(contextModel, relationFieldName);
|
|
3166
3691
|
const relationModel = relationFieldDef.type;
|
|
3167
|
-
const tasks = [];
|
|
3168
3692
|
const fromRelationContext = {
|
|
3169
3693
|
model: contextModel,
|
|
3170
3694
|
field: relationFieldName,
|
|
@@ -3177,27 +3701,35 @@ var BaseOperationHandler = class {
|
|
|
3177
3701
|
}
|
|
3178
3702
|
switch (action) {
|
|
3179
3703
|
case "create": {
|
|
3180
|
-
|
|
3704
|
+
for (const item of enumerate(subPayload)) {
|
|
3705
|
+
await this.create(kysely, relationModel, item, fromRelationContext);
|
|
3706
|
+
}
|
|
3181
3707
|
break;
|
|
3182
3708
|
}
|
|
3183
3709
|
case "createMany": {
|
|
3184
|
-
|
|
3185
|
-
|
|
3710
|
+
invariant9(relationFieldDef.array, "relation must be an array for createMany");
|
|
3711
|
+
await this.createMany(kysely, relationModel, subPayload, false, fromRelationContext);
|
|
3186
3712
|
break;
|
|
3187
3713
|
}
|
|
3188
3714
|
case "connect": {
|
|
3189
|
-
|
|
3715
|
+
await this.connectRelation(kysely, relationModel, subPayload, fromRelationContext);
|
|
3190
3716
|
break;
|
|
3191
3717
|
}
|
|
3192
3718
|
case "connectOrCreate": {
|
|
3193
|
-
|
|
3719
|
+
for (const item of enumerate(subPayload)) {
|
|
3720
|
+
const found = await this.exists(kysely, relationModel, item.where);
|
|
3721
|
+
if (!found) {
|
|
3722
|
+
await this.create(kysely, relationModel, item.create, fromRelationContext);
|
|
3723
|
+
} else {
|
|
3724
|
+
await this.connectRelation(kysely, relationModel, found, fromRelationContext);
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3194
3727
|
break;
|
|
3195
3728
|
}
|
|
3196
3729
|
default:
|
|
3197
3730
|
throw new QueryError(`Invalid relation action: ${action}`);
|
|
3198
3731
|
}
|
|
3199
3732
|
}
|
|
3200
|
-
return Promise.all(tasks);
|
|
3201
3733
|
}
|
|
3202
3734
|
async createMany(kysely, model, input, returnData, fromRelation) {
|
|
3203
3735
|
if (!input.data || Array.isArray(input.data) && input.data.length === 0) {
|
|
@@ -3218,7 +3750,7 @@ var BaseOperationHandler = class {
|
|
|
3218
3750
|
const newItem = {};
|
|
3219
3751
|
for (const [name, value] of Object.entries(item)) {
|
|
3220
3752
|
const fieldDef = this.requireField(model, name);
|
|
3221
|
-
|
|
3753
|
+
invariant9(!fieldDef.relation, "createMany does not support relations");
|
|
3222
3754
|
newItem[name] = this.dialect.transformPrimitive(value, fieldDef.type, !!fieldDef.array);
|
|
3223
3755
|
}
|
|
3224
3756
|
if (fromRelation) {
|
|
@@ -3268,7 +3800,7 @@ var BaseOperationHandler = class {
|
|
|
3268
3800
|
count: Number(result.numAffectedRows)
|
|
3269
3801
|
};
|
|
3270
3802
|
} else {
|
|
3271
|
-
const idFields =
|
|
3803
|
+
const idFields = requireIdFields(this.schema, model);
|
|
3272
3804
|
const result = await query.returning(idFields).execute();
|
|
3273
3805
|
return result;
|
|
3274
3806
|
}
|
|
@@ -3277,7 +3809,7 @@ var BaseOperationHandler = class {
|
|
|
3277
3809
|
const thisCreateRows = [];
|
|
3278
3810
|
const remainingFieldRows = [];
|
|
3279
3811
|
const discriminatorField = getDiscriminatorField(this.schema, model);
|
|
3280
|
-
|
|
3812
|
+
invariant9(discriminatorField, `Base model "${model}" must have a discriminator field`);
|
|
3281
3813
|
for (const createFields of createRows) {
|
|
3282
3814
|
const thisCreateFields = {};
|
|
3283
3815
|
const remainingFields = {};
|
|
@@ -3373,7 +3905,7 @@ var BaseOperationHandler = class {
|
|
|
3373
3905
|
}
|
|
3374
3906
|
} else {
|
|
3375
3907
|
const fromRelationFieldDef = this.requireField(fromRelation.model, fromRelation.field);
|
|
3376
|
-
|
|
3908
|
+
invariant9(fromRelationFieldDef.relation?.opposite);
|
|
3377
3909
|
parentWhere[fromRelationFieldDef.relation.opposite] = {
|
|
3378
3910
|
some: fromRelation.ids
|
|
3379
3911
|
};
|
|
@@ -3431,10 +3963,7 @@ var BaseOperationHandler = class {
|
|
|
3431
3963
|
throw new QueryError(`Relation update not allowed for field "${field}"`);
|
|
3432
3964
|
}
|
|
3433
3965
|
if (!thisEntity) {
|
|
3434
|
-
thisEntity = await this.
|
|
3435
|
-
where: combinedWhere,
|
|
3436
|
-
select: this.makeIdSelect(model)
|
|
3437
|
-
});
|
|
3966
|
+
thisEntity = await this.getEntityIds(kysely, model, combinedWhere);
|
|
3438
3967
|
if (!thisEntity) {
|
|
3439
3968
|
if (throwIfNotFound) {
|
|
3440
3969
|
throw new NotFoundError(model);
|
|
@@ -3452,7 +3981,7 @@ var BaseOperationHandler = class {
|
|
|
3452
3981
|
if (Object.keys(updateFields).length === 0) {
|
|
3453
3982
|
return combinedWhere;
|
|
3454
3983
|
} else {
|
|
3455
|
-
const idFields =
|
|
3984
|
+
const idFields = requireIdFields(this.schema, model);
|
|
3456
3985
|
const query = kysely.updateTable(model).where((eb) => this.dialect.buildFilter(eb, model, model, combinedWhere)).set(updateFields).returning(idFields).modifyEnd(this.makeContextComment({
|
|
3457
3986
|
model,
|
|
3458
3987
|
operation: "update"
|
|
@@ -3497,7 +4026,7 @@ var BaseOperationHandler = class {
|
|
|
3497
4026
|
if (!filter || typeof filter !== "object") {
|
|
3498
4027
|
return false;
|
|
3499
4028
|
}
|
|
3500
|
-
const idFields =
|
|
4029
|
+
const idFields = requireIdFields(this.schema, model);
|
|
3501
4030
|
return idFields.length === Object.keys(filter).length && idFields.every((field) => field in filter);
|
|
3502
4031
|
}
|
|
3503
4032
|
async processBaseModelUpdate(kysely, model, where, updateFields, throwIfNotFound) {
|
|
@@ -3518,20 +4047,20 @@ var BaseOperationHandler = class {
|
|
|
3518
4047
|
};
|
|
3519
4048
|
}
|
|
3520
4049
|
transformIncrementalUpdate(model, field, fieldDef, payload) {
|
|
3521
|
-
|
|
4050
|
+
invariant9(Object.keys(payload).length === 1, 'Only one of "set", "increment", "decrement", "multiply", or "divide" can be provided');
|
|
3522
4051
|
const key = Object.keys(payload)[0];
|
|
3523
4052
|
const value = this.dialect.transformPrimitive(payload[key], fieldDef.type, false);
|
|
3524
|
-
const eb =
|
|
4053
|
+
const eb = expressionBuilder4();
|
|
3525
4054
|
const fieldRef = this.dialect.fieldRef(model, field, eb);
|
|
3526
4055
|
return match9(key).with("set", () => value).with("increment", () => eb(fieldRef, "+", value)).with("decrement", () => eb(fieldRef, "-", value)).with("multiply", () => eb(fieldRef, "*", value)).with("divide", () => eb(fieldRef, "/", value)).otherwise(() => {
|
|
3527
4056
|
throw new InternalError(`Invalid incremental update operation: ${key}`);
|
|
3528
4057
|
});
|
|
3529
4058
|
}
|
|
3530
4059
|
transformScalarListUpdate(model, field, fieldDef, payload) {
|
|
3531
|
-
|
|
4060
|
+
invariant9(Object.keys(payload).length === 1, 'Only one of "set", "push" can be provided');
|
|
3532
4061
|
const key = Object.keys(payload)[0];
|
|
3533
4062
|
const value = this.dialect.transformPrimitive(payload[key], fieldDef.type, true);
|
|
3534
|
-
const eb =
|
|
4063
|
+
const eb = expressionBuilder4();
|
|
3535
4064
|
const fieldRef = this.dialect.fieldRef(model, field, eb);
|
|
3536
4065
|
return match9(key).with("set", () => value).with("push", () => {
|
|
3537
4066
|
return eb(fieldRef, "||", eb.val(ensureArray(value)));
|
|
@@ -3543,7 +4072,7 @@ var BaseOperationHandler = class {
|
|
|
3543
4072
|
return NUMERIC_FIELD_TYPES.includes(fieldDef.type) && !fieldDef.array;
|
|
3544
4073
|
}
|
|
3545
4074
|
makeContextComment(_context) {
|
|
3546
|
-
return
|
|
4075
|
+
return sql5``;
|
|
3547
4076
|
}
|
|
3548
4077
|
async updateMany(kysely, model, where, data, limit, returnData, filterModel) {
|
|
3549
4078
|
if (typeof data !== "object") {
|
|
@@ -3602,7 +4131,7 @@ var BaseOperationHandler = class {
|
|
|
3602
4131
|
count: Number(result.numAffectedRows)
|
|
3603
4132
|
};
|
|
3604
4133
|
} else {
|
|
3605
|
-
const idFields =
|
|
4134
|
+
const idFields = requireIdFields(this.schema, model);
|
|
3606
4135
|
const result = await query.returning(idFields).execute();
|
|
3607
4136
|
return result;
|
|
3608
4137
|
}
|
|
@@ -3625,11 +4154,10 @@ var BaseOperationHandler = class {
|
|
|
3625
4154
|
};
|
|
3626
4155
|
}
|
|
3627
4156
|
buildIdFieldRefs(kysely, model) {
|
|
3628
|
-
const idFields =
|
|
4157
|
+
const idFields = requireIdFields(this.schema, model);
|
|
3629
4158
|
return idFields.map((f) => kysely.dynamic.ref(`${model}.${f}`));
|
|
3630
4159
|
}
|
|
3631
4160
|
async processRelationUpdates(kysely, model, field, fieldDef, parentIds, args, throwIfNotFound) {
|
|
3632
|
-
const tasks = [];
|
|
3633
4161
|
const fieldModel = fieldDef.type;
|
|
3634
4162
|
const fromRelationContext = {
|
|
3635
4163
|
model,
|
|
@@ -3640,68 +4168,73 @@ var BaseOperationHandler = class {
|
|
|
3640
4168
|
for (const [key, value] of Object.entries(args)) {
|
|
3641
4169
|
switch (key) {
|
|
3642
4170
|
case "create": {
|
|
3643
|
-
|
|
3644
|
-
|
|
4171
|
+
invariant9(!Array.isArray(value) || fieldDef.array, "relation must be an array if create is an array");
|
|
4172
|
+
for (const item of enumerate(value)) {
|
|
4173
|
+
await this.create(kysely, fieldModel, item, fromRelationContext);
|
|
4174
|
+
}
|
|
3645
4175
|
break;
|
|
3646
4176
|
}
|
|
3647
4177
|
case "createMany": {
|
|
3648
|
-
|
|
3649
|
-
|
|
4178
|
+
invariant9(fieldDef.array, "relation must be an array for createMany");
|
|
4179
|
+
await this.createMany(kysely, fieldModel, value, false, fromRelationContext);
|
|
3650
4180
|
break;
|
|
3651
4181
|
}
|
|
3652
4182
|
case "connect": {
|
|
3653
|
-
|
|
4183
|
+
await this.connectRelation(kysely, fieldModel, value, fromRelationContext);
|
|
3654
4184
|
break;
|
|
3655
4185
|
}
|
|
3656
4186
|
case "connectOrCreate": {
|
|
3657
|
-
|
|
4187
|
+
await this.connectOrCreateRelation(kysely, fieldModel, value, fromRelationContext);
|
|
3658
4188
|
break;
|
|
3659
4189
|
}
|
|
3660
4190
|
case "disconnect": {
|
|
3661
|
-
|
|
4191
|
+
await this.disconnectRelation(kysely, fieldModel, value, fromRelationContext);
|
|
3662
4192
|
break;
|
|
3663
4193
|
}
|
|
3664
4194
|
case "set": {
|
|
3665
|
-
|
|
3666
|
-
|
|
4195
|
+
invariant9(fieldDef.array, "relation must be an array");
|
|
4196
|
+
await this.setRelation(kysely, fieldModel, value, fromRelationContext);
|
|
3667
4197
|
break;
|
|
3668
4198
|
}
|
|
3669
4199
|
case "update": {
|
|
3670
|
-
|
|
4200
|
+
for (const _item of enumerate(value)) {
|
|
4201
|
+
const item = _item;
|
|
3671
4202
|
let where;
|
|
3672
4203
|
let data;
|
|
3673
|
-
if ("
|
|
4204
|
+
if ("data" in item && typeof item.data === "object") {
|
|
3674
4205
|
where = item.where;
|
|
3675
4206
|
data = item.data;
|
|
3676
4207
|
} else {
|
|
3677
4208
|
where = void 0;
|
|
3678
4209
|
data = item;
|
|
3679
4210
|
}
|
|
3680
|
-
|
|
3681
|
-
}
|
|
4211
|
+
await this.update(kysely, fieldModel, where, data, fromRelationContext, true, throwIfNotFound);
|
|
4212
|
+
}
|
|
3682
4213
|
break;
|
|
3683
4214
|
}
|
|
3684
4215
|
case "upsert": {
|
|
3685
|
-
|
|
4216
|
+
for (const _item of enumerate(value)) {
|
|
4217
|
+
const item = _item;
|
|
3686
4218
|
const updated = await this.update(kysely, fieldModel, item.where, item.update, fromRelationContext, true, false);
|
|
3687
|
-
if (updated) {
|
|
3688
|
-
|
|
3689
|
-
} else {
|
|
3690
|
-
return this.create(kysely, fieldModel, item.create, fromRelationContext);
|
|
4219
|
+
if (!updated) {
|
|
4220
|
+
await this.create(kysely, fieldModel, item.create, fromRelationContext);
|
|
3691
4221
|
}
|
|
3692
|
-
}
|
|
4222
|
+
}
|
|
3693
4223
|
break;
|
|
3694
4224
|
}
|
|
3695
4225
|
case "updateMany": {
|
|
3696
|
-
|
|
4226
|
+
for (const _item of enumerate(value)) {
|
|
4227
|
+
const item = _item;
|
|
4228
|
+
await this.update(kysely, fieldModel, item.where, item.data, fromRelationContext, false, false);
|
|
4229
|
+
}
|
|
3697
4230
|
break;
|
|
3698
4231
|
}
|
|
3699
4232
|
case "delete": {
|
|
3700
|
-
|
|
4233
|
+
await this.deleteRelation(kysely, fieldModel, value, fromRelationContext, true);
|
|
3701
4234
|
break;
|
|
3702
4235
|
}
|
|
3703
4236
|
case "deleteMany": {
|
|
3704
|
-
|
|
4237
|
+
await this.deleteRelation(kysely, fieldModel, value, fromRelationContext, false);
|
|
3705
4238
|
break;
|
|
3706
4239
|
}
|
|
3707
4240
|
default: {
|
|
@@ -3709,7 +4242,6 @@ var BaseOperationHandler = class {
|
|
|
3709
4242
|
}
|
|
3710
4243
|
}
|
|
3711
4244
|
}
|
|
3712
|
-
await Promise.all(tasks);
|
|
3713
4245
|
return fromRelationContext.parentUpdates;
|
|
3714
4246
|
}
|
|
3715
4247
|
// #region relation manipulation
|
|
@@ -3720,18 +4252,22 @@ var BaseOperationHandler = class {
|
|
|
3720
4252
|
}
|
|
3721
4253
|
const m2m = getManyToManyRelation(this.schema, fromRelation.model, fromRelation.field);
|
|
3722
4254
|
if (m2m) {
|
|
3723
|
-
const
|
|
4255
|
+
const results = [];
|
|
4256
|
+
for (const d of _data) {
|
|
3724
4257
|
const ids = await this.getEntityIds(kysely, model, d);
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
4258
|
+
if (!ids) {
|
|
4259
|
+
throw new NotFoundError(model);
|
|
4260
|
+
}
|
|
4261
|
+
const r = await this.handleManyToManyRelation(kysely, "connect", fromRelation.model, fromRelation.field, fromRelation.ids, m2m.otherModel, m2m.otherField, ids, m2m.joinTable);
|
|
4262
|
+
results.push(r);
|
|
4263
|
+
}
|
|
3728
4264
|
if (_data.length > results.filter((r) => !!r).length) {
|
|
3729
4265
|
throw new NotFoundError(model);
|
|
3730
4266
|
}
|
|
3731
4267
|
} else {
|
|
3732
4268
|
const { ownedByModel, keyPairs } = getRelationForeignKeyFieldPairs(this.schema, fromRelation.model, fromRelation.field);
|
|
3733
4269
|
if (ownedByModel) {
|
|
3734
|
-
|
|
4270
|
+
invariant9(_data.length === 1, "only one entity can be connected");
|
|
3735
4271
|
const target = await this.readUnique(kysely, model, {
|
|
3736
4272
|
where: _data[0]
|
|
3737
4273
|
});
|
|
@@ -3744,7 +4280,7 @@ var BaseOperationHandler = class {
|
|
|
3744
4280
|
} else {
|
|
3745
4281
|
const relationFieldDef = this.requireField(fromRelation.model, fromRelation.field);
|
|
3746
4282
|
if (!relationFieldDef.array) {
|
|
3747
|
-
const query2 = kysely.updateTable(model).where((eb) => eb.and(keyPairs.map(({ fk, pk }) => eb(
|
|
4283
|
+
const query2 = kysely.updateTable(model).where((eb) => eb.and(keyPairs.map(({ fk, pk }) => eb(sql5.ref(fk), "=", fromRelation.ids[pk])))).set(keyPairs.reduce((acc, { fk }) => ({
|
|
3748
4284
|
...acc,
|
|
3749
4285
|
[fk]: null
|
|
3750
4286
|
}), {})).modifyEnd(this.makeContextComment({
|
|
@@ -3772,16 +4308,16 @@ var BaseOperationHandler = class {
|
|
|
3772
4308
|
if (_data.length === 0) {
|
|
3773
4309
|
return;
|
|
3774
4310
|
}
|
|
3775
|
-
|
|
4311
|
+
for (const { where, create } of _data) {
|
|
3776
4312
|
const existing = await this.exists(kysely, model, where);
|
|
3777
4313
|
if (existing) {
|
|
3778
|
-
|
|
4314
|
+
await this.connectRelation(kysely, model, [
|
|
3779
4315
|
where
|
|
3780
4316
|
], fromRelation);
|
|
3781
4317
|
} else {
|
|
3782
|
-
|
|
4318
|
+
await this.create(kysely, model, create, fromRelation);
|
|
3783
4319
|
}
|
|
3784
|
-
}
|
|
4320
|
+
}
|
|
3785
4321
|
}
|
|
3786
4322
|
async disconnectRelation(kysely, model, data, fromRelation) {
|
|
3787
4323
|
let disconnectConditions = [];
|
|
@@ -3804,19 +4340,18 @@ var BaseOperationHandler = class {
|
|
|
3804
4340
|
}
|
|
3805
4341
|
const m2m = getManyToManyRelation(this.schema, fromRelation.model, fromRelation.field);
|
|
3806
4342
|
if (m2m) {
|
|
3807
|
-
const
|
|
4343
|
+
for (const d of disconnectConditions) {
|
|
3808
4344
|
const ids = await this.getEntityIds(kysely, model, d);
|
|
3809
4345
|
if (!ids) {
|
|
3810
4346
|
return;
|
|
3811
4347
|
}
|
|
3812
|
-
|
|
3813
|
-
}
|
|
3814
|
-
await Promise.all(actions);
|
|
4348
|
+
await this.handleManyToManyRelation(kysely, "disconnect", fromRelation.model, fromRelation.field, fromRelation.ids, m2m.otherModel, m2m.otherField, ids, m2m.joinTable);
|
|
4349
|
+
}
|
|
3815
4350
|
} else {
|
|
3816
4351
|
const { ownedByModel, keyPairs } = getRelationForeignKeyFieldPairs(this.schema, fromRelation.model, fromRelation.field);
|
|
3817
|
-
const eb =
|
|
4352
|
+
const eb = expressionBuilder4();
|
|
3818
4353
|
if (ownedByModel) {
|
|
3819
|
-
|
|
4354
|
+
invariant9(disconnectConditions.length === 1, "only one entity can be disconnected");
|
|
3820
4355
|
const condition = disconnectConditions[0];
|
|
3821
4356
|
if (condition === true) {
|
|
3822
4357
|
for (const { fk } of keyPairs) {
|
|
@@ -3875,11 +4410,14 @@ var BaseOperationHandler = class {
|
|
|
3875
4410
|
const m2m = getManyToManyRelation(this.schema, fromRelation.model, fromRelation.field);
|
|
3876
4411
|
if (m2m) {
|
|
3877
4412
|
await this.resetManyToManyRelation(kysely, fromRelation.model, fromRelation.field, fromRelation.ids);
|
|
3878
|
-
const
|
|
4413
|
+
const results = [];
|
|
4414
|
+
for (const d of _data) {
|
|
3879
4415
|
const ids = await this.getEntityIds(kysely, model, d);
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
4416
|
+
if (!ids) {
|
|
4417
|
+
throw new NotFoundError(model);
|
|
4418
|
+
}
|
|
4419
|
+
results.push(await this.handleManyToManyRelation(kysely, "connect", fromRelation.model, fromRelation.field, fromRelation.ids, m2m.otherModel, m2m.otherField, ids, m2m.joinTable));
|
|
4420
|
+
}
|
|
3883
4421
|
if (_data.length > results.filter((r) => !!r).length) {
|
|
3884
4422
|
throw new NotFoundError(model);
|
|
3885
4423
|
}
|
|
@@ -3945,7 +4483,7 @@ var BaseOperationHandler = class {
|
|
|
3945
4483
|
if (m2m) {
|
|
3946
4484
|
deleteFromModel = model;
|
|
3947
4485
|
const fieldDef = this.requireField(fromRelation.model, fromRelation.field);
|
|
3948
|
-
|
|
4486
|
+
invariant9(fieldDef.relation?.opposite);
|
|
3949
4487
|
deleteResult = await this.delete(kysely, model, {
|
|
3950
4488
|
AND: [
|
|
3951
4489
|
{
|
|
@@ -3969,7 +4507,7 @@ var BaseOperationHandler = class {
|
|
|
3969
4507
|
throw new NotFoundError(fromRelation.model);
|
|
3970
4508
|
}
|
|
3971
4509
|
const fieldDef = this.requireField(fromRelation.model, fromRelation.field);
|
|
3972
|
-
|
|
4510
|
+
invariant9(fieldDef.relation?.opposite);
|
|
3973
4511
|
deleteResult = await this.delete(kysely, model, {
|
|
3974
4512
|
AND: [
|
|
3975
4513
|
// filter for parent
|
|
@@ -4099,7 +4637,7 @@ var BaseOperationHandler = class {
|
|
|
4099
4637
|
// reused the filter if it's a complete id filter (without extra fields)
|
|
4100
4638
|
// otherwise, read the entity by the filter
|
|
4101
4639
|
getEntityIds(kysely, model, uniqueFilter) {
|
|
4102
|
-
const idFields =
|
|
4640
|
+
const idFields = requireIdFields(this.schema, model);
|
|
4103
4641
|
if (
|
|
4104
4642
|
// all id fields are provided
|
|
4105
4643
|
idFields.every((f) => f in uniqueFilter && uniqueFilter[f] !== void 0) && // no non-id filter exists
|
|
@@ -4201,7 +4739,7 @@ var AggregateOperationHandler = class extends BaseOperationHandler {
|
|
|
4201
4739
|
if (field === "_all") {
|
|
4202
4740
|
query = query.select((eb) => eb.cast(eb.fn.countAll(), "integer").as(`_count._all`));
|
|
4203
4741
|
} else {
|
|
4204
|
-
query = query.select((eb) => eb.cast(eb.fn.count(
|
|
4742
|
+
query = query.select((eb) => eb.cast(eb.fn.count(sql6.ref(`$sub.${field}`)), "integer").as(`${key}.${field}`));
|
|
4205
4743
|
}
|
|
4206
4744
|
}
|
|
4207
4745
|
});
|
|
@@ -4216,7 +4754,7 @@ var AggregateOperationHandler = class extends BaseOperationHandler {
|
|
|
4216
4754
|
if (val === true) {
|
|
4217
4755
|
query = query.select((eb) => {
|
|
4218
4756
|
const fn = match10(key).with("_sum", () => eb.fn.sum).with("_avg", () => eb.fn.avg).with("_max", () => eb.fn.max).with("_min", () => eb.fn.min).exhaustive();
|
|
4219
|
-
return fn(
|
|
4757
|
+
return fn(sql6.ref(`$sub.${field}`)).as(`${key}.${field}`);
|
|
4220
4758
|
});
|
|
4221
4759
|
}
|
|
4222
4760
|
});
|
|
@@ -4263,7 +4801,7 @@ var AggregateOperationHandler = class extends BaseOperationHandler {
|
|
|
4263
4801
|
};
|
|
4264
4802
|
|
|
4265
4803
|
// src/client/crud/operations/count.ts
|
|
4266
|
-
import { sql as
|
|
4804
|
+
import { sql as sql7 } from "kysely";
|
|
4267
4805
|
var CountOperationHandler = class extends BaseOperationHandler {
|
|
4268
4806
|
static {
|
|
4269
4807
|
__name(this, "CountOperationHandler");
|
|
@@ -4287,7 +4825,7 @@ var CountOperationHandler = class extends BaseOperationHandler {
|
|
|
4287
4825
|
return subQuery.as(subQueryName);
|
|
4288
4826
|
});
|
|
4289
4827
|
if (parsedArgs?.select && typeof parsedArgs.select === "object") {
|
|
4290
|
-
query = query.select((eb) => Object.keys(parsedArgs.select).map((key) => key === "_all" ? eb.cast(eb.fn.countAll(), "integer").as("_all") : eb.cast(eb.fn.count(
|
|
4828
|
+
query = query.select((eb) => Object.keys(parsedArgs.select).map((key) => key === "_all" ? eb.cast(eb.fn.countAll(), "integer").as("_all") : eb.cast(eb.fn.count(sql7.ref(`${subQueryName}.${key}`)), "integer").as(key)));
|
|
4291
4829
|
const result = await this.executeQuery(this.kysely, query, "count");
|
|
4292
4830
|
return result.rows[0];
|
|
4293
4831
|
} else {
|
|
@@ -4323,7 +4861,7 @@ var CreateOperationHandler = class extends BaseOperationHandler {
|
|
|
4323
4861
|
});
|
|
4324
4862
|
});
|
|
4325
4863
|
if (!result && this.hasPolicyEnabled) {
|
|
4326
|
-
throw new RejectedByPolicyError(this.model, `result is not allowed to be read back`);
|
|
4864
|
+
throw new RejectedByPolicyError(this.model, RejectedByPolicyReason.CANNOT_READ_BACK, `result is not allowed to be read back`);
|
|
4327
4865
|
}
|
|
4328
4866
|
return result;
|
|
4329
4867
|
}
|
|
@@ -4369,15 +4907,15 @@ var DeleteOperationHandler = class extends BaseOperationHandler {
|
|
|
4369
4907
|
omit: args.omit,
|
|
4370
4908
|
where: args.where
|
|
4371
4909
|
});
|
|
4372
|
-
if (!existing) {
|
|
4373
|
-
throw new NotFoundError(this.model);
|
|
4374
|
-
}
|
|
4375
4910
|
await this.safeTransaction(async (tx) => {
|
|
4376
4911
|
const result = await this.delete(tx, this.model, args.where);
|
|
4377
4912
|
if (result.count === 0) {
|
|
4378
4913
|
throw new NotFoundError(this.model);
|
|
4379
4914
|
}
|
|
4380
4915
|
});
|
|
4916
|
+
if (!existing && this.hasPolicyEnabled) {
|
|
4917
|
+
throw new RejectedByPolicyError(this.model, RejectedByPolicyReason.CANNOT_READ_BACK, "result is not allowed to be read back");
|
|
4918
|
+
}
|
|
4381
4919
|
return existing;
|
|
4382
4920
|
}
|
|
4383
4921
|
async runDeleteMany(args) {
|
|
@@ -4411,7 +4949,7 @@ var FindOperationHandler = class extends BaseOperationHandler {
|
|
|
4411
4949
|
};
|
|
4412
4950
|
|
|
4413
4951
|
// src/client/crud/operations/group-by.ts
|
|
4414
|
-
import { expressionBuilder as
|
|
4952
|
+
import { expressionBuilder as expressionBuilder5 } from "kysely";
|
|
4415
4953
|
import { match as match13 } from "ts-pattern";
|
|
4416
4954
|
var GroupByOperationHandler = class extends BaseOperationHandler {
|
|
4417
4955
|
static {
|
|
@@ -4433,7 +4971,7 @@ var GroupByOperationHandler = class extends BaseOperationHandler {
|
|
|
4433
4971
|
subQuery = this.dialect.buildOrderBy(subQuery, this.model, this.model, void 0, skip !== void 0 || take !== void 0, negateOrderBy);
|
|
4434
4972
|
return subQuery.as("$sub");
|
|
4435
4973
|
});
|
|
4436
|
-
const fieldRef = /* @__PURE__ */ __name((field) => this.dialect.fieldRef(this.model, field,
|
|
4974
|
+
const fieldRef = /* @__PURE__ */ __name((field) => this.dialect.fieldRef(this.model, field, expressionBuilder5(), "$sub"), "fieldRef");
|
|
4437
4975
|
const bys = typeof parsedArgs.by === "string" ? [
|
|
4438
4976
|
parsedArgs.by
|
|
4439
4977
|
] : parsedArgs.by;
|
|
@@ -4548,7 +5086,7 @@ var UpdateOperationHandler = class extends BaseOperationHandler {
|
|
|
4548
5086
|
});
|
|
4549
5087
|
if (!readBackResult) {
|
|
4550
5088
|
if (this.hasPolicyEnabled) {
|
|
4551
|
-
throw new RejectedByPolicyError(this.model, "result is not allowed to be read back");
|
|
5089
|
+
throw new RejectedByPolicyError(this.model, RejectedByPolicyReason.CANNOT_READ_BACK, "result is not allowed to be read back");
|
|
4552
5090
|
} else {
|
|
4553
5091
|
return null;
|
|
4554
5092
|
}
|
|
@@ -4565,16 +5103,24 @@ var UpdateOperationHandler = class extends BaseOperationHandler {
|
|
|
4565
5103
|
if (!args) {
|
|
4566
5104
|
return [];
|
|
4567
5105
|
}
|
|
4568
|
-
|
|
4569
|
-
const
|
|
4570
|
-
|
|
5106
|
+
const { readBackResult, updateResult } = await this.safeTransaction(async (tx) => {
|
|
5107
|
+
const updateResult2 = await this.updateMany(tx, this.model, args.where, args.data, args.limit, true);
|
|
5108
|
+
const readBackResult2 = await this.read(tx, this.model, {
|
|
4571
5109
|
select: args.select,
|
|
4572
5110
|
omit: args.omit,
|
|
4573
5111
|
where: {
|
|
4574
|
-
OR:
|
|
5112
|
+
OR: updateResult2.map((item) => getIdValues(this.schema, this.model, item))
|
|
4575
5113
|
}
|
|
4576
5114
|
});
|
|
5115
|
+
return {
|
|
5116
|
+
readBackResult: readBackResult2,
|
|
5117
|
+
updateResult: updateResult2
|
|
5118
|
+
};
|
|
4577
5119
|
});
|
|
5120
|
+
if (readBackResult.length < updateResult.length && this.hasPolicyEnabled) {
|
|
5121
|
+
throw new RejectedByPolicyError(this.model, RejectedByPolicyReason.CANNOT_READ_BACK, "result is not allowed to be read back");
|
|
5122
|
+
}
|
|
5123
|
+
return readBackResult;
|
|
4578
5124
|
}
|
|
4579
5125
|
async runUpsert(args) {
|
|
4580
5126
|
const result = await this.safeTransaction(async (tx) => {
|
|
@@ -4590,18 +5136,32 @@ var UpdateOperationHandler = class extends BaseOperationHandler {
|
|
|
4590
5136
|
});
|
|
4591
5137
|
});
|
|
4592
5138
|
if (!result && this.hasPolicyEnabled) {
|
|
4593
|
-
throw new RejectedByPolicyError(this.model, "result is not allowed to be read back");
|
|
5139
|
+
throw new RejectedByPolicyError(this.model, RejectedByPolicyReason.CANNOT_READ_BACK, "result is not allowed to be read back");
|
|
4594
5140
|
}
|
|
4595
5141
|
return result;
|
|
4596
5142
|
}
|
|
4597
5143
|
};
|
|
4598
5144
|
|
|
4599
5145
|
// src/client/crud/validator.ts
|
|
4600
|
-
import { invariant as
|
|
4601
|
-
import
|
|
5146
|
+
import { invariant as invariant10 } from "@zenstackhq/common-helpers";
|
|
5147
|
+
import Decimal3 from "decimal.js";
|
|
4602
5148
|
import stableStringify from "json-stable-stringify";
|
|
4603
5149
|
import { match as match15, P as P2 } from "ts-pattern";
|
|
4604
5150
|
import { z } from "zod";
|
|
5151
|
+
|
|
5152
|
+
// src/utils/zod-utils.ts
|
|
5153
|
+
import { fromError as fromError3 } from "zod-validation-error/v3";
|
|
5154
|
+
import { fromError as fromError4 } from "zod-validation-error/v4";
|
|
5155
|
+
function formatError(error) {
|
|
5156
|
+
if ("_zod" in error) {
|
|
5157
|
+
return fromError4(error).toString();
|
|
5158
|
+
} else {
|
|
5159
|
+
return fromError3(error).toString();
|
|
5160
|
+
}
|
|
5161
|
+
}
|
|
5162
|
+
__name(formatError, "formatError");
|
|
5163
|
+
|
|
5164
|
+
// src/client/crud/validator.ts
|
|
4605
5165
|
var InputValidator = class {
|
|
4606
5166
|
static {
|
|
4607
5167
|
__name(this, "InputValidator");
|
|
@@ -4663,7 +5223,7 @@ var InputValidator = class {
|
|
|
4663
5223
|
}
|
|
4664
5224
|
const { error } = schema.safeParse(args);
|
|
4665
5225
|
if (error) {
|
|
4666
|
-
throw new InputValidationError(`Invalid ${operation} args: ${error
|
|
5226
|
+
throw new InputValidationError(`Invalid ${operation} args: ${formatError(error)}`, error);
|
|
4667
5227
|
}
|
|
4668
5228
|
return args;
|
|
4669
5229
|
}
|
|
@@ -4707,7 +5267,7 @@ var InputValidator = class {
|
|
|
4707
5267
|
z.bigint()
|
|
4708
5268
|
])).with("Decimal", () => z.union([
|
|
4709
5269
|
z.number(),
|
|
4710
|
-
z.instanceof(
|
|
5270
|
+
z.instanceof(Decimal3),
|
|
4711
5271
|
z.string()
|
|
4712
5272
|
])).with("DateTime", () => z.union([
|
|
4713
5273
|
z.date(),
|
|
@@ -4722,7 +5282,7 @@ var InputValidator = class {
|
|
|
4722
5282
|
return schema;
|
|
4723
5283
|
}
|
|
4724
5284
|
const typeDef = this.schema.typeDefs?.[type];
|
|
4725
|
-
|
|
5285
|
+
invariant10(typeDef, `Type definition "${type}" not found in schema`);
|
|
4726
5286
|
schema = z.object(Object.fromEntries(Object.entries(typeDef.fields).map(([field, def]) => {
|
|
4727
5287
|
let fieldSchema = this.makePrimitiveSchema(def.type);
|
|
4728
5288
|
if (def.array) {
|
|
@@ -4790,7 +5350,7 @@ var InputValidator = class {
|
|
|
4790
5350
|
for (const uniqueField of uniqueFields) {
|
|
4791
5351
|
if ("defs" in uniqueField) {
|
|
4792
5352
|
fields[uniqueField.name] = z.object(Object.fromEntries(Object.entries(uniqueField.defs).map(([key, def]) => {
|
|
4793
|
-
|
|
5353
|
+
invariant10(!def.relation, "unique field cannot be a relation");
|
|
4794
5354
|
let fieldSchema;
|
|
4795
5355
|
const enumDef = getEnum(this.schema, def.type);
|
|
4796
5356
|
if (enumDef) {
|
|
@@ -4996,9 +5556,16 @@ var InputValidator = class {
|
|
|
4996
5556
|
fields[field] = z.boolean().optional();
|
|
4997
5557
|
}
|
|
4998
5558
|
}
|
|
5559
|
+
const _countSchema = this.makeCountSelectionSchema(modelDef);
|
|
5560
|
+
if (_countSchema) {
|
|
5561
|
+
fields["_count"] = _countSchema;
|
|
5562
|
+
}
|
|
5563
|
+
return z.strictObject(fields);
|
|
5564
|
+
}
|
|
5565
|
+
makeCountSelectionSchema(modelDef) {
|
|
4999
5566
|
const toManyRelations = Object.values(modelDef.fields).filter((def) => def.relation && def.array);
|
|
5000
5567
|
if (toManyRelations.length > 0) {
|
|
5001
|
-
|
|
5568
|
+
return z.union([
|
|
5002
5569
|
z.literal(true),
|
|
5003
5570
|
z.strictObject({
|
|
5004
5571
|
select: z.strictObject(toManyRelations.reduce((acc, fieldDef) => ({
|
|
@@ -5012,8 +5579,9 @@ var InputValidator = class {
|
|
|
5012
5579
|
}), {}))
|
|
5013
5580
|
})
|
|
5014
5581
|
]).optional();
|
|
5582
|
+
} else {
|
|
5583
|
+
return void 0;
|
|
5015
5584
|
}
|
|
5016
|
-
return z.strictObject(fields);
|
|
5017
5585
|
}
|
|
5018
5586
|
makeRelationSelectIncludeSchema(fieldDef) {
|
|
5019
5587
|
let objSchema = z.strictObject({
|
|
@@ -5060,6 +5628,10 @@ var InputValidator = class {
|
|
|
5060
5628
|
fields[field] = this.makeRelationSelectIncludeSchema(fieldDef).optional();
|
|
5061
5629
|
}
|
|
5062
5630
|
}
|
|
5631
|
+
const _countSchema = this.makeCountSelectionSchema(modelDef);
|
|
5632
|
+
if (_countSchema) {
|
|
5633
|
+
fields["_count"] = _countSchema;
|
|
5634
|
+
}
|
|
5063
5635
|
return z.strictObject(fields);
|
|
5064
5636
|
}
|
|
5065
5637
|
makeOrderBySchema(model, withRelation, WithAggregation) {
|
|
@@ -5126,13 +5698,15 @@ var InputValidator = class {
|
|
|
5126
5698
|
// #region Create
|
|
5127
5699
|
makeCreateSchema(model) {
|
|
5128
5700
|
const dataSchema = this.makeCreateDataSchema(model, false);
|
|
5129
|
-
|
|
5701
|
+
let schema = z.strictObject({
|
|
5130
5702
|
data: dataSchema,
|
|
5131
5703
|
select: this.makeSelectSchema(model).optional(),
|
|
5132
5704
|
include: this.makeIncludeSchema(model).optional(),
|
|
5133
5705
|
omit: this.makeOmitSchema(model).optional()
|
|
5134
5706
|
});
|
|
5135
|
-
|
|
5707
|
+
schema = this.refineForSelectIncludeMutuallyExclusive(schema);
|
|
5708
|
+
schema = this.refineForSelectOmitMutuallyExclusive(schema);
|
|
5709
|
+
return schema;
|
|
5136
5710
|
}
|
|
5137
5711
|
makeCreateManySchema(model) {
|
|
5138
5712
|
return this.makeCreateManyDataSchema(model, []).optional();
|
|
@@ -5257,11 +5831,11 @@ var InputValidator = class {
|
|
|
5257
5831
|
fields["delete"] = this.makeDeleteRelationDataSchema(fieldType, array, true).optional();
|
|
5258
5832
|
}
|
|
5259
5833
|
fields["update"] = array ? this.orArray(z.strictObject({
|
|
5260
|
-
where: this.makeWhereSchema(fieldType, true),
|
|
5834
|
+
where: this.makeWhereSchema(fieldType, true).optional(),
|
|
5261
5835
|
data: this.makeUpdateDataSchema(fieldType, withoutFields)
|
|
5262
5836
|
}), true).optional() : z.union([
|
|
5263
5837
|
z.strictObject({
|
|
5264
|
-
where: this.makeWhereSchema(fieldType, true),
|
|
5838
|
+
where: this.makeWhereSchema(fieldType, true).optional(),
|
|
5265
5839
|
data: this.makeUpdateDataSchema(fieldType, withoutFields)
|
|
5266
5840
|
}),
|
|
5267
5841
|
this.makeUpdateDataSchema(fieldType, withoutFields)
|
|
@@ -5321,14 +5895,16 @@ var InputValidator = class {
|
|
|
5321
5895
|
// #endregion
|
|
5322
5896
|
// #region Update
|
|
5323
5897
|
makeUpdateSchema(model) {
|
|
5324
|
-
|
|
5898
|
+
let schema = z.strictObject({
|
|
5325
5899
|
where: this.makeWhereSchema(model, true),
|
|
5326
5900
|
data: this.makeUpdateDataSchema(model),
|
|
5327
5901
|
select: this.makeSelectSchema(model).optional(),
|
|
5328
5902
|
include: this.makeIncludeSchema(model).optional(),
|
|
5329
5903
|
omit: this.makeOmitSchema(model).optional()
|
|
5330
5904
|
});
|
|
5331
|
-
|
|
5905
|
+
schema = this.refineForSelectIncludeMutuallyExclusive(schema);
|
|
5906
|
+
schema = this.refineForSelectOmitMutuallyExclusive(schema);
|
|
5907
|
+
return schema;
|
|
5332
5908
|
}
|
|
5333
5909
|
makeUpdateManySchema(model) {
|
|
5334
5910
|
return z.strictObject({
|
|
@@ -5339,14 +5915,15 @@ var InputValidator = class {
|
|
|
5339
5915
|
}
|
|
5340
5916
|
makeUpdateManyAndReturnSchema(model) {
|
|
5341
5917
|
const base = this.makeUpdateManySchema(model);
|
|
5342
|
-
|
|
5918
|
+
let schema = base.extend({
|
|
5343
5919
|
select: this.makeSelectSchema(model).optional(),
|
|
5344
5920
|
omit: this.makeOmitSchema(model).optional()
|
|
5345
5921
|
});
|
|
5346
|
-
|
|
5922
|
+
schema = this.refineForSelectOmitMutuallyExclusive(schema);
|
|
5923
|
+
return schema;
|
|
5347
5924
|
}
|
|
5348
5925
|
makeUpsertSchema(model) {
|
|
5349
|
-
|
|
5926
|
+
let schema = z.strictObject({
|
|
5350
5927
|
where: this.makeWhereSchema(model, true),
|
|
5351
5928
|
create: this.makeCreateDataSchema(model, false),
|
|
5352
5929
|
update: this.makeUpdateDataSchema(model),
|
|
@@ -5354,7 +5931,9 @@ var InputValidator = class {
|
|
|
5354
5931
|
include: this.makeIncludeSchema(model).optional(),
|
|
5355
5932
|
omit: this.makeOmitSchema(model).optional()
|
|
5356
5933
|
});
|
|
5357
|
-
|
|
5934
|
+
schema = this.refineForSelectIncludeMutuallyExclusive(schema);
|
|
5935
|
+
schema = this.refineForSelectOmitMutuallyExclusive(schema);
|
|
5936
|
+
return schema;
|
|
5358
5937
|
}
|
|
5359
5938
|
makeUpdateDataSchema(model, withoutFields = [], withoutRelationFields = false) {
|
|
5360
5939
|
const uncheckedVariantFields = {};
|
|
@@ -5431,12 +6010,14 @@ var InputValidator = class {
|
|
|
5431
6010
|
// #endregion
|
|
5432
6011
|
// #region Delete
|
|
5433
6012
|
makeDeleteSchema(model) {
|
|
5434
|
-
|
|
6013
|
+
let schema = z.strictObject({
|
|
5435
6014
|
where: this.makeWhereSchema(model, true),
|
|
5436
6015
|
select: this.makeSelectSchema(model).optional(),
|
|
5437
6016
|
include: this.makeIncludeSchema(model).optional()
|
|
5438
6017
|
});
|
|
5439
|
-
|
|
6018
|
+
schema = this.refineForSelectIncludeMutuallyExclusive(schema);
|
|
6019
|
+
schema = this.refineForSelectOmitMutuallyExclusive(schema);
|
|
6020
|
+
return schema;
|
|
5440
6021
|
}
|
|
5441
6022
|
makeDeleteManySchema(model) {
|
|
5442
6023
|
return z.object({
|
|
@@ -5786,30 +6367,13 @@ function performanceNow() {
|
|
|
5786
6367
|
__name(performanceNow, "performanceNow");
|
|
5787
6368
|
|
|
5788
6369
|
// src/client/executor/zenstack-query-executor.ts
|
|
5789
|
-
import { invariant as
|
|
5790
|
-
import { AndNode as AndNode2, DefaultQueryExecutor, DeleteQueryNode as DeleteQueryNode2, InsertQueryNode as InsertQueryNode2, ReturningNode as ReturningNode2, SelectionNode as SelectionNode4, SingleConnectionProvider, TableNode as
|
|
6370
|
+
import { invariant as invariant12 } from "@zenstackhq/common-helpers";
|
|
6371
|
+
import { AndNode as AndNode2, DefaultQueryExecutor, DeleteQueryNode as DeleteQueryNode2, InsertQueryNode as InsertQueryNode2, ReturningNode as ReturningNode2, SelectionNode as SelectionNode4, SingleConnectionProvider, TableNode as TableNode6, UpdateQueryNode as UpdateQueryNode2, WhereNode as WhereNode3 } from "kysely";
|
|
5791
6372
|
import { match as match16 } from "ts-pattern";
|
|
5792
6373
|
|
|
5793
|
-
// src/client/executor/kysely-utils.ts
|
|
5794
|
-
import { AliasNode as AliasNode4 } from "kysely";
|
|
5795
|
-
function stripAlias(node) {
|
|
5796
|
-
if (AliasNode4.is(node)) {
|
|
5797
|
-
return {
|
|
5798
|
-
alias: node.alias,
|
|
5799
|
-
node: node.node
|
|
5800
|
-
};
|
|
5801
|
-
} else {
|
|
5802
|
-
return {
|
|
5803
|
-
alias: void 0,
|
|
5804
|
-
node
|
|
5805
|
-
};
|
|
5806
|
-
}
|
|
5807
|
-
}
|
|
5808
|
-
__name(stripAlias, "stripAlias");
|
|
5809
|
-
|
|
5810
6374
|
// src/client/executor/name-mapper.ts
|
|
5811
|
-
import { invariant as
|
|
5812
|
-
import { AliasNode as AliasNode5, ColumnNode as
|
|
6375
|
+
import { invariant as invariant11 } from "@zenstackhq/common-helpers";
|
|
6376
|
+
import { AliasNode as AliasNode5, ColumnNode as ColumnNode4, FromNode as FromNode3, IdentifierNode as IdentifierNode3, OperationNodeTransformer as OperationNodeTransformer2, ReferenceNode as ReferenceNode4, SelectAllNode, SelectionNode as SelectionNode3, TableNode as TableNode5 } from "kysely";
|
|
5813
6377
|
var QueryNameMapper = class extends OperationNodeTransformer2 {
|
|
5814
6378
|
static {
|
|
5815
6379
|
__name(this, "QueryNameMapper");
|
|
@@ -5878,7 +6442,7 @@ var QueryNameMapper = class extends OperationNodeTransformer2 {
|
|
|
5878
6442
|
};
|
|
5879
6443
|
}
|
|
5880
6444
|
transformReference(node) {
|
|
5881
|
-
if (!
|
|
6445
|
+
if (!ColumnNode4.is(node.column)) {
|
|
5882
6446
|
return super.transformReference(node);
|
|
5883
6447
|
}
|
|
5884
6448
|
const scope = this.resolveFieldFromScopes(node.column.column.name, node.table?.table.identifier.name);
|
|
@@ -5891,7 +6455,7 @@ var QueryNameMapper = class extends OperationNodeTransformer2 {
|
|
|
5891
6455
|
mappedTableName = this.mapTableName(scope.model);
|
|
5892
6456
|
}
|
|
5893
6457
|
}
|
|
5894
|
-
return
|
|
6458
|
+
return ReferenceNode4.create(ColumnNode4.create(mappedFieldName), mappedTableName ? TableNode5.create(mappedTableName) : void 0);
|
|
5895
6459
|
} else {
|
|
5896
6460
|
return super.transformReference(node);
|
|
5897
6461
|
}
|
|
@@ -5902,14 +6466,14 @@ var QueryNameMapper = class extends OperationNodeTransformer2 {
|
|
|
5902
6466
|
return super.transformColumn(node);
|
|
5903
6467
|
}
|
|
5904
6468
|
const mappedName = this.mapFieldName(scope.model, node.column.name);
|
|
5905
|
-
return
|
|
6469
|
+
return ColumnNode4.create(mappedName);
|
|
5906
6470
|
}
|
|
5907
6471
|
transformUpdateQuery(node) {
|
|
5908
6472
|
if (!node.table) {
|
|
5909
6473
|
return super.transformUpdateQuery(node);
|
|
5910
6474
|
}
|
|
5911
6475
|
const { alias, node: innerTable } = stripAlias(node.table);
|
|
5912
|
-
if (!innerTable || !
|
|
6476
|
+
if (!innerTable || !TableNode5.is(innerTable)) {
|
|
5913
6477
|
return super.transformUpdateQuery(node);
|
|
5914
6478
|
}
|
|
5915
6479
|
return this.withScope({
|
|
@@ -5927,14 +6491,14 @@ var QueryNameMapper = class extends OperationNodeTransformer2 {
|
|
|
5927
6491
|
const scopes = node.from.froms.map((node2) => {
|
|
5928
6492
|
const { alias, node: innerNode } = stripAlias(node2);
|
|
5929
6493
|
return {
|
|
5930
|
-
model:
|
|
6494
|
+
model: extractModelName(innerNode),
|
|
5931
6495
|
alias,
|
|
5932
6496
|
namesMapped: false
|
|
5933
6497
|
};
|
|
5934
6498
|
});
|
|
5935
6499
|
const froms = node.from.froms.map((from) => {
|
|
5936
6500
|
const { alias, node: innerNode } = stripAlias(from);
|
|
5937
|
-
if (
|
|
6501
|
+
if (TableNode5.is(innerNode)) {
|
|
5938
6502
|
return this.wrapAlias(this.processTableRef(innerNode), alias);
|
|
5939
6503
|
} else {
|
|
5940
6504
|
return super.transformNode(from);
|
|
@@ -5959,13 +6523,13 @@ var QueryNameMapper = class extends OperationNodeTransformer2 {
|
|
|
5959
6523
|
} else {
|
|
5960
6524
|
selections.push(super.transformSelection(selection));
|
|
5961
6525
|
}
|
|
5962
|
-
} else if (
|
|
6526
|
+
} else if (ReferenceNode4.is(selection.selection) || ColumnNode4.is(selection.selection)) {
|
|
5963
6527
|
const transformed = this.transformNode(selection.selection);
|
|
5964
6528
|
if (AliasNode5.is(transformed)) {
|
|
5965
6529
|
selections.push(SelectionNode3.create(transformed));
|
|
5966
6530
|
} else {
|
|
5967
|
-
const origFieldName =
|
|
5968
|
-
const fieldName =
|
|
6531
|
+
const origFieldName = extractFieldName(selection.selection);
|
|
6532
|
+
const fieldName = extractFieldName(transformed);
|
|
5969
6533
|
if (fieldName !== origFieldName) {
|
|
5970
6534
|
selections.push(SelectionNode3.create(this.wrapAlias(transformed, origFieldName ? IdentifierNode3.create(origFieldName) : void 0)));
|
|
5971
6535
|
} else {
|
|
@@ -6035,10 +6599,10 @@ var QueryNameMapper = class extends OperationNodeTransformer2 {
|
|
|
6035
6599
|
if (!node) {
|
|
6036
6600
|
return node;
|
|
6037
6601
|
}
|
|
6038
|
-
if (!
|
|
6602
|
+
if (!TableNode5.is(node)) {
|
|
6039
6603
|
return super.transformNode(node);
|
|
6040
6604
|
}
|
|
6041
|
-
return
|
|
6605
|
+
return TableNode5.create(this.mapTableName(node.table.identifier.name));
|
|
6042
6606
|
}
|
|
6043
6607
|
getMappedName(def) {
|
|
6044
6608
|
const mapAttr = def.attributes?.find((attr) => attr.name === "@@map" || attr.name === "@map");
|
|
@@ -6074,12 +6638,12 @@ var QueryNameMapper = class extends OperationNodeTransformer2 {
|
|
|
6074
6638
|
// convert a "from" node to a nested query if there are columns with name mapping
|
|
6075
6639
|
processSelectTable(node) {
|
|
6076
6640
|
const { alias, node: innerNode } = stripAlias(node);
|
|
6077
|
-
if (innerNode &&
|
|
6641
|
+
if (innerNode && TableNode5.is(innerNode)) {
|
|
6078
6642
|
const modelName = innerNode.table.identifier.name;
|
|
6079
6643
|
const mappedName = this.mapTableName(modelName);
|
|
6080
6644
|
const finalAlias = alias ?? (mappedName !== modelName ? IdentifierNode3.create(modelName) : void 0);
|
|
6081
6645
|
return {
|
|
6082
|
-
node: this.wrapAlias(
|
|
6646
|
+
node: this.wrapAlias(TableNode5.create(mappedName), finalAlias),
|
|
6083
6647
|
scope: {
|
|
6084
6648
|
alias: alias ?? IdentifierNode3.create(modelName),
|
|
6085
6649
|
model: modelName,
|
|
@@ -6101,7 +6665,7 @@ var QueryNameMapper = class extends OperationNodeTransformer2 {
|
|
|
6101
6665
|
const modelDef = requireModel(this.schema, model);
|
|
6102
6666
|
return this.getModelFields(modelDef).map((fieldDef) => {
|
|
6103
6667
|
const columnName = this.mapFieldName(model, fieldDef.name);
|
|
6104
|
-
const columnRef =
|
|
6668
|
+
const columnRef = ReferenceNode4.create(ColumnNode4.create(columnName), alias && IdentifierNode3.is(alias) ? TableNode5.create(alias.name) : void 0);
|
|
6105
6669
|
if (columnName !== fieldDef.name) {
|
|
6106
6670
|
const aliased = AliasNode5.create(columnRef, IdentifierNode3.create(fieldDef.name));
|
|
6107
6671
|
return SelectionNode3.create(aliased);
|
|
@@ -6132,37 +6696,24 @@ var QueryNameMapper = class extends OperationNodeTransformer2 {
|
|
|
6132
6696
|
processSelection(node) {
|
|
6133
6697
|
let alias;
|
|
6134
6698
|
if (!AliasNode5.is(node)) {
|
|
6135
|
-
alias =
|
|
6699
|
+
alias = extractFieldName(node);
|
|
6136
6700
|
}
|
|
6137
6701
|
const result = super.transformNode(node);
|
|
6138
6702
|
return this.wrapAlias(result, alias ? IdentifierNode3.create(alias) : void 0);
|
|
6139
6703
|
}
|
|
6140
6704
|
processSelectAll(node) {
|
|
6141
6705
|
const scope = this.scopes[this.scopes.length - 1];
|
|
6142
|
-
|
|
6706
|
+
invariant11(scope);
|
|
6143
6707
|
if (!scope.model || !this.hasMappedColumns(scope.model)) {
|
|
6144
6708
|
return super.transformSelectAll(node);
|
|
6145
6709
|
}
|
|
6146
6710
|
const modelDef = requireModel(this.schema, scope.model);
|
|
6147
6711
|
return this.getModelFields(modelDef).map((fieldDef) => {
|
|
6148
6712
|
const columnName = this.mapFieldName(modelDef.name, fieldDef.name);
|
|
6149
|
-
const columnRef =
|
|
6713
|
+
const columnRef = ReferenceNode4.create(ColumnNode4.create(columnName));
|
|
6150
6714
|
return columnName !== fieldDef.name ? this.wrapAlias(columnRef, IdentifierNode3.create(fieldDef.name)) : columnRef;
|
|
6151
6715
|
});
|
|
6152
6716
|
}
|
|
6153
|
-
extractModelName(node) {
|
|
6154
|
-
const { node: innerNode } = stripAlias(node);
|
|
6155
|
-
return TableNode4.is(innerNode) ? innerNode.table.identifier.name : void 0;
|
|
6156
|
-
}
|
|
6157
|
-
extractFieldName(node) {
|
|
6158
|
-
if (ReferenceNode3.is(node) && ColumnNode3.is(node.column)) {
|
|
6159
|
-
return node.column.column.name;
|
|
6160
|
-
} else if (ColumnNode3.is(node)) {
|
|
6161
|
-
return node.column.name;
|
|
6162
|
-
} else {
|
|
6163
|
-
return void 0;
|
|
6164
|
-
}
|
|
6165
|
-
}
|
|
6166
6717
|
};
|
|
6167
6718
|
|
|
6168
6719
|
// src/client/executor/zenstack-query-executor.ts
|
|
@@ -6367,17 +6918,17 @@ var ZenStackQueryExecutor = class _ZenStackQueryExecutor extends DefaultQueryExe
|
|
|
6367
6918
|
}
|
|
6368
6919
|
getMutationModel(queryNode) {
|
|
6369
6920
|
return match16(queryNode).when(InsertQueryNode2.is, (node) => {
|
|
6370
|
-
|
|
6921
|
+
invariant12(node.into, "InsertQueryNode must have an into clause");
|
|
6371
6922
|
return node.into.table.identifier.name;
|
|
6372
6923
|
}).when(UpdateQueryNode2.is, (node) => {
|
|
6373
|
-
|
|
6924
|
+
invariant12(node.table, "UpdateQueryNode must have a table");
|
|
6374
6925
|
const { node: tableNode } = stripAlias(node.table);
|
|
6375
|
-
|
|
6926
|
+
invariant12(TableNode6.is(tableNode), "UpdateQueryNode must use a TableNode");
|
|
6376
6927
|
return tableNode.table.identifier.name;
|
|
6377
6928
|
}).when(DeleteQueryNode2.is, (node) => {
|
|
6378
|
-
|
|
6929
|
+
invariant12(node.from.froms.length === 1, "Delete query must have exactly one from table");
|
|
6379
6930
|
const { node: tableNode } = stripAlias(node.from.froms[0]);
|
|
6380
|
-
|
|
6931
|
+
invariant12(TableNode6.is(tableNode), "DeleteQueryNode must use a TableNode");
|
|
6381
6932
|
return tableNode.table.identifier.name;
|
|
6382
6933
|
}).otherwise((node) => {
|
|
6383
6934
|
throw new InternalError(`Invalid query node: ${node}`);
|
|
@@ -6475,53 +7026,58 @@ __export(functions_exports, {
|
|
|
6475
7026
|
search: () => search,
|
|
6476
7027
|
startsWith: () => startsWith
|
|
6477
7028
|
});
|
|
6478
|
-
import { invariant as
|
|
6479
|
-
import { sql as
|
|
7029
|
+
import { invariant as invariant13, lowerCaseFirst, upperCaseFirst } from "@zenstackhq/common-helpers";
|
|
7030
|
+
import { sql as sql8, ValueNode as ValueNode5 } from "kysely";
|
|
6480
7031
|
import { match as match17 } from "ts-pattern";
|
|
6481
|
-
var contains = /* @__PURE__ */ __name((eb, args) =>
|
|
6482
|
-
const [field, search2, caseInsensitive = false] = args;
|
|
6483
|
-
if (!field) {
|
|
6484
|
-
throw new Error('"field" parameter is required');
|
|
6485
|
-
}
|
|
6486
|
-
if (!search2) {
|
|
6487
|
-
throw new Error('"search" parameter is required');
|
|
6488
|
-
}
|
|
6489
|
-
const searchExpr = eb.fn("CONCAT", [
|
|
6490
|
-
sql7.lit("%"),
|
|
6491
|
-
search2,
|
|
6492
|
-
sql7.lit("%")
|
|
6493
|
-
]);
|
|
6494
|
-
return eb(field, caseInsensitive ? "ilike" : "like", searchExpr);
|
|
6495
|
-
}, "contains");
|
|
7032
|
+
var contains = /* @__PURE__ */ __name((eb, args, context) => textMatch(eb, args, context, "contains"), "contains");
|
|
6496
7033
|
var search = /* @__PURE__ */ __name((_eb, _args) => {
|
|
6497
7034
|
throw new Error(`"search" function is not implemented yet`);
|
|
6498
7035
|
}, "search");
|
|
6499
|
-
var startsWith = /* @__PURE__ */ __name((eb, args) =>
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
}
|
|
6504
|
-
if (!search2) {
|
|
6505
|
-
throw new Error('"search" parameter is required');
|
|
6506
|
-
}
|
|
6507
|
-
return eb(field, "like", eb.fn("CONCAT", [
|
|
6508
|
-
search2,
|
|
6509
|
-
sql7.lit("%")
|
|
6510
|
-
]));
|
|
6511
|
-
}, "startsWith");
|
|
6512
|
-
var endsWith = /* @__PURE__ */ __name((eb, args) => {
|
|
6513
|
-
const [field, search2] = args;
|
|
7036
|
+
var startsWith = /* @__PURE__ */ __name((eb, args, context) => textMatch(eb, args, context, "startsWith"), "startsWith");
|
|
7037
|
+
var endsWith = /* @__PURE__ */ __name((eb, args, context) => textMatch(eb, args, context, "endsWith"), "endsWith");
|
|
7038
|
+
var textMatch = /* @__PURE__ */ __name((eb, args, { dialect }, method) => {
|
|
7039
|
+
const [field, search2, caseInsensitive = void 0] = args;
|
|
6514
7040
|
if (!field) {
|
|
6515
7041
|
throw new Error('"field" parameter is required');
|
|
6516
7042
|
}
|
|
6517
7043
|
if (!search2) {
|
|
6518
7044
|
throw new Error('"search" parameter is required');
|
|
6519
7045
|
}
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
7046
|
+
const casingBehavior = dialect.getStringCasingBehavior();
|
|
7047
|
+
const caseInsensitiveValue = readBoolean(caseInsensitive, false);
|
|
7048
|
+
let op;
|
|
7049
|
+
let fieldExpr = field;
|
|
7050
|
+
let searchExpr = search2;
|
|
7051
|
+
if (caseInsensitiveValue) {
|
|
7052
|
+
if (casingBehavior.supportsILike) {
|
|
7053
|
+
op = "ilike";
|
|
7054
|
+
} else {
|
|
7055
|
+
op = "like";
|
|
7056
|
+
if (casingBehavior.likeCaseSensitive === true) {
|
|
7057
|
+
fieldExpr = eb.fn("LOWER", [
|
|
7058
|
+
fieldExpr
|
|
7059
|
+
]);
|
|
7060
|
+
searchExpr = eb.fn("LOWER", [
|
|
7061
|
+
searchExpr
|
|
7062
|
+
]);
|
|
7063
|
+
}
|
|
7064
|
+
}
|
|
7065
|
+
} else {
|
|
7066
|
+
op = "like";
|
|
7067
|
+
}
|
|
7068
|
+
searchExpr = match17(method).with("contains", () => eb.fn("CONCAT", [
|
|
7069
|
+
sql8.lit("%"),
|
|
7070
|
+
sql8`CAST(${searchExpr} as text)`,
|
|
7071
|
+
sql8.lit("%")
|
|
7072
|
+
])).with("startsWith", () => eb.fn("CONCAT", [
|
|
7073
|
+
sql8`CAST(${searchExpr} as text)`,
|
|
7074
|
+
sql8.lit("%")
|
|
7075
|
+
])).with("endsWith", () => eb.fn("CONCAT", [
|
|
7076
|
+
sql8.lit("%"),
|
|
7077
|
+
sql8`CAST(${searchExpr} as text)`
|
|
7078
|
+
])).exhaustive();
|
|
7079
|
+
return eb(fieldExpr, op, searchExpr);
|
|
7080
|
+
}, "textMatch");
|
|
6525
7081
|
var has = /* @__PURE__ */ __name((eb, args) => {
|
|
6526
7082
|
const [field, search2] = args;
|
|
6527
7083
|
if (!field) {
|
|
@@ -6559,18 +7115,16 @@ var isEmpty = /* @__PURE__ */ __name((eb, args, { dialect }) => {
|
|
|
6559
7115
|
if (!field) {
|
|
6560
7116
|
throw new Error('"field" parameter is required');
|
|
6561
7117
|
}
|
|
6562
|
-
return eb(dialect.buildArrayLength(eb, field), "=",
|
|
7118
|
+
return eb(dialect.buildArrayLength(eb, field), "=", sql8.lit(0));
|
|
6563
7119
|
}, "isEmpty");
|
|
6564
|
-
var now = /* @__PURE__ */ __name((
|
|
6565
|
-
return match17(dialect.provider).with("postgresql", () => eb.fn("now")).with("sqlite", () => sql7.raw("CURRENT_TIMESTAMP")).exhaustive();
|
|
6566
|
-
}, "now");
|
|
7120
|
+
var now = /* @__PURE__ */ __name(() => sql8.raw("CURRENT_TIMESTAMP"), "now");
|
|
6567
7121
|
var currentModel = /* @__PURE__ */ __name((_eb, args, { model }) => {
|
|
6568
7122
|
let result = model;
|
|
6569
7123
|
const [casing] = args;
|
|
6570
7124
|
if (casing) {
|
|
6571
7125
|
result = processCasing(casing, result, model);
|
|
6572
7126
|
}
|
|
6573
|
-
return
|
|
7127
|
+
return sql8.lit(result);
|
|
6574
7128
|
}, "currentModel");
|
|
6575
7129
|
var currentOperation = /* @__PURE__ */ __name((_eb, args, { operation }) => {
|
|
6576
7130
|
let result = operation;
|
|
@@ -6578,21 +7132,30 @@ var currentOperation = /* @__PURE__ */ __name((_eb, args, { operation }) => {
|
|
|
6578
7132
|
if (casing) {
|
|
6579
7133
|
result = processCasing(casing, result, operation);
|
|
6580
7134
|
}
|
|
6581
|
-
return
|
|
7135
|
+
return sql8.lit(result);
|
|
6582
7136
|
}, "currentOperation");
|
|
6583
7137
|
function processCasing(casing, result, model) {
|
|
6584
7138
|
const opNode = casing.toOperationNode();
|
|
6585
|
-
|
|
7139
|
+
invariant13(ValueNode5.is(opNode) && typeof opNode.value === "string", '"casting" parameter must be a string value');
|
|
6586
7140
|
result = match17(opNode.value).with("original", () => model).with("upper", () => result.toUpperCase()).with("lower", () => result.toLowerCase()).with("capitalize", () => upperCaseFirst(result)).with("uncapitalize", () => lowerCaseFirst(result)).otherwise(() => {
|
|
6587
7141
|
throw new Error(`Invalid casing value: ${opNode.value}. Must be "original", "upper", "lower", "capitalize", or "uncapitalize".`);
|
|
6588
7142
|
});
|
|
6589
7143
|
return result;
|
|
6590
7144
|
}
|
|
6591
7145
|
__name(processCasing, "processCasing");
|
|
7146
|
+
function readBoolean(expr2, defaultValue) {
|
|
7147
|
+
if (expr2 === void 0) {
|
|
7148
|
+
return defaultValue;
|
|
7149
|
+
}
|
|
7150
|
+
const opNode = expr2.toOperationNode();
|
|
7151
|
+
invariant13(ValueNode5.is(opNode), "expression must be a literal value");
|
|
7152
|
+
return !!opNode.value;
|
|
7153
|
+
}
|
|
7154
|
+
__name(readBoolean, "readBoolean");
|
|
6592
7155
|
|
|
6593
7156
|
// src/client/helpers/schema-db-pusher.ts
|
|
6594
|
-
import { invariant as
|
|
6595
|
-
import { sql as
|
|
7157
|
+
import { invariant as invariant14 } from "@zenstackhq/common-helpers";
|
|
7158
|
+
import { sql as sql9 } from "kysely";
|
|
6596
7159
|
import toposort from "toposort";
|
|
6597
7160
|
import { match as match18 } from "ts-pattern";
|
|
6598
7161
|
var SchemaDbPusher = class {
|
|
@@ -6613,7 +7176,8 @@ var SchemaDbPusher = class {
|
|
|
6613
7176
|
await createEnum.execute();
|
|
6614
7177
|
}
|
|
6615
7178
|
}
|
|
6616
|
-
const
|
|
7179
|
+
const models = Object.values(this.schema.models).filter((m) => !m.isView);
|
|
7180
|
+
const sortedModels = this.sortModels(models);
|
|
6617
7181
|
for (const modelDef of sortedModels) {
|
|
6618
7182
|
const createTable = this.createModelTable(tx, modelDef);
|
|
6619
7183
|
await createTable.execute();
|
|
@@ -6622,7 +7186,7 @@ var SchemaDbPusher = class {
|
|
|
6622
7186
|
}
|
|
6623
7187
|
sortModels(models) {
|
|
6624
7188
|
const graph = [];
|
|
6625
|
-
for (const model of
|
|
7189
|
+
for (const model of models) {
|
|
6626
7190
|
let added = false;
|
|
6627
7191
|
if (model.baseModel) {
|
|
6628
7192
|
const baseDef = requireModel(this.schema, model.baseModel);
|
|
@@ -6707,7 +7271,7 @@ var SchemaDbPusher = class {
|
|
|
6707
7271
|
}
|
|
6708
7272
|
addUniqueConstraint(table, modelDef) {
|
|
6709
7273
|
for (const [key, value] of Object.entries(modelDef.uniqueFields)) {
|
|
6710
|
-
|
|
7274
|
+
invariant14(typeof value === "object", "expecting an object");
|
|
6711
7275
|
if ("type" in value) {
|
|
6712
7276
|
const fieldDef = modelDef.fields[key];
|
|
6713
7277
|
if (fieldDef.unique) {
|
|
@@ -6730,7 +7294,7 @@ var SchemaDbPusher = class {
|
|
|
6730
7294
|
if (fieldDef.default !== void 0) {
|
|
6731
7295
|
if (typeof fieldDef.default === "object" && "kind" in fieldDef.default) {
|
|
6732
7296
|
if (ExpressionUtils.isCall(fieldDef.default) && fieldDef.default.function === "now") {
|
|
6733
|
-
col = col.defaultTo(
|
|
7297
|
+
col = col.defaultTo(sql9`CURRENT_TIMESTAMP`);
|
|
6734
7298
|
}
|
|
6735
7299
|
} else {
|
|
6736
7300
|
col = col.defaultTo(fieldDef.default);
|
|
@@ -6750,7 +7314,7 @@ var SchemaDbPusher = class {
|
|
|
6750
7314
|
}
|
|
6751
7315
|
mapFieldType(fieldDef) {
|
|
6752
7316
|
if (this.schema.enums?.[fieldDef.type]) {
|
|
6753
|
-
return this.schema.provider.type === "postgresql" ?
|
|
7317
|
+
return this.schema.provider.type === "postgresql" ? sql9.ref(fieldDef.type) : "text";
|
|
6754
7318
|
}
|
|
6755
7319
|
if (this.isAutoIncrement(fieldDef) && this.schema.provider.type === "postgresql") {
|
|
6756
7320
|
return "serial";
|
|
@@ -6763,7 +7327,7 @@ var SchemaDbPusher = class {
|
|
|
6763
7327
|
throw new Error(`Unsupported field type: ${type}`);
|
|
6764
7328
|
});
|
|
6765
7329
|
if (fieldDef.array) {
|
|
6766
|
-
return
|
|
7330
|
+
return sql9.raw(`${result}[]`);
|
|
6767
7331
|
} else {
|
|
6768
7332
|
return result;
|
|
6769
7333
|
}
|
|
@@ -6775,7 +7339,7 @@ var SchemaDbPusher = class {
|
|
|
6775
7339
|
return fieldDef.default && ExpressionUtils.isCall(fieldDef.default) && fieldDef.default.function === "autoincrement";
|
|
6776
7340
|
}
|
|
6777
7341
|
addForeignKeyConstraint(table, model, fieldName, fieldDef) {
|
|
6778
|
-
|
|
7342
|
+
invariant14(fieldDef.relation, "field must be a relation");
|
|
6779
7343
|
if (!fieldDef.relation.fields || !fieldDef.relation.references) {
|
|
6780
7344
|
return table;
|
|
6781
7345
|
}
|
|
@@ -6832,16 +7396,15 @@ function valueToPromise(thing) {
|
|
|
6832
7396
|
__name(valueToPromise, "valueToPromise");
|
|
6833
7397
|
|
|
6834
7398
|
// src/client/result-processor.ts
|
|
6835
|
-
import { invariant as invariant13 } from "@zenstackhq/common-helpers";
|
|
6836
|
-
import Decimal2 from "decimal.js";
|
|
6837
|
-
import { match as match19 } from "ts-pattern";
|
|
6838
7399
|
var ResultProcessor = class {
|
|
6839
7400
|
static {
|
|
6840
7401
|
__name(this, "ResultProcessor");
|
|
6841
7402
|
}
|
|
6842
7403
|
schema;
|
|
6843
|
-
|
|
7404
|
+
dialect;
|
|
7405
|
+
constructor(schema, options) {
|
|
6844
7406
|
this.schema = schema;
|
|
7407
|
+
this.dialect = getCrudDialect(schema, options);
|
|
6845
7408
|
}
|
|
6846
7409
|
processResult(data, model, args) {
|
|
6847
7410
|
const result = this.doProcessResult(data, model);
|
|
@@ -6870,7 +7433,7 @@ var ResultProcessor = class {
|
|
|
6870
7433
|
}
|
|
6871
7434
|
if (key.startsWith(DELEGATE_JOINED_FIELD_PREFIX)) {
|
|
6872
7435
|
if (value) {
|
|
6873
|
-
const subRow = this.
|
|
7436
|
+
const subRow = this.dialect.transformOutput(value, "Json");
|
|
6874
7437
|
const subModel = key.slice(DELEGATE_JOINED_FIELD_PREFIX.length);
|
|
6875
7438
|
const idValues = getIdValues(this.schema, subModel, subRow);
|
|
6876
7439
|
if (Object.values(idValues).some((v) => v === null || v === void 0)) {
|
|
@@ -6904,10 +7467,10 @@ var ResultProcessor = class {
|
|
|
6904
7467
|
processFieldValue(value, fieldDef) {
|
|
6905
7468
|
const type = fieldDef.type;
|
|
6906
7469
|
if (Array.isArray(value)) {
|
|
6907
|
-
value.forEach((v, i) => value[i] = this.
|
|
7470
|
+
value.forEach((v, i) => value[i] = this.dialect.transformOutput(v, type));
|
|
6908
7471
|
return value;
|
|
6909
7472
|
} else {
|
|
6910
|
-
return this.
|
|
7473
|
+
return this.dialect.transformOutput(value, type);
|
|
6911
7474
|
}
|
|
6912
7475
|
}
|
|
6913
7476
|
processRelation(value, fieldDef) {
|
|
@@ -6921,42 +7484,6 @@ var ResultProcessor = class {
|
|
|
6921
7484
|
}
|
|
6922
7485
|
return this.doProcessResult(relationData, fieldDef.type);
|
|
6923
7486
|
}
|
|
6924
|
-
transformScalar(value, type) {
|
|
6925
|
-
if (this.schema.typeDefs && type in this.schema.typeDefs) {
|
|
6926
|
-
return this.transformJson(value);
|
|
6927
|
-
} else {
|
|
6928
|
-
return match19(type).with("Boolean", () => this.transformBoolean(value)).with("DateTime", () => this.transformDate(value)).with("Bytes", () => this.transformBytes(value)).with("Decimal", () => this.transformDecimal(value)).with("BigInt", () => this.transformBigInt(value)).with("Json", () => this.transformJson(value)).otherwise(() => value);
|
|
6929
|
-
}
|
|
6930
|
-
}
|
|
6931
|
-
transformDecimal(value) {
|
|
6932
|
-
if (value instanceof Decimal2) {
|
|
6933
|
-
return value;
|
|
6934
|
-
}
|
|
6935
|
-
invariant13(typeof value === "string" || typeof value === "number" || value instanceof Decimal2, `Expected string, number or Decimal, got ${typeof value}`);
|
|
6936
|
-
return new Decimal2(value);
|
|
6937
|
-
}
|
|
6938
|
-
transformBigInt(value) {
|
|
6939
|
-
if (typeof value === "bigint") {
|
|
6940
|
-
return value;
|
|
6941
|
-
}
|
|
6942
|
-
invariant13(typeof value === "string" || typeof value === "number", `Expected string or number, got ${typeof value}`);
|
|
6943
|
-
return BigInt(value);
|
|
6944
|
-
}
|
|
6945
|
-
transformBoolean(value) {
|
|
6946
|
-
return !!value;
|
|
6947
|
-
}
|
|
6948
|
-
transformDate(value) {
|
|
6949
|
-
if (typeof value === "number") {
|
|
6950
|
-
return new Date(value);
|
|
6951
|
-
} else if (typeof value === "string") {
|
|
6952
|
-
return new Date(Date.parse(value));
|
|
6953
|
-
} else {
|
|
6954
|
-
return value;
|
|
6955
|
-
}
|
|
6956
|
-
}
|
|
6957
|
-
transformBytes(value) {
|
|
6958
|
-
return Buffer.isBuffer(value) ? Uint8Array.from(value) : value;
|
|
6959
|
-
}
|
|
6960
7487
|
fixReversedResult(data, model, args) {
|
|
6961
7488
|
if (!data) {
|
|
6962
7489
|
return;
|
|
@@ -6981,12 +7508,6 @@ var ResultProcessor = class {
|
|
|
6981
7508
|
}
|
|
6982
7509
|
}
|
|
6983
7510
|
}
|
|
6984
|
-
transformJson(value) {
|
|
6985
|
-
return match19(this.schema.provider.type).with("sqlite", () => {
|
|
6986
|
-
invariant13(typeof value === "string", "Expected string, got " + typeof value);
|
|
6987
|
-
return JSON.parse(value);
|
|
6988
|
-
}).otherwise(() => value);
|
|
6989
|
-
}
|
|
6990
7511
|
};
|
|
6991
7512
|
|
|
6992
7513
|
// src/client/client-impl.ts
|
|
@@ -7009,7 +7530,7 @@ var ClientImpl = class _ClientImpl {
|
|
|
7009
7530
|
this.schema = schema;
|
|
7010
7531
|
this.options = options;
|
|
7011
7532
|
this.$schema = schema;
|
|
7012
|
-
this.$options = options
|
|
7533
|
+
this.$options = options;
|
|
7013
7534
|
this.$options.functions = {
|
|
7014
7535
|
...functions_exports,
|
|
7015
7536
|
...this.$options.functions
|
|
@@ -7060,7 +7581,7 @@ var ClientImpl = class _ClientImpl {
|
|
|
7060
7581
|
}
|
|
7061
7582
|
// implementation
|
|
7062
7583
|
async $transaction(input, options) {
|
|
7063
|
-
|
|
7584
|
+
invariant15(typeof input === "function" || Array.isArray(input) && input.every((p) => p.then && p.cb), "Invalid transaction input, expected a function or an array of ZenStackPromise");
|
|
7064
7585
|
if (typeof input === "function") {
|
|
7065
7586
|
return this.interactiveTransaction(input, options);
|
|
7066
7587
|
} else {
|
|
@@ -7176,7 +7697,7 @@ var ClientImpl = class _ClientImpl {
|
|
|
7176
7697
|
}
|
|
7177
7698
|
$executeRaw(query, ...values) {
|
|
7178
7699
|
return createZenStackPromise(async () => {
|
|
7179
|
-
const result = await
|
|
7700
|
+
const result = await sql10(query, ...values).execute(this.kysely);
|
|
7180
7701
|
return Number(result.numAffectedRows ?? 0);
|
|
7181
7702
|
});
|
|
7182
7703
|
}
|
|
@@ -7189,7 +7710,7 @@ var ClientImpl = class _ClientImpl {
|
|
|
7189
7710
|
}
|
|
7190
7711
|
$queryRaw(query, ...values) {
|
|
7191
7712
|
return createZenStackPromise(async () => {
|
|
7192
|
-
const result = await
|
|
7713
|
+
const result = await sql10(query, ...values).execute(this.kysely);
|
|
7193
7714
|
return result.rows;
|
|
7194
7715
|
});
|
|
7195
7716
|
}
|
|
@@ -7210,7 +7731,7 @@ var ClientImpl = class _ClientImpl {
|
|
|
7210
7731
|
};
|
|
7211
7732
|
function createClientProxy(client) {
|
|
7212
7733
|
const inputValidator = new InputValidator(client.$schema);
|
|
7213
|
-
const resultProcessor = new ResultProcessor(client.$schema);
|
|
7734
|
+
const resultProcessor = new ResultProcessor(client.$schema, client.$options);
|
|
7214
7735
|
return new Proxy(client, {
|
|
7215
7736
|
get: /* @__PURE__ */ __name((target, prop, receiver) => {
|
|
7216
7737
|
if (typeof prop === "string" && prop.startsWith("$")) {
|
|
@@ -7328,7 +7849,7 @@ function definePlugin(plugin) {
|
|
|
7328
7849
|
__name(definePlugin, "definePlugin");
|
|
7329
7850
|
|
|
7330
7851
|
// src/client/index.ts
|
|
7331
|
-
import { sql as
|
|
7852
|
+
import { sql as sql11 } from "kysely";
|
|
7332
7853
|
export {
|
|
7333
7854
|
InputValidationError,
|
|
7334
7855
|
InternalError,
|
|
@@ -7336,6 +7857,6 @@ export {
|
|
|
7336
7857
|
QueryError,
|
|
7337
7858
|
ZenStackClient,
|
|
7338
7859
|
definePlugin,
|
|
7339
|
-
|
|
7860
|
+
sql11 as sql
|
|
7340
7861
|
};
|
|
7341
7862
|
//# sourceMappingURL=index.js.map
|