@zenstackhq/runtime 3.0.0-beta.3 → 3.0.0-beta.5
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-CusA0mQO.d.cts → contract-BJce14-p.d.cts} +58 -17
- package/dist/{contract-CusA0mQO.d.ts → contract-BJce14-p.d.ts} +58 -17
- package/dist/index.cjs +1401 -786
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1286 -671
- package/dist/index.js.map +1 -1
- package/dist/plugins/policy/index.cjs +965 -375
- 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 +880 -301
- package/dist/plugins/policy/index.js.map +1 -1
- package/dist/plugins/policy/plugin.zmodel +10 -0
- package/dist/schema.cjs +3 -0
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.cts +1 -0
- package/dist/schema.d.ts +1 -0
- package/dist/schema.js +3 -0
- package/dist/schema.js.map +1 -1
- package/package.json +11 -8
|
@@ -2,46 +2,50 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
3
|
|
|
4
4
|
// src/plugins/policy/errors.ts
|
|
5
|
+
var RejectedByPolicyReason = /* @__PURE__ */ function(RejectedByPolicyReason2) {
|
|
6
|
+
RejectedByPolicyReason2["NO_ACCESS"] = "no-access";
|
|
7
|
+
RejectedByPolicyReason2["CANNOT_READ_BACK"] = "cannot-read-back";
|
|
8
|
+
RejectedByPolicyReason2["OTHER"] = "other";
|
|
9
|
+
return RejectedByPolicyReason2;
|
|
10
|
+
}({});
|
|
5
11
|
var RejectedByPolicyError = class extends Error {
|
|
6
12
|
static {
|
|
7
13
|
__name(this, "RejectedByPolicyError");
|
|
8
14
|
}
|
|
9
15
|
model;
|
|
10
16
|
reason;
|
|
11
|
-
constructor(model, reason) {
|
|
12
|
-
super(
|
|
17
|
+
constructor(model, reason = "no-access", message) {
|
|
18
|
+
super(message ?? `Operation rejected by policy${model ? ": " + model : ""}`), this.model = model, this.reason = reason;
|
|
13
19
|
}
|
|
14
20
|
};
|
|
15
21
|
|
|
16
|
-
// src/plugins/policy/
|
|
17
|
-
import { invariant as
|
|
18
|
-
import {
|
|
19
|
-
import { match as match8 } from "ts-pattern";
|
|
22
|
+
// src/plugins/policy/functions.ts
|
|
23
|
+
import { invariant as invariant8 } from "@zenstackhq/common-helpers";
|
|
24
|
+
import { ExpressionWrapper as ExpressionWrapper2, ValueNode as ValueNode4 } from "kysely";
|
|
20
25
|
|
|
21
|
-
// src/client/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
import { match as match3 } from "ts-pattern";
|
|
28
|
-
|
|
29
|
-
// src/client/constants.ts
|
|
30
|
-
var DELEGATE_JOINED_FIELD_PREFIX = "$delegate$";
|
|
31
|
-
var LOGICAL_COMBINATORS = [
|
|
32
|
-
"AND",
|
|
33
|
-
"OR",
|
|
34
|
-
"NOT"
|
|
35
|
-
];
|
|
36
|
-
var AGGREGATE_OPERATORS = [
|
|
37
|
-
"_count",
|
|
38
|
-
"_sum",
|
|
39
|
-
"_avg",
|
|
40
|
-
"_min",
|
|
41
|
-
"_max"
|
|
26
|
+
// src/client/contract.ts
|
|
27
|
+
var CRUD = [
|
|
28
|
+
"create",
|
|
29
|
+
"read",
|
|
30
|
+
"update",
|
|
31
|
+
"delete"
|
|
42
32
|
];
|
|
43
33
|
|
|
34
|
+
// src/client/kysely-utils.ts
|
|
35
|
+
import { AliasNode, ColumnNode, ReferenceNode, TableNode } from "kysely";
|
|
36
|
+
function extractFieldName(node) {
|
|
37
|
+
if (ReferenceNode.is(node) && ColumnNode.is(node.column)) {
|
|
38
|
+
return node.column.column.name;
|
|
39
|
+
} else if (ColumnNode.is(node)) {
|
|
40
|
+
return node.column.name;
|
|
41
|
+
} else {
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
__name(extractFieldName, "extractFieldName");
|
|
46
|
+
|
|
44
47
|
// src/client/query-utils.ts
|
|
48
|
+
import { invariant } from "@zenstackhq/common-helpers";
|
|
45
49
|
import { match } from "ts-pattern";
|
|
46
50
|
|
|
47
51
|
// src/schema/expression.ts
|
|
@@ -109,6 +113,9 @@ var ExpressionUtils = {
|
|
|
109
113
|
or: /* @__PURE__ */ __name((expr2, ...expressions) => {
|
|
110
114
|
return expressions.reduce((acc, exp) => ExpressionUtils.binary(acc, "||", exp), expr2);
|
|
111
115
|
}, "or"),
|
|
116
|
+
not: /* @__PURE__ */ __name((expr2) => {
|
|
117
|
+
return ExpressionUtils.unary("!", expr2);
|
|
118
|
+
}, "not"),
|
|
112
119
|
is: /* @__PURE__ */ __name((value, kind) => {
|
|
113
120
|
return !!value && typeof value === "object" && "kind" in value && value.kind === kind;
|
|
114
121
|
}, "is"),
|
|
@@ -145,15 +152,19 @@ var InternalError = class extends Error {
|
|
|
145
152
|
|
|
146
153
|
// src/client/query-utils.ts
|
|
147
154
|
function getModel(schema, model) {
|
|
148
|
-
return schema.models
|
|
155
|
+
return Object.values(schema.models).find((m) => m.name.toLowerCase() === model.toLowerCase());
|
|
149
156
|
}
|
|
150
157
|
__name(getModel, "getModel");
|
|
158
|
+
function getTypeDef(schema, type) {
|
|
159
|
+
return schema.typeDefs?.[type];
|
|
160
|
+
}
|
|
161
|
+
__name(getTypeDef, "getTypeDef");
|
|
151
162
|
function requireModel(schema, model) {
|
|
152
|
-
const
|
|
153
|
-
if (!
|
|
163
|
+
const modelDef = getModel(schema, model);
|
|
164
|
+
if (!modelDef) {
|
|
154
165
|
throw new QueryError(`Model "${model}" not found in schema`);
|
|
155
166
|
}
|
|
156
|
-
return
|
|
167
|
+
return modelDef;
|
|
157
168
|
}
|
|
158
169
|
__name(requireModel, "requireModel");
|
|
159
170
|
function getField(schema, model, field) {
|
|
@@ -161,19 +172,35 @@ function getField(schema, model, field) {
|
|
|
161
172
|
return modelDef?.fields[field];
|
|
162
173
|
}
|
|
163
174
|
__name(getField, "getField");
|
|
164
|
-
function requireField(schema,
|
|
165
|
-
const modelDef =
|
|
166
|
-
if (
|
|
167
|
-
|
|
175
|
+
function requireField(schema, modelOrType, field) {
|
|
176
|
+
const modelDef = getModel(schema, modelOrType);
|
|
177
|
+
if (modelDef) {
|
|
178
|
+
if (!modelDef.fields[field]) {
|
|
179
|
+
throw new QueryError(`Field "${field}" not found in model "${modelOrType}"`);
|
|
180
|
+
} else {
|
|
181
|
+
return modelDef.fields[field];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const typeDef = getTypeDef(schema, modelOrType);
|
|
185
|
+
if (typeDef) {
|
|
186
|
+
if (!typeDef.fields[field]) {
|
|
187
|
+
throw new QueryError(`Field "${field}" not found in type "${modelOrType}"`);
|
|
188
|
+
} else {
|
|
189
|
+
return typeDef.fields[field];
|
|
190
|
+
}
|
|
168
191
|
}
|
|
169
|
-
|
|
192
|
+
throw new QueryError(`Model or type "${modelOrType}" not found in schema`);
|
|
170
193
|
}
|
|
171
194
|
__name(requireField, "requireField");
|
|
172
|
-
function
|
|
195
|
+
function requireIdFields(schema, model) {
|
|
173
196
|
const modelDef = requireModel(schema, model);
|
|
174
|
-
|
|
197
|
+
const result = modelDef?.idFields;
|
|
198
|
+
if (!result) {
|
|
199
|
+
throw new InternalError(`Model "${model}" does not have ID field(s)`);
|
|
200
|
+
}
|
|
201
|
+
return result;
|
|
175
202
|
}
|
|
176
|
-
__name(
|
|
203
|
+
__name(requireIdFields, "requireIdFields");
|
|
177
204
|
function getRelationForeignKeyFieldPairs(schema, model, relationField) {
|
|
178
205
|
const fieldDef = requireField(schema, model, relationField);
|
|
179
206
|
if (!fieldDef?.relation) {
|
|
@@ -266,7 +293,7 @@ function buildFieldRef(schema, model, field, options, eb, modelAlias, inlineComp
|
|
|
266
293
|
throw new QueryError(`Computed field "${field}" implementation not provided for model "${model}"`);
|
|
267
294
|
}
|
|
268
295
|
return computer(eb, {
|
|
269
|
-
|
|
296
|
+
modelAlias
|
|
270
297
|
});
|
|
271
298
|
}
|
|
272
299
|
}
|
|
@@ -293,7 +320,7 @@ function buildJoinPairs(schema, model, modelAlias, relationField, relationModelA
|
|
|
293
320
|
}
|
|
294
321
|
__name(buildJoinPairs, "buildJoinPairs");
|
|
295
322
|
function makeDefaultOrderBy(schema, model) {
|
|
296
|
-
const idFields =
|
|
323
|
+
const idFields = requireIdFields(schema, model);
|
|
297
324
|
return idFields.map((f) => ({
|
|
298
325
|
[f]: "asc"
|
|
299
326
|
}));
|
|
@@ -332,11 +359,17 @@ function getManyToManyRelation(schema, model, field) {
|
|
|
332
359
|
"A"
|
|
333
360
|
];
|
|
334
361
|
}
|
|
362
|
+
const modelIdFields = requireIdFields(schema, model);
|
|
363
|
+
invariant(modelIdFields.length === 1, "Only single-field ID is supported for many-to-many relation");
|
|
364
|
+
const otherIdFields = requireIdFields(schema, fieldDef.type);
|
|
365
|
+
invariant(otherIdFields.length === 1, "Only single-field ID is supported for many-to-many relation");
|
|
335
366
|
return {
|
|
336
367
|
parentFkName: orderedFK[0],
|
|
368
|
+
parentPKName: modelIdFields[0],
|
|
337
369
|
otherModel: fieldDef.type,
|
|
338
370
|
otherField: fieldDef.relation.opposite,
|
|
339
371
|
otherFkName: orderedFK[1],
|
|
372
|
+
otherPKName: otherIdFields[0],
|
|
340
373
|
joinTable: fieldDef.relation.name ? `_${fieldDef.relation.name}` : `_${sortedModelNames[0]}To${sortedModelNames[1]}`
|
|
341
374
|
};
|
|
342
375
|
} else {
|
|
@@ -392,8 +425,37 @@ function aggregate(eb, expr2, op) {
|
|
|
392
425
|
}
|
|
393
426
|
__name(aggregate, "aggregate");
|
|
394
427
|
|
|
395
|
-
// src/
|
|
396
|
-
import { invariant
|
|
428
|
+
// src/plugins/policy/policy-handler.ts
|
|
429
|
+
import { invariant as invariant7 } from "@zenstackhq/common-helpers";
|
|
430
|
+
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";
|
|
431
|
+
import { match as match8 } from "ts-pattern";
|
|
432
|
+
|
|
433
|
+
// src/client/crud/dialects/index.ts
|
|
434
|
+
import { match as match5 } from "ts-pattern";
|
|
435
|
+
|
|
436
|
+
// src/client/crud/dialects/postgresql.ts
|
|
437
|
+
import { invariant as invariant3 } from "@zenstackhq/common-helpers";
|
|
438
|
+
import Decimal from "decimal.js";
|
|
439
|
+
import { sql as sql2 } from "kysely";
|
|
440
|
+
import { match as match3 } from "ts-pattern";
|
|
441
|
+
|
|
442
|
+
// src/client/constants.ts
|
|
443
|
+
var DELEGATE_JOINED_FIELD_PREFIX = "$delegate$";
|
|
444
|
+
var LOGICAL_COMBINATORS = [
|
|
445
|
+
"AND",
|
|
446
|
+
"OR",
|
|
447
|
+
"NOT"
|
|
448
|
+
];
|
|
449
|
+
var AGGREGATE_OPERATORS = [
|
|
450
|
+
"_count",
|
|
451
|
+
"_sum",
|
|
452
|
+
"_avg",
|
|
453
|
+
"_min",
|
|
454
|
+
"_max"
|
|
455
|
+
];
|
|
456
|
+
|
|
457
|
+
// src/client/crud/dialects/base-dialect.ts
|
|
458
|
+
import { invariant as invariant2, isPlainObject } from "@zenstackhq/common-helpers";
|
|
397
459
|
import { expressionBuilder, sql } from "kysely";
|
|
398
460
|
import { match as match2, P } from "ts-pattern";
|
|
399
461
|
|
|
@@ -411,7 +473,7 @@ function enumerate(x) {
|
|
|
411
473
|
}
|
|
412
474
|
__name(enumerate, "enumerate");
|
|
413
475
|
|
|
414
|
-
// src/client/crud/dialects/base.ts
|
|
476
|
+
// src/client/crud/dialects/base-dialect.ts
|
|
415
477
|
var BaseCrudDialect = class {
|
|
416
478
|
static {
|
|
417
479
|
__name(this, "BaseCrudDialect");
|
|
@@ -425,6 +487,9 @@ var BaseCrudDialect = class {
|
|
|
425
487
|
transformPrimitive(value, _type, _forArrayField) {
|
|
426
488
|
return value;
|
|
427
489
|
}
|
|
490
|
+
transformOutput(value, _type) {
|
|
491
|
+
return value;
|
|
492
|
+
}
|
|
428
493
|
// #region common query builders
|
|
429
494
|
buildSelectModel(eb, model, modelAlias) {
|
|
430
495
|
const modelDef = requireModel(this.schema, model);
|
|
@@ -592,9 +657,11 @@ var BaseCrudDialect = class {
|
|
|
592
657
|
const buildPkFkWhereRefs = /* @__PURE__ */ __name((eb2) => {
|
|
593
658
|
const m2m = getManyToManyRelation(this.schema, model, field);
|
|
594
659
|
if (m2m) {
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
660
|
+
const modelIdFields = requireIdFields(this.schema, model);
|
|
661
|
+
invariant2(modelIdFields.length === 1, "many-to-many relation must have exactly one id field");
|
|
662
|
+
const relationIdFields = requireIdFields(this.schema, relationModel);
|
|
663
|
+
invariant2(relationIdFields.length === 1, "many-to-many relation must have exactly one id field");
|
|
664
|
+
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]}`)));
|
|
598
665
|
} else {
|
|
599
666
|
const relationKeyPairs = getRelationForeignKeyFieldPairs(this.schema, model, field);
|
|
600
667
|
let result2 = this.true(eb2);
|
|
@@ -704,14 +771,14 @@ var BaseCrudDialect = class {
|
|
|
704
771
|
}
|
|
705
772
|
const rhs = Array.isArray(value) ? value.map(getRhs) : getRhs(value);
|
|
706
773
|
const condition = match2(op).with("equals", () => rhs === null ? eb(lhs, "is", null) : eb(lhs, "=", rhs)).with("in", () => {
|
|
707
|
-
|
|
774
|
+
invariant2(Array.isArray(rhs), "right hand side must be an array");
|
|
708
775
|
if (rhs.length === 0) {
|
|
709
776
|
return this.false(eb);
|
|
710
777
|
} else {
|
|
711
778
|
return eb(lhs, "in", rhs);
|
|
712
779
|
}
|
|
713
780
|
}).with("notIn", () => {
|
|
714
|
-
|
|
781
|
+
invariant2(Array.isArray(rhs), "right hand side must be an array");
|
|
715
782
|
if (rhs.length === 0) {
|
|
716
783
|
return this.true(eb);
|
|
717
784
|
} else {
|
|
@@ -829,18 +896,18 @@ var BaseCrudDialect = class {
|
|
|
829
896
|
"_min",
|
|
830
897
|
"_max"
|
|
831
898
|
].includes(field)) {
|
|
832
|
-
|
|
899
|
+
invariant2(value && typeof value === "object", `invalid orderBy value for field "${field}"`);
|
|
833
900
|
for (const [k, v] of Object.entries(value)) {
|
|
834
|
-
|
|
901
|
+
invariant2(v === "asc" || v === "desc", `invalid orderBy value for field "${field}"`);
|
|
835
902
|
result = result.orderBy((eb) => aggregate(eb, this.fieldRef(model, k, eb, modelAlias), field), sql.raw(this.negateSort(v, negated)));
|
|
836
903
|
}
|
|
837
904
|
continue;
|
|
838
905
|
}
|
|
839
906
|
switch (field) {
|
|
840
907
|
case "_count": {
|
|
841
|
-
|
|
908
|
+
invariant2(value && typeof value === "object", 'invalid orderBy value for field "_count"');
|
|
842
909
|
for (const [k, v] of Object.entries(value)) {
|
|
843
|
-
|
|
910
|
+
invariant2(v === "asc" || v === "desc", `invalid orderBy value for field "${field}"`);
|
|
844
911
|
result = result.orderBy((eb) => eb.fn.count(this.fieldRef(model, k, eb, modelAlias)), sql.raw(this.negateSort(v, negated)));
|
|
845
912
|
}
|
|
846
913
|
continue;
|
|
@@ -863,7 +930,7 @@ var BaseCrudDialect = class {
|
|
|
863
930
|
throw new QueryError(`invalid orderBy value for field "${field}"`);
|
|
864
931
|
}
|
|
865
932
|
if ("_count" in value) {
|
|
866
|
-
|
|
933
|
+
invariant2(value._count === "asc" || value._count === "desc", 'invalid orderBy value for field "_count"');
|
|
867
934
|
const sort = this.negateSort(value._count, negated);
|
|
868
935
|
result = result.orderBy((eb) => {
|
|
869
936
|
const subQueryAlias = `${modelAlias}$orderBy$${field}$count`;
|
|
@@ -935,7 +1002,7 @@ var BaseCrudDialect = class {
|
|
|
935
1002
|
}
|
|
936
1003
|
}
|
|
937
1004
|
buildDelegateJoin(thisModel, thisModelAlias, otherModelAlias, query) {
|
|
938
|
-
const idFields =
|
|
1005
|
+
const idFields = requireIdFields(this.schema, thisModel);
|
|
939
1006
|
query = query.leftJoin(otherModelAlias, (qb) => {
|
|
940
1007
|
for (const idField of idFields) {
|
|
941
1008
|
qb = qb.onRef(`${thisModelAlias}.${idField}`, "=", `${otherModelAlias}.${idField}`);
|
|
@@ -957,10 +1024,16 @@ var BaseCrudDialect = class {
|
|
|
957
1024
|
for (const [field, value] of Object.entries(selections.select)) {
|
|
958
1025
|
const fieldDef = requireField(this.schema, model, field);
|
|
959
1026
|
const fieldModel = fieldDef.type;
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
fieldCountQuery =
|
|
1027
|
+
let fieldCountQuery;
|
|
1028
|
+
const m2m = getManyToManyRelation(this.schema, model, field);
|
|
1029
|
+
if (m2m) {
|
|
1030
|
+
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}`));
|
|
1031
|
+
} else {
|
|
1032
|
+
fieldCountQuery = eb.selectFrom(fieldModel).select(eb.fn.countAll().as(`_count$${field}`));
|
|
1033
|
+
const joinPairs = buildJoinPairs(this.schema, model, parentAlias, field, fieldModel);
|
|
1034
|
+
for (const [left, right] of joinPairs) {
|
|
1035
|
+
fieldCountQuery = fieldCountQuery.whereRef(left, "=", right);
|
|
1036
|
+
}
|
|
964
1037
|
}
|
|
965
1038
|
if (value && typeof value === "object" && "where" in value && value.where && typeof value.where === "object") {
|
|
966
1039
|
const filter = this.buildFilter(eb, fieldModel, fieldModel, value.where);
|
|
@@ -1040,6 +1113,9 @@ var PostgresCrudDialect = class extends BaseCrudDialect {
|
|
|
1040
1113
|
static {
|
|
1041
1114
|
__name(this, "PostgresCrudDialect");
|
|
1042
1115
|
}
|
|
1116
|
+
constructor(schema, options) {
|
|
1117
|
+
super(schema, options);
|
|
1118
|
+
}
|
|
1043
1119
|
get provider() {
|
|
1044
1120
|
return "postgresql";
|
|
1045
1121
|
}
|
|
@@ -1054,9 +1130,41 @@ var PostgresCrudDialect = class extends BaseCrudDialect {
|
|
|
1054
1130
|
return value.map((v) => this.transformPrimitive(v, type, false));
|
|
1055
1131
|
}
|
|
1056
1132
|
} else {
|
|
1057
|
-
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);
|
|
1133
|
+
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);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
transformOutput(value, type) {
|
|
1137
|
+
if (value === null || value === void 0) {
|
|
1138
|
+
return value;
|
|
1139
|
+
}
|
|
1140
|
+
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));
|
|
1141
|
+
}
|
|
1142
|
+
transformOutputBigInt(value) {
|
|
1143
|
+
if (typeof value === "bigint") {
|
|
1144
|
+
return value;
|
|
1145
|
+
}
|
|
1146
|
+
invariant3(typeof value === "string" || typeof value === "number", `Expected string or number, got ${typeof value}`);
|
|
1147
|
+
return BigInt(value);
|
|
1148
|
+
}
|
|
1149
|
+
transformDecimal(value) {
|
|
1150
|
+
if (value instanceof Decimal) {
|
|
1151
|
+
return value;
|
|
1152
|
+
}
|
|
1153
|
+
invariant3(typeof value === "string" || typeof value === "number" || value instanceof Decimal, `Expected string, number or Decimal, got ${typeof value}`);
|
|
1154
|
+
return new Decimal(value);
|
|
1155
|
+
}
|
|
1156
|
+
transformOutputDate(value) {
|
|
1157
|
+
if (typeof value === "string") {
|
|
1158
|
+
return new Date(value);
|
|
1159
|
+
} else if (value instanceof Date && this.options.fixPostgresTimezone !== false) {
|
|
1160
|
+
return new Date(value.getTime() - value.getTimezoneOffset() * 60 * 1e3);
|
|
1161
|
+
} else {
|
|
1162
|
+
return value;
|
|
1058
1163
|
}
|
|
1059
1164
|
}
|
|
1165
|
+
transformOutputBytes(value) {
|
|
1166
|
+
return Buffer.isBuffer(value) ? Uint8Array.from(value) : value;
|
|
1167
|
+
}
|
|
1060
1168
|
buildRelationSelection(query, model, relationField, parentAlias, payload) {
|
|
1061
1169
|
const relationResultName = `${parentAlias}$${relationField}`;
|
|
1062
1170
|
const joinedQuery = this.buildRelationJSON(model, query, relationField, parentAlias, payload, relationResultName);
|
|
@@ -1087,10 +1195,10 @@ var PostgresCrudDialect = class extends BaseCrudDialect {
|
|
|
1087
1195
|
buildRelationJoinFilter(query, model, relationField, relationModel, relationModelAlias, parentAlias) {
|
|
1088
1196
|
const m2m = getManyToManyRelation(this.schema, model, relationField);
|
|
1089
1197
|
if (m2m) {
|
|
1090
|
-
const parentIds =
|
|
1091
|
-
const relationIds =
|
|
1092
|
-
|
|
1093
|
-
|
|
1198
|
+
const parentIds = requireIdFields(this.schema, model);
|
|
1199
|
+
const relationIds = requireIdFields(this.schema, relationModel);
|
|
1200
|
+
invariant3(parentIds.length === 1, "many-to-many relation must have exactly one id field");
|
|
1201
|
+
invariant3(relationIds.length === 1, "many-to-many relation must have exactly one id field");
|
|
1094
1202
|
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}`)));
|
|
1095
1203
|
} else {
|
|
1096
1204
|
const joinPairs = buildJoinPairs(this.schema, model, parentAlias, relationField, relationModelAlias);
|
|
@@ -1202,10 +1310,32 @@ var PostgresCrudDialect = class extends BaseCrudDialect {
|
|
|
1202
1310
|
get supportInsertWithDefault() {
|
|
1203
1311
|
return true;
|
|
1204
1312
|
}
|
|
1313
|
+
getFieldSqlType(fieldDef) {
|
|
1314
|
+
if (fieldDef.relation) {
|
|
1315
|
+
throw new QueryError("Cannot get SQL type of a relation field");
|
|
1316
|
+
}
|
|
1317
|
+
let result;
|
|
1318
|
+
if (this.schema.enums?.[fieldDef.type]) {
|
|
1319
|
+
result = "text";
|
|
1320
|
+
} else {
|
|
1321
|
+
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");
|
|
1322
|
+
}
|
|
1323
|
+
if (fieldDef.array) {
|
|
1324
|
+
result += "[]";
|
|
1325
|
+
}
|
|
1326
|
+
return result;
|
|
1327
|
+
}
|
|
1328
|
+
getStringCasingBehavior() {
|
|
1329
|
+
return {
|
|
1330
|
+
supportsILike: true,
|
|
1331
|
+
likeCaseSensitive: true
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1205
1334
|
};
|
|
1206
1335
|
|
|
1207
1336
|
// src/client/crud/dialects/sqlite.ts
|
|
1208
|
-
import { invariant as
|
|
1337
|
+
import { invariant as invariant4 } from "@zenstackhq/common-helpers";
|
|
1338
|
+
import Decimal2 from "decimal.js";
|
|
1209
1339
|
import { sql as sql3 } from "kysely";
|
|
1210
1340
|
import { match as match4 } from "ts-pattern";
|
|
1211
1341
|
var SqliteCrudDialect = class extends BaseCrudDialect {
|
|
@@ -1225,10 +1355,58 @@ var SqliteCrudDialect = class extends BaseCrudDialect {
|
|
|
1225
1355
|
if (this.schema.typeDefs && type in this.schema.typeDefs) {
|
|
1226
1356
|
return JSON.stringify(value);
|
|
1227
1357
|
} else {
|
|
1228
|
-
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);
|
|
1358
|
+
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);
|
|
1229
1359
|
}
|
|
1230
1360
|
}
|
|
1231
1361
|
}
|
|
1362
|
+
transformOutput(value, type) {
|
|
1363
|
+
if (value === null || value === void 0) {
|
|
1364
|
+
return value;
|
|
1365
|
+
} else if (this.schema.typeDefs && type in this.schema.typeDefs) {
|
|
1366
|
+
return this.transformOutputJson(value);
|
|
1367
|
+
} else {
|
|
1368
|
+
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));
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
transformOutputDecimal(value) {
|
|
1372
|
+
if (value instanceof Decimal2) {
|
|
1373
|
+
return value;
|
|
1374
|
+
}
|
|
1375
|
+
invariant4(typeof value === "string" || typeof value === "number" || value instanceof Decimal2, `Expected string, number or Decimal, got ${typeof value}`);
|
|
1376
|
+
return new Decimal2(value);
|
|
1377
|
+
}
|
|
1378
|
+
transformOutputBigInt(value) {
|
|
1379
|
+
if (typeof value === "bigint") {
|
|
1380
|
+
return value;
|
|
1381
|
+
}
|
|
1382
|
+
invariant4(typeof value === "string" || typeof value === "number", `Expected string or number, got ${typeof value}`);
|
|
1383
|
+
return BigInt(value);
|
|
1384
|
+
}
|
|
1385
|
+
transformOutputBoolean(value) {
|
|
1386
|
+
return !!value;
|
|
1387
|
+
}
|
|
1388
|
+
transformOutputDate(value) {
|
|
1389
|
+
if (typeof value === "number") {
|
|
1390
|
+
return new Date(value);
|
|
1391
|
+
} else if (typeof value === "string") {
|
|
1392
|
+
return new Date(value);
|
|
1393
|
+
} else {
|
|
1394
|
+
return value;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
transformOutputBytes(value) {
|
|
1398
|
+
return Buffer.isBuffer(value) ? Uint8Array.from(value) : value;
|
|
1399
|
+
}
|
|
1400
|
+
transformOutputJson(value) {
|
|
1401
|
+
if (typeof value === "string") {
|
|
1402
|
+
try {
|
|
1403
|
+
return JSON.parse(value);
|
|
1404
|
+
} catch (e) {
|
|
1405
|
+
throw new QueryError("Invalid JSON returned", e);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
return value;
|
|
1409
|
+
}
|
|
1232
1410
|
buildRelationSelection(query, model, relationField, parentAlias, payload) {
|
|
1233
1411
|
return query.select((eb) => this.buildRelationJSON(model, eb, relationField, parentAlias, payload).as(relationField));
|
|
1234
1412
|
}
|
|
@@ -1310,10 +1488,10 @@ var SqliteCrudDialect = class extends BaseCrudDialect {
|
|
|
1310
1488
|
const relationModel = fieldDef.type;
|
|
1311
1489
|
const m2m = getManyToManyRelation(this.schema, model, relationField);
|
|
1312
1490
|
if (m2m) {
|
|
1313
|
-
const parentIds =
|
|
1314
|
-
const relationIds =
|
|
1315
|
-
|
|
1316
|
-
|
|
1491
|
+
const parentIds = requireIdFields(this.schema, model);
|
|
1492
|
+
const relationIds = requireIdFields(this.schema, relationModel);
|
|
1493
|
+
invariant4(parentIds.length === 1, "many-to-many relation must have exactly one id field");
|
|
1494
|
+
invariant4(relationIds.length === 1, "many-to-many relation must have exactly one id field");
|
|
1317
1495
|
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}`)));
|
|
1318
1496
|
} else {
|
|
1319
1497
|
const { keyPairs, ownedByModel } = getRelationForeignKeyFieldPairs(this.schema, model, relationField);
|
|
@@ -1365,6 +1543,24 @@ var SqliteCrudDialect = class extends BaseCrudDialect {
|
|
|
1365
1543
|
get supportInsertWithDefault() {
|
|
1366
1544
|
return false;
|
|
1367
1545
|
}
|
|
1546
|
+
getFieldSqlType(fieldDef) {
|
|
1547
|
+
if (fieldDef.relation) {
|
|
1548
|
+
throw new QueryError("Cannot get SQL type of a relation field");
|
|
1549
|
+
}
|
|
1550
|
+
if (fieldDef.array) {
|
|
1551
|
+
throw new QueryError("SQLite does not support scalar list type");
|
|
1552
|
+
}
|
|
1553
|
+
if (this.schema.enums?.[fieldDef.type]) {
|
|
1554
|
+
return "text";
|
|
1555
|
+
}
|
|
1556
|
+
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");
|
|
1557
|
+
}
|
|
1558
|
+
getStringCasingBehavior() {
|
|
1559
|
+
return {
|
|
1560
|
+
supportsILike: false,
|
|
1561
|
+
likeCaseSensitive: false
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1368
1564
|
};
|
|
1369
1565
|
|
|
1370
1566
|
// src/client/crud/dialects/index.ts
|
|
@@ -1692,12 +1888,12 @@ var ColumnCollector = class extends DefaultOperationNodeVisitor {
|
|
|
1692
1888
|
};
|
|
1693
1889
|
|
|
1694
1890
|
// src/plugins/policy/expression-transformer.ts
|
|
1695
|
-
import { invariant as
|
|
1696
|
-
import { AliasNode as
|
|
1891
|
+
import { invariant as invariant6 } from "@zenstackhq/common-helpers";
|
|
1892
|
+
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";
|
|
1697
1893
|
import { match as match7 } from "ts-pattern";
|
|
1698
1894
|
|
|
1699
1895
|
// src/plugins/policy/expression-evaluator.ts
|
|
1700
|
-
import { invariant as
|
|
1896
|
+
import { invariant as invariant5 } from "@zenstackhq/common-helpers";
|
|
1701
1897
|
import { match as match6 } from "ts-pattern";
|
|
1702
1898
|
var ExpressionEvaluator = class {
|
|
1703
1899
|
static {
|
|
@@ -1741,18 +1937,18 @@ var ExpressionEvaluator = class {
|
|
|
1741
1937
|
const right = this.evaluate(expr2.right, context);
|
|
1742
1938
|
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", () => {
|
|
1743
1939
|
const _right = right ?? [];
|
|
1744
|
-
|
|
1940
|
+
invariant5(Array.isArray(_right), 'expected array for "in" operator');
|
|
1745
1941
|
return _right.includes(left);
|
|
1746
1942
|
}).exhaustive();
|
|
1747
1943
|
}
|
|
1748
1944
|
evaluateCollectionPredicate(expr2, context) {
|
|
1749
1945
|
const op = expr2.op;
|
|
1750
|
-
|
|
1946
|
+
invariant5(op === "?" || op === "!" || op === "^", 'expected "?" or "!" or "^" operator');
|
|
1751
1947
|
const left = this.evaluate(expr2.left, context);
|
|
1752
1948
|
if (!left) {
|
|
1753
1949
|
return false;
|
|
1754
1950
|
}
|
|
1755
|
-
|
|
1951
|
+
invariant5(Array.isArray(left), "expected array");
|
|
1756
1952
|
return match6(op).with("?", () => left.some((item) => this.evaluate(expr2.right, {
|
|
1757
1953
|
...context,
|
|
1758
1954
|
thisValue: item
|
|
@@ -1767,7 +1963,7 @@ var ExpressionEvaluator = class {
|
|
|
1767
1963
|
};
|
|
1768
1964
|
|
|
1769
1965
|
// src/plugins/policy/utils.ts
|
|
1770
|
-
import { AliasNode, AndNode, BinaryOperationNode, FunctionNode, OperatorNode, OrNode, ParensNode, ReferenceNode, TableNode, UnaryOperationNode, ValueNode } from "kysely";
|
|
1966
|
+
import { AliasNode as AliasNode2, AndNode, BinaryOperationNode, FunctionNode, OperatorNode, OrNode, ParensNode, ReferenceNode as ReferenceNode2, TableNode as TableNode2, UnaryOperationNode, ValueNode } from "kysely";
|
|
1771
1967
|
function trueNode(dialect) {
|
|
1772
1968
|
return ValueNode.createImmediate(dialect.transformPrimitive(true, "Boolean", false));
|
|
1773
1969
|
}
|
|
@@ -1785,6 +1981,12 @@ function isFalseNode(node) {
|
|
|
1785
1981
|
}
|
|
1786
1982
|
__name(isFalseNode, "isFalseNode");
|
|
1787
1983
|
function conjunction(dialect, nodes) {
|
|
1984
|
+
if (nodes.length === 0) {
|
|
1985
|
+
return trueNode(dialect);
|
|
1986
|
+
}
|
|
1987
|
+
if (nodes.length === 1) {
|
|
1988
|
+
return nodes[0];
|
|
1989
|
+
}
|
|
1788
1990
|
if (nodes.some(isFalseNode)) {
|
|
1789
1991
|
return falseNode(dialect);
|
|
1790
1992
|
}
|
|
@@ -1792,10 +1994,16 @@ function conjunction(dialect, nodes) {
|
|
|
1792
1994
|
if (items.length === 0) {
|
|
1793
1995
|
return trueNode(dialect);
|
|
1794
1996
|
}
|
|
1795
|
-
return items.reduce((acc, node) =>
|
|
1997
|
+
return items.reduce((acc, node) => AndNode.create(wrapParensIf(acc, OrNode.is), wrapParensIf(node, OrNode.is)));
|
|
1796
1998
|
}
|
|
1797
1999
|
__name(conjunction, "conjunction");
|
|
1798
2000
|
function disjunction(dialect, nodes) {
|
|
2001
|
+
if (nodes.length === 0) {
|
|
2002
|
+
return falseNode(dialect);
|
|
2003
|
+
}
|
|
2004
|
+
if (nodes.length === 1) {
|
|
2005
|
+
return nodes[0];
|
|
2006
|
+
}
|
|
1799
2007
|
if (nodes.some(isTrueNode)) {
|
|
1800
2008
|
return trueNode(dialect);
|
|
1801
2009
|
}
|
|
@@ -1803,13 +2011,23 @@ function disjunction(dialect, nodes) {
|
|
|
1803
2011
|
if (items.length === 0) {
|
|
1804
2012
|
return falseNode(dialect);
|
|
1805
2013
|
}
|
|
1806
|
-
return items.reduce((acc, node) =>
|
|
2014
|
+
return items.reduce((acc, node) => OrNode.create(wrapParensIf(acc, AndNode.is), wrapParensIf(node, AndNode.is)));
|
|
1807
2015
|
}
|
|
1808
2016
|
__name(disjunction, "disjunction");
|
|
1809
|
-
function logicalNot(node) {
|
|
1810
|
-
|
|
2017
|
+
function logicalNot(dialect, node) {
|
|
2018
|
+
if (isTrueNode(node)) {
|
|
2019
|
+
return falseNode(dialect);
|
|
2020
|
+
}
|
|
2021
|
+
if (isFalseNode(node)) {
|
|
2022
|
+
return trueNode(dialect);
|
|
2023
|
+
}
|
|
2024
|
+
return UnaryOperationNode.create(OperatorNode.create("not"), wrapParensIf(node, (n) => AndNode.is(n) || OrNode.is(n)));
|
|
1811
2025
|
}
|
|
1812
2026
|
__name(logicalNot, "logicalNot");
|
|
2027
|
+
function wrapParensIf(node, predicate) {
|
|
2028
|
+
return predicate(node) ? ParensNode.create(node) : node;
|
|
2029
|
+
}
|
|
2030
|
+
__name(wrapParensIf, "wrapParensIf");
|
|
1813
2031
|
function buildIsFalse(node, dialect) {
|
|
1814
2032
|
if (isFalseNode(node)) {
|
|
1815
2033
|
return trueNode(dialect);
|
|
@@ -1831,11 +2049,11 @@ function getTableName(node) {
|
|
|
1831
2049
|
if (!node) {
|
|
1832
2050
|
return node;
|
|
1833
2051
|
}
|
|
1834
|
-
if (
|
|
2052
|
+
if (TableNode2.is(node)) {
|
|
1835
2053
|
return node.table.identifier.name;
|
|
1836
|
-
} else if (
|
|
2054
|
+
} else if (AliasNode2.is(node)) {
|
|
1837
2055
|
return getTableName(node.node);
|
|
1838
|
-
} else if (
|
|
2056
|
+
} else if (ReferenceNode2.is(node) && node.table) {
|
|
1839
2057
|
return getTableName(node.table);
|
|
1840
2058
|
}
|
|
1841
2059
|
return void 0;
|
|
@@ -1868,16 +2086,21 @@ var ExpressionTransformer = class {
|
|
|
1868
2086
|
static {
|
|
1869
2087
|
__name(this, "ExpressionTransformer");
|
|
1870
2088
|
}
|
|
1871
|
-
|
|
1872
|
-
clientOptions;
|
|
1873
|
-
auth;
|
|
2089
|
+
client;
|
|
1874
2090
|
dialect;
|
|
1875
|
-
constructor(
|
|
1876
|
-
this.
|
|
1877
|
-
this.clientOptions = clientOptions;
|
|
1878
|
-
this.auth = auth;
|
|
2091
|
+
constructor(client) {
|
|
2092
|
+
this.client = client;
|
|
1879
2093
|
this.dialect = getCrudDialect(this.schema, this.clientOptions);
|
|
1880
2094
|
}
|
|
2095
|
+
get schema() {
|
|
2096
|
+
return this.client.$schema;
|
|
2097
|
+
}
|
|
2098
|
+
get clientOptions() {
|
|
2099
|
+
return this.client.$options;
|
|
2100
|
+
}
|
|
2101
|
+
get auth() {
|
|
2102
|
+
return this.client.$auth;
|
|
2103
|
+
}
|
|
1881
2104
|
get authType() {
|
|
1882
2105
|
if (!this.schema.authType) {
|
|
1883
2106
|
throw new InternalError('Schema does not have an "authType" specified');
|
|
@@ -1900,11 +2123,7 @@ var ExpressionTransformer = class {
|
|
|
1900
2123
|
_field(expr2, context) {
|
|
1901
2124
|
const fieldDef = requireField(this.schema, context.model, expr2.field);
|
|
1902
2125
|
if (!fieldDef.relation) {
|
|
1903
|
-
|
|
1904
|
-
return context.thisEntity[expr2.field];
|
|
1905
|
-
} else {
|
|
1906
|
-
return this.createColumnRef(expr2.field, context);
|
|
1907
|
-
}
|
|
2126
|
+
return this.createColumnRef(expr2.field, context);
|
|
1908
2127
|
} else {
|
|
1909
2128
|
const { memberFilter, memberSelect, ...restContext } = context;
|
|
1910
2129
|
const relation = this.transformRelationAccess(expr2.field, fieldDef.type, restContext);
|
|
@@ -1945,14 +2164,15 @@ var ExpressionTransformer = class {
|
|
|
1945
2164
|
]);
|
|
1946
2165
|
}
|
|
1947
2166
|
if (this.isAuthCall(expr2.left) || this.isAuthCall(expr2.right)) {
|
|
1948
|
-
return this.transformAuthBinary(expr2);
|
|
2167
|
+
return this.transformAuthBinary(expr2, context);
|
|
1949
2168
|
}
|
|
1950
2169
|
const op = expr2.op;
|
|
1951
2170
|
if (op === "?" || op === "!" || op === "^") {
|
|
1952
2171
|
return this.transformCollectionPredicate(expr2, context);
|
|
1953
2172
|
}
|
|
1954
|
-
const
|
|
1955
|
-
const
|
|
2173
|
+
const { normalizedLeft, normalizedRight } = this.normalizeBinaryOperationOperands(expr2, context);
|
|
2174
|
+
const left = this.transform(normalizedLeft, context);
|
|
2175
|
+
const right = this.transform(normalizedRight, context);
|
|
1956
2176
|
if (op === "in") {
|
|
1957
2177
|
if (this.isNullNode(left)) {
|
|
1958
2178
|
return this.transformValue(false, "Boolean");
|
|
@@ -1967,29 +2187,65 @@ var ExpressionTransformer = class {
|
|
|
1967
2187
|
}
|
|
1968
2188
|
}
|
|
1969
2189
|
if (this.isNullNode(right)) {
|
|
1970
|
-
return
|
|
2190
|
+
return this.transformNullCheck(left, expr2.op);
|
|
1971
2191
|
} else if (this.isNullNode(left)) {
|
|
1972
|
-
return
|
|
2192
|
+
return this.transformNullCheck(right, expr2.op);
|
|
2193
|
+
} else {
|
|
2194
|
+
return BinaryOperationNode2.create(left, this.transformOperator(op), right);
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
transformNullCheck(expr2, operator) {
|
|
2198
|
+
invariant6(operator === "==" || operator === "!=", 'operator must be "==" or "!=" for null comparison');
|
|
2199
|
+
if (ValueNode2.is(expr2)) {
|
|
2200
|
+
if (expr2.value === null) {
|
|
2201
|
+
return operator === "==" ? trueNode(this.dialect) : falseNode(this.dialect);
|
|
2202
|
+
} else {
|
|
2203
|
+
return operator === "==" ? falseNode(this.dialect) : trueNode(this.dialect);
|
|
2204
|
+
}
|
|
2205
|
+
} else {
|
|
2206
|
+
return operator === "==" ? BinaryOperationNode2.create(expr2, OperatorNode2.create("is"), ValueNode2.createImmediate(null)) : BinaryOperationNode2.create(expr2, OperatorNode2.create("is not"), ValueNode2.createImmediate(null));
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
normalizeBinaryOperationOperands(expr2, context) {
|
|
2210
|
+
let normalizedLeft = expr2.left;
|
|
2211
|
+
if (this.isRelationField(expr2.left, context.model)) {
|
|
2212
|
+
invariant6(ExpressionUtils.isNull(expr2.right), "only null comparison is supported for relation field");
|
|
2213
|
+
const leftRelDef = this.getFieldDefFromFieldRef(expr2.left, context.model);
|
|
2214
|
+
invariant6(leftRelDef, "failed to get relation field definition");
|
|
2215
|
+
const idFields = requireIdFields(this.schema, leftRelDef.type);
|
|
2216
|
+
normalizedLeft = this.makeOrAppendMember(normalizedLeft, idFields[0]);
|
|
2217
|
+
}
|
|
2218
|
+
let normalizedRight = expr2.right;
|
|
2219
|
+
if (this.isRelationField(expr2.right, context.model)) {
|
|
2220
|
+
invariant6(ExpressionUtils.isNull(expr2.left), "only null comparison is supported for relation field");
|
|
2221
|
+
const rightRelDef = this.getFieldDefFromFieldRef(expr2.right, context.model);
|
|
2222
|
+
invariant6(rightRelDef, "failed to get relation field definition");
|
|
2223
|
+
const idFields = requireIdFields(this.schema, rightRelDef.type);
|
|
2224
|
+
normalizedRight = this.makeOrAppendMember(normalizedRight, idFields[0]);
|
|
1973
2225
|
}
|
|
1974
|
-
return
|
|
2226
|
+
return {
|
|
2227
|
+
normalizedLeft,
|
|
2228
|
+
normalizedRight
|
|
2229
|
+
};
|
|
1975
2230
|
}
|
|
1976
2231
|
transformCollectionPredicate(expr2, context) {
|
|
1977
|
-
|
|
2232
|
+
invariant6(expr2.op === "?" || expr2.op === "!" || expr2.op === "^", 'expected "?" or "!" or "^" operator');
|
|
1978
2233
|
if (this.isAuthCall(expr2.left) || this.isAuthMember(expr2.left)) {
|
|
1979
2234
|
const value = new ExpressionEvaluator().evaluate(expr2, {
|
|
1980
2235
|
auth: this.auth
|
|
1981
2236
|
});
|
|
1982
2237
|
return this.transformValue(value, "Boolean");
|
|
1983
2238
|
}
|
|
1984
|
-
|
|
2239
|
+
invariant6(ExpressionUtils.isField(expr2.left) || ExpressionUtils.isMember(expr2.left), "left operand must be field or member access");
|
|
1985
2240
|
let newContextModel;
|
|
1986
|
-
|
|
1987
|
-
|
|
2241
|
+
const fieldDef = this.getFieldDefFromFieldRef(expr2.left, context.model);
|
|
2242
|
+
if (fieldDef) {
|
|
2243
|
+
invariant6(fieldDef.relation, `field is not a relation: ${JSON.stringify(expr2.left)}`);
|
|
1988
2244
|
newContextModel = fieldDef.type;
|
|
1989
2245
|
} else {
|
|
1990
|
-
|
|
1991
|
-
const
|
|
1992
|
-
newContextModel =
|
|
2246
|
+
invariant6(ExpressionUtils.isMember(expr2.left) && ExpressionUtils.isField(expr2.left.receiver), "left operand must be member access with field receiver");
|
|
2247
|
+
const fieldDef2 = requireField(this.schema, context.model, expr2.left.receiver.field);
|
|
2248
|
+
newContextModel = fieldDef2.type;
|
|
1993
2249
|
for (const member of expr2.left.members) {
|
|
1994
2250
|
const memberDef = requireField(this.schema, newContextModel, member);
|
|
1995
2251
|
newContextModel = memberDef.type;
|
|
@@ -1998,11 +2254,10 @@ var ExpressionTransformer = class {
|
|
|
1998
2254
|
let predicateFilter = this.transform(expr2.right, {
|
|
1999
2255
|
...context,
|
|
2000
2256
|
model: newContextModel,
|
|
2001
|
-
alias: void 0
|
|
2002
|
-
thisEntity: void 0
|
|
2257
|
+
alias: void 0
|
|
2003
2258
|
});
|
|
2004
2259
|
if (expr2.op === "!") {
|
|
2005
|
-
predicateFilter = logicalNot(predicateFilter);
|
|
2260
|
+
predicateFilter = logicalNot(this.dialect, predicateFilter);
|
|
2006
2261
|
}
|
|
2007
2262
|
const count = FunctionNode2.create("count", [
|
|
2008
2263
|
ValueNode2.createImmediate(1)
|
|
@@ -2010,32 +2265,66 @@ var ExpressionTransformer = class {
|
|
|
2010
2265
|
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();
|
|
2011
2266
|
return this.transform(expr2.left, {
|
|
2012
2267
|
...context,
|
|
2013
|
-
memberSelect: SelectionNode.create(
|
|
2268
|
+
memberSelect: SelectionNode.create(AliasNode3.create(predicateResult, IdentifierNode.create("$t"))),
|
|
2014
2269
|
memberFilter: predicateFilter
|
|
2015
2270
|
});
|
|
2016
2271
|
}
|
|
2017
|
-
transformAuthBinary(expr2) {
|
|
2272
|
+
transformAuthBinary(expr2, context) {
|
|
2018
2273
|
if (expr2.op !== "==" && expr2.op !== "!=") {
|
|
2019
|
-
throw new
|
|
2274
|
+
throw new QueryError(`Unsupported operator for \`auth()\` in policy of model "${context.model}": ${expr2.op}`);
|
|
2020
2275
|
}
|
|
2276
|
+
let authExpr;
|
|
2021
2277
|
let other;
|
|
2022
2278
|
if (this.isAuthCall(expr2.left)) {
|
|
2279
|
+
authExpr = expr2.left;
|
|
2023
2280
|
other = expr2.right;
|
|
2024
2281
|
} else {
|
|
2282
|
+
authExpr = expr2.right;
|
|
2025
2283
|
other = expr2.left;
|
|
2026
2284
|
}
|
|
2027
2285
|
if (ExpressionUtils.isNull(other)) {
|
|
2028
2286
|
return this.transformValue(expr2.op === "==" ? !this.auth : !!this.auth, "Boolean");
|
|
2029
2287
|
} else {
|
|
2030
|
-
|
|
2288
|
+
const authModel = getModel(this.schema, this.authType);
|
|
2289
|
+
if (!authModel) {
|
|
2290
|
+
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`);
|
|
2291
|
+
}
|
|
2292
|
+
const idFields = Object.values(authModel.fields).filter((f) => f.id).map((f) => f.name);
|
|
2293
|
+
invariant6(idFields.length > 0, "auth type model must have at least one id field");
|
|
2294
|
+
const conditions = idFields.map((fieldName) => ExpressionUtils.binary(ExpressionUtils.member(authExpr, [
|
|
2295
|
+
fieldName
|
|
2296
|
+
]), "==", this.makeOrAppendMember(other, fieldName)));
|
|
2297
|
+
let result = this.buildAnd(conditions);
|
|
2298
|
+
if (expr2.op === "!=") {
|
|
2299
|
+
result = this.buildLogicalNot(result);
|
|
2300
|
+
}
|
|
2301
|
+
return this.transform(result, context);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
makeOrAppendMember(other, fieldName) {
|
|
2305
|
+
if (ExpressionUtils.isMember(other)) {
|
|
2306
|
+
return ExpressionUtils.member(other.receiver, [
|
|
2307
|
+
...other.members,
|
|
2308
|
+
fieldName
|
|
2309
|
+
]);
|
|
2310
|
+
} else {
|
|
2311
|
+
return ExpressionUtils.member(other, [
|
|
2312
|
+
fieldName
|
|
2313
|
+
]);
|
|
2031
2314
|
}
|
|
2032
2315
|
}
|
|
2033
2316
|
transformValue(value, type) {
|
|
2034
|
-
|
|
2317
|
+
if (value === true) {
|
|
2318
|
+
return trueNode(this.dialect);
|
|
2319
|
+
} else if (value === false) {
|
|
2320
|
+
return falseNode(this.dialect);
|
|
2321
|
+
} else {
|
|
2322
|
+
return ValueNode2.create(this.dialect.transformPrimitive(value, type, false) ?? null);
|
|
2323
|
+
}
|
|
2035
2324
|
}
|
|
2036
2325
|
_unary(expr2, context) {
|
|
2037
|
-
|
|
2038
|
-
return
|
|
2326
|
+
invariant6(expr2.op === "!", 'only "!" operator is supported');
|
|
2327
|
+
return logicalNot(this.dialect, this.transform(expr2.operand, context));
|
|
2039
2328
|
}
|
|
2040
2329
|
transformOperator(op) {
|
|
2041
2330
|
const mappedOp = match7(op).with("==", () => "=").otherwise(() => op);
|
|
@@ -2046,23 +2335,37 @@ var ExpressionTransformer = class {
|
|
|
2046
2335
|
return result.toOperationNode();
|
|
2047
2336
|
}
|
|
2048
2337
|
transformCall(expr2, context) {
|
|
2049
|
-
const func = this.
|
|
2338
|
+
const func = this.getFunctionImpl(expr2.function);
|
|
2050
2339
|
if (!func) {
|
|
2051
2340
|
throw new QueryError(`Function not implemented: ${expr2.function}`);
|
|
2052
2341
|
}
|
|
2053
2342
|
const eb = expressionBuilder2();
|
|
2054
2343
|
return func(eb, (expr2.args ?? []).map((arg) => this.transformCallArg(eb, arg, context)), {
|
|
2344
|
+
client: this.client,
|
|
2055
2345
|
dialect: this.dialect,
|
|
2056
2346
|
model: context.model,
|
|
2347
|
+
modelAlias: context.alias ?? context.model,
|
|
2057
2348
|
operation: context.operation
|
|
2058
2349
|
});
|
|
2059
2350
|
}
|
|
2351
|
+
getFunctionImpl(functionName) {
|
|
2352
|
+
let func = this.clientOptions.functions?.[functionName];
|
|
2353
|
+
if (!func) {
|
|
2354
|
+
for (const plugin of this.clientOptions.plugins ?? []) {
|
|
2355
|
+
if (plugin.functions?.[functionName]) {
|
|
2356
|
+
func = plugin.functions[functionName];
|
|
2357
|
+
break;
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
return func;
|
|
2362
|
+
}
|
|
2060
2363
|
transformCallArg(eb, arg, context) {
|
|
2061
2364
|
if (ExpressionUtils.isLiteral(arg)) {
|
|
2062
2365
|
return eb.val(arg.value);
|
|
2063
2366
|
}
|
|
2064
2367
|
if (ExpressionUtils.isField(arg)) {
|
|
2065
|
-
return
|
|
2368
|
+
return eb.ref(arg.field);
|
|
2066
2369
|
}
|
|
2067
2370
|
if (ExpressionUtils.isCall(arg)) {
|
|
2068
2371
|
return this.transformCall(arg, context);
|
|
@@ -2077,14 +2380,32 @@ var ExpressionTransformer = class {
|
|
|
2077
2380
|
if (this.isAuthCall(expr2.receiver)) {
|
|
2078
2381
|
return this.valueMemberAccess(this.auth, expr2, this.authType);
|
|
2079
2382
|
}
|
|
2080
|
-
|
|
2383
|
+
invariant6(ExpressionUtils.isField(expr2.receiver) || ExpressionUtils.isThis(expr2.receiver), 'expect receiver to be field expression or "this"');
|
|
2384
|
+
let members = expr2.members;
|
|
2385
|
+
let receiver;
|
|
2081
2386
|
const { memberFilter, memberSelect, ...restContext } = context;
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2387
|
+
if (ExpressionUtils.isThis(expr2.receiver)) {
|
|
2388
|
+
if (expr2.members.length === 1) {
|
|
2389
|
+
return this._field(ExpressionUtils.field(expr2.members[0]), context);
|
|
2390
|
+
} else {
|
|
2391
|
+
const firstMemberFieldDef = requireField(this.schema, context.model, expr2.members[0]);
|
|
2392
|
+
receiver = this.transformRelationAccess(expr2.members[0], firstMemberFieldDef.type, restContext);
|
|
2393
|
+
members = expr2.members.slice(1);
|
|
2394
|
+
}
|
|
2395
|
+
} else {
|
|
2396
|
+
receiver = this.transform(expr2.receiver, restContext);
|
|
2397
|
+
}
|
|
2398
|
+
invariant6(SelectQueryNode.is(receiver), "expected receiver to be select query");
|
|
2399
|
+
let startType;
|
|
2400
|
+
if (ExpressionUtils.isField(expr2.receiver)) {
|
|
2401
|
+
const receiverField = requireField(this.schema, context.model, expr2.receiver.field);
|
|
2402
|
+
startType = receiverField.type;
|
|
2403
|
+
} else {
|
|
2404
|
+
startType = context.model;
|
|
2405
|
+
}
|
|
2085
2406
|
const memberFields = [];
|
|
2086
|
-
let currType =
|
|
2087
|
-
for (const member of
|
|
2407
|
+
let currType = startType;
|
|
2408
|
+
for (const member of members) {
|
|
2088
2409
|
const fieldDef = requireField(this.schema, currType, member);
|
|
2089
2410
|
memberFields.push({
|
|
2090
2411
|
fieldDef,
|
|
@@ -2093,22 +2414,21 @@ var ExpressionTransformer = class {
|
|
|
2093
2414
|
currType = fieldDef.type;
|
|
2094
2415
|
}
|
|
2095
2416
|
let currNode = void 0;
|
|
2096
|
-
for (let i =
|
|
2097
|
-
const member =
|
|
2417
|
+
for (let i = members.length - 1; i >= 0; i--) {
|
|
2418
|
+
const member = members[i];
|
|
2098
2419
|
const { fieldDef, fromModel } = memberFields[i];
|
|
2099
2420
|
if (fieldDef.relation) {
|
|
2100
2421
|
const relation = this.transformRelationAccess(member, fieldDef.type, {
|
|
2101
2422
|
...restContext,
|
|
2102
2423
|
model: fromModel,
|
|
2103
|
-
alias: void 0
|
|
2104
|
-
thisEntity: void 0
|
|
2424
|
+
alias: void 0
|
|
2105
2425
|
});
|
|
2106
2426
|
if (currNode) {
|
|
2107
|
-
|
|
2427
|
+
invariant6(SelectQueryNode.is(currNode), "expected select query node");
|
|
2108
2428
|
currNode = {
|
|
2109
2429
|
...relation,
|
|
2110
2430
|
selections: [
|
|
2111
|
-
SelectionNode.create(
|
|
2431
|
+
SelectionNode.create(AliasNode3.create(currNode, IdentifierNode.create(members[i + 1])))
|
|
2112
2432
|
]
|
|
2113
2433
|
};
|
|
2114
2434
|
} else {
|
|
@@ -2121,15 +2441,15 @@ var ExpressionTransformer = class {
|
|
|
2121
2441
|
};
|
|
2122
2442
|
}
|
|
2123
2443
|
} else {
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
currNode =
|
|
2444
|
+
invariant6(i === members.length - 1, "plain field access must be the last segment");
|
|
2445
|
+
invariant6(!currNode, "plain field access must be the last segment");
|
|
2446
|
+
currNode = ColumnNode2.create(member);
|
|
2127
2447
|
}
|
|
2128
2448
|
}
|
|
2129
2449
|
return {
|
|
2130
2450
|
...receiver,
|
|
2131
2451
|
selections: [
|
|
2132
|
-
SelectionNode.create(
|
|
2452
|
+
SelectionNode.create(AliasNode3.create(currNode, IdentifierNode.create("$t")))
|
|
2133
2453
|
]
|
|
2134
2454
|
};
|
|
2135
2455
|
}
|
|
@@ -2146,40 +2466,33 @@ var ExpressionTransformer = class {
|
|
|
2146
2466
|
return this.transformValue(fieldValue, fieldDef.type);
|
|
2147
2467
|
}
|
|
2148
2468
|
transformRelationAccess(field, relationModel, context) {
|
|
2469
|
+
const m2m = getManyToManyRelation(this.schema, context.model, field);
|
|
2470
|
+
if (m2m) {
|
|
2471
|
+
return this.transformManyToManyRelationAccess(m2m, context);
|
|
2472
|
+
}
|
|
2149
2473
|
const fromModel = context.model;
|
|
2150
2474
|
const { keyPairs, ownedByModel } = getRelationForeignKeyFieldPairs(this.schema, fromModel, field);
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
condition = conjunction(this.dialect, keyPairs.map(({ fk, pk }) => BinaryOperationNode2.create(ReferenceNode2.create(ColumnNode.create(pk), TableNode2.create(relationModel)), OperatorNode2.create("="), context.thisEntity[fk])));
|
|
2155
|
-
} else {
|
|
2156
|
-
condition = conjunction(this.dialect, keyPairs.map(({ fk, pk }) => BinaryOperationNode2.create(ReferenceNode2.create(ColumnNode.create(fk), TableNode2.create(relationModel)), OperatorNode2.create("="), context.thisEntity[pk])));
|
|
2157
|
-
}
|
|
2158
|
-
return {
|
|
2159
|
-
kind: "SelectQueryNode",
|
|
2160
|
-
from: FromNode.create([
|
|
2161
|
-
TableNode2.create(relationModel)
|
|
2162
|
-
]),
|
|
2163
|
-
where: WhereNode.create(condition)
|
|
2164
|
-
};
|
|
2475
|
+
let condition;
|
|
2476
|
+
if (ownedByModel) {
|
|
2477
|
+
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)))));
|
|
2165
2478
|
} else {
|
|
2166
|
-
|
|
2167
|
-
if (ownedByModel) {
|
|
2168
|
-
condition = conjunction(this.dialect, keyPairs.map(({ fk, pk }) => BinaryOperationNode2.create(ReferenceNode2.create(ColumnNode.create(fk), TableNode2.create(context.alias ?? fromModel)), OperatorNode2.create("="), ReferenceNode2.create(ColumnNode.create(pk), TableNode2.create(relationModel)))));
|
|
2169
|
-
} else {
|
|
2170
|
-
condition = conjunction(this.dialect, keyPairs.map(({ fk, pk }) => BinaryOperationNode2.create(ReferenceNode2.create(ColumnNode.create(pk), TableNode2.create(context.alias ?? fromModel)), OperatorNode2.create("="), ReferenceNode2.create(ColumnNode.create(fk), TableNode2.create(relationModel)))));
|
|
2171
|
-
}
|
|
2172
|
-
return {
|
|
2173
|
-
kind: "SelectQueryNode",
|
|
2174
|
-
from: FromNode.create([
|
|
2175
|
-
TableNode2.create(relationModel)
|
|
2176
|
-
]),
|
|
2177
|
-
where: WhereNode.create(condition)
|
|
2178
|
-
};
|
|
2479
|
+
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)))));
|
|
2179
2480
|
}
|
|
2481
|
+
return {
|
|
2482
|
+
kind: "SelectQueryNode",
|
|
2483
|
+
from: FromNode.create([
|
|
2484
|
+
TableNode3.create(relationModel)
|
|
2485
|
+
]),
|
|
2486
|
+
where: WhereNode.create(condition)
|
|
2487
|
+
};
|
|
2488
|
+
}
|
|
2489
|
+
transformManyToManyRelationAccess(m2m, context) {
|
|
2490
|
+
const eb = expressionBuilder2();
|
|
2491
|
+
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}`));
|
|
2492
|
+
return relationQuery.toOperationNode();
|
|
2180
2493
|
}
|
|
2181
2494
|
createColumnRef(column, context) {
|
|
2182
|
-
return
|
|
2495
|
+
return ReferenceNode3.create(ColumnNode2.create(column), TableNode3.create(context.alias ?? context.model));
|
|
2183
2496
|
}
|
|
2184
2497
|
isAuthCall(value) {
|
|
2185
2498
|
return ExpressionUtils.isCall(value) && value.function === "auth";
|
|
@@ -2190,6 +2503,31 @@ var ExpressionTransformer = class {
|
|
|
2190
2503
|
isNullNode(node) {
|
|
2191
2504
|
return ValueNode2.is(node) && node.value === null;
|
|
2192
2505
|
}
|
|
2506
|
+
buildLogicalNot(result) {
|
|
2507
|
+
return ExpressionUtils.unary("!", result);
|
|
2508
|
+
}
|
|
2509
|
+
buildAnd(conditions) {
|
|
2510
|
+
if (conditions.length === 0) {
|
|
2511
|
+
return ExpressionUtils.literal(true);
|
|
2512
|
+
} else if (conditions.length === 1) {
|
|
2513
|
+
return conditions[0];
|
|
2514
|
+
} else {
|
|
2515
|
+
return conditions.reduce((acc, condition) => ExpressionUtils.binary(acc, "&&", condition));
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
isRelationField(expr2, model) {
|
|
2519
|
+
const fieldDef = this.getFieldDefFromFieldRef(expr2, model);
|
|
2520
|
+
return !!fieldDef?.relation;
|
|
2521
|
+
}
|
|
2522
|
+
getFieldDefFromFieldRef(expr2, model) {
|
|
2523
|
+
if (ExpressionUtils.isField(expr2)) {
|
|
2524
|
+
return requireField(this.schema, model, expr2.field);
|
|
2525
|
+
} else if (ExpressionUtils.isMember(expr2) && expr2.members.length === 1 && ExpressionUtils.isThis(expr2.receiver)) {
|
|
2526
|
+
return requireField(this.schema, model, expr2.members[0]);
|
|
2527
|
+
} else {
|
|
2528
|
+
return void 0;
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2193
2531
|
};
|
|
2194
2532
|
_ts_decorate([
|
|
2195
2533
|
expr("literal"),
|
|
@@ -2276,103 +2614,288 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
2276
2614
|
}
|
|
2277
2615
|
async handle(node, proceed) {
|
|
2278
2616
|
if (!this.isCrudQueryNode(node)) {
|
|
2279
|
-
throw new RejectedByPolicyError(void 0, "non-CRUD queries are not allowed");
|
|
2617
|
+
throw new RejectedByPolicyError(void 0, RejectedByPolicyReason.OTHER, "non-CRUD queries are not allowed");
|
|
2280
2618
|
}
|
|
2281
2619
|
if (!this.isMutationQueryNode(node)) {
|
|
2282
2620
|
return proceed(this.transformNode(node));
|
|
2283
2621
|
}
|
|
2284
|
-
|
|
2285
|
-
const mutationModel = this.getMutationModel(node);
|
|
2622
|
+
const { mutationModel } = this.getMutationModel(node);
|
|
2286
2623
|
if (InsertQueryNode.is(node)) {
|
|
2287
|
-
const
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2624
|
+
const isManyToManyJoinTable = this.isManyToManyJoinTable(mutationModel);
|
|
2625
|
+
let needCheckPreCreate = true;
|
|
2626
|
+
if (!isManyToManyJoinTable) {
|
|
2627
|
+
const constCondition = this.tryGetConstantPolicy(mutationModel, "create");
|
|
2628
|
+
if (constCondition === true) {
|
|
2629
|
+
needCheckPreCreate = false;
|
|
2630
|
+
} else if (constCondition === false) {
|
|
2631
|
+
throw new RejectedByPolicyError(mutationModel);
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
if (needCheckPreCreate) {
|
|
2635
|
+
await this.enforcePreCreatePolicy(node, mutationModel, isManyToManyJoinTable, proceed);
|
|
2292
2636
|
}
|
|
2293
2637
|
}
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
await this.enforcePreCreatePolicy(node, proceed);
|
|
2299
|
-
}
|
|
2300
|
-
const transformedNode = this.transformNode(node);
|
|
2301
|
-
const result = await proceed(transformedNode);
|
|
2302
|
-
if (!this.onlyReturningId(node)) {
|
|
2638
|
+
const result = await proceed(this.transformNode(node));
|
|
2639
|
+
if (!node.returning || this.onlyReturningId(node)) {
|
|
2640
|
+
return result;
|
|
2641
|
+
} else {
|
|
2303
2642
|
const readBackResult = await this.processReadBack(node, result, proceed);
|
|
2304
2643
|
if (readBackResult.rows.length !== result.rows.length) {
|
|
2305
|
-
throw new RejectedByPolicyError(mutationModel, "result is not allowed to be read back");
|
|
2644
|
+
throw new RejectedByPolicyError(mutationModel, RejectedByPolicyReason.CANNOT_READ_BACK, "result is not allowed to be read back");
|
|
2306
2645
|
}
|
|
2307
2646
|
return readBackResult;
|
|
2308
|
-
}
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
// #region overrides
|
|
2650
|
+
transformSelectQuery(node) {
|
|
2651
|
+
let whereNode = this.transformNode(node.where);
|
|
2652
|
+
const policyFilter = this.createPolicyFilterForFrom(node.from);
|
|
2653
|
+
if (policyFilter) {
|
|
2654
|
+
whereNode = WhereNode2.create(whereNode?.where ? conjunction(this.dialect, [
|
|
2655
|
+
whereNode.where,
|
|
2656
|
+
policyFilter
|
|
2657
|
+
]) : policyFilter);
|
|
2658
|
+
}
|
|
2659
|
+
const baseResult = super.transformSelectQuery({
|
|
2660
|
+
...node,
|
|
2661
|
+
where: void 0
|
|
2662
|
+
});
|
|
2663
|
+
return {
|
|
2664
|
+
...baseResult,
|
|
2665
|
+
where: whereNode
|
|
2666
|
+
};
|
|
2667
|
+
}
|
|
2668
|
+
transformJoin(node) {
|
|
2669
|
+
const table = this.extractTableName(node.table);
|
|
2670
|
+
if (!table) {
|
|
2671
|
+
return super.transformJoin(node);
|
|
2672
|
+
}
|
|
2673
|
+
const filter = this.buildPolicyFilter(table.model, table.alias, "read");
|
|
2674
|
+
const nestedSelect = {
|
|
2675
|
+
kind: "SelectQueryNode",
|
|
2676
|
+
from: FromNode2.create([
|
|
2677
|
+
node.table
|
|
2678
|
+
]),
|
|
2679
|
+
selections: [
|
|
2680
|
+
SelectionNode2.createSelectAll()
|
|
2681
|
+
],
|
|
2682
|
+
where: WhereNode2.create(filter)
|
|
2683
|
+
};
|
|
2684
|
+
return {
|
|
2685
|
+
...node,
|
|
2686
|
+
table: AliasNode4.create(ParensNode2.create(nestedSelect), IdentifierNode2.create(table.alias ?? table.model))
|
|
2687
|
+
};
|
|
2688
|
+
}
|
|
2689
|
+
transformInsertQuery(node) {
|
|
2690
|
+
let onConflict = node.onConflict;
|
|
2691
|
+
if (onConflict?.updates) {
|
|
2692
|
+
const { mutationModel, alias } = this.getMutationModel(node);
|
|
2693
|
+
const filter = this.buildPolicyFilter(mutationModel, alias, "update");
|
|
2694
|
+
if (onConflict.updateWhere) {
|
|
2695
|
+
onConflict = {
|
|
2696
|
+
...onConflict,
|
|
2697
|
+
updateWhere: WhereNode2.create(conjunction(this.dialect, [
|
|
2698
|
+
onConflict.updateWhere.where,
|
|
2699
|
+
filter
|
|
2700
|
+
]))
|
|
2701
|
+
};
|
|
2702
|
+
} else {
|
|
2703
|
+
onConflict = {
|
|
2704
|
+
...onConflict,
|
|
2705
|
+
updateWhere: WhereNode2.create(filter)
|
|
2706
|
+
};
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
const processedNode = onConflict ? {
|
|
2710
|
+
...node,
|
|
2711
|
+
onConflict
|
|
2712
|
+
} : node;
|
|
2713
|
+
const result = super.transformInsertQuery(processedNode);
|
|
2714
|
+
if (!node.returning) {
|
|
2715
|
+
return result;
|
|
2716
|
+
}
|
|
2717
|
+
if (this.onlyReturningId(node)) {
|
|
2309
2718
|
return result;
|
|
2719
|
+
} else {
|
|
2720
|
+
const { mutationModel } = this.getMutationModel(node);
|
|
2721
|
+
const idFields = requireIdFields(this.client.$schema, mutationModel);
|
|
2722
|
+
return {
|
|
2723
|
+
...result,
|
|
2724
|
+
returning: ReturningNode.create(idFields.map((field) => SelectionNode2.create(ColumnNode3.create(field))))
|
|
2725
|
+
};
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
transformUpdateQuery(node) {
|
|
2729
|
+
const result = super.transformUpdateQuery(node);
|
|
2730
|
+
const { mutationModel, alias } = this.getMutationModel(node);
|
|
2731
|
+
let filter = this.buildPolicyFilter(mutationModel, alias, "update");
|
|
2732
|
+
if (node.from) {
|
|
2733
|
+
const joinFilter = this.createPolicyFilterForFrom(node.from);
|
|
2734
|
+
if (joinFilter) {
|
|
2735
|
+
filter = conjunction(this.dialect, [
|
|
2736
|
+
filter,
|
|
2737
|
+
joinFilter
|
|
2738
|
+
]);
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
return {
|
|
2742
|
+
...result,
|
|
2743
|
+
where: WhereNode2.create(result.where ? conjunction(this.dialect, [
|
|
2744
|
+
result.where.where,
|
|
2745
|
+
filter
|
|
2746
|
+
]) : filter)
|
|
2747
|
+
};
|
|
2748
|
+
}
|
|
2749
|
+
transformDeleteQuery(node) {
|
|
2750
|
+
const result = super.transformDeleteQuery(node);
|
|
2751
|
+
const { mutationModel, alias } = this.getMutationModel(node);
|
|
2752
|
+
let filter = this.buildPolicyFilter(mutationModel, alias, "delete");
|
|
2753
|
+
if (node.using) {
|
|
2754
|
+
const joinFilter = this.createPolicyFilterForTables(node.using.tables);
|
|
2755
|
+
if (joinFilter) {
|
|
2756
|
+
filter = conjunction(this.dialect, [
|
|
2757
|
+
filter,
|
|
2758
|
+
joinFilter
|
|
2759
|
+
]);
|
|
2760
|
+
}
|
|
2310
2761
|
}
|
|
2762
|
+
return {
|
|
2763
|
+
...result,
|
|
2764
|
+
where: WhereNode2.create(result.where ? conjunction(this.dialect, [
|
|
2765
|
+
result.where.where,
|
|
2766
|
+
filter
|
|
2767
|
+
]) : filter)
|
|
2768
|
+
};
|
|
2311
2769
|
}
|
|
2770
|
+
// #endregion
|
|
2771
|
+
// #region helpers
|
|
2312
2772
|
onlyReturningId(node) {
|
|
2313
2773
|
if (!node.returning) {
|
|
2314
2774
|
return true;
|
|
2315
2775
|
}
|
|
2316
|
-
const
|
|
2776
|
+
const { mutationModel } = this.getMutationModel(node);
|
|
2777
|
+
const idFields = requireIdFields(this.client.$schema, mutationModel);
|
|
2317
2778
|
const collector = new ColumnCollector();
|
|
2318
2779
|
const selectedColumns = collector.collect(node.returning);
|
|
2319
2780
|
return selectedColumns.every((c) => idFields.includes(c));
|
|
2320
2781
|
}
|
|
2321
|
-
async enforcePreCreatePolicy(node, proceed) {
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
const fields = node.columns.map((c) => c.column.name);
|
|
2327
|
-
const valueRows = this.unwrapCreateValueRows(node.values, model, fields);
|
|
2782
|
+
async enforcePreCreatePolicy(node, mutationModel, isManyToManyJoinTable, proceed) {
|
|
2783
|
+
const fields = node.columns?.map((c) => c.column.name) ?? [];
|
|
2784
|
+
const valueRows = node.values ? this.unwrapCreateValueRows(node.values, mutationModel, fields, isManyToManyJoinTable) : [
|
|
2785
|
+
[]
|
|
2786
|
+
];
|
|
2328
2787
|
for (const values of valueRows) {
|
|
2329
|
-
|
|
2788
|
+
if (isManyToManyJoinTable) {
|
|
2789
|
+
await this.enforcePreCreatePolicyForManyToManyJoinTable(mutationModel, fields, values.map((v) => v.node), proceed);
|
|
2790
|
+
} else {
|
|
2791
|
+
await this.enforcePreCreatePolicyForOne(mutationModel, fields, values.map((v) => v.node), proceed);
|
|
2792
|
+
}
|
|
2330
2793
|
}
|
|
2331
2794
|
}
|
|
2332
|
-
async
|
|
2333
|
-
const
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2795
|
+
async enforcePreCreatePolicyForManyToManyJoinTable(tableName, fields, values, proceed) {
|
|
2796
|
+
const m2m = this.resolveManyToManyJoinTable(tableName);
|
|
2797
|
+
invariant7(m2m);
|
|
2798
|
+
invariant7(fields.includes("A") && fields.includes("B"), "many-to-many join table must have A and B fk fields");
|
|
2799
|
+
const aIndex = fields.indexOf("A");
|
|
2800
|
+
const aNode = values[aIndex];
|
|
2801
|
+
const bIndex = fields.indexOf("B");
|
|
2802
|
+
const bNode = values[bIndex];
|
|
2803
|
+
invariant7(ValueNode3.is(aNode) && ValueNode3.is(bNode), "A and B values must be ValueNode");
|
|
2804
|
+
const aValue = aNode.value;
|
|
2805
|
+
const bValue = bNode.value;
|
|
2806
|
+
invariant7(aValue !== null && aValue !== void 0, "A value cannot be null or undefined");
|
|
2807
|
+
invariant7(bValue !== null && bValue !== void 0, "B value cannot be null or undefined");
|
|
2808
|
+
const eb = expressionBuilder3();
|
|
2809
|
+
const filterA = this.buildPolicyFilter(m2m.firstModel, void 0, "update");
|
|
2810
|
+
const queryA = eb.selectFrom(m2m.firstModel).where(eb(eb.ref(`${m2m.firstModel}.${m2m.firstIdField}`), "=", aValue)).select(() => new ExpressionWrapper(filterA).as("$t"));
|
|
2811
|
+
const filterB = this.buildPolicyFilter(m2m.secondModel, void 0, "update");
|
|
2812
|
+
const queryB = eb.selectFrom(m2m.secondModel).where(eb(eb.ref(`${m2m.secondModel}.${m2m.secondIdField}`), "=", bValue)).select(() => new ExpressionWrapper(filterB).as("$t"));
|
|
2813
|
+
const queryNode = {
|
|
2814
|
+
kind: "SelectQueryNode",
|
|
2815
|
+
selections: [
|
|
2816
|
+
SelectionNode2.create(AliasNode4.create(queryA.toOperationNode(), IdentifierNode2.create("$conditionA"))),
|
|
2817
|
+
SelectionNode2.create(AliasNode4.create(queryB.toOperationNode(), IdentifierNode2.create("$conditionB")))
|
|
2818
|
+
]
|
|
2819
|
+
};
|
|
2820
|
+
const result = await proceed(queryNode);
|
|
2821
|
+
if (!result.rows[0]?.$conditionA) {
|
|
2822
|
+
throw new RejectedByPolicyError(m2m.firstModel, RejectedByPolicyReason.CANNOT_READ_BACK, `many-to-many relation participant model "${m2m.firstModel}" not updatable`);
|
|
2823
|
+
}
|
|
2824
|
+
if (!result.rows[0]?.$conditionB) {
|
|
2825
|
+
throw new RejectedByPolicyError(m2m.secondModel, RejectedByPolicyReason.NO_ACCESS, `many-to-many relation participant model "${m2m.secondModel}" not updatable`);
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
async enforcePreCreatePolicyForOne(model, fields, values, proceed) {
|
|
2829
|
+
const allFields = Object.entries(requireModel(this.client.$schema, model).fields).filter(([, def]) => !def.relation);
|
|
2830
|
+
const allValues = [];
|
|
2831
|
+
for (const [name, _def] of allFields) {
|
|
2832
|
+
const index = fields.indexOf(name);
|
|
2833
|
+
if (index >= 0) {
|
|
2834
|
+
allValues.push(values[index]);
|
|
2835
|
+
} else {
|
|
2836
|
+
allValues.push(ValueNode3.createImmediate(null));
|
|
2837
|
+
}
|
|
2338
2838
|
}
|
|
2339
|
-
const
|
|
2839
|
+
const eb = expressionBuilder3();
|
|
2840
|
+
const constTable = {
|
|
2841
|
+
kind: "SelectQueryNode",
|
|
2842
|
+
from: FromNode2.create([
|
|
2843
|
+
AliasNode4.create(ParensNode2.create(ValuesNode.create([
|
|
2844
|
+
ValueListNode2.create(allValues)
|
|
2845
|
+
])), IdentifierNode2.create("$t"))
|
|
2846
|
+
]),
|
|
2847
|
+
selections: allFields.map(([name, def], index) => {
|
|
2848
|
+
const castedColumnRef = sql4`CAST(${eb.ref(`column${index + 1}`)} as ${sql4.raw(this.dialect.getFieldSqlType(def))})`.as(name);
|
|
2849
|
+
return SelectionNode2.create(castedColumnRef.toOperationNode());
|
|
2850
|
+
})
|
|
2851
|
+
};
|
|
2852
|
+
const filter = this.buildPolicyFilter(model, void 0, "create");
|
|
2340
2853
|
const preCreateCheck = {
|
|
2341
2854
|
kind: "SelectQueryNode",
|
|
2855
|
+
from: FromNode2.create([
|
|
2856
|
+
AliasNode4.create(constTable, IdentifierNode2.create(model))
|
|
2857
|
+
]),
|
|
2342
2858
|
selections: [
|
|
2343
|
-
SelectionNode2.create(
|
|
2344
|
-
|
|
2859
|
+
SelectionNode2.create(AliasNode4.create(BinaryOperationNode3.create(FunctionNode3.create("COUNT", [
|
|
2860
|
+
ValueNode3.createImmediate(1)
|
|
2861
|
+
]), OperatorNode3.create(">"), ValueNode3.createImmediate(0)), IdentifierNode2.create("$condition")))
|
|
2862
|
+
],
|
|
2863
|
+
where: WhereNode2.create(filter)
|
|
2345
2864
|
};
|
|
2346
2865
|
const result = await proceed(preCreateCheck);
|
|
2347
2866
|
if (!result.rows[0]?.$condition) {
|
|
2348
2867
|
throw new RejectedByPolicyError(model);
|
|
2349
2868
|
}
|
|
2350
2869
|
}
|
|
2351
|
-
unwrapCreateValueRows(node, model, fields) {
|
|
2870
|
+
unwrapCreateValueRows(node, model, fields, isManyToManyJoinTable) {
|
|
2352
2871
|
if (ValuesNode.is(node)) {
|
|
2353
|
-
return node.values.map((v) => this.unwrapCreateValueRow(v.values, model, fields));
|
|
2872
|
+
return node.values.map((v) => this.unwrapCreateValueRow(v.values, model, fields, isManyToManyJoinTable));
|
|
2354
2873
|
} else if (PrimitiveValueListNode.is(node)) {
|
|
2355
2874
|
return [
|
|
2356
|
-
this.unwrapCreateValueRow(node.values, model, fields)
|
|
2875
|
+
this.unwrapCreateValueRow(node.values, model, fields, isManyToManyJoinTable)
|
|
2357
2876
|
];
|
|
2358
2877
|
} else {
|
|
2359
2878
|
throw new InternalError(`Unexpected node kind: ${node.kind} for unwrapping create values`);
|
|
2360
2879
|
}
|
|
2361
2880
|
}
|
|
2362
|
-
unwrapCreateValueRow(data, model, fields) {
|
|
2363
|
-
|
|
2881
|
+
unwrapCreateValueRow(data, model, fields, isImplicitManyToManyJoinTable) {
|
|
2882
|
+
invariant7(data.length === fields.length, "data length must match fields length");
|
|
2364
2883
|
const result = [];
|
|
2365
2884
|
for (let i = 0; i < data.length; i++) {
|
|
2366
2885
|
const item = data[i];
|
|
2367
|
-
const fieldDef = requireField(this.client.$schema, model, fields[i]);
|
|
2368
2886
|
if (typeof item === "object" && item && "kind" in item) {
|
|
2369
|
-
|
|
2887
|
+
const fieldDef = requireField(this.client.$schema, model, fields[i]);
|
|
2888
|
+
invariant7(item.kind === "ValueNode", "expecting a ValueNode");
|
|
2370
2889
|
result.push({
|
|
2371
2890
|
node: ValueNode3.create(this.dialect.transformPrimitive(item.value, fieldDef.type, !!fieldDef.array)),
|
|
2372
2891
|
raw: item.value
|
|
2373
2892
|
});
|
|
2374
2893
|
} else {
|
|
2375
|
-
|
|
2894
|
+
let value = item;
|
|
2895
|
+
if (!isImplicitManyToManyJoinTable) {
|
|
2896
|
+
const fieldDef = requireField(this.client.$schema, model, fields[i]);
|
|
2897
|
+
value = this.dialect.transformPrimitive(item, fieldDef.type, !!fieldDef.array);
|
|
2898
|
+
}
|
|
2376
2899
|
if (Array.isArray(value)) {
|
|
2377
2900
|
result.push({
|
|
2378
2901
|
node: RawNode.createWithSql(this.dialect.buildArrayLiteralSQL(value)),
|
|
@@ -2416,16 +2939,13 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
2416
2939
|
if (!this.isMutationQueryNode(node) || !node.returning) {
|
|
2417
2940
|
return result;
|
|
2418
2941
|
}
|
|
2419
|
-
const
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
}
|
|
2423
|
-
const idConditions = this.buildIdConditions(table, result.rows);
|
|
2424
|
-
const policyFilter = this.buildPolicyFilter(table, void 0, "read");
|
|
2942
|
+
const { mutationModel } = this.getMutationModel(node);
|
|
2943
|
+
const idConditions = this.buildIdConditions(mutationModel, result.rows);
|
|
2944
|
+
const policyFilter = this.buildPolicyFilter(mutationModel, void 0, "read");
|
|
2425
2945
|
const select = {
|
|
2426
2946
|
kind: "SelectQueryNode",
|
|
2427
2947
|
from: FromNode2.create([
|
|
2428
|
-
|
|
2948
|
+
TableNode4.create(mutationModel)
|
|
2429
2949
|
]),
|
|
2430
2950
|
where: WhereNode2.create(conjunction(this.dialect, [
|
|
2431
2951
|
idConditions,
|
|
@@ -2437,15 +2957,31 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
2437
2957
|
return selectResult;
|
|
2438
2958
|
}
|
|
2439
2959
|
buildIdConditions(table, rows) {
|
|
2440
|
-
const idFields =
|
|
2441
|
-
return disjunction(this.dialect, rows.map((row) => conjunction(this.dialect, idFields.map((field) => BinaryOperationNode3.create(
|
|
2960
|
+
const idFields = requireIdFields(this.client.$schema, table);
|
|
2961
|
+
return disjunction(this.dialect, rows.map((row) => conjunction(this.dialect, idFields.map((field) => BinaryOperationNode3.create(ColumnNode3.create(field), OperatorNode3.create("="), ValueNode3.create(row[field]))))));
|
|
2442
2962
|
}
|
|
2443
2963
|
getMutationModel(node) {
|
|
2444
|
-
const r = match8(node).when(InsertQueryNode.is, (node2) =>
|
|
2964
|
+
const r = match8(node).when(InsertQueryNode.is, (node2) => ({
|
|
2965
|
+
mutationModel: getTableName(node2.into),
|
|
2966
|
+
alias: void 0
|
|
2967
|
+
})).when(UpdateQueryNode.is, (node2) => {
|
|
2968
|
+
if (!node2.table) {
|
|
2969
|
+
throw new QueryError("Update query must have a table");
|
|
2970
|
+
}
|
|
2971
|
+
const r2 = this.extractTableName(node2.table);
|
|
2972
|
+
return r2 ? {
|
|
2973
|
+
mutationModel: r2.model,
|
|
2974
|
+
alias: r2.alias
|
|
2975
|
+
} : void 0;
|
|
2976
|
+
}).when(DeleteQueryNode.is, (node2) => {
|
|
2445
2977
|
if (node2.from.froms.length !== 1) {
|
|
2446
|
-
throw new
|
|
2978
|
+
throw new QueryError("Only one from table is supported for delete");
|
|
2447
2979
|
}
|
|
2448
|
-
|
|
2980
|
+
const r2 = this.extractTableName(node2.from.froms[0]);
|
|
2981
|
+
return r2 ? {
|
|
2982
|
+
mutationModel: r2.model,
|
|
2983
|
+
alias: r2.alias
|
|
2984
|
+
} : void 0;
|
|
2449
2985
|
}).exhaustive();
|
|
2450
2986
|
if (!r) {
|
|
2451
2987
|
throw new InternalError(`Unable to get table name for query node: ${node}`);
|
|
@@ -2458,13 +2994,17 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
2458
2994
|
isMutationQueryNode(node) {
|
|
2459
2995
|
return InsertQueryNode.is(node) || UpdateQueryNode.is(node) || DeleteQueryNode.is(node);
|
|
2460
2996
|
}
|
|
2461
|
-
buildPolicyFilter(model, alias, operation
|
|
2997
|
+
buildPolicyFilter(model, alias, operation) {
|
|
2998
|
+
const m2mFilter = this.getModelPolicyFilterForManyToManyJoinTable(model, alias, operation);
|
|
2999
|
+
if (m2mFilter) {
|
|
3000
|
+
return m2mFilter;
|
|
3001
|
+
}
|
|
2462
3002
|
const policies = this.getModelPolicies(model, operation);
|
|
2463
3003
|
if (policies.length === 0) {
|
|
2464
3004
|
return falseNode(this.dialect);
|
|
2465
3005
|
}
|
|
2466
|
-
const allows = policies.filter((policy) => policy.kind === "allow").map((policy) => this.
|
|
2467
|
-
const denies = policies.filter((policy) => policy.kind === "deny").map((policy) => this.
|
|
3006
|
+
const allows = policies.filter((policy) => policy.kind === "allow").map((policy) => this.compilePolicyCondition(model, alias, operation, policy));
|
|
3007
|
+
const denies = policies.filter((policy) => policy.kind === "deny").map((policy) => this.compilePolicyCondition(model, alias, operation, policy));
|
|
2468
3008
|
let combinedPolicy;
|
|
2469
3009
|
if (allows.length === 0) {
|
|
2470
3010
|
combinedPolicy = falseNode(this.dialect);
|
|
@@ -2480,102 +3020,59 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
2480
3020
|
}
|
|
2481
3021
|
return combinedPolicy;
|
|
2482
3022
|
}
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
node.from?.froms.forEach((from) => {
|
|
2486
|
-
const extractResult = this.extractTableName(from);
|
|
2487
|
-
if (extractResult) {
|
|
2488
|
-
const { model, alias } = extractResult;
|
|
2489
|
-
const filter = this.buildPolicyFilter(model, alias, "read");
|
|
2490
|
-
whereNode = WhereNode2.create(whereNode?.where ? conjunction(this.dialect, [
|
|
2491
|
-
whereNode.where,
|
|
2492
|
-
filter
|
|
2493
|
-
]) : filter);
|
|
2494
|
-
}
|
|
2495
|
-
});
|
|
2496
|
-
const baseResult = super.transformSelectQuery({
|
|
2497
|
-
...node,
|
|
2498
|
-
where: void 0
|
|
2499
|
-
});
|
|
2500
|
-
return {
|
|
2501
|
-
...baseResult,
|
|
2502
|
-
where: whereNode
|
|
2503
|
-
};
|
|
2504
|
-
}
|
|
2505
|
-
transformInsertQuery(node) {
|
|
2506
|
-
const result = super.transformInsertQuery(node);
|
|
2507
|
-
if (!node.returning) {
|
|
2508
|
-
return result;
|
|
2509
|
-
}
|
|
2510
|
-
if (this.onlyReturningId(node)) {
|
|
2511
|
-
return result;
|
|
2512
|
-
} else {
|
|
2513
|
-
const idFields = getIdFields(this.client.$schema, this.getMutationModel(node));
|
|
2514
|
-
return {
|
|
2515
|
-
...result,
|
|
2516
|
-
returning: ReturningNode.create(idFields.map((field) => SelectionNode2.create(ColumnNode2.create(field))))
|
|
2517
|
-
};
|
|
2518
|
-
}
|
|
2519
|
-
}
|
|
2520
|
-
transformUpdateQuery(node) {
|
|
2521
|
-
const result = super.transformUpdateQuery(node);
|
|
2522
|
-
const mutationModel = this.getMutationModel(node);
|
|
2523
|
-
const filter = this.buildPolicyFilter(mutationModel, void 0, "update");
|
|
2524
|
-
return {
|
|
2525
|
-
...result,
|
|
2526
|
-
where: WhereNode2.create(result.where ? conjunction(this.dialect, [
|
|
2527
|
-
result.where.where,
|
|
2528
|
-
filter
|
|
2529
|
-
]) : filter)
|
|
2530
|
-
};
|
|
2531
|
-
}
|
|
2532
|
-
transformDeleteQuery(node) {
|
|
2533
|
-
const result = super.transformDeleteQuery(node);
|
|
2534
|
-
const mutationModel = this.getMutationModel(node);
|
|
2535
|
-
const filter = this.buildPolicyFilter(mutationModel, void 0, "delete");
|
|
2536
|
-
return {
|
|
2537
|
-
...result,
|
|
2538
|
-
where: WhereNode2.create(result.where ? conjunction(this.dialect, [
|
|
2539
|
-
result.where.where,
|
|
2540
|
-
filter
|
|
2541
|
-
]) : filter)
|
|
2542
|
-
};
|
|
2543
|
-
}
|
|
2544
|
-
extractTableName(from) {
|
|
2545
|
-
if (TableNode3.is(from)) {
|
|
3023
|
+
extractTableName(node) {
|
|
3024
|
+
if (TableNode4.is(node)) {
|
|
2546
3025
|
return {
|
|
2547
|
-
model:
|
|
3026
|
+
model: node.table.identifier.name
|
|
2548
3027
|
};
|
|
2549
3028
|
}
|
|
2550
|
-
if (
|
|
2551
|
-
const inner = this.extractTableName(
|
|
3029
|
+
if (AliasNode4.is(node)) {
|
|
3030
|
+
const inner = this.extractTableName(node.node);
|
|
2552
3031
|
if (!inner) {
|
|
2553
3032
|
return void 0;
|
|
2554
3033
|
}
|
|
2555
3034
|
return {
|
|
2556
3035
|
model: inner.model,
|
|
2557
|
-
alias: IdentifierNode2.is(
|
|
3036
|
+
alias: IdentifierNode2.is(node.alias) ? node.alias.name : void 0
|
|
2558
3037
|
};
|
|
2559
3038
|
} else {
|
|
2560
3039
|
return void 0;
|
|
2561
3040
|
}
|
|
2562
3041
|
}
|
|
2563
|
-
|
|
2564
|
-
|
|
3042
|
+
createPolicyFilterForFrom(node) {
|
|
3043
|
+
if (!node) {
|
|
3044
|
+
return void 0;
|
|
3045
|
+
}
|
|
3046
|
+
return this.createPolicyFilterForTables(node.froms);
|
|
3047
|
+
}
|
|
3048
|
+
createPolicyFilterForTables(tables) {
|
|
3049
|
+
return tables.reduce((acc, table) => {
|
|
3050
|
+
const extractResult = this.extractTableName(table);
|
|
3051
|
+
if (extractResult) {
|
|
3052
|
+
const { model, alias } = extractResult;
|
|
3053
|
+
const filter = this.buildPolicyFilter(model, alias, "read");
|
|
3054
|
+
return acc ? conjunction(this.dialect, [
|
|
3055
|
+
acc,
|
|
3056
|
+
filter
|
|
3057
|
+
]) : filter;
|
|
3058
|
+
}
|
|
3059
|
+
return acc;
|
|
3060
|
+
}, void 0);
|
|
3061
|
+
}
|
|
3062
|
+
compilePolicyCondition(model, alias, operation, policy) {
|
|
3063
|
+
return new ExpressionTransformer(this.client).transform(policy.condition, {
|
|
2565
3064
|
model,
|
|
2566
3065
|
alias,
|
|
2567
3066
|
operation,
|
|
2568
|
-
thisEntity,
|
|
2569
|
-
thisEntityRaw,
|
|
2570
3067
|
auth: this.client.$auth
|
|
2571
3068
|
});
|
|
2572
3069
|
}
|
|
2573
|
-
getModelPolicies(
|
|
2574
|
-
const modelDef = requireModel(this.client.$schema,
|
|
3070
|
+
getModelPolicies(model, operation) {
|
|
3071
|
+
const modelDef = requireModel(this.client.$schema, model);
|
|
2575
3072
|
const result = [];
|
|
2576
3073
|
const extractOperations = /* @__PURE__ */ __name((expr2) => {
|
|
2577
|
-
|
|
2578
|
-
|
|
3074
|
+
invariant7(ExpressionUtils.isLiteral(expr2), "expecting a literal");
|
|
3075
|
+
invariant7(typeof expr2.value === "string", "expecting a string literal");
|
|
2579
3076
|
return expr2.value.split(",").filter((v) => !!v).map((v) => v.trim());
|
|
2580
3077
|
}, "extractOperations");
|
|
2581
3078
|
if (modelDef.attributes) {
|
|
@@ -2587,8 +3084,84 @@ var PolicyHandler = class extends OperationNodeTransformer {
|
|
|
2587
3084
|
}
|
|
2588
3085
|
return result;
|
|
2589
3086
|
}
|
|
3087
|
+
resolveManyToManyJoinTable(tableName) {
|
|
3088
|
+
for (const model of Object.values(this.client.$schema.models)) {
|
|
3089
|
+
for (const field of Object.values(model.fields)) {
|
|
3090
|
+
const m2m = getManyToManyRelation(this.client.$schema, model.name, field.name);
|
|
3091
|
+
if (m2m?.joinTable === tableName) {
|
|
3092
|
+
const sortedRecord = [
|
|
3093
|
+
{
|
|
3094
|
+
model: model.name,
|
|
3095
|
+
field: field.name
|
|
3096
|
+
},
|
|
3097
|
+
{
|
|
3098
|
+
model: m2m.otherModel,
|
|
3099
|
+
field: m2m.otherField
|
|
3100
|
+
}
|
|
3101
|
+
].sort(this.manyToManySorter);
|
|
3102
|
+
const firstIdFields = requireIdFields(this.client.$schema, sortedRecord[0].model);
|
|
3103
|
+
const secondIdFields = requireIdFields(this.client.$schema, sortedRecord[1].model);
|
|
3104
|
+
invariant7(firstIdFields.length === 1 && secondIdFields.length === 1, "only single-field id is supported for implicit many-to-many join table");
|
|
3105
|
+
return {
|
|
3106
|
+
firstModel: sortedRecord[0].model,
|
|
3107
|
+
firstField: sortedRecord[0].field,
|
|
3108
|
+
firstIdField: firstIdFields[0],
|
|
3109
|
+
secondModel: sortedRecord[1].model,
|
|
3110
|
+
secondField: sortedRecord[1].field,
|
|
3111
|
+
secondIdField: secondIdFields[0]
|
|
3112
|
+
};
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
return void 0;
|
|
3117
|
+
}
|
|
3118
|
+
manyToManySorter(a, b) {
|
|
3119
|
+
return a.model !== b.model ? a.model.localeCompare(b.model) : a.field.localeCompare(b.field);
|
|
3120
|
+
}
|
|
3121
|
+
isManyToManyJoinTable(tableName) {
|
|
3122
|
+
return !!this.resolveManyToManyJoinTable(tableName);
|
|
3123
|
+
}
|
|
3124
|
+
getModelPolicyFilterForManyToManyJoinTable(tableName, alias, operation) {
|
|
3125
|
+
const m2m = this.resolveManyToManyJoinTable(tableName);
|
|
3126
|
+
if (!m2m) {
|
|
3127
|
+
return void 0;
|
|
3128
|
+
}
|
|
3129
|
+
const checkForOperation = operation === "read" ? "read" : "update";
|
|
3130
|
+
const eb = expressionBuilder3();
|
|
3131
|
+
const joinTable = alias ?? tableName;
|
|
3132
|
+
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"));
|
|
3133
|
+
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"));
|
|
3134
|
+
return eb.and([
|
|
3135
|
+
aQuery,
|
|
3136
|
+
bQuery
|
|
3137
|
+
]).toOperationNode();
|
|
3138
|
+
}
|
|
2590
3139
|
};
|
|
2591
3140
|
|
|
3141
|
+
// src/plugins/policy/functions.ts
|
|
3142
|
+
var check = /* @__PURE__ */ __name((eb, args, { client, model, modelAlias, operation }) => {
|
|
3143
|
+
invariant8(args.length === 1 || args.length === 2, '"check" function requires 1 or 2 arguments');
|
|
3144
|
+
const arg1Node = args[0].toOperationNode();
|
|
3145
|
+
const arg2Node = args.length === 2 ? args[1].toOperationNode() : void 0;
|
|
3146
|
+
if (arg2Node) {
|
|
3147
|
+
invariant8(ValueNode4.is(arg2Node) && typeof arg2Node.value === "string", '"operation" parameter must be a string literal when provided');
|
|
3148
|
+
invariant8(CRUD.includes(arg2Node.value), '"operation" parameter must be one of "create", "read", "update", "delete"');
|
|
3149
|
+
}
|
|
3150
|
+
const fieldName = extractFieldName(arg1Node);
|
|
3151
|
+
invariant8(fieldName, 'Failed to extract field name from the first argument of "check" function');
|
|
3152
|
+
const fieldDef = requireField(client.$schema, model, fieldName);
|
|
3153
|
+
invariant8(fieldDef.relation, `Field "${fieldName}" is not a relation field in model "${model}"`);
|
|
3154
|
+
invariant8(!fieldDef.array, `Field "${fieldName}" is a to-many relation, which is not supported by "check"`);
|
|
3155
|
+
const relationModel = fieldDef.type;
|
|
3156
|
+
const op = arg2Node ? arg2Node.value : operation;
|
|
3157
|
+
const policyHandler = new PolicyHandler(client);
|
|
3158
|
+
const joinPairs = buildJoinPairs(client.$schema, model, modelAlias, fieldName, relationModel);
|
|
3159
|
+
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))));
|
|
3160
|
+
const policyCondition = policyHandler.buildPolicyFilter(relationModel, void 0, op);
|
|
3161
|
+
const result = eb.selectFrom(relationModel).where(joinCondition).select(new ExpressionWrapper2(policyCondition).as("$condition"));
|
|
3162
|
+
return result;
|
|
3163
|
+
}, "check");
|
|
3164
|
+
|
|
2592
3165
|
// src/plugins/policy/plugin.ts
|
|
2593
3166
|
var PolicyPlugin = class {
|
|
2594
3167
|
static {
|
|
@@ -2603,6 +3176,11 @@ var PolicyPlugin = class {
|
|
|
2603
3176
|
get description() {
|
|
2604
3177
|
return "Enforces access policies defined in the schema.";
|
|
2605
3178
|
}
|
|
3179
|
+
get functions() {
|
|
3180
|
+
return {
|
|
3181
|
+
check
|
|
3182
|
+
};
|
|
3183
|
+
}
|
|
2606
3184
|
onKyselyQuery({
|
|
2607
3185
|
query,
|
|
2608
3186
|
client,
|
|
@@ -2619,6 +3197,7 @@ var PolicyPlugin = class {
|
|
|
2619
3197
|
};
|
|
2620
3198
|
export {
|
|
2621
3199
|
PolicyPlugin,
|
|
2622
|
-
RejectedByPolicyError
|
|
3200
|
+
RejectedByPolicyError,
|
|
3201
|
+
RejectedByPolicyReason
|
|
2623
3202
|
};
|
|
2624
3203
|
//# sourceMappingURL=index.js.map
|