Skip to content

Commit 3db450a

Browse files
committed
got todo sample running with policies
1 parent 7bc1b2e commit 3db450a

File tree

21 files changed

+248
-276
lines changed

21 files changed

+248
-276
lines changed

packages/cli/test/ts-schema-gen.test.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,7 @@ model Post {
144144
kind: 'array',
145145
items: [
146146
{
147-
kind: 'ref',
148-
model: 'Post',
147+
kind: 'field',
149148
field: 'authorId',
150149
},
151150
],
@@ -157,8 +156,7 @@ model Post {
157156
kind: 'array',
158157
items: [
159158
{
160-
kind: 'ref',
161-
model: 'User',
159+
kind: 'field',
162160
field: 'id',
163161
},
164162
],
@@ -167,9 +165,8 @@ model Post {
167165
{
168166
name: 'onDelete',
169167
value: {
170-
kind: 'ref',
171-
model: 'ReferentialAction',
172-
field: 'Cascade',
168+
kind: 'literal',
169+
value: 'Cascade',
173170
},
174171
},
175172
],

packages/runtime/src/client/crud/operations/base.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,7 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
865865
this.dialect.buildFilter(eb, model, model, combinedWhere)
866866
)
867867
.set(updateFields)
868+
// TODO: return selectively
868869
.returningAll();
869870

870871
let updatedEntity: any;
@@ -1555,6 +1556,7 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
15551556
const query = kysely
15561557
.deleteFrom(model)
15571558
.where((eb) => this.dialect.buildFilter(eb, model, model, where))
1559+
// TODO: return selectively
15581560
.$if(returnData, (qb) => qb.returningAll());
15591561

15601562
// const result = await this.queryExecutor.execute(kysely, query);
@@ -1607,4 +1609,17 @@ export abstract class BaseOperationHandler<Schema extends SchemaDef> {
16071609
}
16081610
return returnRelation;
16091611
}
1612+
1613+
protected async safeTransaction<T>(
1614+
callback: (tx: ToKysely<Schema>) => Promise<T>
1615+
) {
1616+
if (this.kysely.isTransaction) {
1617+
return callback(this.kysely);
1618+
} else {
1619+
return this.kysely
1620+
.transaction()
1621+
.setIsolationLevel('repeatable read')
1622+
.execute(callback);
1623+
}
1624+
}
16101625
}

packages/runtime/src/client/crud/operations/create.ts

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { match } from 'ts-pattern';
2+
import { RejectedByPolicyError } from '../../../plugins/policy/errors';
23
import type { GetModels, SchemaDef } from '../../../schema';
34
import type { CreateArgs, CreateManyArgs } from '../../crud-types';
4-
import { RejectedByPolicyError } from '../../errors';
55
import { getIdValues } from '../../query-utils';
66
import { BaseOperationHandler } from './base';
77

@@ -27,31 +27,15 @@ export class CreateOperationHandler<
2727
}
2828

2929
private async runCreate(args: CreateArgs<Schema, GetModels<Schema>>) {
30-
let result: any;
31-
try {
32-
result = await this.kysely
33-
.transaction()
34-
.setIsolationLevel('repeatable read')
35-
.execute(async (tx) => {
36-
const createResult = await this.create(
37-
tx,
38-
this.model,
39-
args.data
40-
);
41-
return this.readUnique(tx, this.model, {
42-
select: args.select,
43-
include: args.include,
44-
where: getIdValues(
45-
this.schema,
46-
this.model,
47-
createResult
48-
),
49-
});
50-
});
51-
} catch (err) {
52-
// console.error(err);
53-
throw err;
54-
}
30+
// TODO: avoid using transaction for simple create
31+
const result = await this.safeTransaction(async (tx) => {
32+
const createResult = await this.create(tx, this.model, args.data);
33+
return this.readUnique(tx, this.model, {
34+
select: args.select,
35+
include: args.include,
36+
where: getIdValues(this.schema, this.model, createResult),
37+
});
38+
});
5539

5640
if (!result) {
5741
throw new RejectedByPolicyError(

packages/runtime/src/client/crud/operations/delete.ts

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -28,34 +28,24 @@ export class DeleteOperationHandler<
2828
async runDelete(
2929
args: DeleteArgs<Schema, Extract<keyof Schema['models'], string>>
3030
) {
31-
const returnRelations = this.needReturnRelations(this.model, args);
32-
33-
if (returnRelations) {
34-
// employ a transaction
35-
return this.kysely.transaction().execute(async (tx) => {
36-
const existing = await this.readUnique(tx, this.model, {
37-
select: args.select,
38-
include: args.include,
39-
where: args.where,
40-
});
41-
if (!existing) {
42-
throw new NotFoundError(this.model);
43-
}
44-
await this.delete(tx, this.model, args.where, false);
45-
return existing;
46-
});
47-
} else {
48-
const result = await this.delete(
49-
this.kysely,
50-
this.model,
51-
args.where,
52-
true
53-
);
54-
if ((result as unknown[]).length < 1) {
55-
throw new NotFoundError(this.model);
56-
}
57-
return this.trimResult(result[0], args);
31+
const existing = await this.readUnique(this.kysely, this.model, {
32+
select: args.select,
33+
include: args.include,
34+
where: args.where,
35+
});
36+
if (!existing) {
37+
throw new NotFoundError(this.model);
38+
}
39+
const result = await this.delete(
40+
this.kysely,
41+
this.model,
42+
args.where,
43+
false
44+
);
45+
if (result.count === 0) {
46+
throw new NotFoundError(this.model);
5847
}
48+
return existing;
5949
}
6050

6151
async runDeleteMany(

packages/runtime/src/client/crud/operations/update.ts

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,26 +33,23 @@ export class UpdateOperationHandler<
3333
if (hasRelationUpdate) {
3434
// employ a transaction
3535
try {
36-
result = await this.kysely
37-
.transaction()
38-
.setIsolationLevel('repeatable read')
39-
.execute(async (tx) => {
40-
const updateResult = await this.update(
41-
tx,
36+
result = await this.safeTransaction(async (tx) => {
37+
const updateResult = await this.update(
38+
tx,
39+
this.model,
40+
args.where,
41+
args.data
42+
);
43+
return this.readUnique(tx, this.model, {
44+
select: args.select,
45+
include: args.include,
46+
where: getIdValues(
47+
this.schema,
4248
this.model,
43-
args.where,
44-
args.data
45-
);
46-
return this.readUnique(tx, this.model, {
47-
select: args.select,
48-
include: args.include,
49-
where: getIdValues(
50-
this.schema,
51-
this.model,
52-
updateResult
53-
),
54-
});
49+
updateResult
50+
),
5551
});
52+
});
5653
} catch (err) {
5754
// console.error(err);
5855
throw err;

packages/runtime/src/client/errors.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,3 @@ export class NotFoundError extends Error {
1515
super(`Entity not found for model "${model}"`);
1616
}
1717
}
18-
19-
export class RejectedByPolicyError extends Error {
20-
constructor(reason?: string) {
21-
super(reason ?? `Operation rejected by policy`);
22-
}
23-
}

packages/runtime/src/client/executor/zenstack-query-executor.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
Kysely,
88
ReturningNode,
99
SelectionNode,
10+
SelectQueryNode,
1011
SingleConnectionProvider,
1112
UpdateQueryNode,
1213
WhereNode,
@@ -17,7 +18,6 @@ import {
1718
type QueryCompiler,
1819
type QueryResult,
1920
type RootOperationNode,
20-
type SelectQueryNode,
2121
type TableNode,
2222
} from 'kysely';
2323
import { nanoid } from 'nanoid';
@@ -61,6 +61,15 @@ export class ZenStackQueryExecutor<
6161
return this.client.$options;
6262
}
6363

64+
private isCrudQueryNode(node: RootOperationNode) {
65+
return (
66+
SelectQueryNode.is(node) ||
67+
InsertQueryNode.is(node) ||
68+
UpdateQueryNode.is(node) ||
69+
DeleteQueryNode.is(node)
70+
);
71+
}
72+
6473
override async executeQuery(
6574
compiledQuery: CompiledQuery,
6675
queryId: QueryId
@@ -111,7 +120,6 @@ export class ZenStackQueryExecutor<
111120
mutationInterceptionInfo
112121
);
113122

114-
// trim the result to the original query node
115123
if (oldQueryNode !== queryNode) {
116124
// TODO: trim the result to the original query node
117125
}
@@ -162,9 +170,7 @@ export class ZenStackQueryExecutor<
162170
private proceedQuery(query: RootOperationNode, queryId: QueryId) {
163171
// run built-in transformers
164172
const finalQuery = this.nameMapper.transformNode(query);
165-
166173
const compiled = this.compileQuery(finalQuery);
167-
168174
return this.driver.txConnection
169175
? super
170176
.withConnectionProvider(
@@ -415,7 +421,6 @@ export class ZenStackQueryExecutor<
415421
}
416422

417423
plugin.afterEntityMutation({
418-
// context: this.queryContext,
419424
model: this.getMutationModel(queryNode),
420425
action: mutationInterceptionInfo.action,
421426
queryNode,
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* Error thrown when an operation is rejected by access policy.
3+
*/
4+
export class RejectedByPolicyError extends Error {
5+
constructor(reason?: string) {
6+
super(reason ?? `Operation rejected by policy`);
7+
}
8+
}

packages/runtime/src/plugins/policy/expression-transformer.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ export class ExpressionTransformer<Schema extends SchemaDef> {
129129
@expr('null')
130130
// @ts-ignore
131131
private _null() {
132-
return ValueNode.create(null);
132+
return ValueNode.createImmediate(null);
133133
}
134134

135135
@expr('binary')
@@ -275,21 +275,21 @@ export class ExpressionTransformer<Schema extends SchemaDef> {
275275
BinaryOperationNode.create(
276276
count,
277277
OperatorNode.create('>'),
278-
ValueNode.create(0)
278+
ValueNode.createImmediate(0)
279279
)
280280
)
281281
.with('!', () =>
282282
BinaryOperationNode.create(
283283
count,
284284
OperatorNode.create('='),
285-
ValueNode.create(0)
285+
ValueNode.createImmediate(0)
286286
)
287287
)
288288
.with('^', () =>
289289
BinaryOperationNode.create(
290290
count,
291291
OperatorNode.create('='),
292-
ValueNode.create(0)
292+
ValueNode.createImmediate(0)
293293
)
294294
)
295295
.exhaustive()
@@ -361,7 +361,7 @@ export class ExpressionTransformer<Schema extends SchemaDef> {
361361
'Boolean'
362362
);
363363
} else {
364-
throw new Error('Unsupported expression');
364+
throw new Error('Unsupported binary expression with `auth()`');
365365
}
366366
}
367367

@@ -511,7 +511,7 @@ export class ExpressionTransformer<Schema extends SchemaDef> {
511511
receiverType: string
512512
) {
513513
if (!receiver) {
514-
return ValueNode.create(null);
514+
return ValueNode.createImmediate(null);
515515
}
516516

517517
if (expr.members.length !== 1) {

0 commit comments

Comments
 (0)